#![forbid(unsafe_code)]
use std::collections::HashMap;
use crate::core::extent::ChunkId;
#[derive(Debug)]
pub struct MaterializedCache {
entries: HashMap<ChunkId, (Vec<u8>, u64)>,
budget: u64,
used: u64,
clock: u64,
}
impl MaterializedCache {
pub fn new(budget: u64) -> Self {
Self {
entries: HashMap::new(),
budget,
used: 0,
clock: 0,
}
}
pub fn get(&mut self, id: &ChunkId) -> Option<&[u8]> {
let entry = self.entries.get_mut(id)?;
self.clock += 1;
entry.1 = self.clock;
Some(entry.0.as_slice())
}
pub fn insert(&mut self, id: ChunkId, bytes: Vec<u8>) {
if bytes.len() as u64 > self.budget {
if let Some((old, _)) = self.entries.remove(&id) {
self.used = self.used.saturating_sub(old.len() as u64);
}
return;
}
self.clock += 1;
if let Some((old, _)) = self.entries.get(&id) {
self.used = self.used.saturating_sub(old.len() as u64);
}
self.used = self.used.saturating_add(bytes.len() as u64);
self.entries.insert(id, (bytes, self.clock));
while self.used > self.budget && !self.entries.is_empty() {
let victim = self
.entries
.iter()
.min_by_key(|(_, (_, rec))| *rec)
.map(|(k, _)| *k)
.expect("non-empty");
if let Some((bytes, _)) = self.entries.remove(&victim) {
self.used = self.used.saturating_sub(bytes.len() as u64);
}
}
}
pub fn contains(&self, id: &ChunkId) -> bool {
self.entries.contains_key(id)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn used_bytes(&self) -> u64 {
self.used
}
pub fn clear(&mut self) {
self.entries.clear();
self.used = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lru_eviction() {
let mut c = MaterializedCache::new(64);
c.insert(ChunkId::of(b"a"), vec![0u8; 32]);
c.insert(ChunkId::of(b"b"), vec![1u8; 32]);
assert_eq!(c.len(), 2);
let a = ChunkId::of(b"a");
assert!(c.get(&a).is_some());
c.insert(ChunkId::of(b"c"), vec![2u8; 32]);
assert!(c.contains(&a));
assert!(!c.contains(&ChunkId::of(b"b")));
assert!(c.contains(&ChunkId::of(b"c")));
assert!(c.used_bytes() <= 64);
}
#[test]
fn oversized_single_entry() {
let mut c = MaterializedCache::new(16);
let big = ChunkId::of(b"big");
c.insert(big, vec![7u8; 4096]);
assert!(!c.contains(&big));
assert!(c.is_empty());
assert_eq!(c.used_bytes(), 0);
}
#[test]
fn replace_updates_size() {
let mut c = MaterializedCache::new(128);
let id = ChunkId::of(b"x");
c.insert(id, vec![0u8; 64]);
c.insert(id, vec![1u8; 32]);
assert_eq!(c.len(), 1);
assert_eq!(c.used_bytes(), 32);
}
}