1#[allow(dead_code)]
7pub struct CacheEntry {
8 pub key: String,
9 pub data: Vec<u8>,
10 pub size_bytes: usize,
11 pub access_count: u64,
12 pub last_access: u64,
13}
14
15#[allow(dead_code)]
16pub struct AssetCache {
17 pub entries: Vec<CacheEntry>,
18 pub max_bytes: usize,
19 pub total_bytes: usize,
20 pub tick: u64,
21 pub hits: u64,
22 pub misses: u64,
23}
24
25#[allow(dead_code)]
26pub fn new_cache(max_bytes: usize) -> AssetCache {
27 AssetCache {
28 entries: Vec::new(),
29 max_bytes,
30 total_bytes: 0,
31 tick: 0,
32 hits: 0,
33 misses: 0,
34 }
35}
36
37#[allow(dead_code)]
38pub fn cache_insert(cache: &mut AssetCache, key: &str, data: Vec<u8>) {
39 if let Some(pos) = cache.entries.iter().position(|e| e.key == key) {
41 let old_size = cache.entries[pos].size_bytes;
42 cache.entries.remove(pos);
43 cache.total_bytes -= old_size;
44 }
45 let size = data.len();
46 cache.tick += 1;
47 let entry = CacheEntry {
48 key: key.to_string(),
49 data,
50 size_bytes: size,
51 access_count: 0,
52 last_access: cache.tick,
53 };
54 cache.entries.push(entry);
55 cache.total_bytes += size;
56 evict_until_fits(cache);
57}
58
59#[allow(dead_code)]
60pub fn cache_get<'a>(cache: &'a mut AssetCache, key: &str) -> Option<&'a [u8]> {
61 cache.tick += 1;
62 let tick = cache.tick;
63 if let Some(pos) = cache.entries.iter().position(|e| e.key == key) {
64 cache.entries[pos].access_count += 1;
65 cache.entries[pos].last_access = tick;
66 cache.hits += 1;
67 Some(&cache.entries[pos].data)
68 } else {
69 cache.misses += 1;
70 None
71 }
72}
73
74#[allow(dead_code)]
75pub fn cache_remove(cache: &mut AssetCache, key: &str) -> bool {
76 if let Some(pos) = cache.entries.iter().position(|e| e.key == key) {
77 let size = cache.entries[pos].size_bytes;
78 cache.entries.remove(pos);
79 cache.total_bytes -= size;
80 true
81 } else {
82 false
83 }
84}
85
86#[allow(dead_code)]
87pub fn cache_contains(cache: &AssetCache, key: &str) -> bool {
88 cache.entries.iter().any(|e| e.key == key)
89}
90
91#[allow(dead_code)]
92pub fn evict_lru(cache: &mut AssetCache) {
93 if cache.entries.is_empty() {
94 return;
95 }
96 let lru_pos = cache
97 .entries
98 .iter()
99 .enumerate()
100 .min_by_key(|(_, e)| e.last_access)
101 .map(|(i, _)| i);
102 let Some(lru_pos) = lru_pos else { return };
103 let size = cache.entries[lru_pos].size_bytes;
104 cache.entries.remove(lru_pos);
105 cache.total_bytes -= size;
106}
107
108#[allow(dead_code)]
109pub fn evict_until_fits(cache: &mut AssetCache) {
110 while cache.total_bytes > cache.max_bytes && !cache.entries.is_empty() {
111 evict_lru(cache);
112 }
113}
114
115#[allow(dead_code)]
116pub fn cache_size(cache: &AssetCache) -> usize {
117 cache.total_bytes
118}
119
120#[allow(dead_code)]
121pub fn cache_count(cache: &AssetCache) -> usize {
122 cache.entries.len()
123}
124
125#[allow(dead_code)]
126pub fn cache_hit_rate(cache: &AssetCache) -> f32 {
127 let total = cache.hits + cache.misses;
128 if total == 0 {
129 return 0.0;
130 }
131 cache.hits as f32 / total as f32
132}
133
134#[allow(dead_code)]
135pub fn cache_clear(cache: &mut AssetCache) {
136 cache.entries.clear();
137 cache.total_bytes = 0;
138}
139
140#[allow(dead_code)]
141pub fn most_accessed(cache: &AssetCache) -> Option<&CacheEntry> {
142 cache.entries.iter().max_by_key(|e| e.access_count)
143}
144
145#[allow(dead_code)]
146pub fn cache_stats(cache: &AssetCache) -> (usize, usize, f32) {
147 (cache_count(cache), cache_size(cache), cache_hit_rate(cache))
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_insert_and_get() {
156 let mut cache = new_cache(1024);
157 cache_insert(&mut cache, "foo", vec![1, 2, 3]);
158 let data = cache_get(&mut cache, "foo");
159 assert_eq!(data, Some([1u8, 2, 3].as_slice()));
160 }
161
162 #[test]
163 fn test_get_miss_returns_none() {
164 let mut cache = new_cache(1024);
165 let data = cache_get(&mut cache, "missing");
166 assert!(data.is_none());
167 }
168
169 #[test]
170 fn test_eviction_when_over_max() {
171 let mut cache = new_cache(10);
172 cache_insert(&mut cache, "a", vec![0u8; 6]);
173 cache_insert(&mut cache, "b", vec![0u8; 6]);
174 assert!(cache.total_bytes <= 10);
176 }
177
178 #[test]
179 fn test_hit_rate_calculation() {
180 let mut cache = new_cache(1024);
181 cache_insert(&mut cache, "x", vec![1]);
182 cache_get(&mut cache, "x");
183 cache_get(&mut cache, "x");
184 cache_get(&mut cache, "missing");
185 let rate = cache_hit_rate(&cache);
186 assert!((rate - 2.0 / 3.0).abs() < 1e-4);
187 }
188
189 #[test]
190 fn test_hit_rate_no_access() {
191 let cache = new_cache(1024);
192 assert_eq!(cache_hit_rate(&cache), 0.0);
193 }
194
195 #[test]
196 fn test_contains() {
197 let mut cache = new_cache(1024);
198 cache_insert(&mut cache, "k", vec![9]);
199 assert!(cache_contains(&cache, "k"));
200 assert!(!cache_contains(&cache, "nope"));
201 }
202
203 #[test]
204 fn test_remove() {
205 let mut cache = new_cache(1024);
206 cache_insert(&mut cache, "del", vec![1, 2]);
207 assert!(cache_remove(&mut cache, "del"));
208 assert!(!cache_contains(&cache, "del"));
209 assert!(!cache_remove(&mut cache, "del"));
210 }
211
212 #[test]
213 fn test_clear() {
214 let mut cache = new_cache(1024);
215 cache_insert(&mut cache, "a", vec![1]);
216 cache_insert(&mut cache, "b", vec![2]);
217 cache_clear(&mut cache);
218 assert_eq!(cache_count(&cache), 0);
219 assert_eq!(cache_size(&cache), 0);
220 }
221
222 #[test]
223 fn test_most_accessed() {
224 let mut cache = new_cache(1024);
225 cache_insert(&mut cache, "a", vec![1]);
226 cache_insert(&mut cache, "b", vec![2]);
227 cache_get(&mut cache, "b");
228 cache_get(&mut cache, "b");
229 cache_get(&mut cache, "a");
230 let top = most_accessed(&cache);
231 assert!(top.is_some());
232 assert_eq!(top.expect("should succeed").key, "b");
233 }
234
235 #[test]
236 fn test_most_accessed_empty() {
237 let cache = new_cache(1024);
238 assert!(most_accessed(&cache).is_none());
239 }
240
241 #[test]
242 fn test_lru_eviction_order() {
243 let mut cache = new_cache(20);
244 cache_insert(&mut cache, "first", vec![0u8; 8]);
245 cache_insert(&mut cache, "second", vec![0u8; 8]);
246 cache_get(&mut cache, "first");
248 cache_insert(&mut cache, "third", vec![0u8; 8]);
250 assert!(!cache_contains(&cache, "second"));
252 assert!(cache_contains(&cache, "first"));
253 }
254
255 #[test]
256 fn test_cache_size_tracking() {
257 let mut cache = new_cache(1024);
258 cache_insert(&mut cache, "a", vec![1, 2, 3]);
259 cache_insert(&mut cache, "b", vec![4, 5]);
260 assert_eq!(cache_size(&cache), 5);
261 }
262
263 #[test]
264 fn test_cache_count() {
265 let mut cache = new_cache(1024);
266 assert_eq!(cache_count(&cache), 0);
267 cache_insert(&mut cache, "a", vec![1]);
268 assert_eq!(cache_count(&cache), 1);
269 cache_insert(&mut cache, "b", vec![2]);
270 assert_eq!(cache_count(&cache), 2);
271 }
272
273 #[test]
274 fn test_cache_stats() {
275 let mut cache = new_cache(1024);
276 cache_insert(&mut cache, "x", vec![9]);
277 cache_get(&mut cache, "x");
278 let (count, size, rate) = cache_stats(&cache);
279 assert_eq!(count, 1);
280 assert_eq!(size, 1);
281 assert!((rate - 1.0).abs() < 1e-4);
282 }
283
284 #[test]
285 fn test_insert_overwrite_same_key() {
286 let mut cache = new_cache(1024);
287 cache_insert(&mut cache, "k", vec![1, 2, 3]);
288 cache_insert(&mut cache, "k", vec![9]);
289 assert_eq!(cache_count(&cache), 1);
290 assert_eq!(cache_size(&cache), 1);
291 }
292
293 #[test]
294 fn test_evict_lru_removes_oldest() {
295 let mut cache = new_cache(1024);
296 cache_insert(&mut cache, "old", vec![1]);
297 cache_insert(&mut cache, "new", vec![2]);
298 evict_lru(&mut cache);
299 assert!(!cache_contains(&cache, "old"));
300 assert!(cache_contains(&cache, "new"));
301 }
302}