oxihuman_core/
cache_entry.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct CacheEntryItem {
9 pub key: String,
10 pub value: Vec<u8>,
11 pub ttl_ms: u64,
12 pub created_at: u64,
13 pub last_access: u64,
14 pub access_count: u64,
15 pub dirty: bool,
16}
17
18#[allow(dead_code)]
20#[derive(Debug, Clone)]
21pub struct CacheStore {
22 entries: Vec<CacheEntryItem>,
23 capacity: usize,
24 current_time: u64,
25}
26
27#[allow(dead_code)]
28impl CacheStore {
29 pub fn new(capacity: usize) -> Self {
30 Self {
31 entries: Vec::new(),
32 capacity: capacity.max(1),
33 current_time: 0,
34 }
35 }
36
37 pub fn advance_time(&mut self, ms: u64) {
38 self.current_time += ms;
39 }
40
41 pub fn put(&mut self, key: &str, value: Vec<u8>, ttl_ms: u64) {
42 if let Some(entry) = self.entries.iter_mut().find(|e| e.key == key) {
43 entry.value = value;
44 entry.ttl_ms = ttl_ms;
45 entry.created_at = self.current_time;
46 entry.last_access = self.current_time;
47 entry.dirty = true;
48 return;
49 }
50 if self.entries.len() >= self.capacity {
51 self.evict_oldest();
52 }
53 self.entries.push(CacheEntryItem {
54 key: key.to_string(),
55 value,
56 ttl_ms,
57 created_at: self.current_time,
58 last_access: self.current_time,
59 access_count: 0,
60 dirty: false,
61 });
62 }
63
64 pub fn get(&mut self, key: &str) -> Option<&[u8]> {
65 let now = self.current_time;
66 if let Some(entry) = self.entries.iter_mut().find(|e| e.key == key) {
67 if entry.ttl_ms > 0 && now - entry.created_at > entry.ttl_ms {
68 return None;
69 }
70 entry.last_access = now;
71 entry.access_count += 1;
72 Some(entry.value.as_slice())
73 } else {
74 None
75 }
76 }
77
78 pub fn contains(&self, key: &str) -> bool {
79 self.entries.iter().any(|e| e.key == key)
80 }
81
82 pub fn remove(&mut self, key: &str) -> bool {
83 let before = self.entries.len();
84 self.entries.retain(|e| e.key != key);
85 self.entries.len() < before
86 }
87
88 fn evict_oldest(&mut self) {
89 if self.entries.is_empty() {
90 return;
91 }
92 let mut oldest_idx = 0;
93 let mut oldest_access = u64::MAX;
94 #[allow(clippy::needless_range_loop)]
95 for i in 0..self.entries.len() {
96 if self.entries[i].last_access < oldest_access {
97 oldest_access = self.entries[i].last_access;
98 oldest_idx = i;
99 }
100 }
101 self.entries.remove(oldest_idx);
102 }
103
104 pub fn evict_expired(&mut self) {
105 let now = self.current_time;
106 self.entries
107 .retain(|e| e.ttl_ms == 0 || now - e.created_at <= e.ttl_ms);
108 }
109
110 pub fn count(&self) -> usize {
111 self.entries.len()
112 }
113
114 pub fn capacity(&self) -> usize {
115 self.capacity
116 }
117
118 pub fn clear(&mut self) {
119 self.entries.clear();
120 }
121
122 pub fn total_bytes(&self) -> usize {
123 self.entries.iter().map(|e| e.value.len()).sum()
124 }
125
126 pub fn most_accessed(&self) -> Option<&str> {
127 self.entries
128 .iter()
129 .max_by_key(|e| e.access_count)
130 .map(|e| e.key.as_str())
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_new() {
140 let cs = CacheStore::new(10);
141 assert_eq!(cs.count(), 0);
142 assert_eq!(cs.capacity(), 10);
143 }
144
145 #[test]
146 fn test_put_and_get() {
147 let mut cs = CacheStore::new(10);
148 cs.put("k", vec![1, 2, 3], 0);
149 assert_eq!(cs.get("k"), Some([1u8, 2, 3].as_slice()));
150 }
151
152 #[test]
153 fn test_contains() {
154 let mut cs = CacheStore::new(10);
155 cs.put("a", vec![1], 0);
156 assert!(cs.contains("a"));
157 assert!(!cs.contains("b"));
158 }
159
160 #[test]
161 fn test_remove() {
162 let mut cs = CacheStore::new(10);
163 cs.put("a", vec![1], 0);
164 assert!(cs.remove("a"));
165 assert!(!cs.contains("a"));
166 }
167
168 #[test]
169 fn test_evict_on_capacity() {
170 let mut cs = CacheStore::new(2);
171 cs.put("a", vec![1], 0);
172 cs.put("b", vec![2], 0);
173 cs.put("c", vec![3], 0);
174 assert_eq!(cs.count(), 2);
175 assert!(cs.contains("c"));
176 }
177
178 #[test]
179 fn test_ttl_expiry() {
180 let mut cs = CacheStore::new(10);
181 cs.put("a", vec![1], 100);
182 cs.advance_time(200);
183 assert!(cs.get("a").is_none());
184 }
185
186 #[test]
187 fn test_evict_expired() {
188 let mut cs = CacheStore::new(10);
189 cs.put("a", vec![1], 50);
190 cs.put("b", vec![2], 0);
191 cs.advance_time(100);
192 cs.evict_expired();
193 assert_eq!(cs.count(), 1);
194 }
195
196 #[test]
197 fn test_total_bytes() {
198 let mut cs = CacheStore::new(10);
199 cs.put("a", vec![1, 2, 3], 0);
200 cs.put("b", vec![4, 5], 0);
201 assert_eq!(cs.total_bytes(), 5);
202 }
203
204 #[test]
205 fn test_most_accessed() {
206 let mut cs = CacheStore::new(10);
207 cs.put("a", vec![1], 0);
208 cs.put("b", vec![2], 0);
209 cs.get("b");
210 cs.get("b");
211 cs.get("a");
212 assert_eq!(cs.most_accessed(), Some("b"));
213 }
214
215 #[test]
216 fn test_clear() {
217 let mut cs = CacheStore::new(10);
218 cs.put("a", vec![1], 0);
219 cs.clear();
220 assert_eq!(cs.count(), 0);
221 }
222}