cranpose_render_common/
bounded_lru_cache.rs1use std::{borrow::Borrow, 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<Q>(&self, key: &Q) -> bool
77 where
78 K: Borrow<Q>,
79 Q: Hash + Eq + ?Sized,
80 {
81 self.index.contains_key(key)
82 }
83
84 pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
85 where
86 K: Borrow<Q>,
87 Q: Hash + Eq + ?Sized,
88 {
89 let slot = *self.index.get(key)?;
90 self.promote(slot);
91 Some(&self.slot(slot).value)
92 }
93
94 pub fn peek<Q>(&self, key: &Q) -> Option<&V>
95 where
96 K: Borrow<Q>,
97 Q: Hash + Eq + ?Sized,
98 {
99 let slot = *self.index.get(key)?;
100 Some(&self.slot(slot).value)
101 }
102
103 pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
104 where
105 K: Borrow<Q>,
106 Q: Hash + Eq + ?Sized,
107 {
108 let slot = *self.index.get(key)?;
109 self.promote(slot);
110 Some(&mut self.slot_mut(slot).value)
111 }
112
113 pub fn push(&mut self, key: K, value: V) -> Option<(K, V)> {
114 if let Some(&slot) = self.index.get(&key) {
115 self.promote(slot);
116 let old_value = std::mem::replace(&mut self.slot_mut(slot).value, value);
117 return Some((key, old_value));
118 }
119
120 let evicted = if self.index.len() == self.cap.get() {
121 self.pop_lru()
122 } else {
123 None
124 };
125
126 let slot = self.claim_slot(key.clone(), value);
127 self.index.insert(key, slot);
128 self.link_newest(slot);
129 evicted
130 }
131
132 pub fn put(&mut self, key: K, value: V) -> Option<V> {
133 self.push(key, value).map(|(_, value)| value)
134 }
135
136 pub fn peek_lru(&self) -> Option<(&K, &V)> {
138 let entry = self.slot(self.oldest?);
139 Some((&entry.key, &entry.value))
140 }
141
142 pub fn pop_lru(&mut self) -> Option<(K, V)> {
143 let slot = self.oldest?;
144 self.unlink(slot);
145 let entry = self.release_slot(slot);
146 self.index.remove(&entry.key);
147 Some((entry.key, entry.value))
148 }
149
150 pub fn pop(&mut self, key: &K) -> Option<V> {
151 let slot = self.index.remove(key)?;
152 self.unlink(slot);
153 Some(self.release_slot(slot).value)
154 }
155
156 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
158 let mut next = self.newest;
159 std::iter::from_fn(move || {
160 let entry = self.slot(next?);
161 next = entry.older;
162 Some((&entry.key, &entry.value))
163 })
164 }
165
166 fn slot(&self, slot: usize) -> &CacheSlot<K, V> {
167 self.slots[slot]
168 .as_ref()
169 .expect("a linked cache slot is always occupied")
170 }
171
172 fn slot_mut(&mut self, slot: usize) -> &mut CacheSlot<K, V> {
173 self.slots[slot]
174 .as_mut()
175 .expect("a linked cache slot is always occupied")
176 }
177
178 fn promote(&mut self, slot: usize) {
179 if self.newest == Some(slot) {
180 return;
181 }
182 self.unlink(slot);
183 self.link_newest(slot);
184 }
185
186 fn link_newest(&mut self, slot: usize) {
187 let previous_newest = self.newest;
188 {
189 let entry = self.slot_mut(slot);
190 entry.newer = None;
191 entry.older = previous_newest;
192 }
193 if let Some(previous) = previous_newest {
194 self.slot_mut(previous).newer = Some(slot);
195 }
196 self.newest = Some(slot);
197 if self.oldest.is_none() {
198 self.oldest = Some(slot);
199 }
200 }
201
202 fn unlink(&mut self, slot: usize) {
203 let (newer, older) = {
204 let entry = self.slot_mut(slot);
205 (entry.newer.take(), entry.older.take())
206 };
207 match newer {
208 Some(newer) => self.slot_mut(newer).older = older,
209 None => self.newest = older,
210 }
211 match older {
212 Some(older) => self.slot_mut(older).newer = newer,
213 None => self.oldest = newer,
214 }
215 }
216
217 fn claim_slot(&mut self, key: K, value: V) -> usize {
218 let entry = CacheSlot {
219 key,
220 value,
221 newer: None,
222 older: None,
223 };
224 match self.free.pop() {
225 Some(slot) => {
226 self.slots[slot] = Some(entry);
227 slot
228 }
229 None => {
230 self.slots.push(Some(entry));
231 self.slots.len() - 1
232 }
233 }
234 }
235
236 fn release_slot(&mut self, slot: usize) -> CacheSlot<K, V> {
237 let entry = self.slots[slot]
238 .take()
239 .expect("a slot being released is always occupied");
240 self.free.push(slot);
241 entry
242 }
243}
244
245#[cfg(test)]
246#[path = "tests/bounded_lru_cache_tests.rs"]
247mod tests;