use std::collections::HashMap;
use std::hash::Hash;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Silence {
grace: u64,
}
impl Silence {
pub const fn new(grace: u64) -> Self {
assert!(grace > 0, "a zero grace forgets the world on the first sweep");
Self { grace }
}
pub const fn grace(&self) -> u64 {
self.grace
}
pub fn keeps(&self, seen: u64, now: u64) -> bool {
now.saturating_sub(seen) < self.grace
}
pub fn sweep<K: Eq + Hash, V, F: Fn(&K, &V) -> Option<u64>>(
&self,
entities: &mut HashMap<K, V>,
now: u64,
seen_of: F,
) -> usize {
let before = entities.len();
entities.retain(|key, value| match seen_of(key, value) {
Some(seen) => self.keeps(seen, now),
None => true,
});
before - entities.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn silence_ends_an_entity_only_after_the_grace() {
let silence = Silence::new(3);
let mut entities: HashMap<u32, u64> = [(1, 10u64)].into();
assert_eq!(silence.sweep(&mut entities, 12, |_, seen| Some(*seen)), 0, "two frames quiet is not gone");
assert_eq!(silence.sweep(&mut entities, 13, |_, seen| Some(*seen)), 1, "three is");
assert!(entities.is_empty());
}
#[test]
fn an_exempt_entity_outlives_any_silence() {
let silence = Silence::new(2);
let mut entities: HashMap<u32, u64> = [(1, 0u64), (2, 0)].into();
let dropped = silence.sweep(&mut entities, 1000, |key, seen| (*key != 1).then_some(*seen));
assert_eq!(dropped, 1);
assert!(entities.contains_key(&1), "the exemption held");
}
#[test]
fn a_mention_now_is_never_silence() {
let silence = Silence::new(1);
assert!(silence.keeps(5, 5), "mentioned this frame");
assert!(!silence.keeps(4, 5), "the tightest grace drops on the first quiet frame");
}
}