1use rustc_hash::FxHashMap;
2
3pub struct SieveCache<V> {
6 entries: Vec<SieveEntry<V>>,
7 index: FxHashMap<u64, usize>,
9 hand: usize,
11 len: usize,
13 capacity: usize,
14}
15
16struct SieveEntry<V> {
17 key: u64,
18 value: V,
19 visited: bool,
20 dirty: bool,
21 occupied: bool,
22}
23
24impl<V> SieveEntry<V> {
25 fn empty(value: V) -> Self {
26 Self {
27 key: 0,
28 value,
29 visited: false,
30 dirty: false,
31 occupied: false,
32 }
33 }
34}
35
36impl<V: Default> SieveCache<V> {
37 pub fn new(capacity: usize) -> Self {
38 assert!(capacity > 0, "cache capacity must be > 0");
39 let mut entries = Vec::with_capacity(capacity);
40 for _ in 0..capacity {
41 entries.push(SieveEntry::empty(V::default()));
42 }
43 Self {
44 entries,
45 index: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
46 hand: 0,
47 len: 0,
48 capacity,
49 }
50 }
51
52 pub fn get(&mut self, key: u64) -> Option<&V> {
53 if let Some(&idx) = self.index.get(&key) {
54 self.entries[idx].visited = true;
55 Some(&self.entries[idx].value)
56 } else {
57 None
58 }
59 }
60
61 pub fn get_mut(&mut self, key: u64) -> Option<&mut V> {
62 if let Some(&idx) = self.index.get(&key) {
63 self.entries[idx].visited = true;
64 Some(&mut self.entries[idx].value)
65 } else {
66 None
67 }
68 }
69
70 pub fn contains(&self, key: u64) -> bool {
71 self.index.contains_key(&key)
72 }
73
74 #[allow(clippy::result_unit_err)]
76 pub fn insert(&mut self, key: u64, value: V) -> Result<Option<(u64, V)>, ()> {
77 if let Some(&idx) = self.index.get(&key) {
78 self.entries[idx].value = value;
79 self.entries[idx].visited = true;
80 return Ok(None);
81 }
82
83 if self.len < self.capacity {
84 let idx = self.find_empty_slot();
85 self.entries[idx].key = key;
86 self.entries[idx].value = value;
87 self.entries[idx].visited = true;
88 self.entries[idx].dirty = false;
89 self.entries[idx].occupied = true;
90 self.index.insert(key, idx);
91 self.len += 1;
92 return Ok(None);
93 }
94
95 let evicted = self.evict()?;
96 let idx = self.find_empty_slot();
97 self.entries[idx].key = key;
98 self.entries[idx].value = value;
99 self.entries[idx].visited = true;
100 self.entries[idx].dirty = false;
101 self.entries[idx].occupied = true;
102 self.index.insert(key, idx);
103 self.len += 1;
104
105 Ok(Some(evicted))
106 }
107
108 fn evict(&mut self) -> Result<(u64, V), ()> {
109 let mut scanned = 0;
110
111 loop {
112 if scanned >= self.capacity * 2 {
113 return Err(());
115 }
116
117 let idx = self.hand;
118 self.hand = (self.hand + 1) % self.capacity;
119 scanned += 1;
120
121 if !self.entries[idx].occupied {
122 continue;
123 }
124
125 if self.entries[idx].dirty {
126 continue;
127 }
128
129 if self.entries[idx].visited {
130 self.entries[idx].visited = false;
131 continue;
132 }
133
134 let evicted_key = self.entries[idx].key;
135 let evicted_value = std::mem::take(&mut self.entries[idx].value);
136 self.entries[idx].occupied = false;
137 self.index.remove(&evicted_key);
138 self.len -= 1;
139
140 return Ok((evicted_key, evicted_value));
141 }
142 }
143
144 fn find_empty_slot(&self) -> usize {
145 for (i, entry) in self.entries.iter().enumerate() {
146 if !entry.occupied {
147 return i;
148 }
149 }
150 unreachable!("find_empty_slot called when cache is full");
151 }
152
153 pub fn set_dirty(&mut self, key: u64) {
154 if let Some(&idx) = self.index.get(&key) {
155 self.entries[idx].dirty = true;
156 }
157 }
158
159 pub fn clear_dirty(&mut self, key: u64) {
160 if let Some(&idx) = self.index.get(&key) {
161 self.entries[idx].dirty = false;
162 }
163 }
164
165 pub fn is_dirty(&self, key: u64) -> bool {
166 self.index
167 .get(&key)
168 .map(|&idx| self.entries[idx].dirty)
169 .unwrap_or(false)
170 }
171
172 pub fn dirty_entries(&self) -> impl Iterator<Item = (u64, &V)> {
173 self.entries
174 .iter()
175 .filter(|e| e.occupied && e.dirty)
176 .map(|e| (e.key, &e.value))
177 }
178
179 pub fn dirty_entries_mut(&mut self) -> impl Iterator<Item = (u64, &mut V)> {
180 self.entries
181 .iter_mut()
182 .filter(|e| e.occupied && e.dirty)
183 .map(|e| (e.key, &mut e.value))
184 }
185
186 pub fn clear_all_dirty(&mut self) {
187 for entry in &mut self.entries {
188 if entry.occupied {
189 entry.dirty = false;
190 }
191 }
192 }
193
194 pub fn remove(&mut self, key: u64) -> Option<V> {
195 if let Some(idx) = self.index.remove(&key) {
196 let value = std::mem::take(&mut self.entries[idx].value);
197 self.entries[idx].occupied = false;
198 self.len -= 1;
199 Some(value)
200 } else {
201 None
202 }
203 }
204
205 pub fn len(&self) -> usize {
206 self.len
207 }
208
209 pub fn is_empty(&self) -> bool {
210 self.len == 0
211 }
212
213 pub fn capacity(&self) -> usize {
214 self.capacity
215 }
216
217 pub fn dirty_count(&self) -> usize {
218 self.entries
219 .iter()
220 .filter(|e| e.occupied && e.dirty)
221 .count()
222 }
223
224 pub fn clear(&mut self) {
225 for entry in &mut self.entries {
226 entry.occupied = false;
227 entry.visited = false;
228 entry.dirty = false;
229 }
230 self.index.clear();
231 self.len = 0;
232 self.hand = 0;
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn basic_insert_and_get() {
242 let mut cache = SieveCache::<u32>::new(4);
243 cache.insert(1, 100).unwrap();
244 cache.insert(2, 200).unwrap();
245
246 assert_eq!(cache.get(1), Some(&100));
247 assert_eq!(cache.get(2), Some(&200));
248 assert_eq!(cache.get(3), None);
249 }
250
251 #[test]
252 fn eviction_when_full() {
253 let mut cache = SieveCache::<u32>::new(3);
254 cache.insert(1, 100).unwrap();
255 cache.insert(2, 200).unwrap();
256 cache.insert(3, 300).unwrap();
257 assert_eq!(cache.len(), 3);
258
259 for entry in &mut cache.entries {
260 entry.visited = false;
261 }
262
263 let result = cache.insert(4, 400).unwrap();
264 assert!(result.is_some());
265 assert_eq!(cache.len(), 3);
266 }
267
268 #[test]
269 fn visited_entries_survive_eviction() {
270 let mut cache = SieveCache::<u32>::new(3);
271 cache.insert(1, 100).unwrap();
272 cache.insert(2, 200).unwrap();
273 cache.insert(3, 300).unwrap();
274
275 for entry in &mut cache.entries {
276 entry.visited = false;
277 }
278
279 cache.get(2);
280
281 let evicted = cache.insert(4, 400).unwrap().unwrap();
282 assert_ne!(evicted.0, 2, "visited entry should not be evicted");
283 assert!(cache.contains(2));
284 assert!(cache.contains(4));
285 }
286
287 #[test]
288 fn dirty_entries_not_evicted() {
289 let mut cache = SieveCache::<u32>::new(2);
290 cache.insert(1, 100).unwrap();
291 cache.insert(2, 200).unwrap();
292
293 for entry in &mut cache.entries {
294 entry.visited = false;
295 }
296
297 cache.set_dirty(1);
298
299 let evicted = cache.insert(3, 300).unwrap().unwrap();
300 assert_eq!(evicted.0, 2);
301 assert!(cache.contains(1));
302 assert!(cache.contains(3));
303 }
304
305 #[test]
306 fn all_dirty_returns_err() {
307 let mut cache = SieveCache::<u32>::new(2);
308 cache.insert(1, 100).unwrap();
309 cache.insert(2, 200).unwrap();
310
311 cache.set_dirty(1);
312 cache.set_dirty(2);
313
314 for entry in &mut cache.entries {
315 entry.visited = false;
316 }
317
318 let result = cache.insert(3, 300);
319 assert!(result.is_err());
320 }
321
322 #[test]
323 fn clear_dirty() {
324 let mut cache = SieveCache::<u32>::new(2);
325 cache.insert(1, 100).unwrap();
326 cache.set_dirty(1);
327 assert!(cache.is_dirty(1));
328
329 cache.clear_dirty(1);
330 assert!(!cache.is_dirty(1));
331 }
332
333 #[test]
334 fn dirty_entries_iterator() {
335 let mut cache = SieveCache::<u32>::new(4);
336 cache.insert(1, 100).unwrap();
337 cache.insert(2, 200).unwrap();
338 cache.insert(3, 300).unwrap();
339
340 cache.set_dirty(1);
341 cache.set_dirty(3);
342
343 let dirty: Vec<_> = cache.dirty_entries().collect();
344 assert_eq!(dirty.len(), 2);
345 assert_eq!(cache.dirty_count(), 2);
346 }
347
348 #[test]
349 fn clear_all_dirty() {
350 let mut cache = SieveCache::<u32>::new(3);
351 cache.insert(1, 100).unwrap();
352 cache.insert(2, 200).unwrap();
353 cache.set_dirty(1);
354 cache.set_dirty(2);
355
356 cache.clear_all_dirty();
357 assert_eq!(cache.dirty_count(), 0);
358 }
359
360 #[test]
361 fn remove_entry() {
362 let mut cache = SieveCache::<u32>::new(4);
363 cache.insert(1, 100).unwrap();
364 cache.insert(2, 200).unwrap();
365
366 let removed = cache.remove(1);
367 assert_eq!(removed, Some(100));
368 assert!(!cache.contains(1));
369 assert_eq!(cache.len(), 1);
370 }
371
372 #[test]
373 fn update_existing_key() {
374 let mut cache = SieveCache::<u32>::new(4);
375 cache.insert(1, 100).unwrap();
376 cache.insert(1, 200).unwrap();
377
378 assert_eq!(cache.get(1), Some(&200));
379 assert_eq!(cache.len(), 1);
380 }
381}