use rustc_hash::FxHashMap;
use crate::key::Key;
pub(crate) struct Kept<T> {
cur: Vec<T>,
prev: Vec<T>,
}
impl<T> Default for Kept<T> {
fn default() -> Self {
Self {
cur: Vec::new(),
prev: Vec::new(),
}
}
}
impl<T> Kept<T> {
pub(crate) fn begin(&mut self, keep: bool) {
if keep {
std::mem::swap(&mut self.cur, &mut self.prev);
} else {
self.prev.clear();
}
self.cur.clear();
}
pub(crate) fn prev(&self) -> &[T] {
&self.prev
}
}
impl<T> std::ops::Deref for Kept<T> {
type Target = Vec<T>;
fn deref(&self) -> &Vec<T> {
&self.cur
}
}
impl<T> std::ops::DerefMut for Kept<T> {
fn deref_mut(&mut self) -> &mut Vec<T> {
&mut self.cur
}
}
pub(crate) fn evict_undeclared<V>(
map: &mut FxHashMap<Key, V>,
cap: usize,
declared_at: u64,
stamp: impl Fn(&V) -> u64,
pinned: impl Fn(Key) -> bool,
mut removed: impl FnMut(Key),
) {
let mut undeclared: Vec<(u64, Key)> = map
.iter()
.filter(|(k, v)| stamp(v) < declared_at && !pinned(**k))
.map(|(k, v)| (stamp(v), *k))
.collect();
let Some(excess) = undeclared.len().checked_sub(cap) else {
return;
};
if excess == 0 {
return;
}
undeclared.sort_unstable();
for (_, key) in &undeclared[..excess] {
map.remove(key);
removed(*key);
}
}
pub(crate) const SWEEP_EVERY: u64 = 240;
pub(crate) const KEEP_FOR: u64 = 300;
#[inline]
pub(crate) fn sweep_cutoff(frame_no: u64) -> Option<u64> {
frame_no
.is_multiple_of(SWEEP_EVERY)
.then(|| frame_no.saturating_sub(KEEP_FOR))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_kept_list_keeps_the_previous_frame_only_when_asked() {
let mut k: Kept<u32> = Kept::default();
k.push(1);
k.begin(true);
assert_eq!(k.prev(), &[1]);
assert!(k.is_empty());
k.push(2);
k.begin(false);
assert!(k.prev().is_empty(), "not kept: the spare is emptied");
assert!(k.is_empty());
}
#[test]
fn eviction_is_a_ceiling_on_the_undeclared_and_spares_the_pinned() {
let mut map: FxHashMap<Key, u64> = FxHashMap::default();
for i in 0..6u64 {
map.insert(Key::ROOT.index(i), i);
}
let pinned = Key::ROOT.index(0);
let mut gone = Vec::new();
evict_undeclared(&mut map, 2, 5, |s| *s, |k| k == pinned, |k| gone.push(k));
assert_eq!(
map.len(),
4,
"the declared one, the pinned one, and the two newest undeclared"
);
assert!(map.contains_key(&pinned));
assert!(map.contains_key(&Key::ROOT.index(5)));
assert_eq!(
gone,
vec![Key::ROOT.index(1), Key::ROOT.index(2)],
"the two oldest unpinned undeclared went, oldest first"
);
}
#[test]
fn a_sweep_happens_every_so_many_frames() {
assert_eq!(sweep_cutoff(1), None);
assert_eq!(sweep_cutoff(SWEEP_EVERY), Some(0));
assert_eq!(
sweep_cutoff(SWEEP_EVERY * 2),
Some(SWEEP_EVERY * 2 - KEEP_FOR)
);
}
}