use std::collections::HashMap;
use std::hash::Hash;
use crate::error::{ClawError, ClawResult};
#[derive(Debug)]
pub struct ClawCache<K, V> {
capacity: usize,
order: std::collections::VecDeque<K>,
store: HashMap<K, V>,
}
impl<K, V> ClawCache<K, V>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> ClawResult<Self> {
if capacity == 0 {
return Err(ClawError::Cache("cache capacity must be >= 1".to_string()));
}
Ok(ClawCache {
capacity,
order: std::collections::VecDeque::new(),
store: HashMap::with_capacity(capacity),
})
}
pub fn insert(&mut self, key: K, value: V) {
if self.store.contains_key(&key) {
self.order.retain(|k| k != &key);
} else if self.store.len() >= self.capacity {
if let Some(lru_key) = self.order.pop_front() {
self.store.remove(&lru_key);
}
}
self.order.push_back(key.clone());
self.store.insert(key, value);
}
pub fn get(&mut self, key: &K) -> Option<&V> {
if self.store.contains_key(key) {
self.order.retain(|k| k != key);
self.order.push_back(key.clone());
self.store.get(key)
} else {
None
}
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let value = self.store.remove(key);
if value.is_some() {
self.order.retain(|k| k != key);
}
value
}
pub fn invalidate(&mut self, key: &K) {
if self.store.remove(key).is_some() {
self.order.retain(|k| k != key);
}
}
pub fn clear(&mut self) {
self.store.clear();
self.order.clear();
}
pub fn len(&self) -> usize {
self.store.len()
}
pub fn is_empty(&self) -> bool {
self.store.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub hit_count: u64,
pub miss_count: u64,
pub insert_count: u64,
pub evict_count: u64,
rolling: std::collections::VecDeque<bool>,
}
impl CacheStats {
pub fn new() -> Self {
CacheStats {
hit_count: 0,
miss_count: 0,
insert_count: 0,
evict_count: 0,
rolling: std::collections::VecDeque::with_capacity(1001),
}
}
pub(crate) fn record_hit(&mut self) {
self.hit_count += 1;
if self.rolling.len() >= 1000 {
self.rolling.pop_front();
}
self.rolling.push_back(true);
}
pub(crate) fn record_miss(&mut self) {
self.miss_count += 1;
if self.rolling.len() >= 1000 {
self.rolling.pop_front();
}
self.rolling.push_back(false);
}
pub fn rolling_hit_rate(&self) -> f64 {
if self.rolling.is_empty() {
return 0.0;
}
let hits = self.rolling.iter().filter(|&&h| h).count();
hits as f64 / self.rolling.len() as f64
}
pub fn hit_ratio(&self) -> f64 {
let total = self.hit_count + self.miss_count;
if total == 0 {
0.0
} else {
self.hit_count as f64 / total as f64
}
}
}