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}