pub const RETENTION_FRAMES: u64 = 8;
pub fn evict_decision<K: Copy>(
chains: impl IntoIterator<Item = (K, u64)>,
now: u64,
retention: u64,
) -> Vec<K> {
chains
.into_iter()
.filter(|&(_, last_used)| now.wrapping_sub(last_used) >= retention)
.map(|(key, _)| key)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PingPongKey;
fn key(w: u32, h: u32, levels: u32) -> PingPongKey {
PingPongKey {
size: [w, h],
levels,
}
}
#[test]
fn evict_decision_keeps_a_chain_used_this_frame() {
let k = key(100, 80, 1);
let stale = evict_decision([(k, 5)], 5, RETENTION_FRAMES);
assert!(stale.is_empty());
}
#[test]
fn evict_decision_keeps_a_chain_inside_the_window() {
let k = key(100, 80, 1);
let stale = evict_decision([(k, 2)], 5, RETENTION_FRAMES);
assert!(stale.is_empty());
}
#[test]
fn evict_decision_evicts_a_chain_past_the_window() {
let k = key(100, 80, 1);
let stale = evict_decision([(k, 0)], 8, RETENTION_FRAMES);
assert_eq!(stale, vec![k]);
}
#[test]
fn evict_decision_splits_fresh_from_stale() {
let fresh = key(100, 80, 1);
let stale = key(200, 100, 5);
let out = evict_decision([(fresh, 9), (stale, 0)], 10, RETENTION_FRAMES);
assert_eq!(out, vec![stale]);
}
#[test]
fn evict_decision_is_wrap_safe_across_a_u64_rollover() {
let k = key(100, 80, 1);
let last_used = u64::MAX - 1;
let now = 2; let stale = evict_decision([(k, last_used)], now, RETENTION_FRAMES);
assert!(
stale.is_empty(),
"a 4-frame wrap distance is inside the 8-frame window"
);
}
}