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}