1use std::collections::HashMap;
27use std::hash::Hash;
28use std::time::{Duration, Instant};
29
30#[derive(Debug, Clone)]
32struct CacheEntry<V> {
33 value: V,
34 inserted_at: Instant,
35 last_accessed: Instant,
36 access_count: u64,
37}
38
39impl<V> CacheEntry<V> {
40 #[inline]
41 fn new(value: V) -> Self {
42 let now = Instant::now();
43 Self {
44 value,
45 inserted_at: now,
46 last_accessed: now,
47 access_count: 0,
48 }
49 }
50
51 #[inline]
52 fn is_expired(&self, ttl: Duration) -> bool {
53 self.inserted_at.elapsed() > ttl
54 }
55
56 #[inline]
57 fn access(&mut self) {
58 self.last_accessed = Instant::now();
59 self.access_count += 1;
60 }
61}
62
63pub struct TtlCache<K, V> {
65 entries: HashMap<K, CacheEntry<V>>,
66 max_capacity: usize,
67 ttl: Duration,
68 hits: u64,
69 misses: u64,
70}
71
72impl<K: Eq + Hash + Clone, V: Clone> TtlCache<K, V> {
73 #[must_use]
79 #[inline]
80 pub fn new(max_capacity: usize, ttl: Duration) -> Self {
81 Self {
82 entries: HashMap::with_capacity(max_capacity),
83 max_capacity,
84 ttl,
85 hits: 0,
86 misses: 0,
87 }
88 }
89
90 #[inline]
92 pub fn insert(&mut self, key: K, value: V) {
93 if self.entries.len() >= self.max_capacity && !self.entries.contains_key(&key) {
95 self.evict_one();
96 }
97
98 self.entries.insert(key, CacheEntry::new(value));
99 }
100
101 #[inline]
103 pub fn get(&mut self, key: &K) -> Option<V> {
104 if self.entries.len() > 10 && self.hits % 100 == 0 {
106 self.cleanup_expired();
107 }
108
109 let is_expired = match self.entries.get(key) {
111 Some(entry) => entry.is_expired(self.ttl),
112 None => {
113 self.misses += 1;
114 return None;
115 }
116 };
117
118 if is_expired {
119 self.entries.remove(key);
121 self.misses += 1;
122 None
123 } else {
124 if let Some(entry) = self.entries.get_mut(key) {
126 entry.access();
127 self.hits += 1;
128 Some(entry.value.clone())
129 } else {
130 self.misses += 1;
131 None
132 }
133 }
134 }
135
136 #[must_use]
138 #[inline]
139 pub fn contains_key(&mut self, key: &K) -> bool {
140 self.get(key).is_some()
141 }
142
143 #[inline]
145 pub fn remove(&mut self, key: &K) -> Option<V> {
146 self.entries.remove(key).map(|e| e.value)
147 }
148
149 #[inline]
151 pub fn clear(&mut self) {
152 self.entries.clear();
153 self.hits = 0;
154 self.misses = 0;
155 }
156
157 #[must_use]
159 #[inline]
160 pub fn len(&self) -> usize {
161 self.entries.len()
162 }
163
164 #[must_use]
166 #[inline]
167 pub fn is_empty(&self) -> bool {
168 self.entries.is_empty()
169 }
170
171 #[must_use]
173 #[inline]
174 pub fn hit_rate(&self) -> f64 {
175 let total = self.hits + self.misses;
176 if total == 0 {
177 0.0
178 } else {
179 self.hits as f64 / total as f64
180 }
181 }
182
183 #[must_use]
185 #[inline]
186 pub fn stats(&self) -> CacheStats {
187 CacheStats {
188 size: self.entries.len(),
189 capacity: self.max_capacity,
190 hits: self.hits,
191 misses: self.misses,
192 hit_rate: self.hit_rate(),
193 }
194 }
195
196 fn cleanup_expired(&mut self) {
198 self.entries.retain(|_, entry| !entry.is_expired(self.ttl));
199 }
200
201 fn evict_one(&mut self) {
203 if let Some(lru_key) = self.find_lru_key() {
204 self.entries.remove(&lru_key);
205 }
206 }
207
208 fn find_lru_key(&self) -> Option<K> {
210 self.entries
211 .iter()
212 .min_by_key(|(_, entry)| entry.last_accessed)
213 .map(|(k, _)| k.clone())
214 }
215}
216
217#[derive(Debug, Clone)]
219pub struct CacheStats {
220 pub size: usize,
222 pub capacity: usize,
224 pub hits: u64,
226 pub misses: u64,
228 pub hit_rate: f64,
230}
231
232pub struct TieredCache<K, V> {
234 l1: TtlCache<K, V>,
235 l2: TtlCache<K, V>,
236}
237
238impl<K: Eq + Hash + Clone, V: Clone> TieredCache<K, V> {
239 #[must_use]
246 #[inline]
247 pub fn new(l1_capacity: usize, l2_capacity: usize, ttl: Duration) -> Self {
248 Self {
249 l1: TtlCache::new(l1_capacity, ttl),
250 l2: TtlCache::new(l2_capacity, ttl),
251 }
252 }
253
254 #[inline]
256 pub fn insert(&mut self, key: K, value: V) {
257 self.l1.insert(key, value);
258 }
259
260 #[inline]
262 pub fn get(&mut self, key: &K) -> Option<V> {
263 if let Some(value) = self.l1.get(key) {
265 return Some(value.clone());
266 }
267
268 if let Some(value) = self.l2.get(key) {
270 let value_clone = value.clone();
271 self.l1.insert(key.clone(), value_clone.clone());
272 return Some(value_clone);
273 }
274
275 None
276 }
277
278 pub fn clear(&mut self) {
280 self.l1.clear();
281 self.l2.clear();
282 }
283
284 #[must_use]
286 #[inline]
287 pub fn stats(&self) -> (CacheStats, CacheStats) {
288 (self.l1.stats(), self.l2.stats())
289 }
290}
291
292pub struct SizedCache<K> {
294 entries: HashMap<K, (Vec<u8>, Instant)>,
295 current_size: usize,
296 max_size: usize,
297 ttl: Duration,
298}
299
300impl<K: Eq + Hash + Clone> SizedCache<K> {
301 #[must_use]
303 #[inline]
304 pub fn new(max_size: usize, ttl: Duration) -> Self {
305 Self {
306 entries: HashMap::new(),
307 current_size: 0,
308 max_size,
309 ttl,
310 }
311 }
312
313 #[inline]
315 pub fn insert(&mut self, key: K, data: Vec<u8>) {
316 let size = data.len();
317
318 while self.current_size + size > self.max_size && !self.entries.is_empty() {
320 self.evict_oldest();
321 }
322
323 if size > self.max_size {
325 return;
326 }
327
328 if let Some((old_data, _)) = self.entries.remove(&key) {
330 self.current_size -= old_data.len();
331 }
332
333 self.entries.insert(key, (data, Instant::now()));
334 self.current_size += size;
335 }
336
337 #[inline]
339 pub fn get(&mut self, key: &K) -> Option<Vec<u8>> {
340 self.cleanup_expired();
341
342 let is_expired = match self.entries.get(key) {
344 Some((_, inserted_at)) => inserted_at.elapsed() >= self.ttl,
345 None => return None,
346 };
347
348 if is_expired {
349 if let Some((data, _)) = self.entries.remove(key) {
351 self.current_size -= data.len();
352 }
353 None
354 } else {
355 self.entries.get(key).map(|(data, _)| data.clone())
357 }
358 }
359
360 #[must_use]
362 #[inline]
363 pub fn current_size(&self) -> usize {
364 self.current_size
365 }
366
367 #[inline]
369 pub fn clear(&mut self) {
370 self.entries.clear();
371 self.current_size = 0;
372 }
373
374 fn cleanup_expired(&mut self) {
375 let ttl = self.ttl;
376 let mut removed_size = 0;
377
378 self.entries.retain(|_, (data, inserted_at)| {
379 if inserted_at.elapsed() >= ttl {
380 removed_size += data.len();
381 false
382 } else {
383 true
384 }
385 });
386
387 self.current_size -= removed_size;
388 }
389
390 fn evict_oldest(&mut self) {
391 if let Some(oldest_key) = self.find_oldest_key() {
392 if let Some((data, _)) = self.entries.remove(&oldest_key) {
393 self.current_size -= data.len();
394 }
395 }
396 }
397
398 fn find_oldest_key(&self) -> Option<K> {
399 self.entries
400 .iter()
401 .min_by_key(|(_, (_, inserted_at))| inserted_at)
402 .map(|(k, _)| k.clone())
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use std::thread;
410
411 #[test]
412 fn test_ttl_cache_basic() {
413 let mut cache = TtlCache::new(10, Duration::from_secs(60));
414
415 cache.insert("key1".to_string(), "value1".to_string());
416 assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
417 assert_eq!(cache.len(), 1);
418 }
419
420 #[test]
421 fn test_ttl_cache_expiration() {
422 let mut cache = TtlCache::new(10, Duration::from_millis(50));
423
424 cache.insert("key1".to_string(), "value1".to_string());
425 assert!(cache.get(&"key1".to_string()).is_some());
426
427 thread::sleep(Duration::from_millis(100));
428 assert!(cache.get(&"key1".to_string()).is_none());
429 }
430
431 #[test]
432 fn test_ttl_cache_eviction() {
433 let mut cache = TtlCache::new(3, Duration::from_secs(60));
434
435 cache.insert(1, "a");
436 cache.insert(2, "b");
437 cache.insert(3, "c");
438
439 cache.get(&1);
441
442 thread::sleep(Duration::from_millis(10));
443
444 cache.insert(4, "d");
446
447 assert!(cache.get(&1).is_some());
448 assert!(cache.get(&2).is_none()); assert!(cache.get(&3).is_some());
450 assert!(cache.get(&4).is_some());
451 }
452
453 #[test]
454 fn test_ttl_cache_stats() {
455 let mut cache = TtlCache::new(10, Duration::from_secs(60));
456
457 cache.insert("key1".to_string(), "value1".to_string());
458
459 cache.get(&"key1".to_string()); cache.get(&"key2".to_string()); let stats = cache.stats();
463 assert_eq!(stats.hits, 1);
464 assert_eq!(stats.misses, 1);
465 assert_eq!(stats.hit_rate, 0.5);
466 }
467
468 #[test]
469 fn test_tiered_cache() {
470 let mut cache = TieredCache::new(2, 5, Duration::from_secs(60));
471
472 cache.insert("key1".to_string(), "value1".to_string());
473 cache.insert("key2".to_string(), "value2".to_string());
474
475 assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
476 }
477
478 #[test]
479 fn test_sized_cache() {
480 let mut cache = SizedCache::new(100, Duration::from_secs(60));
481
482 cache.insert("key1", vec![1u8; 40]);
483 cache.insert("key2", vec![2u8; 40]);
484
485 assert_eq!(cache.current_size(), 80);
486
487 cache.insert("key3", vec![3u8; 40]);
489
490 assert!(cache.get(&"key1").is_none());
491 assert_eq!(cache.get(&"key2"), Some(vec![2u8; 40]));
492 assert_eq!(cache.get(&"key3"), Some(vec![3u8; 40]));
493 }
494
495 #[test]
496 fn test_sized_cache_expiration() {
497 let mut cache = SizedCache::new(100, Duration::from_millis(50));
498
499 cache.insert("key1", vec![1u8; 40]);
500 assert_eq!(cache.get(&"key1"), Some(vec![1u8; 40]));
501
502 thread::sleep(Duration::from_millis(100));
503 assert!(cache.get(&"key1").is_none());
504 assert_eq!(cache.current_size(), 0);
505 }
506}