1use presentar_core::Size;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub struct CacheKey {
9 pub widget_id: u64,
11 pub constraints_hash: u64,
13}
14
15#[derive(Debug, Clone, Copy)]
17pub(crate) struct CacheEntry {
18 pub size: Size,
20 pub last_used_frame: u64,
22}
23
24#[derive(Debug, Default)]
26pub struct LayoutCache {
27 entries: HashMap<CacheKey, CacheEntry>,
28 current_frame: u64,
29 hits: usize,
30 misses: usize,
31}
32
33impl LayoutCache {
34 #[must_use]
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 #[must_use]
42 pub fn get(&mut self, key: CacheKey) -> Option<Size> {
43 if let Some(entry) = self.entries.get_mut(&key) {
44 entry.last_used_frame = self.current_frame;
45 self.hits += 1;
46 Some(entry.size)
47 } else {
48 self.misses += 1;
49 None
50 }
51 }
52
53 pub fn insert(&mut self, key: CacheKey, size: Size) {
55 self.entries.insert(
56 key,
57 CacheEntry {
58 size,
59 last_used_frame: self.current_frame,
60 },
61 );
62 }
63
64 pub fn clear(&mut self) {
66 self.entries.clear();
67 self.hits = 0;
68 self.misses = 0;
69 }
70
71 #[must_use]
73 pub const fn hits(&self) -> usize {
74 self.hits
75 }
76
77 #[must_use]
79 pub const fn misses(&self) -> usize {
80 self.misses
81 }
82
83 pub fn advance_frame(&mut self) {
85 self.current_frame += 1;
86
87 let threshold = self.current_frame.saturating_sub(2);
89 self.entries
90 .retain(|_, entry| entry.last_used_frame >= threshold);
91 }
92
93 #[must_use]
95 pub fn len(&self) -> usize {
96 self.entries.len()
97 }
98
99 #[must_use]
101 pub fn is_empty(&self) -> bool {
102 self.entries.is_empty()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_cache_new() {
112 let cache = LayoutCache::new();
113 assert!(cache.is_empty());
114 assert_eq!(cache.len(), 0);
115 }
116
117 #[test]
118 fn test_cache_insert_get() {
119 let mut cache = LayoutCache::new();
120 let key = CacheKey {
121 widget_id: 1,
122 constraints_hash: 100,
123 };
124 let size = Size::new(50.0, 50.0);
125
126 cache.insert(key, size);
127 assert_eq!(cache.get(key), Some(size));
128 assert_eq!(cache.len(), 1);
129 }
130
131 #[test]
132 fn test_cache_miss() {
133 let mut cache = LayoutCache::new();
134 let key = CacheKey {
135 widget_id: 1,
136 constraints_hash: 100,
137 };
138
139 assert_eq!(cache.get(key), None);
140 }
141
142 #[test]
143 fn test_cache_clear() {
144 let mut cache = LayoutCache::new();
145 let key = CacheKey {
146 widget_id: 1,
147 constraints_hash: 100,
148 };
149
150 cache.insert(key, Size::new(10.0, 10.0));
151 assert!(!cache.is_empty());
152
153 cache.clear();
154 assert!(cache.is_empty());
155 }
156
157 #[test]
158 fn test_cache_eviction() {
159 let mut cache = LayoutCache::new();
160 let key = CacheKey {
161 widget_id: 1,
162 constraints_hash: 100,
163 };
164
165 cache.insert(key, Size::new(10.0, 10.0));
166
167 cache.advance_frame();
169 cache.advance_frame();
170 cache.advance_frame();
171
172 assert!(cache.is_empty());
174 }
175
176 #[test]
177 fn test_cache_not_evicted_when_used() {
178 let mut cache = LayoutCache::new();
179 let key = CacheKey {
180 widget_id: 1,
181 constraints_hash: 100,
182 };
183
184 cache.insert(key, Size::new(10.0, 10.0));
185
186 for _ in 0..5 {
188 cache.advance_frame();
189 let _ = cache.get(key); }
191
192 assert!(!cache.is_empty());
193 }
194
195 #[test]
196 fn test_cache_hits_and_misses() {
197 let mut cache = LayoutCache::new();
198 let key = CacheKey {
199 widget_id: 1,
200 constraints_hash: 100,
201 };
202
203 assert_eq!(cache.hits(), 0);
204 assert_eq!(cache.misses(), 0);
205
206 let _ = cache.get(key);
208 assert_eq!(cache.hits(), 0);
209 assert_eq!(cache.misses(), 1);
210
211 cache.insert(key, Size::new(10.0, 10.0));
213 let _ = cache.get(key);
214 assert_eq!(cache.hits(), 1);
215 assert_eq!(cache.misses(), 1);
216
217 let _ = cache.get(key);
219 assert_eq!(cache.hits(), 2);
220 assert_eq!(cache.misses(), 1);
221 }
222
223 #[test]
224 fn test_cache_clear_resets_stats() {
225 let mut cache = LayoutCache::new();
226 let key = CacheKey {
227 widget_id: 1,
228 constraints_hash: 100,
229 };
230
231 cache.insert(key, Size::new(10.0, 10.0));
232 let _ = cache.get(key);
233 let _ = cache.get(CacheKey {
234 widget_id: 2,
235 constraints_hash: 200,
236 });
237
238 assert_eq!(cache.hits(), 1);
239 assert_eq!(cache.misses(), 1);
240
241 cache.clear();
242 assert_eq!(cache.hits(), 0);
243 assert_eq!(cache.misses(), 0);
244 }
245
246 #[test]
251 fn test_cache_key_equality() {
252 let key1 = CacheKey {
253 widget_id: 1,
254 constraints_hash: 100,
255 };
256 let key2 = CacheKey {
257 widget_id: 1,
258 constraints_hash: 100,
259 };
260 assert_eq!(key1, key2);
261 }
262
263 #[test]
264 fn test_cache_key_inequality_widget_id() {
265 let key1 = CacheKey {
266 widget_id: 1,
267 constraints_hash: 100,
268 };
269 let key2 = CacheKey {
270 widget_id: 2,
271 constraints_hash: 100,
272 };
273 assert_ne!(key1, key2);
274 }
275
276 #[test]
277 fn test_cache_key_inequality_constraints() {
278 let key1 = CacheKey {
279 widget_id: 1,
280 constraints_hash: 100,
281 };
282 let key2 = CacheKey {
283 widget_id: 1,
284 constraints_hash: 200,
285 };
286 assert_ne!(key1, key2);
287 }
288
289 #[test]
290 fn test_cache_key_clone() {
291 let key = CacheKey {
292 widget_id: 42,
293 constraints_hash: 999,
294 };
295 let cloned = key;
296 assert_eq!(key, cloned);
297 }
298
299 #[test]
300 fn test_cache_key_debug() {
301 let key = CacheKey {
302 widget_id: 1,
303 constraints_hash: 100,
304 };
305 let debug = format!("{:?}", key);
306 assert!(debug.contains("widget_id"));
307 assert!(debug.contains("constraints_hash"));
308 }
309
310 #[test]
315 fn test_cache_multiple_entries() {
316 let mut cache = LayoutCache::new();
317
318 for i in 0..10 {
319 let key = CacheKey {
320 widget_id: i,
321 constraints_hash: i * 100,
322 };
323 cache.insert(key, Size::new(i as f32, i as f32));
324 }
325
326 assert_eq!(cache.len(), 10);
327
328 for i in 0..10 {
330 let key = CacheKey {
331 widget_id: i,
332 constraints_hash: i * 100,
333 };
334 assert_eq!(cache.get(key), Some(Size::new(i as f32, i as f32)));
335 }
336 }
337
338 #[test]
339 fn test_cache_overwrite_entry() {
340 let mut cache = LayoutCache::new();
341 let key = CacheKey {
342 widget_id: 1,
343 constraints_hash: 100,
344 };
345
346 cache.insert(key, Size::new(10.0, 10.0));
347 assert_eq!(cache.get(key), Some(Size::new(10.0, 10.0)));
348
349 cache.insert(key, Size::new(20.0, 20.0));
351 assert_eq!(cache.get(key), Some(Size::new(20.0, 20.0)));
352 assert_eq!(cache.len(), 1);
353 }
354
355 #[test]
360 fn test_cache_eviction_threshold() {
361 let mut cache = LayoutCache::new();
362
363 let key1 = CacheKey {
364 widget_id: 1,
365 constraints_hash: 100,
366 };
367 let key2 = CacheKey {
368 widget_id: 2,
369 constraints_hash: 200,
370 };
371
372 cache.insert(key1, Size::new(10.0, 10.0));
373 cache.advance_frame();
374 cache.insert(key2, Size::new(20.0, 20.0));
375 cache.advance_frame();
376
377 assert_eq!(cache.len(), 2);
379
380 let _ = cache.get(key2);
382 cache.advance_frame();
383
384 assert_eq!(cache.len(), 1);
386 assert_eq!(cache.get(key2), Some(Size::new(20.0, 20.0)));
387 }
388
389 #[test]
390 fn test_cache_eviction_empty_cache() {
391 let mut cache = LayoutCache::new();
392
393 for _ in 0..10 {
395 cache.advance_frame();
396 }
397
398 assert!(cache.is_empty());
399 }
400
401 #[test]
406 fn test_cache_default() {
407 let cache = LayoutCache::default();
408 assert!(cache.is_empty());
409 assert_eq!(cache.hits(), 0);
410 assert_eq!(cache.misses(), 0);
411 }
412
413 #[test]
418 fn test_cache_debug() {
419 let cache = LayoutCache::new();
420 let debug = format!("{:?}", cache);
421 assert!(debug.contains("LayoutCache"));
422 }
423
424 #[test]
429 fn test_cache_with_zero_size() {
430 let mut cache = LayoutCache::new();
431 let key = CacheKey {
432 widget_id: 1,
433 constraints_hash: 100,
434 };
435
436 cache.insert(key, Size::new(0.0, 0.0));
437 assert_eq!(cache.get(key), Some(Size::new(0.0, 0.0)));
438 }
439
440 #[test]
441 fn test_cache_with_large_size() {
442 let mut cache = LayoutCache::new();
443 let key = CacheKey {
444 widget_id: 1,
445 constraints_hash: 100,
446 };
447
448 cache.insert(key, Size::new(10000.0, 10000.0));
449 assert_eq!(cache.get(key), Some(Size::new(10000.0, 10000.0)));
450 }
451
452 #[test]
453 fn test_cache_with_fractional_size() {
454 let mut cache = LayoutCache::new();
455 let key = CacheKey {
456 widget_id: 1,
457 constraints_hash: 100,
458 };
459
460 cache.insert(key, Size::new(10.5, 20.75));
461 assert_eq!(cache.get(key), Some(Size::new(10.5, 20.75)));
462 }
463
464 #[test]
469 fn test_cache_different_widget_same_constraints() {
470 let mut cache = LayoutCache::new();
471
472 let key1 = CacheKey {
473 widget_id: 1,
474 constraints_hash: 100,
475 };
476 let key2 = CacheKey {
477 widget_id: 2,
478 constraints_hash: 100,
479 };
480
481 cache.insert(key1, Size::new(10.0, 10.0));
482 cache.insert(key2, Size::new(20.0, 20.0));
483
484 assert_eq!(cache.get(key1), Some(Size::new(10.0, 10.0)));
485 assert_eq!(cache.get(key2), Some(Size::new(20.0, 20.0)));
486 assert_eq!(cache.len(), 2);
487 }
488
489 #[test]
490 fn test_cache_same_widget_different_constraints() {
491 let mut cache = LayoutCache::new();
492
493 let key1 = CacheKey {
494 widget_id: 1,
495 constraints_hash: 100,
496 };
497 let key2 = CacheKey {
498 widget_id: 1,
499 constraints_hash: 200,
500 };
501
502 cache.insert(key1, Size::new(10.0, 10.0));
503 cache.insert(key2, Size::new(20.0, 20.0));
504
505 assert_eq!(cache.get(key1), Some(Size::new(10.0, 10.0)));
506 assert_eq!(cache.get(key2), Some(Size::new(20.0, 20.0)));
507 assert_eq!(cache.len(), 2);
508 }
509
510 #[test]
515 fn test_cache_frame_counter_overflow() {
516 let mut cache = LayoutCache::new();
517 let key = CacheKey {
518 widget_id: 1,
519 constraints_hash: 100,
520 };
521
522 cache.insert(key, Size::new(10.0, 10.0));
523
524 for _ in 0..100 {
526 let _ = cache.get(key);
527 cache.advance_frame();
528 }
529
530 assert_eq!(cache.get(key), Some(Size::new(10.0, 10.0)));
532 }
533}