use alloc::collections::BTreeMap;
use alloc::vec::Vec;
struct Entry {
data: Vec<u8>,
last: u64,
}
pub struct BlockCache {
entries: BTreeMap<u64, Entry>,
tick: u64,
cap_bytes: usize,
cur_bytes: usize,
}
impl BlockCache {
pub fn new(cap_bytes: usize) -> Self {
Self {
entries: BTreeMap::new(),
tick: 0,
cap_bytes,
cur_bytes: 0,
}
}
pub fn get(&mut self, key: u64) -> Option<&[u8]> {
self.tick = self.tick.wrapping_add(1);
let tick = self.tick;
let entry = self.entries.get_mut(&key)?;
entry.last = tick;
Some(&entry.data)
}
pub fn insert(&mut self, key: u64, data: Vec<u8>) {
if self.cap_bytes == 0 || data.len() > self.cap_bytes {
return;
}
if let Some(old) = self.entries.remove(&key) {
self.cur_bytes -= old.data.len();
}
while self.cur_bytes + data.len() > self.cap_bytes {
match self.evict_one() {
Some(freed) => self.cur_bytes -= freed,
None => break,
}
}
self.tick = self.tick.wrapping_add(1);
self.cur_bytes += data.len();
self.entries.insert(
key,
Entry {
data,
last: self.tick,
},
);
}
pub fn len_bytes(&self) -> usize {
self.cur_bytes
}
fn evict_one(&mut self) -> Option<usize> {
let victim = self
.entries
.iter()
.min_by_key(|(_, e)| e.last)
.map(|(k, _)| *k)?;
self.entries.remove(&victim).map(|e| e.data.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn hit_and_miss() {
let mut c = BlockCache::new(1024);
assert!(c.get(1).is_none());
c.insert(1, vec![0xAA; 100]);
assert_eq!(c.get(1).unwrap().len(), 100);
assert_eq!(c.get(1).unwrap()[0], 0xAA);
}
#[test]
fn evicts_least_recently_used() {
let mut c = BlockCache::new(250);
c.insert(1, vec![1u8; 100]);
c.insert(2, vec![2u8; 100]);
assert!(c.get(1).is_some());
c.insert(3, vec![3u8; 100]); assert!(c.get(2).is_none(), "2 was LRU and should be evicted");
assert!(c.get(1).is_some());
assert!(c.get(3).is_some());
assert!(c.len_bytes() <= 250);
}
#[test]
fn zero_cap_disables() {
let mut c = BlockCache::new(0);
c.insert(1, vec![0u8; 10]);
assert!(c.get(1).is_none());
assert_eq!(c.len_bytes(), 0);
}
#[test]
fn reinsert_same_key_replaces() {
let mut c = BlockCache::new(1024);
c.insert(1, vec![1u8; 100]);
c.insert(1, vec![2u8; 50]);
assert_eq!(c.get(1).unwrap().len(), 50);
assert_eq!(c.len_bytes(), 50);
}
}