1use std::{fmt::Debug, hash::Hash};
19
20use ahash::{AHashMap, AHashSet};
21use arraydeque::ArrayDeque;
22
23#[derive(Debug)]
63pub struct FifoCache<T, const N: usize>
64where
65 T: Clone + Debug + Eq + Hash,
66{
67 order: ArrayDeque<T, N>,
68 index: AHashSet<T>,
69}
70
71impl<T, const N: usize> FifoCache<T, N>
72where
73 T: Clone + Debug + Eq + Hash,
74{
75 #[must_use]
81 pub fn new() -> Self {
82 const { assert!(N > 0, "FifoCache capacity must be greater than zero") };
83
84 Self {
85 order: ArrayDeque::new(),
86 index: AHashSet::with_capacity(N),
87 }
88 }
89
90 #[must_use]
92 pub const fn capacity(&self) -> usize {
93 N
94 }
95
96 #[must_use]
98 pub fn len(&self) -> usize {
99 self.index.len()
100 }
101
102 #[must_use]
104 pub fn is_empty(&self) -> bool {
105 self.index.is_empty()
106 }
107
108 #[must_use]
110 pub fn contains(&self, id: &T) -> bool {
111 self.index.contains(id)
112 }
113
114 pub fn add(&mut self, id: T) {
119 if self.index.contains(&id) {
120 return;
121 }
122
123 if self.order.is_full()
124 && let Some(evicted) = self.order.pop_back()
125 {
126 self.index.remove(&evicted);
127 }
128
129 if self.order.push_front(id.clone()).is_ok() {
130 self.index.insert(id);
131 }
132 }
133
134 pub fn remove(&mut self, id: &T) {
136 if self.index.remove(id) {
137 self.order.retain(|x| x != id);
138 }
139 }
140
141 pub fn clear(&mut self) {
143 self.order.clear();
144 self.index.clear();
145 }
146}
147
148impl<T, const N: usize> Default for FifoCache<T, N>
149where
150 T: Clone + Debug + Eq + Hash,
151{
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157#[derive(Debug)]
188pub struct FifoCacheMap<K, V, const N: usize>
189where
190 K: Clone + Debug + Eq + Hash,
191{
192 order: ArrayDeque<K, N>,
193 index: AHashMap<K, V>,
194}
195
196impl<K, V, const N: usize> FifoCacheMap<K, V, N>
197where
198 K: Clone + Debug + Eq + Hash,
199{
200 #[must_use]
206 pub fn new() -> Self {
207 const { assert!(N > 0, "FifoCacheMap capacity must be greater than zero") };
208
209 Self {
210 order: ArrayDeque::new(),
211 index: AHashMap::with_capacity(N),
212 }
213 }
214
215 #[must_use]
217 pub const fn capacity(&self) -> usize {
218 N
219 }
220
221 #[must_use]
223 pub fn len(&self) -> usize {
224 self.index.len()
225 }
226
227 #[must_use]
229 pub fn is_empty(&self) -> bool {
230 self.index.is_empty()
231 }
232
233 #[must_use]
235 pub fn contains_key(&self, key: &K) -> bool {
236 self.index.contains_key(key)
237 }
238
239 #[must_use]
241 pub fn get(&self, key: &K) -> Option<&V> {
242 self.index.get(key)
243 }
244
245 pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
247 self.index.get_mut(key)
248 }
249
250 pub fn insert(&mut self, key: K, value: V) {
255 if self.index.contains_key(&key) {
256 self.index.insert(key, value);
257 return;
258 }
259
260 if self.order.is_full()
261 && let Some(evicted) = self.order.pop_back()
262 {
263 self.index.remove(&evicted);
264 }
265
266 if self.order.push_front(key.clone()).is_ok() {
267 self.index.insert(key, value);
268 }
269 }
270
271 pub fn remove(&mut self, key: &K) -> Option<V> {
273 if let Some(value) = self.index.remove(key) {
274 self.order.retain(|x| x != key);
275 Some(value)
276 } else {
277 None
278 }
279 }
280
281 pub fn clear(&mut self) {
283 self.order.clear();
284 self.index.clear();
285 }
286}
287
288impl<K, V, const N: usize> Default for FifoCacheMap<K, V, N>
289where
290 K: Clone + Debug + Eq + Hash,
291{
292 fn default() -> Self {
293 Self::new()
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use rstest::rstest;
300
301 use super::*;
302
303 #[rstest]
304 fn test_add_and_contains() {
305 let mut cache: FifoCache<u32, 4> = FifoCache::new();
306 cache.add(1);
307 cache.add(2);
308 cache.add(3);
309
310 assert!(cache.contains(&1));
311 assert!(cache.contains(&2));
312 assert!(cache.contains(&3));
313 assert!(!cache.contains(&4));
314 assert_eq!(cache.len(), 3);
315 }
316
317 #[rstest]
318 fn test_eviction_at_capacity() {
319 let mut cache: FifoCache<u32, 3> = FifoCache::new();
320 cache.add(1);
321 cache.add(2);
322 cache.add(3);
323 assert_eq!(cache.len(), 3);
324
325 cache.add(4);
327 assert_eq!(cache.len(), 3);
328 assert!(!cache.contains(&1));
329 assert!(cache.contains(&2));
330 assert!(cache.contains(&3));
331 assert!(cache.contains(&4));
332 }
333
334 #[rstest]
335 fn test_duplicate_add_is_noop() {
336 let mut cache: FifoCache<u32, 3> = FifoCache::new();
337 cache.add(1);
338 cache.add(2);
339 cache.add(1); assert_eq!(cache.len(), 2);
342 assert!(cache.contains(&1));
343 assert!(cache.contains(&2));
344 }
345
346 #[rstest]
347 fn test_remove() {
348 let mut cache: FifoCache<u32, 4> = FifoCache::new();
349 cache.add(1);
350 cache.add(2);
351 cache.add(3);
352
353 cache.remove(&2);
354 assert_eq!(cache.len(), 2);
355 assert!(cache.contains(&1));
356 assert!(!cache.contains(&2));
357 assert!(cache.contains(&3));
358 }
359
360 #[rstest]
361 fn test_remove_nonexistent_is_noop() {
362 let mut cache: FifoCache<u32, 4> = FifoCache::new();
363 cache.add(1);
364 cache.remove(&99);
365 assert_eq!(cache.len(), 1);
366 }
367
368 #[rstest]
369 fn test_capacity() {
370 let cache: FifoCache<u32, 10> = FifoCache::new();
371 assert_eq!(cache.capacity(), 10);
372 }
373
374 #[rstest]
375 fn test_is_empty() {
376 let mut cache: FifoCache<u32, 4> = FifoCache::new();
377 assert!(cache.is_empty());
378 cache.add(1);
379 assert!(!cache.is_empty());
380 }
381
382 #[rstest]
383 fn test_capacity_one_evicts_immediately() {
384 let mut cache: FifoCache<u32, 1> = FifoCache::new();
385 cache.add(1);
386 assert!(cache.contains(&1));
387 assert_eq!(cache.len(), 1);
388
389 cache.add(2);
390 assert!(!cache.contains(&1));
391 assert!(cache.contains(&2));
392 assert_eq!(cache.len(), 1);
393 }
394
395 #[rstest]
396 fn test_sequential_eviction_order() {
397 let mut cache: FifoCache<u32, 3> = FifoCache::new();
398
399 cache.add(1);
401 cache.add(2);
402 cache.add(3);
403
404 cache.add(4);
406 assert!(!cache.contains(&1));
407 assert!(cache.contains(&2));
408
409 cache.add(5);
411 assert!(!cache.contains(&2));
412 assert!(cache.contains(&3));
413
414 cache.add(6);
416 assert!(!cache.contains(&3));
417 assert!(cache.contains(&4));
418 assert!(cache.contains(&5));
419 assert!(cache.contains(&6));
420 }
421
422 #[rstest]
423 fn test_remove_then_readd() {
424 let mut cache: FifoCache<u32, 3> = FifoCache::new();
425 cache.add(1);
426 cache.add(2);
427 cache.remove(&1);
428 assert!(!cache.contains(&1));
429 assert_eq!(cache.len(), 1);
430
431 cache.add(1);
432 assert!(cache.contains(&1));
433 assert_eq!(cache.len(), 2);
434 }
435
436 #[rstest]
437 fn test_remove_frees_slot_for_new_element() {
438 let mut cache: FifoCache<u32, 3> = FifoCache::new();
439
440 cache.add(1);
441 cache.add(2);
442 cache.add(3);
443 cache.remove(&2);
444 assert_eq!(cache.len(), 2);
445
446 cache.add(4);
448 assert_eq!(cache.len(), 3);
449 assert!(cache.contains(&1));
450 assert!(cache.contains(&3));
451 assert!(cache.contains(&4));
452 }
453
454 #[rstest]
455 fn test_duplicate_add_does_not_refresh_position() {
456 let mut cache: FifoCache<u32, 3> = FifoCache::new();
457
458 cache.add(1);
460 cache.add(2);
461 cache.add(3);
462
463 cache.add(1);
465
466 cache.add(4);
468 assert!(!cache.contains(&1));
469 assert!(cache.contains(&2));
470 assert!(cache.contains(&3));
471 assert!(cache.contains(&4));
472 }
473
474 #[rstest]
475 fn test_interleaved_add_remove() {
476 let mut cache: FifoCache<u32, 4> = FifoCache::new();
477
478 cache.add(1);
479 cache.add(2);
480 cache.remove(&1);
481 cache.add(3);
482 cache.add(4);
483 cache.remove(&3);
484 cache.add(5);
485
486 assert!(!cache.contains(&1));
487 assert!(cache.contains(&2));
488 assert!(!cache.contains(&3));
489 assert!(cache.contains(&4));
490 assert!(cache.contains(&5));
491 assert_eq!(cache.len(), 3);
492 }
493
494 #[rstest]
495 fn test_remove_all_elements() {
496 let mut cache: FifoCache<u32, 3> = FifoCache::new();
497 cache.add(1);
498 cache.add(2);
499 cache.add(3);
500
501 cache.remove(&1);
502 cache.remove(&2);
503 cache.remove(&3);
504
505 assert!(cache.is_empty());
506 assert_eq!(cache.len(), 0);
507 }
508
509 #[rstest]
510 fn test_string_type() {
511 let mut cache: FifoCache<String, 2> = FifoCache::new();
512 cache.add("hello".to_string());
513 cache.add("world".to_string());
514
515 assert!(cache.contains(&"hello".to_string()));
516 assert!(cache.contains(&"world".to_string()));
517
518 cache.add("foo".to_string());
519 assert!(!cache.contains(&"hello".to_string()));
520 }
521
522 #[rstest]
523 fn test_map_insert_and_get() {
524 let mut cache: FifoCacheMap<u32, String, 4> = FifoCacheMap::new();
525 cache.insert(1, "one".to_string());
526 cache.insert(2, "two".to_string());
527 cache.insert(3, "three".to_string());
528
529 assert_eq!(cache.get(&1), Some(&"one".to_string()));
530 assert_eq!(cache.get(&2), Some(&"two".to_string()));
531 assert_eq!(cache.get(&3), Some(&"three".to_string()));
532 assert_eq!(cache.get(&4), None);
533 assert_eq!(cache.len(), 3);
534 }
535
536 #[rstest]
537 fn test_map_eviction_at_capacity() {
538 let mut cache: FifoCacheMap<u32, &str, 3> = FifoCacheMap::new();
539 cache.insert(1, "one");
540 cache.insert(2, "two");
541 cache.insert(3, "three");
542 assert_eq!(cache.len(), 3);
543
544 cache.insert(4, "four");
546 assert_eq!(cache.len(), 3);
547 assert_eq!(cache.get(&1), None);
548 assert_eq!(cache.get(&2), Some(&"two"));
549 assert_eq!(cache.get(&3), Some(&"three"));
550 assert_eq!(cache.get(&4), Some(&"four"));
551 }
552
553 #[rstest]
554 fn test_map_update_existing_key() {
555 let mut cache: FifoCacheMap<u32, &str, 3> = FifoCacheMap::new();
556 cache.insert(1, "one");
557 cache.insert(2, "two");
558 cache.insert(3, "three");
559
560 cache.insert(1, "ONE");
562 assert_eq!(cache.len(), 3);
563 assert_eq!(cache.get(&1), Some(&"ONE"));
564 assert_eq!(cache.get(&2), Some(&"two"));
565 assert_eq!(cache.get(&3), Some(&"three"));
566 }
567
568 #[rstest]
569 fn test_map_remove() {
570 let mut cache: FifoCacheMap<u32, &str, 4> = FifoCacheMap::new();
571 cache.insert(1, "one");
572 cache.insert(2, "two");
573 cache.insert(3, "three");
574
575 let removed = cache.remove(&2);
576 assert_eq!(removed, Some("two"));
577 assert_eq!(cache.len(), 2);
578 assert!(cache.contains_key(&1));
579 assert!(!cache.contains_key(&2));
580 assert!(cache.contains_key(&3));
581 }
582
583 #[rstest]
584 fn test_map_remove_nonexistent() {
585 let mut cache: FifoCacheMap<u32, &str, 4> = FifoCacheMap::new();
586 cache.insert(1, "one");
587 let removed = cache.remove(&99);
588 assert_eq!(removed, None);
589 assert_eq!(cache.len(), 1);
590 }
591
592 #[rstest]
593 fn test_map_get_mut() {
594 let mut cache: FifoCacheMap<u32, String, 4> = FifoCacheMap::new();
595 cache.insert(1, "one".to_string());
596
597 if let Some(value) = cache.get_mut(&1) {
598 value.push_str("_modified");
599 }
600
601 assert_eq!(cache.get(&1), Some(&"one_modified".to_string()));
602 }
603
604 #[rstest]
605 fn test_map_capacity() {
606 let cache: FifoCacheMap<u32, &str, 10> = FifoCacheMap::new();
607 assert_eq!(cache.capacity(), 10);
608 }
609
610 #[rstest]
611 fn test_map_is_empty() {
612 let mut cache: FifoCacheMap<u32, &str, 4> = FifoCacheMap::new();
613 assert!(cache.is_empty());
614 cache.insert(1, "one");
615 assert!(!cache.is_empty());
616 }
617
618 #[rstest]
619 fn test_map_capacity_one() {
620 let mut cache: FifoCacheMap<u32, &str, 1> = FifoCacheMap::new();
621 cache.insert(1, "one");
622 assert_eq!(cache.get(&1), Some(&"one"));
623
624 cache.insert(2, "two");
625 assert_eq!(cache.get(&1), None);
626 assert_eq!(cache.get(&2), Some(&"two"));
627 assert_eq!(cache.len(), 1);
628 }
629
630 #[rstest]
631 fn test_map_sequential_eviction() {
632 let mut cache: FifoCacheMap<u32, u32, 3> = FifoCacheMap::new();
633
634 cache.insert(1, 10);
635 cache.insert(2, 20);
636 cache.insert(3, 30);
637
638 cache.insert(4, 40);
640 assert!(!cache.contains_key(&1));
641 assert!(cache.contains_key(&2));
642
643 cache.insert(5, 50);
645 assert!(!cache.contains_key(&2));
646 assert!(cache.contains_key(&3));
647 }
648
649 #[rstest]
650 fn test_map_update_does_not_change_eviction_order() {
651 let mut cache: FifoCacheMap<u32, &str, 3> = FifoCacheMap::new();
652
653 cache.insert(1, "one");
654 cache.insert(2, "two");
655 cache.insert(3, "three");
656
657 cache.insert(1, "ONE");
659
660 cache.insert(4, "four");
662 assert!(!cache.contains_key(&1));
663 assert!(cache.contains_key(&2));
664 assert!(cache.contains_key(&3));
665 assert!(cache.contains_key(&4));
666 }
667
668 #[rstest]
669 fn test_map_remove_frees_slot() {
670 let mut cache: FifoCacheMap<u32, &str, 3> = FifoCacheMap::new();
671
672 cache.insert(1, "one");
673 cache.insert(2, "two");
674 cache.insert(3, "three");
675
676 cache.remove(&2);
677 assert_eq!(cache.len(), 2);
678
679 cache.insert(4, "four");
681 assert_eq!(cache.len(), 3);
682 assert!(cache.contains_key(&1));
683 assert!(cache.contains_key(&3));
684 assert!(cache.contains_key(&4));
685 }
686
687 use proptest::prelude::*;
688
689 #[derive(Clone, Debug)]
691 enum Op {
692 Add(u8),
693 Remove(u8),
694 }
695
696 fn op_strategy() -> impl Strategy<Value = Op> {
697 prop_oneof![(0..50u8).prop_map(Op::Add), (0..50u8).prop_map(Op::Remove),]
698 }
699
700 fn ops_strategy() -> impl Strategy<Value = Vec<Op>> {
701 proptest::collection::vec(op_strategy(), 0..100)
702 }
703
704 fn apply_ops<const N: usize>(ops: &[Op]) -> FifoCache<u8, N> {
706 let mut cache = FifoCache::<u8, N>::new();
707
708 for op in ops {
709 match op {
710 Op::Add(id) => cache.add(*id),
711 Op::Remove(id) => cache.remove(id),
712 }
713 }
714 cache
715 }
716
717 proptest! {
718 #[rstest]
720 fn prop_len_never_exceeds_capacity(ops in ops_strategy()) {
721 let cache = apply_ops::<8>(&ops);
722 prop_assert!(cache.len() <= cache.capacity());
723 }
724
725 #[rstest]
727 fn prop_is_empty_consistent_with_len(ops in ops_strategy()) {
728 let cache = apply_ops::<8>(&ops);
729 if cache.is_empty() {
730 prop_assert_eq!(cache.len(), 0);
731 } else {
732 prop_assert!(!cache.is_empty());
733 }
734 }
735
736 #[rstest]
738 fn prop_add_duplicate_is_idempotent(
739 ops in ops_strategy(),
740 id in 0..50u8
741 ) {
742 let mut cache = apply_ops::<8>(&ops);
743 cache.add(id);
744 let len_after_first = cache.len();
745 let contained_after_first = cache.contains(&id);
746
747 cache.add(id);
748 prop_assert_eq!(cache.len(), len_after_first);
749 prop_assert_eq!(cache.contains(&id), contained_after_first);
750 }
751
752 #[rstest]
754 fn prop_remove_ensures_not_contained(
755 ops in ops_strategy(),
756 id in 0..50u8
757 ) {
758 let mut cache = apply_ops::<8>(&ops);
759 cache.remove(&id);
760 prop_assert!(!cache.contains(&id));
761 }
762
763 #[rstest]
765 fn prop_add_ensures_contained_if_capacity(id in 0..50u8) {
766 let mut cache: FifoCache<u8, 8> = FifoCache::new();
767 cache.add(id);
768 prop_assert!(cache.contains(&id));
769 }
770
771 #[rstest]
773 fn prop_fifo_eviction_order(extra in 0..20u8) {
774 let mut cache: FifoCache<u8, 4> = FifoCache::new();
775
776 for i in 0..4u8 {
778 cache.add(i);
779 }
780 prop_assert_eq!(cache.len(), 4);
781
782 for i in 0..extra {
784 let new_id = 100 + i;
785 cache.add(new_id);
786
787 let evicted = i;
789 if evicted < 4 {
790 prop_assert!(!cache.contains(&evicted),
791 "Element {} should have been evicted", evicted);
792 }
793 }
794 }
795
796 #[rstest]
798 fn prop_remove_on_empty_is_noop(id in 0..50u8) {
799 let mut cache: FifoCache<u8, 8> = FifoCache::new();
800 cache.remove(&id);
801 prop_assert!(cache.is_empty());
802 prop_assert_eq!(cache.len(), 0);
803 }
804
805 #[rstest]
807 fn prop_remove_decreases_len(
808 ops in ops_strategy(),
809 id in 0..50u8
810 ) {
811 let mut cache = apply_ops::<8>(&ops);
812 cache.add(id); let len_before = cache.len();
814
815 cache.remove(&id);
816
817 if cache.contains(&id) {
818 prop_assert!(false, "Element still contained after remove");
819 }
820 prop_assert!(cache.len() < len_before || len_before == 0);
821 }
822
823 #[rstest]
825 fn prop_add_at_capacity_maintains_len(new_id in 50..100u8) {
826 let mut cache: FifoCache<u8, 4> = FifoCache::new();
827
828 for i in 0..4u8 {
830 cache.add(i);
831 }
832 prop_assert_eq!(cache.len(), 4);
833
834 cache.add(new_id);
836 prop_assert_eq!(cache.len(), 4);
837 }
838
839 #[rstest]
841 fn prop_recent_adds_are_contained(recent in proptest::collection::vec(0..50u8, 1..5)) {
842 let mut cache: FifoCache<u8, 8> = FifoCache::new();
843
844 for &id in &recent {
845 cache.add(id);
846 }
847
848 let mut unique: Vec<u8> = recent;
850 unique.sort_unstable();
851 unique.dedup();
852 let expected_len = unique.len().min(8);
853
854 prop_assert_eq!(cache.len(), expected_len);
855
856 for id in unique {
858 prop_assert!(cache.contains(&id), "Recently added {} not contained", id);
859 }
860 }
861
862 #[rstest]
864 fn prop_map_len_never_exceeds_capacity(
865 keys in proptest::collection::vec(0..50u8, 0..100)
866 ) {
867 let mut cache: FifoCacheMap<u8, u8, 8> = FifoCacheMap::new();
868 for key in keys {
869 cache.insert(key, key);
870 }
871 prop_assert!(cache.len() <= cache.capacity());
872 }
873
874 #[rstest]
876 fn prop_map_is_empty_consistent_with_len(
877 keys in proptest::collection::vec(0..50u8, 0..20)
878 ) {
879 let mut cache: FifoCacheMap<u8, u8, 8> = FifoCacheMap::new();
880 for key in keys {
881 cache.insert(key, key);
882 }
883
884 if cache.is_empty() {
885 prop_assert_eq!(cache.len(), 0);
886 } else {
887 prop_assert!(!cache.is_empty());
888 }
889 }
890
891 #[rstest]
893 fn prop_map_update_is_idempotent_for_len(
894 keys in proptest::collection::vec(0..50u8, 1..10),
895 key in 0..50u8
896 ) {
897 let mut cache: FifoCacheMap<u8, u8, 8> = FifoCacheMap::new();
898 for k in keys {
899 cache.insert(k, k);
900 }
901 cache.insert(key, 100);
902 let len_after_first = cache.len();
903
904 cache.insert(key, 200);
905 prop_assert_eq!(cache.len(), len_after_first);
906 }
907
908 #[rstest]
910 fn prop_map_remove_ensures_not_contained(
911 keys in proptest::collection::vec(0..50u8, 0..20),
912 key in 0..50u8
913 ) {
914 let mut cache: FifoCacheMap<u8, u8, 8> = FifoCacheMap::new();
915 for k in keys {
916 cache.insert(k, k);
917 }
918 cache.remove(&key);
919 prop_assert!(cache.get(&key).is_none());
920 }
921
922 #[rstest]
924 fn prop_map_insert_ensures_get(key in 0..50u8, value in 0..100u8) {
925 let mut cache: FifoCacheMap<u8, u8, 8> = FifoCacheMap::new();
926 cache.insert(key, value);
927 prop_assert_eq!(cache.get(&key), Some(&value));
928 }
929
930 #[rstest]
932 fn prop_map_insert_at_capacity_maintains_len(new_key in 50..100u8) {
933 let mut cache: FifoCacheMap<u8, u8, 4> = FifoCacheMap::new();
934
935 for i in 0..4u8 {
936 cache.insert(i, i * 10);
937 }
938 prop_assert_eq!(cache.len(), 4);
939
940 cache.insert(new_key, 99);
941 prop_assert_eq!(cache.len(), 4);
942 }
943
944 #[rstest]
946 fn prop_map_fifo_eviction(extra in 0..20u8) {
947 let mut cache: FifoCacheMap<u8, u8, 4> = FifoCacheMap::new();
948
949 for i in 0..4u8 {
950 cache.insert(i, i * 10);
951 }
952
953 for i in 0..extra {
954 let new_key = 100 + i;
955 cache.insert(new_key, new_key);
956
957 let evicted = i;
958 if evicted < 4 {
959 prop_assert!(cache.get(&evicted).is_none(),
960 "Key {} should have been evicted", evicted);
961 }
962 }
963 }
964 }
965}