all_is_cubes_render/
headless.rs

1use alloc::vec::Vec;
2
3use embedded_graphics::Drawable;
4use embedded_graphics::mono_font::MonoTextStyle;
5use embedded_graphics::mono_font::iso_8859_1::FONT_7X13_BOLD;
6use embedded_graphics::prelude::{PixelColor, Point};
7use embedded_graphics::text::{Baseline, Text};
8
9use all_is_cubes::character::Cursor;
10
11use crate::camera::{ImageSize, Layers};
12use crate::{Flaws, RenderError};
13
14/// Rendering a previously-specified scene to an in-memory image.
15///
16/// This trait is object-safe so that different renderers can be used without generics.
17/// Therefore, all its `async` methods use boxed futures.
18pub trait HeadlessRenderer {
19    /// Update the renderer's internal copy of the scene from the data sources
20    /// (`Handle<Character>` etc.) it is tracking.
21    ///
22    /// Returns [`RenderError::Read`] if said sources are in use or if some other
23    /// prohibitive failure occurred. The resulting state of the renderer in such cases
24    /// is not specified, but a good implementation should attempt recovery on a future
25    /// call.
26    fn update(
27        &mut self,
28        read_tickets: Layers<all_is_cubes::universe::ReadTicket<'_>>,
29        cursor: Option<&Cursor>,
30    ) -> Result<(), RenderError>;
31
32    /// Produce an image of the current state of the scene this renderer was created to
33    /// track, as of the last call to [`Self::update()`], with the given overlaid text.
34    ///
35    /// This operation should not attempt to access the scene objects and therefore may be
36    /// called while the [`Universe`] is being stepped on another thread.
37    ///
38    /// [`Universe`]: all_is_cubes::universe::Universe
39    fn draw<'a>(
40        &'a mut self,
41        info_text: &'a str,
42    ) -> futures_core::future::BoxFuture<'a, Result<Rendering, RenderError>>;
43}
44
45/// Image container produced by a [`HeadlessRenderer`].
46// ---
47// TODO: This is not very compatible with future changes, but it's not clear how to
48// improve extensibility. We would also like to have renderer-specific performance info.
49#[derive(Clone, Debug)]
50#[expect(clippy::exhaustive_structs)]
51pub struct Rendering {
52    /// Width and height of the image.
53    pub size: ImageSize,
54    /// Image data, RGBA, 8 bits per component, in the sRGB color space.
55    pub data: Vec<[u8; 4]>,
56    /// Deficiencies of the rendering; ways in which it fails to accurately represent the
57    /// scene or apply the renderer’s configuration.
58    pub flaws: Flaws,
59}
60
61impl From<Rendering> for imgref::ImgVec<[u8; 4]> {
62    fn from(value: Rendering) -> Self {
63        imgref::Img::new(
64            value.data,
65            // cannot overflow — we statically assert size_of(usize) >= size_of(u32)
66            value.size.width as usize,
67            value.size.height as usize,
68        )
69    }
70}
71impl<'a> From<&'a Rendering> for imgref::ImgRef<'a, [u8; 4]> {
72    fn from(value: &'a Rendering) -> Self {
73        imgref::Img::new(
74            value.data.as_slice(),
75            // cannot overflow — we statically assert size_of(usize) >= size_of(u32)
76            value.size.width as usize,
77            value.size.height as usize,
78        )
79    }
80}
81
82/// Provides the standard text style and positioning to draw the “debug info text”
83/// (as in [`HeadlessRenderer::draw()`]'s parameter).
84///
85/// Note: The conventional color is white with a black drop-shadow, but the exact color
86/// format and means by which the shadow is accomplished depends on the specific renderer,
87/// so this function makes no assumption about color.
88#[doc(hidden)] // TODO: decide whether to make public
89pub fn info_text_drawable<C: PixelColor + 'static>(
90    text: &str,
91    color_value: C,
92) -> impl Drawable<Color = C> + '_ {
93    Text::with_baseline(
94        text,
95        Point::new(5, 5),
96        MonoTextStyle::new(&FONT_7X13_BOLD, color_value),
97        Baseline::Top,
98    )
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    fn _headless_renderer_is_object_safe(_: &dyn HeadlessRenderer) {}
106}