1use std::collections::HashMap;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13static NEXT_RETAINED_KEY: AtomicU64 = AtomicU64::new(0);
15
16#[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 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#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct RetainedEntry {
45 pub extent_px: (u32, u32),
47 pub scale: f32,
48}
49
50#[derive(Debug, Clone, Default, PartialEq)]
52pub struct RetainedLedger {
53 pub entries: HashMap<u64, RetainedEntry>,
55 pub frame: u64,
58}
59
60impl RetainedLedger {
61 const SCALE_EPSILON: f32 = 1e-3;
62
63 pub fn empty() -> Self {
65 Self::default()
66 }
67
68 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 pub fn covers(&self, serial: u64) -> bool {
84 self.frame >= serial
85 }
86
87 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}