Skip to main content

agentic_reality/cache/
lru.rs

1use super::metrics::CacheMetrics;
2use std::collections::HashMap;
3use std::hash::Hash;
4use std::time::{Duration, Instant};
5
6struct CacheEntry<V> {
7    value: V,
8    inserted_at: Instant,
9    last_accessed: Instant,
10}
11
12pub struct LruCache<K, V> {
13    store: HashMap<K, CacheEntry<V>>,
14    max_size: usize,
15    ttl: Duration,
16    metrics: CacheMetrics,
17}
18
19impl<K: Eq + Hash + Clone, V: Clone> LruCache<K, V> {
20    pub fn new(max_size: usize, ttl: Duration) -> Self {
21        Self {
22            store: HashMap::with_capacity(max_size),
23            max_size,
24            ttl,
25            metrics: CacheMetrics::new(),
26        }
27    }
28    pub fn get(&mut self, key: &K) -> Option<V> {
29        let now = Instant::now();
30        if let Some(entry) = self.store.get_mut(key) {
31            if now.duration_since(entry.inserted_at) > self.ttl {
32                self.store.remove(key);
33                self.metrics.record_eviction();
34                self.metrics.record_miss();
35                return None;
36            }
37            entry.last_accessed = now;
38            self.metrics.record_hit();
39            return Some(entry.value.clone());
40        }
41        self.metrics.record_miss();
42        None
43    }
44    pub fn insert(&mut self, key: K, value: V) {
45        if self.store.len() >= self.max_size && !self.store.contains_key(&key) {
46            self.evict_lru();
47        }
48        let now = Instant::now();
49        self.store.insert(
50            key,
51            CacheEntry {
52                value,
53                inserted_at: now,
54                last_accessed: now,
55            },
56        );
57        self.metrics.set_size(self.store.len());
58    }
59    pub fn invalidate(&mut self, key: &K) -> bool {
60        let removed = self.store.remove(key).is_some();
61        if removed {
62            self.metrics.record_eviction();
63            self.metrics.set_size(self.store.len());
64        }
65        removed
66    }
67    pub fn clear(&mut self) {
68        self.store.clear();
69        self.metrics.set_size(0);
70    }
71    pub fn contains(&self, key: &K) -> bool {
72        self.store
73            .get(key)
74            .is_some_and(|e| Instant::now().duration_since(e.inserted_at) <= self.ttl)
75    }
76    pub fn len(&self) -> usize {
77        self.store.len()
78    }
79    pub fn is_empty(&self) -> bool {
80        self.store.is_empty()
81    }
82    pub fn metrics(&self) -> &CacheMetrics {
83        &self.metrics
84    }
85    fn evict_lru(&mut self) {
86        if let Some(key) = self
87            .store
88            .iter()
89            .min_by_key(|(_, e)| e.last_accessed)
90            .map(|(k, _)| k.clone())
91        {
92            self.store.remove(&key);
93            self.metrics.record_eviction();
94        }
95    }
96}