Skip to main content

frust_core/
retained.rs

1//! Retained-page keys and the resident-key ledger.
2//!
3//! The renderer publishes a [`RetainedLedger`] of the pages it holds after each
4//! frame; the shell hands it to `RenderRoot::set_retained_ledger` before the next
5//! paint, so it is one frame stale by design. A page resident under an older
6//! recording is invisible to the renderer (a newer scene can be dropped on the
7//! way), so a widget that recorded at paint serial `n` draws the page only when
8//! the ledger [covers](RetainedLedger::covers) `n`.
9
10use std::collections::HashMap;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13/// `Relaxed` suffices: the counter only produces distinct values.
14static NEXT_RETAINED_KEY: AtomicU64 = AtomicU64::new(0);
15
16/// One retained page's identity; minted from one process-wide counter, since
17/// the renderer caches by key alone and a shared key would alias content.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct RetainedKey(pub u64);
20
21impl RetainedKey {
22    pub fn mint() -> Self {
23        Self(NEXT_RETAINED_KEY.fetch_add(1, Ordering::Relaxed))
24    }
25
26    /// Reserve `count` consecutive keys and return the first.
27    pub fn mint_range(count: u64) -> Self {
28        Self(NEXT_RETAINED_KEY.fetch_add(count, Ordering::Relaxed))
29    }
30
31    pub fn get(self) -> u64 {
32        self.0
33    }
34}
35
36impl From<u64> for RetainedKey {
37    fn from(raw: u64) -> Self {
38        Self(raw)
39    }
40}
41
42/// What the renderer holds for one resident page.
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct RetainedEntry {
45    /// Rasterized extent, in physical pixels.
46    pub extent_px: (u32, u32),
47    pub scale: f32,
48}
49
50/// The retained pages the renderer reported resident after a frame.
51#[derive(Debug, Clone, Default, PartialEq)]
52pub struct RetainedLedger {
53    /// Resident pages by raw [`RetainedKey`].
54    pub entries: HashMap<u64, RetainedEntry>,
55    /// Paint serial of the last scene whose retained-store commit completed
56    /// (`0` before any); a refused or failed frame does not advance it.
57    pub frame: u64,
58}
59
60impl RetainedLedger {
61    const SCALE_EPSILON: f32 = 1e-3;
62
63    /// Nothing resident: what every shell starts with.
64    pub fn empty() -> Self {
65        Self::default()
66    }
67
68    /// From `(key, extent_px, scale)` triples, after serial `frame` committed.
69    pub fn from_entries(
70        frame: u64,
71        entries: impl IntoIterator<Item = (u64, (u32, u32), f32)>,
72    ) -> Self {
73        Self {
74            entries: entries
75                .into_iter()
76                .map(|(key, extent_px, scale)| (key, RetainedEntry { extent_px, scale }))
77                .collect(),
78            frame,
79        }
80    }
81
82    /// `frame >= serial`; serial `0` (an unnumbered paint) is always covered.
83    pub fn covers(&self, serial: u64) -> bool {
84        self.frame >= serial
85    }
86
87    /// Whether `key` is resident at exactly `extent_px` and `scale` (within `1e-3`).
88    pub fn is_resident(&self, key: RetainedKey, extent_px: (u32, u32), scale: f32) -> bool {
89        self.entries.get(&key.0).is_some_and(|e| {
90            e.extent_px == extent_px && (e.scale - scale).abs() <= Self::SCALE_EPSILON
91        })
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn minted_keys_are_distinct_and_ranges_are_disjoint() {
101        let a = RetainedKey::mint();
102        let b = RetainedKey::mint();
103        assert_ne!(a, b);
104        let r = RetainedKey::mint_range(2);
105        let c = RetainedKey::mint();
106        assert!(c.get() >= r.get() + 2);
107    }
108
109    #[test]
110    fn lookups_require_exact_extent_and_close_scale() {
111        let key = RetainedKey(7);
112        let ledger = RetainedLedger::from_entries(3, [(7, (100, 50), 2.0)]);
113        assert!(ledger.is_resident(key, (100, 50), 2.0));
114        assert!(ledger.is_resident(key, (100, 50), 2.0005));
115        assert!(!ledger.is_resident(key, (101, 50), 2.0));
116        assert!(!ledger.is_resident(key, (100, 50), 2.01));
117        assert!(!ledger.is_resident(RetainedKey(8), (100, 50), 2.0));
118    }
119
120    #[test]
121    fn a_ledger_covers_its_own_serial_and_every_earlier_one() {
122        let ledger = RetainedLedger::from_entries(5, [(7, (1, 1), 1.0)]);
123        assert!(ledger.covers(0));
124        assert!(ledger.covers(4));
125        assert!(ledger.covers(5));
126        assert!(!ledger.covers(6), "a later recording is not vouched for");
127        assert!(RetainedLedger::empty().covers(0));
128        assert!(!RetainedLedger::empty().covers(1));
129    }
130
131    #[test]
132    fn empty_ledger_is_never_resident() {
133        let ledger = RetainedLedger::empty();
134        assert!(!ledger.is_resident(RetainedKey(0), (0, 0), 1.0));
135        assert!(!ledger.is_resident(RetainedKey(1), (10, 10), 1.0));
136    }
137}