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}