cranpose_render_common/
bounded_lru_cache.rs1use std::{hash::Hash, num::NonZeroUsize};
2
3use cranpose_core::collections::map::HashMap;
4
5struct CacheSlot<K, V> {
6 key: K,
7 value: V,
8 newer: Option<usize>,
9 older: Option<usize>,
10}
11
12pub struct BoundedLruCache<K, V> {
34 index: HashMap<K, usize>,
35 slots: Vec<Option<CacheSlot<K, V>>>,
36 free: Vec<usize>,
37 newest: Option<usize>,
38 oldest: Option<usize>,
39 cap: NonZeroUsize,
40}
41
42impl<K, V> BoundedLruCache<K, V>
43where
44 K: Clone + Eq + Hash,
45{
46 pub fn new(cap: NonZeroUsize) -> Self {
47 Self {
48 index: HashMap::default(),
49 slots: Vec::new(),
50 free: Vec::new(),
51 newest: None,
52 oldest: None,
53 cap,
54 }
55 }
56
57 pub fn with_capacity_at_least_one(cap: usize) -> Self {
58 let cap = NonZeroUsize::new(cap).unwrap_or(NonZeroUsize::MIN);
59 Self::new(cap)
60 }
61
62 pub fn len(&self) -> usize {
63 self.index.len()
64 }
65
66 pub fn is_empty(&self) -> bool {
67 self.index.is_empty()
68 }
69
70 pub fn cap(&self) -> NonZeroUsize {
71 self.cap
72 }
73
74 pub fn contains(&self, key: &K) -> bool {
75 self.index.contains_key(key)
76 }
77
78 pub fn get(&mut self, key: &K) -> Option<&V> {
79 let slot = *self.index.get(key)?;
80 self.promote(slot);
81 Some(&self.slot(slot).value)
82 }
83
84 pub fn peek(&self, key: &K) -> Option<&V> {
85 let slot = *self.index.get(key)?;
86 Some(&self.slot(slot).value)
87 }
88
89 pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
90 let slot = *self.index.get(key)?;
91 self.promote(slot);
92 Some(&mut self.slot_mut(slot).value)
93 }
94
95 pub fn push(&mut self, key: K, value: V) -> Option<(K, V)> {
96 if let Some(&slot) = self.index.get(&key) {
97 self.promote(slot);
98 let old_value = std::mem::replace(&mut self.slot_mut(slot).value, value);
99 return Some((key, old_value));
100 }
101
102 let evicted = if self.index.len() == self.cap.get() {
103 self.pop_lru()
104 } else {
105 None
106 };
107
108 let slot = self.claim_slot(key.clone(), value);
109 self.index.insert(key, slot);
110 self.link_newest(slot);
111 evicted
112 }
113
114 pub fn put(&mut self, key: K, value: V) -> Option<V> {
115 self.push(key, value).map(|(_, value)| value)
116 }
117
118 pub fn peek_lru(&self) -> Option<(&K, &V)> {
120 let entry = self.slot(self.oldest?);
121 Some((&entry.key, &entry.value))
122 }
123
124 pub fn pop_lru(&mut self) -> Option<(K, V)> {
125 let slot = self.oldest?;
126 self.unlink(slot);
127 let entry = self.release_slot(slot);
128 self.index.remove(&entry.key);
129 Some((entry.key, entry.value))
130 }
131
132 pub fn pop(&mut self, key: &K) -> Option<V> {
133 let slot = self.index.remove(key)?;
134 self.unlink(slot);
135 Some(self.release_slot(slot).value)
136 }
137
138 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
140 let mut next = self.newest;
141 std::iter::from_fn(move || {
142 let entry = self.slot(next?);
143 next = entry.older;
144 Some((&entry.key, &entry.value))
145 })
146 }
147
148 fn slot(&self, slot: usize) -> &CacheSlot<K, V> {
149 self.slots[slot]
150 .as_ref()
151 .expect("a linked cache slot is always occupied")
152 }
153
154 fn slot_mut(&mut self, slot: usize) -> &mut CacheSlot<K, V> {
155 self.slots[slot]
156 .as_mut()
157 .expect("a linked cache slot is always occupied")
158 }
159
160 fn promote(&mut self, slot: usize) {
161 if self.newest == Some(slot) {
162 return;
163 }
164 self.unlink(slot);
165 self.link_newest(slot);
166 }
167
168 fn link_newest(&mut self, slot: usize) {
169 let previous_newest = self.newest;
170 {
171 let entry = self.slot_mut(slot);
172 entry.newer = None;
173 entry.older = previous_newest;
174 }
175 if let Some(previous) = previous_newest {
176 self.slot_mut(previous).newer = Some(slot);
177 }
178 self.newest = Some(slot);
179 if self.oldest.is_none() {
180 self.oldest = Some(slot);
181 }
182 }
183
184 fn unlink(&mut self, slot: usize) {
185 let (newer, older) = {
186 let entry = self.slot_mut(slot);
187 (entry.newer.take(), entry.older.take())
188 };
189 match newer {
190 Some(newer) => self.slot_mut(newer).older = older,
191 None => self.newest = older,
192 }
193 match older {
194 Some(older) => self.slot_mut(older).newer = newer,
195 None => self.oldest = newer,
196 }
197 }
198
199 fn claim_slot(&mut self, key: K, value: V) -> usize {
200 let entry = CacheSlot {
201 key,
202 value,
203 newer: None,
204 older: None,
205 };
206 match self.free.pop() {
207 Some(slot) => {
208 self.slots[slot] = Some(entry);
209 slot
210 }
211 None => {
212 self.slots.push(Some(entry));
213 self.slots.len() - 1
214 }
215 }
216 }
217
218 fn release_slot(&mut self, slot: usize) -> CacheSlot<K, V> {
219 let entry = self.slots[slot]
220 .take()
221 .expect("a slot being released is always occupied");
222 self.free.push(slot);
223 entry
224 }
225}
226
227#[cfg(test)]
228#[path = "tests/bounded_lru_cache_tests.rs"]
229mod tests;