Skip to main content

rez_next_cache/
unified_cache.rs

1//! Unified cache interface and implementations
2//!
3//! This module provides a common trait for all cache types in rez-core,
4//! enabling unified management and intelligent caching strategies.
5
6use crate::{CacheError, UnifiedCacheStats};
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use std::fmt::Debug;
10use std::hash::Hash;
11
12/// Unified cache interface for all rez-core cache types
13///
14/// This trait provides a common interface for SolverCache, RepositoryCache,
15/// RexCache, and any future cache implementations. It enables unified
16/// management through the IntelligentCacheManager.
17#[async_trait]
18pub trait UnifiedCache<K, V>: Send + Sync + Debug
19where
20    K: Clone + Hash + Eq + Send + Sync + Debug,
21    V: Clone + Send + Sync + Debug,
22{
23    /// Get a value from the cache
24    ///
25    /// Returns `Some(value)` if the key exists and is valid,
26    /// `None` if the key doesn't exist or has expired.
27    async fn get(&self, key: &K) -> Option<V>;
28
29    /// Put a value into the cache
30    ///
31    /// Stores the key-value pair in the cache. May trigger eviction
32    /// if the cache is at capacity.
33    async fn put(&self, key: K, value: V) -> Result<(), CacheError>;
34
35    /// Remove a value from the cache
36    ///
37    /// Returns `true` if the key was present and removed,
38    /// `false` if the key was not found.
39    async fn remove(&self, key: &K) -> bool;
40
41    /// Check if a key exists in the cache
42    ///
43    /// Returns `true` if the key exists and is valid,
44    /// `false` otherwise.
45    async fn contains_key(&self, key: &K) -> bool;
46
47    /// Get cache statistics
48    ///
49    /// Returns comprehensive statistics about cache performance,
50    /// including hit rates, memory usage, and entry counts.
51    async fn get_stats(&self) -> UnifiedCacheStats;
52
53    /// Clear all entries from the cache
54    ///
55    /// Removes all cached entries. This operation cannot be undone.
56    async fn clear(&self) -> Result<(), CacheError>;
57
58    /// Get the current number of entries in the cache
59    async fn size(&self) -> usize;
60
61    /// Check if the cache is empty
62    async fn is_empty(&self) -> bool {
63        self.size().await == 0
64    }
65
66    /// Get cache capacity (maximum number of entries)
67    async fn capacity(&self) -> usize;
68
69    /// Get cache type identifier
70    fn cache_type(&self) -> &'static str;
71}
72
73/// Cache entry metadata for unified management
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct CacheEntryMetadata {
76    /// Entry creation timestamp (Unix timestamp)
77    pub created_at: u64,
78    /// Last access timestamp (Unix timestamp)
79    pub last_accessed: u64,
80    /// Access count
81    pub access_count: u64,
82    /// Time-to-live in seconds
83    pub ttl: u64,
84    /// Entry size in bytes (estimated)
85    pub size_bytes: u64,
86    /// Cache level (L1, L2, etc.)
87    pub cache_level: CacheLevel,
88    /// Priority score for eviction decisions
89    pub priority_score: f64,
90}
91
92impl CacheEntryMetadata {
93    /// Create new metadata for a cache entry
94    pub fn new(ttl: u64, size_bytes: u64, cache_level: CacheLevel) -> Self {
95        let now = std::time::SystemTime::now()
96            .duration_since(std::time::UNIX_EPOCH)
97            .map(|d| d.as_secs())
98            .unwrap_or(0);
99
100        Self {
101            created_at: now,
102            last_accessed: now,
103            access_count: 0,
104            ttl,
105            size_bytes,
106            cache_level,
107            priority_score: 1.0,
108        }
109    }
110
111    /// Mark the entry as accessed
112    pub fn mark_accessed(&mut self) {
113        self.access_count += 1;
114        self.last_accessed = std::time::SystemTime::now()
115            .duration_since(std::time::UNIX_EPOCH)
116            .map(|d| d.as_secs())
117            .unwrap_or(0);
118    }
119
120    /// Check if the entry is expired
121    pub fn is_expired(&self) -> bool {
122        let now = std::time::SystemTime::now()
123            .duration_since(std::time::UNIX_EPOCH)
124            .map(|d| d.as_secs())
125            .unwrap_or(0);
126        now > self.created_at + self.ttl
127    }
128
129    /// Calculate cache retention score for eviction decisions
130    pub fn retention_score(&self) -> f64 {
131        let age_factor =
132            1.0 / (1.0 + (self.last_accessed as f64 - self.created_at as f64) / 3600.0);
133        let frequency_factor = (self.access_count as f64).ln().max(1.0);
134        let size_factor = 1.0 / (1.0 + self.size_bytes as f64 / 1024.0);
135
136        self.priority_score * frequency_factor * age_factor * size_factor
137    }
138}
139
140/// Cache level enumeration for multi-level caching
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142pub enum CacheLevel {
143    /// L1 memory cache (fastest access)
144    L1,
145    /// L2 disk cache (larger capacity)
146    L2,
147    /// L3 remote cache (highest capacity)
148    L3,
149}
150
151impl CacheLevel {
152    /// Get the access speed ranking (lower is faster)
153    pub fn speed_rank(&self) -> u8 {
154        match self {
155            CacheLevel::L1 => 1,
156            CacheLevel::L2 => 2,
157            CacheLevel::L3 => 3,
158        }
159    }
160
161    /// Get the capacity ranking (higher is larger)
162    pub fn capacity_rank(&self) -> u8 {
163        match self {
164            CacheLevel::L1 => 1,
165            CacheLevel::L2 => 2,
166            CacheLevel::L3 => 3,
167        }
168    }
169}
170
171/// Wrapper for cache entries with metadata
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct CacheEntry<V> {
174    /// The cached value
175    pub value: V,
176    /// Entry metadata
177    pub metadata: CacheEntryMetadata,
178}
179
180impl<V> CacheEntry<V> {
181    /// Create a new cache entry
182    pub fn new(value: V, ttl: u64, size_bytes: u64, cache_level: CacheLevel) -> Self {
183        Self {
184            value,
185            metadata: CacheEntryMetadata::new(ttl, size_bytes, cache_level),
186        }
187    }
188
189    /// Mark the entry as accessed and return the value
190    pub fn access(&mut self) -> &V {
191        self.metadata.mark_accessed();
192        &self.value
193    }
194
195    /// Check if the entry is expired
196    pub fn is_expired(&self) -> bool {
197        self.metadata.is_expired()
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_cache_entry_metadata() {
207        let metadata = CacheEntryMetadata::new(3600, 1024, CacheLevel::L1);
208        assert_eq!(metadata.ttl, 3600);
209        assert_eq!(metadata.size_bytes, 1024);
210        assert_eq!(metadata.cache_level, CacheLevel::L1);
211        assert!(!metadata.is_expired());
212    }
213
214    #[test]
215    fn test_cache_level_rankings() {
216        assert!(CacheLevel::L1.speed_rank() < CacheLevel::L2.speed_rank());
217        assert!(CacheLevel::L2.speed_rank() < CacheLevel::L3.speed_rank());
218        assert!(CacheLevel::L1.capacity_rank() < CacheLevel::L2.capacity_rank());
219    }
220
221    #[test]
222    fn test_cache_entry() {
223        let entry = CacheEntry::new("test_value".to_string(), 3600, 100, CacheLevel::L1);
224        assert_eq!(entry.value, "test_value");
225        assert!(!entry.is_expired());
226    }
227}