Skip to main content

compose_rust/
asset.rs

1//! Pictures: registering the bytes once and drawing them by id.
2//!
3//! An asset is not part of the tree. It is bytes that have to reach the Renderer before
4//! anything can draw them and have to outlive the frame that does, which is the opposite
5//! of a node: a node is re-sent whenever it changes and points at nothing after the call
6//! that carried it.
7//!
8//! So an asset is registered and forgotten, the way a message is posted and forgotten. It
9//! rides out on the batch the current call produces, and everything after that is the id.
10
11use crate::schema::AssetKind;
12use std::cell::{Cell, RefCell};
13
14/// One registration, waiting for the batch that carries it.
15pub(crate) struct PendingAsset {
16    pub(crate) asset_id: u32,
17    pub(crate) kind: AssetKind,
18    pub(crate) bytes: &'static [u8],
19}
20
21thread_local! {
22    /// What has been registered, so the same picture asked for twice is one registration.
23    ///
24    /// The kind is kept beside the bytes because a new Renderer has to be told all of this
25    /// again, and a registration is bytes, an id and what the bytes are.
26    static REGISTERED: RefCell<Vec<(&'static [u8], u32, AssetKind)>> =
27        const { RefCell::new(Vec::new()) };
28    static QUEUE: RefCell<Vec<PendingAsset>> = const { RefCell::new(Vec::new()) };
29    /// Ids the application let go of, waiting for the batch that tells the Renderer.
30    static RELEASES: RefCell<Vec<u32>> = const { RefCell::new(Vec::new()) };
31    /// The last id handed out. Counted here and not read off the registry's length,
32    /// because a released id leaves a hole and the next registration must not reuse it:
33    /// a stale `Paint` would then silently name a different brush.
34    static LAST_ID: Cell<u32> = const { Cell::new(0) };
35}
36
37/// Registers one picture and returns the id an [`crate::Image`] draws it by.
38///
39/// ```ignore
40/// static HERO: &[u8] = include_bytes!("../assets/hero.svg");
41///
42/// rsx! { Image { asset_id: asset(AssetKind::Svg, HERO), height: 240.0 } }
43/// ```
44///
45/// Call it wherever the picture is drawn, including in a component body that runs every
46/// render. The second call with the same bytes returns the first call's id and queues
47/// nothing, so a screen that draws the same picture every frame registers it once.
48///
49/// The bytes are `'static` because a registration is carried by whichever batch the
50/// current call produces, which is not always the call this was made in, and because
51/// `include_bytes!` is what an application almost always has.
52pub fn asset(kind: AssetKind, bytes: &'static [u8]) -> u32 {
53    if let Some(id) = REGISTERED.with_borrow(|registered| registered_id(registered, bytes)) {
54        return id;
55    }
56    // Ids start at one. Zero is what a node with no asset property reads as, so handing it
57    // out would make "no picture" and "the first picture" the same value on the wire.
58    let asset_id = REGISTERED.with_borrow_mut(|registered| {
59        let asset_id = LAST_ID.get() + 1;
60        LAST_ID.set(asset_id);
61        registered.push((bytes, asset_id, kind));
62        asset_id
63    });
64    QUEUE.with_borrow_mut(|queue| {
65        queue.push(PendingAsset {
66            asset_id,
67            kind,
68            bytes,
69        });
70    });
71    asset_id
72}
73
74/// Registers bytes this process built rather than bytes it was compiled with.
75///
76/// A gradient is worked out at run time, so there is no `include_bytes!` to point at. The
77/// bytes are handed to the registry to keep for the life of the process, which is what
78/// `'static` means here and what a registration is anyway: an asset has to outlive every
79/// frame that draws it, and nothing ever un-registers by dropping.
80///
81/// The content comparison runs first, so building the same gradient on every render keeps
82/// one copy. Only a brush nobody has registered before is kept.
83pub fn register_owned(kind: AssetKind, bytes: Vec<u8>) -> u32 {
84    if let Some(id) = REGISTERED.with_borrow(|registered| {
85        registered
86            .iter()
87            .find(|(known, _, _)| *known == bytes.as_slice())
88            .map(|(_, id, _)| *id)
89    }) {
90        return id;
91    }
92    asset(kind, Box::leak(bytes.into_boxed_slice()))
93}
94
95/// The id these bytes were registered under, by address first and by content after.
96///
97/// Address first because that is the answer almost every time and it costs a comparison.
98/// Content after because the same picture really can arrive at two addresses: a `const`
99/// holding a reference is inlined at each use, and each use can get an allocation of its
100/// own, so a catalogue written as a `const` array hands out two pointers to one file and
101/// the shop registers the same cover twice under two ids. The full comparison only runs
102/// when the address misses, which is once per new picture and never in the steady state.
103fn registered_id(
104    registered: &[(&'static [u8], u32, AssetKind)],
105    bytes: &'static [u8],
106) -> Option<u32> {
107    registered
108        .iter()
109        .find(|(known, _, _)| std::ptr::eq(*known, bytes))
110        .or_else(|| registered.iter().find(|(known, _, _)| *known == bytes))
111        .map(|(_, id, _)| *id)
112}
113
114/// Hands every queued registration to `emit` and empties the queue.
115///
116/// The buffer is moved out and put back, so the steady state, where nothing new has been
117/// registered, touches an empty `Vec` and allocates nothing.
118pub(crate) fn drain(mut emit: impl FnMut(&PendingAsset)) {
119    let mut taken = QUEUE.with_borrow_mut(std::mem::take);
120    if taken.is_empty() {
121        return;
122    }
123    for pending in &taken {
124        emit(pending);
125    }
126    taken.clear();
127    QUEUE.with_borrow_mut(|queue| {
128        if queue.is_empty() {
129            *queue = taken;
130        }
131    });
132}
133
134/// Lets go of one registration and queues the release for the Renderer.
135///
136/// Returns whether anything was released: false for an id that was never registered or
137/// was already released. A registration still waiting for its batch is simply dropped,
138/// because the Renderer never heard of it and a release for it would be a protocol error.
139/// The id is not handed out again, so a copy of the old `Paint` stays a dangling name
140/// (reported as a protocol error) and never silently names a different asset.
141pub(crate) fn release(asset_id: u32, kind: AssetKind) -> bool {
142    let removed = REGISTERED.with_borrow_mut(|registered| {
143        let at = registered
144            .iter()
145            .position(|(_, id, known)| *id == asset_id && *known == kind)?;
146        registered.remove(at);
147        Some(())
148    });
149    if removed.is_none() {
150        return false;
151    }
152    let was_waiting = QUEUE.with_borrow_mut(|queue| {
153        let before = queue.len();
154        queue.retain(|pending| pending.asset_id != asset_id);
155        queue.len() != before
156    });
157    if !was_waiting {
158        RELEASES.with_borrow_mut(|releases| releases.push(asset_id));
159    }
160    true
161}
162
163/// Hands every queued release to `emit` and empties the queue.
164pub(crate) fn drain_releases(mut emit: impl FnMut(u32)) {
165    let mut taken = RELEASES.with_borrow_mut(std::mem::take);
166    if taken.is_empty() {
167        return;
168    }
169    for asset_id in &taken {
170        emit(*asset_id);
171    }
172    taken.clear();
173    RELEASES.with_borrow_mut(|releases| {
174        if releases.is_empty() {
175            *releases = taken;
176        }
177    });
178}
179
180/// Forgets every registration and every id. Used between tests.
181#[doc(hidden)]
182pub fn reset_assets() {
183    REGISTERED.with_borrow_mut(Vec::clear);
184    QUEUE.with_borrow_mut(Vec::clear);
185    RELEASES.with_borrow_mut(Vec::clear);
186    LAST_ID.set(0);
187}
188
189/// Queues every registration again, for a Renderer that has not been told any of them.
190///
191/// What a new `Host` does instead of forgetting them. A new Host means a Renderer whose
192/// cache is empty, so an id handed out before it names nothing over there, and the cure
193/// for that is to say all of them again rather than to drop them.
194///
195/// Dropping them was wrong in a way that only shows on the screen. A theme names its fonts
196/// by id and a window names its icon by id, and both are built by the caller before the
197/// Host is made out of them: the ids travelled and the bytes they named had been thrown
198/// away, so the window wore no icon and the screen was set in the machine's own face while
199/// every batch looked correct.
200///
201/// Ids do not move. The same bytes keep the id they were given, which is what lets a theme
202/// built before this still name the right thing after it.
203pub(crate) fn requeue_all() {
204    let all: Vec<PendingAsset> = REGISTERED.with_borrow(|registered| {
205        registered
206            .iter()
207            .map(|(bytes, asset_id, kind)| PendingAsset {
208                asset_id: *asset_id,
209                kind: *kind,
210                bytes,
211            })
212            .collect()
213    });
214    // A new Renderer never held what was released, so telling it would be an error.
215    RELEASES.with_borrow_mut(Vec::clear);
216    if all.is_empty() {
217        return;
218    }
219    QUEUE.with_borrow_mut(|queue| {
220        // Anything already waiting is in `all` as well, because everything queued was
221        // registered. Replacing rather than appending is what keeps it sent once.
222        *queue = all;
223    });
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    /// Releasing a brush: a release reaches the Renderer once, a registration
231    /// that never left is dropped silently, and ids are never reused.
232    #[test]
233    fn fr23_released_brush_ids_are_sent_once_and_never_reused() {
234        reset_assets();
235        let kept = register_owned(AssetKind::Brush, vec![1, 2, 3]);
236        let sent = register_owned(AssetKind::Brush, vec![4, 5, 6]);
237        drain(|_| {});
238        let waiting = register_owned(AssetKind::Brush, vec![7, 8, 9]);
239
240        assert!(release(sent, AssetKind::Brush));
241        assert!(release(waiting, AssetKind::Brush));
242        assert!(
243            !release(sent, AssetKind::Brush),
244            "second release is a no-op"
245        );
246        assert!(!release(kept, AssetKind::Png), "wrong kind is not released");
247
248        let mut released = Vec::new();
249        drain_releases(|id| released.push(id));
250        assert_eq!(released, [sent], "only the registration the Renderer saw");
251        drain(|pending| panic!("nothing should be waiting, found {}", pending.asset_id));
252
253        let again = register_owned(AssetKind::Brush, vec![4, 5, 6]);
254        assert!(again > waiting, "a released id is not handed out again");
255        reset_assets();
256    }
257}