Skip to main content

cache_rs/
entry.rs

1//! Unified Cache Entry Type
2//!
3//! This module provides a unified `CacheEntry<K, V, M>` structure that can be used
4//! across all cache algorithm implementations. The generic `M` parameter allows
5//! each algorithm to store its own metadata without affecting the core entry structure.
6//!
7//! # Design Philosophy
8//!
9//! The unified entry design provides several benefits:
10//! - **Consistency**: All cache algorithms use the same core entry structure
11//! - **Extensibility**: Algorithm-specific metadata via the `M` generic parameter
12//! - **Timestamps**: Built-in creation and last-accessed timestamps for TTL and monitoring
13//! - **Size-awareness**: Explicit size field for dual-limit capacity management
14//!
15//! # Memory Layout
16//!
17//! Each entry consists of:
18//! - `key: K` - User's key type
19//! - `value: V` - User's value type  
20//! - `metadata: CacheMetadata<M>` - Contains size, timestamps, and algorithm-specific data
21//!
22//! `CacheMetadata<M>` contains:
23//! - `size: u64` - 8 bytes (content size tracking)
24//! - `last_accessed: u64` - 8 bytes (timestamps for monitoring)
25//! - `create_time: u64` - 8 bytes (timestamps for TTL)
26//! - `algorithm: M` - Algorithm-specific metadata (0-16 bytes depending on algorithm)
27//!
28//! # Usage Examples
29//!
30//! ```ignore
31//! use cache_rs::entry::{CacheEntry, CacheMetadata};
32//!
33//! // Simple entry without algorithm-specific metadata (e.g., for LRU)
34//! let entry: CacheEntry<String, Vec<u8>> = CacheEntry::new(
35//!     "image.png".to_string(),
36//!     vec![0u8; 1024],
37//!     1024, // size in bytes
38//! );
39//!
40//! // Entry with frequency metadata (e.g., for LFU)
41//! use cache_rs::lfu::LfuMeta;
42//! let entry = CacheEntry::with_algorithm_metadata(
43//!     "key".to_string(),
44//!     "value".to_string(),
45//!     5, // size
46//!     LfuMeta { frequency: 0 },
47//! );
48//! ```
49//!
50//! # Thread Safety
51//!
52//! The single-threaded cache implementations use plain `u64` for timestamps.
53//! For concurrent access, wrap the cache in appropriate synchronization primitives,
54//! or use the concurrent cache implementations which handle synchronization internally.
55
56extern crate alloc;
57
58use core::fmt;
59
60/// Metadata associated with a cache entry.
61///
62/// This struct holds common cache metadata (size, timestamps) plus algorithm-specific
63/// data via the `M` type parameter. Use `()` for algorithms that don't need extra
64/// per-entry metadata (e.g., LRU).
65///
66/// # Type Parameter
67///
68/// - `M`: Algorithm-specific metadata type (e.g., `LfuMeta`, `GdsfMeta`)
69///
70/// # Examples
71///
72/// ```
73/// use cache_rs::entry::CacheMetadata;
74///
75/// // Metadata without algorithm-specific data (for LRU)
76/// let meta: CacheMetadata<()> = CacheMetadata::new(1024);
77/// assert_eq!(meta.size, 1024);
78/// ```
79pub struct CacheMetadata<M = ()> {
80    /// Size of content this entry represents (user-provided).
81    /// For count-based caches, use 1.
82    /// For size-aware caches, use actual bytes (memory, disk, etc.)
83    pub size: u64,
84
85    /// Last access timestamp (nanos since epoch or monotonic clock).
86    pub last_accessed: u64,
87
88    /// Creation timestamp (nanos since epoch or monotonic clock).
89    pub create_time: u64,
90
91    /// Algorithm-specific metadata (frequency, priority, segment, etc.)
92    pub algorithm: M,
93}
94
95impl<M: Default> CacheMetadata<M> {
96    /// Creates new cache metadata with the specified size.
97    ///
98    /// The algorithm-specific metadata is initialized to its default value.
99    ///
100    /// # Arguments
101    ///
102    /// * `size` - Size of the content this entry represents
103    #[inline]
104    pub fn new(size: u64) -> Self {
105        let now = Self::now_nanos();
106        Self {
107            size,
108            last_accessed: now,
109            create_time: now,
110            algorithm: M::default(),
111        }
112    }
113}
114
115impl<M> CacheMetadata<M> {
116    /// Creates new cache metadata with the specified size and algorithm metadata.
117    ///
118    /// # Arguments
119    ///
120    /// * `size` - Size of the content this entry represents
121    /// * `algorithm` - Algorithm-specific metadata
122    #[inline]
123    pub fn with_algorithm(size: u64, algorithm: M) -> Self {
124        let now = Self::now_nanos();
125        Self {
126            size,
127            last_accessed: now,
128            create_time: now,
129            algorithm,
130        }
131    }
132
133    /// Updates the last_accessed timestamp to the current time.
134    #[inline]
135    pub fn touch(&mut self) {
136        self.last_accessed = Self::now_nanos();
137    }
138
139    /// Gets the age of this entry in nanoseconds.
140    #[inline]
141    pub fn age_nanos(&self) -> u64 {
142        Self::now_nanos().saturating_sub(self.create_time)
143    }
144
145    /// Gets the time since last access in nanoseconds.
146    #[inline]
147    pub fn idle_nanos(&self) -> u64 {
148        Self::now_nanos().saturating_sub(self.last_accessed)
149    }
150
151    /// Returns the current time in nanoseconds.
152    #[cfg(feature = "std")]
153    #[inline]
154    fn now_nanos() -> u64 {
155        extern crate std;
156        use std::time::{SystemTime, UNIX_EPOCH};
157        SystemTime::now()
158            .duration_since(UNIX_EPOCH)
159            .map(|d| d.as_nanos() as u64)
160            .unwrap_or(0)
161    }
162
163    /// Returns 0 in no_std environments where system time is not available.
164    #[cfg(not(feature = "std"))]
165    #[inline]
166    fn now_nanos() -> u64 {
167        0
168    }
169}
170
171impl<M: Clone> Clone for CacheMetadata<M> {
172    fn clone(&self) -> Self {
173        Self {
174            size: self.size,
175            last_accessed: self.last_accessed,
176            create_time: self.create_time,
177            algorithm: self.algorithm.clone(),
178        }
179    }
180}
181
182impl<M: fmt::Debug> fmt::Debug for CacheMetadata<M> {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        f.debug_struct("CacheMetadata")
185            .field("size", &self.size)
186            .field("last_accessed", &self.last_accessed)
187            .field("create_time", &self.create_time)
188            .field("algorithm", &self.algorithm)
189            .finish()
190    }
191}
192
193/// Unified cache entry holding key, value, and metadata.
194///
195/// The `M` parameter allows each algorithm to store its own metadata
196/// without affecting the core entry structure. Use `()` for algorithms
197/// that don't need extra per-entry metadata (e.g., LRU).
198///
199/// # Examples
200///
201/// ```
202/// use cache_rs::entry::CacheEntry;
203///
204/// // Create a simple entry for count-based caching
205/// let entry: CacheEntry<&str, i32> = CacheEntry::new("key", 42, 1);
206/// assert_eq!(entry.key, "key");
207/// assert_eq!(entry.value, 42);
208/// assert_eq!(entry.metadata.size, 1);
209/// ```
210pub struct CacheEntry<K, V, M = ()> {
211    /// The cached key
212    pub key: K,
213
214    /// The cached value (or reference/handle to external storage)
215    pub value: V,
216
217    /// Cache metadata including size, timestamps, and algorithm-specific data
218    pub metadata: CacheMetadata<M>,
219}
220
221impl<K, V, M: Default> CacheEntry<K, V, M> {
222    /// Creates a new cache entry without algorithm-specific metadata.
223    ///
224    /// Use this constructor for algorithms like LRU that don't need
225    /// per-entry metadata beyond the basic key, value, and timestamps.
226    ///
227    /// # Arguments
228    ///
229    /// * `key` - The cache key
230    /// * `value` - The cached value
231    /// * `size` - Size of the content this entry represents (use 1 for count-based caches)
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// use cache_rs::entry::CacheEntry;
237    ///
238    /// // Count-based cache entry
239    /// let entry: CacheEntry<&str, String> = CacheEntry::new("user:123", "Alice".to_string(), 1);
240    ///
241    /// // Size-aware cache entry
242    /// let data = vec![0u8; 1024];
243    /// let entry: CacheEntry<&str, Vec<u8>> = CacheEntry::new("file.bin", data, 1024);
244    /// ```
245    #[inline]
246    pub fn new(key: K, value: V, size: u64) -> Self {
247        Self {
248            key,
249            value,
250            metadata: CacheMetadata::new(size),
251        }
252    }
253}
254
255impl<K, V, M> CacheEntry<K, V, M> {
256    /// Creates a new cache entry with algorithm-specific metadata.
257    ///
258    /// Use this constructor for algorithms like LFU, LFUDA, SLRU, or GDSF
259    /// that need to track additional per-entry state.
260    ///
261    /// # Arguments
262    ///
263    /// * `key` - The cache key
264    /// * `value` - The cached value
265    /// * `size` - Size of the content this entry represents (use 1 for count-based caches)
266    /// * `algorithm_meta` - Algorithm-specific metadata
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// use cache_rs::entry::CacheEntry;
272    /// use cache_rs::lfu::LfuMeta;
273    ///
274    /// let entry = CacheEntry::with_algorithm_metadata(
275    ///     "key".to_string(),
276    ///     vec![1, 2, 3],
277    ///     3,
278    ///     LfuMeta { frequency: 0 },
279    /// );
280    /// assert_eq!(entry.metadata.algorithm.frequency, 0);
281    /// ```
282    #[inline]
283    pub fn with_algorithm_metadata(key: K, value: V, size: u64, algorithm_meta: M) -> Self {
284        Self {
285            key,
286            value,
287            metadata: CacheMetadata::with_algorithm(size, algorithm_meta),
288        }
289    }
290
291    /// Updates the last_accessed timestamp to the current time.
292    #[inline]
293    pub fn touch(&mut self) {
294        self.metadata.touch();
295    }
296
297    /// Gets the age of this entry in nanoseconds.
298    #[inline]
299    pub fn age_nanos(&self) -> u64 {
300        self.metadata.age_nanos()
301    }
302
303    /// Gets the time since last access in nanoseconds.
304    #[inline]
305    pub fn idle_nanos(&self) -> u64 {
306        self.metadata.idle_nanos()
307    }
308
309    /// Returns the size of the cached content.
310    #[inline]
311    pub fn size(&self) -> u64 {
312        self.metadata.size
313    }
314}
315
316impl<K: Clone, V: Clone, M: Clone> Clone for CacheEntry<K, V, M> {
317    fn clone(&self) -> Self {
318        Self {
319            key: self.key.clone(),
320            value: self.value.clone(),
321            metadata: self.metadata.clone(),
322        }
323    }
324}
325
326impl<K: fmt::Debug, V: fmt::Debug, M: fmt::Debug> fmt::Debug for CacheEntry<K, V, M> {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        f.debug_struct("CacheEntry")
329            .field("key", &self.key)
330            .field("value", &self.value)
331            .field("metadata", &self.metadata)
332            .finish()
333    }
334}
335
336// CacheEntry is automatically Send + Sync when K, V, M are Send + Sync
337// since all fields (including u64 timestamps) are Send + Sync.
338
339#[cfg(test)]
340mod tests {
341    extern crate alloc;
342
343    use super::*;
344    use alloc::format;
345    use alloc::vec;
346
347    #[test]
348    fn test_new_entry() {
349        let entry: CacheEntry<&str, i32> = CacheEntry::new("key", 42, 1);
350        assert_eq!(entry.key, "key");
351        assert_eq!(entry.value, 42);
352        assert_eq!(entry.metadata.size, 1);
353    }
354
355    #[test]
356    fn test_entry_with_algorithm_metadata() {
357        #[derive(Debug, Clone, PartialEq, Default)]
358        struct TestMeta {
359            frequency: u64,
360        }
361
362        let entry =
363            CacheEntry::with_algorithm_metadata("key", "value", 5, TestMeta { frequency: 10 });
364        assert_eq!(entry.key, "key");
365        assert_eq!(entry.value, "value");
366        assert_eq!(entry.metadata.size, 5);
367        assert_eq!(entry.metadata.algorithm.frequency, 10);
368    }
369
370    #[test]
371    fn test_touch_updates_last_accessed() {
372        let mut entry: CacheEntry<&str, i32> = CacheEntry::new("key", 42, 1);
373        let initial = entry.metadata.last_accessed;
374        entry.touch();
375        let updated = entry.metadata.last_accessed;
376        // In no_std mode both will be 0, in std mode updated >= initial
377        assert!(updated >= initial);
378    }
379
380    #[test]
381    fn test_clone_entry() {
382        #[derive(Debug, Clone, PartialEq, Default)]
383        struct TestMeta {
384            value: u64,
385        }
386
387        let entry =
388            CacheEntry::with_algorithm_metadata("key", vec![1, 2, 3], 3, TestMeta { value: 100 });
389        let cloned = entry.clone();
390
391        assert_eq!(cloned.key, entry.key);
392        assert_eq!(cloned.value, entry.value);
393        assert_eq!(cloned.metadata.size, entry.metadata.size);
394        assert_eq!(cloned.metadata.algorithm, entry.metadata.algorithm);
395    }
396
397    #[test]
398    fn test_age_and_idle() {
399        let mut entry: CacheEntry<&str, i32> = CacheEntry::new("key", 42, 1);
400
401        // In no_std mode, timestamps will be 0
402        // In std mode, age/idle should be small (just created)
403        let _age = entry.age_nanos();
404        let _idle = entry.idle_nanos();
405
406        // After touch, idle_nanos() should work without panicking
407        entry.touch();
408        let _idle_after = entry.idle_nanos();
409    }
410
411    #[test]
412    fn test_metadata_size() {
413        let meta: CacheMetadata<()> = CacheMetadata::new(1024);
414        assert_eq!(meta.size, 1024);
415    }
416
417    #[test]
418    fn test_debug_impl() {
419        let entry: CacheEntry<&str, i32> = CacheEntry::new("key", 42, 1);
420        let debug_str = format!("{:?}", entry);
421        assert!(debug_str.contains("CacheEntry"));
422        assert!(debug_str.contains("key"));
423        assert!(debug_str.contains("42"));
424    }
425}