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