use std::collections::HashMap;
pub(super) struct Cache<K, V> {
current_tick: u64,
values: Vec<(K, V, u64)>,
map: HashMap<K, usize>,
prev: Vec<Option<usize>>,
next: Vec<Option<usize>>,
head: Option<usize>,
tail: Option<usize>,
free: Vec<usize>,
}
impl<K: std::hash::Hash + Eq + Clone, V: Clone> Cache<K, V> {
pub fn new() -> Self {
Self {
current_tick: 0,
values: Vec::new(),
map: HashMap::new(),
prev: Vec::new(),
next: Vec::new(),
head: None,
tail: None,
free: Vec::new(),
}
}
pub fn new_tick(&mut self) {
self.current_tick += 1;
}
pub fn current_tick(&self) -> u64 {
self.current_tick
}
pub fn get(&mut self, key: &K) -> Option<&V> {
let idx = *self.map.get(key)?;
self.move_to_front(idx);
Some(&self.values[idx].1)
}
pub fn insert(&mut self, key: K, value: V) {
if let Some(&idx) = self.map.get(&key) {
self.values[idx].1 = value;
self.values[idx].2 = self.current_tick;
self.move_to_front(idx);
return;
}
let tick = self.current_tick;
let idx = match self.free.pop() {
Some(slot) => {
self.values[slot] = (key.clone(), value, tick);
self.prev[slot] = None;
self.next[slot] = None;
slot
}
None => {
let slot = self.values.len();
self.values.push((key.clone(), value, tick));
self.prev.push(None);
self.next.push(None);
slot
}
};
self.map.insert(key, idx);
self.attach_front(idx);
}
pub fn remove(&mut self, key: &K) -> bool {
let Some(idx) = self.map.remove(key) else {
return false;
};
self.detach(idx);
self.free.push(idx);
true
}
#[cfg(feature = "serde")]
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.map.iter().map(move |(k, &idx)| (k, &self.values[idx].1))
}
pub fn peek_lru(&self) -> Option<(&K, &V, u64)> {
let idx = self.tail?;
let (k, v, t) = &self.values[idx];
Some((k, v, *t))
}
fn detach(&mut self, idx: usize) {
let p = self.prev[idx];
let n = self.next[idx];
match p {
Some(pi) => self.next[pi] = n,
None => self.head = n, }
match n {
Some(ni) => self.prev[ni] = p,
None => self.tail = p, }
self.prev[idx] = None;
self.next[idx] = None;
}
fn attach_front(&mut self, idx: usize) {
self.prev[idx] = None;
self.next[idx] = self.head;
if let Some(old_head) = self.head {
self.prev[old_head] = Some(idx);
} else {
self.tail = Some(idx);
}
self.head = Some(idx);
}
fn move_to_front(&mut self, idx: usize) {
if self.head == Some(idx) {
return; }
self.detach(idx);
self.attach_front(idx);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_insert_and_get() {
let mut c: Cache<&str, i32> = Cache::new();
c.insert("a", 1);
c.insert("b", 2);
c.insert("c", 3);
assert_eq!(c.get(&"a"), Some(&1));
assert_eq!(c.get(&"b"), Some(&2));
assert_eq!(c.get(&"c"), Some(&3));
}
#[test]
fn peek_lru_returns_tail() {
let mut c: Cache<&str, i32> = Cache::new();
c.insert("a", 1);
c.insert("b", 2);
c.insert("c", 3);
let (k, v, _t) = c.peek_lru().unwrap();
assert_eq!(*k, "a");
assert_eq!(*v, 1);
}
#[test]
fn tick_stored_with_entry() {
let mut c: Cache<&str, i32> = Cache::new();
c.insert("a", 1);
c.new_tick();
c.new_tick();
c.insert("b", 2);
let (_k, _v, tick) = c.peek_lru().unwrap();
assert_eq!(tick, 0);
}
#[test]
fn update_moves_to_front() {
let mut c: Cache<&str, i32> = Cache::new();
c.insert("a", 1);
c.insert("b", 2);
c.insert("c", 3);
c.insert("a", 10);
let (k, _v, _) = c.peek_lru().unwrap();
assert_eq!(*k, "b");
assert_eq!(c.get(&"a"), Some(&10));
}
#[test]
fn remove_returns_value_and_tick() {
let mut c: Cache<&str, i32> = Cache::new();
c.insert("a", 1);
c.new_tick();
c.insert("b", 2);
assert!(c.remove(&"a"));
assert_eq!(c.get(&"a"), None);
}
#[test]
fn remove_updates_lru_order() {
let mut c: Cache<&str, i32> = Cache::new();
c.insert("a", 1);
c.insert("b", 2);
c.insert("c", 3);
c.remove(&"a");
let (k, _v, _) = c.peek_lru().unwrap();
assert_eq!(*k, "b");
}
#[test]
fn remove_nonexistent_returns_none() {
let mut c: Cache<&str, i32> = Cache::new();
assert!(!c.remove(&"x"));
}
}