#[derive(Clone, Copy, Debug)]
pub struct SeenCache<const N: usize> {
keys: [Option<(u32, u16)>; N],
next: usize,
}
impl<const N: usize> SeenCache<N> {
pub const fn new() -> Self {
SeenCache {
keys: [None; N],
next: 0,
}
}
pub fn contains(&self, key: (u32, u16)) -> bool {
self.keys.contains(&Some(key))
}
pub fn record(&mut self, key: (u32, u16)) -> bool {
record_into(&mut self.keys, &mut self.next, key)
}
}
impl<const N: usize> Default for SeenCache<N> {
fn default() -> Self {
Self::new()
}
}
fn record_into(keys: &mut [Option<(u32, u16)>], next: &mut usize, key: (u32, u16)) -> bool {
if keys.contains(&Some(key)) {
return false;
}
if keys.is_empty() {
return true;
}
keys[*next] = Some(key);
*next = (*next + 1) % keys.len();
true
}
#[cfg(any(feature = "alloc", test))]
#[derive(Clone, Debug)]
pub struct DynamicSeenCache {
keys: alloc::vec::Vec<Option<(u32, u16)>>,
next: usize,
}
#[cfg(any(feature = "alloc", test))]
impl DynamicSeenCache {
pub fn new(capacity: usize) -> Self {
DynamicSeenCache {
keys: alloc::vec![None; capacity],
next: 0,
}
}
pub fn capacity(&self) -> usize {
self.keys.len()
}
pub fn contains(&self, key: (u32, u16)) -> bool {
self.keys.contains(&Some(key))
}
pub fn record(&mut self, key: (u32, u16)) -> bool {
record_into(&mut self.keys, &mut self.next, key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_key_is_new_once_then_a_duplicate() {
let mut seen: SeenCache<8> = SeenCache::new();
assert!(seen.record((1, 1)));
assert!(!seen.record((1, 1)));
assert!(seen.contains((1, 1)));
}
#[test]
fn different_sources_and_ids_are_distinct() {
let mut seen: SeenCache<8> = SeenCache::new();
assert!(seen.record((1, 1)));
assert!(seen.record((1, 2)));
assert!(seen.record((2, 1)));
assert!(!seen.record((1, 1)));
}
#[test]
fn the_oldest_key_is_evicted_when_full() {
let mut seen: SeenCache<2> = SeenCache::new();
assert!(seen.record((0, 1)));
assert!(seen.record((0, 2)));
assert!(seen.record((0, 3)));
assert!(!seen.contains((0, 1)));
assert!(seen.contains((0, 2)));
assert!(seen.contains((0, 3)));
assert!(seen.record((0, 1)));
}
#[test]
fn an_empty_cache_remembers_nothing() {
let seen: SeenCache<4> = SeenCache::default();
assert!(!seen.contains((1, 1)));
}
#[test]
fn a_zero_capacity_cache_reads_every_key_as_new_without_panicking() {
let mut seen: SeenCache<0> = SeenCache::new();
assert!(seen.record((1, 1)));
assert!(seen.record((1, 1))); assert!(!seen.contains((1, 1)));
}
#[test]
fn a_runtime_sized_cache_answers_the_same_way() {
let mut fixed: SeenCache<4> = SeenCache::new();
let mut dynamic = DynamicSeenCache::new(4);
for key in [(1u32, 1u16), (1, 1), (1, 2), (2, 1), (1, 3), (1, 4), (1, 1)] {
assert_eq!(
fixed.record(key),
dynamic.record(key),
"the two caches evict identically"
);
assert_eq!(fixed.contains(key), dynamic.contains(key));
}
}
#[test]
fn a_runtime_sized_cache_remembers_what_it_was_sized_for() {
let mut seen = DynamicSeenCache::new(2);
assert_eq!(seen.capacity(), 2);
assert!(seen.record((1, 1)));
assert!(seen.record((1, 2)));
assert!(seen.record((1, 3)));
assert!(
!seen.contains((1, 1)),
"the oldest key is evicted once the cache is full"
);
}
#[test]
fn a_cache_with_no_room_relays_every_copy() {
let mut seen = DynamicSeenCache::new(0);
assert!(seen.record((1, 1)));
assert!(
seen.record((1, 1)),
"with nothing remembered every copy is new"
);
}
}