use presentar_core::Size;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CacheKey {
pub widget_id: u64,
pub constraints_hash: u64,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CacheEntry {
pub size: Size,
pub last_used_frame: u64,
}
#[derive(Debug, Default)]
pub struct LayoutCache {
entries: HashMap<CacheKey, CacheEntry>,
current_frame: u64,
hits: usize,
misses: usize,
}
impl LayoutCache {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn get(&mut self, key: CacheKey) -> Option<Size> {
if let Some(entry) = self.entries.get_mut(&key) {
entry.last_used_frame = self.current_frame;
self.hits += 1;
Some(entry.size)
} else {
self.misses += 1;
None
}
}
pub fn insert(&mut self, key: CacheKey, size: Size) {
self.entries.insert(
key,
CacheEntry {
size,
last_used_frame: self.current_frame,
},
);
}
pub fn clear(&mut self) {
self.entries.clear();
self.hits = 0;
self.misses = 0;
}
#[must_use]
pub const fn hits(&self) -> usize {
self.hits
}
#[must_use]
pub const fn misses(&self) -> usize {
self.misses
}
pub fn advance_frame(&mut self) {
self.current_frame += 1;
let threshold = self.current_frame.saturating_sub(2);
self.entries
.retain(|_, entry| entry.last_used_frame >= threshold);
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_new() {
let cache = LayoutCache::new();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_cache_insert_get() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let size = Size::new(50.0, 50.0);
cache.insert(key, size);
assert_eq!(cache.get(key), Some(size));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_cache_miss() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
assert_eq!(cache.get(key), None);
}
#[test]
fn test_cache_clear() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.0, 10.0));
assert!(!cache.is_empty());
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_cache_eviction() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.0, 10.0));
cache.advance_frame();
cache.advance_frame();
cache.advance_frame();
assert!(cache.is_empty());
}
#[test]
fn test_cache_not_evicted_when_used() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.0, 10.0));
for _ in 0..5 {
cache.advance_frame();
let _ = cache.get(key); }
assert!(!cache.is_empty());
}
#[test]
fn test_cache_hits_and_misses() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
assert_eq!(cache.hits(), 0);
assert_eq!(cache.misses(), 0);
let _ = cache.get(key);
assert_eq!(cache.hits(), 0);
assert_eq!(cache.misses(), 1);
cache.insert(key, Size::new(10.0, 10.0));
let _ = cache.get(key);
assert_eq!(cache.hits(), 1);
assert_eq!(cache.misses(), 1);
let _ = cache.get(key);
assert_eq!(cache.hits(), 2);
assert_eq!(cache.misses(), 1);
}
#[test]
fn test_cache_clear_resets_stats() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.0, 10.0));
let _ = cache.get(key);
let _ = cache.get(CacheKey {
widget_id: 2,
constraints_hash: 200,
});
assert_eq!(cache.hits(), 1);
assert_eq!(cache.misses(), 1);
cache.clear();
assert_eq!(cache.hits(), 0);
assert_eq!(cache.misses(), 0);
}
#[test]
fn test_cache_key_equality() {
let key1 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let key2 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
assert_eq!(key1, key2);
}
#[test]
fn test_cache_key_inequality_widget_id() {
let key1 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let key2 = CacheKey {
widget_id: 2,
constraints_hash: 100,
};
assert_ne!(key1, key2);
}
#[test]
fn test_cache_key_inequality_constraints() {
let key1 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let key2 = CacheKey {
widget_id: 1,
constraints_hash: 200,
};
assert_ne!(key1, key2);
}
#[test]
fn test_cache_key_clone() {
let key = CacheKey {
widget_id: 42,
constraints_hash: 999,
};
let cloned = key;
assert_eq!(key, cloned);
}
#[test]
fn test_cache_key_debug() {
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let debug = format!("{:?}", key);
assert!(debug.contains("widget_id"));
assert!(debug.contains("constraints_hash"));
}
#[test]
fn test_cache_multiple_entries() {
let mut cache = LayoutCache::new();
for i in 0..10 {
let key = CacheKey {
widget_id: i,
constraints_hash: i * 100,
};
cache.insert(key, Size::new(i as f32, i as f32));
}
assert_eq!(cache.len(), 10);
for i in 0..10 {
let key = CacheKey {
widget_id: i,
constraints_hash: i * 100,
};
assert_eq!(cache.get(key), Some(Size::new(i as f32, i as f32)));
}
}
#[test]
fn test_cache_overwrite_entry() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.0, 10.0));
assert_eq!(cache.get(key), Some(Size::new(10.0, 10.0)));
cache.insert(key, Size::new(20.0, 20.0));
assert_eq!(cache.get(key), Some(Size::new(20.0, 20.0)));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_cache_eviction_threshold() {
let mut cache = LayoutCache::new();
let key1 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let key2 = CacheKey {
widget_id: 2,
constraints_hash: 200,
};
cache.insert(key1, Size::new(10.0, 10.0));
cache.advance_frame();
cache.insert(key2, Size::new(20.0, 20.0));
cache.advance_frame();
assert_eq!(cache.len(), 2);
let _ = cache.get(key2);
cache.advance_frame();
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(key2), Some(Size::new(20.0, 20.0)));
}
#[test]
fn test_cache_eviction_empty_cache() {
let mut cache = LayoutCache::new();
for _ in 0..10 {
cache.advance_frame();
}
assert!(cache.is_empty());
}
#[test]
fn test_cache_default() {
let cache = LayoutCache::default();
assert!(cache.is_empty());
assert_eq!(cache.hits(), 0);
assert_eq!(cache.misses(), 0);
}
#[test]
fn test_cache_debug() {
let cache = LayoutCache::new();
let debug = format!("{:?}", cache);
assert!(debug.contains("LayoutCache"));
}
#[test]
fn test_cache_with_zero_size() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(0.0, 0.0));
assert_eq!(cache.get(key), Some(Size::new(0.0, 0.0)));
}
#[test]
fn test_cache_with_large_size() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10000.0, 10000.0));
assert_eq!(cache.get(key), Some(Size::new(10000.0, 10000.0)));
}
#[test]
fn test_cache_with_fractional_size() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.5, 20.75));
assert_eq!(cache.get(key), Some(Size::new(10.5, 20.75)));
}
#[test]
fn test_cache_different_widget_same_constraints() {
let mut cache = LayoutCache::new();
let key1 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let key2 = CacheKey {
widget_id: 2,
constraints_hash: 100,
};
cache.insert(key1, Size::new(10.0, 10.0));
cache.insert(key2, Size::new(20.0, 20.0));
assert_eq!(cache.get(key1), Some(Size::new(10.0, 10.0)));
assert_eq!(cache.get(key2), Some(Size::new(20.0, 20.0)));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_cache_same_widget_different_constraints() {
let mut cache = LayoutCache::new();
let key1 = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
let key2 = CacheKey {
widget_id: 1,
constraints_hash: 200,
};
cache.insert(key1, Size::new(10.0, 10.0));
cache.insert(key2, Size::new(20.0, 20.0));
assert_eq!(cache.get(key1), Some(Size::new(10.0, 10.0)));
assert_eq!(cache.get(key2), Some(Size::new(20.0, 20.0)));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_cache_frame_counter_overflow() {
let mut cache = LayoutCache::new();
let key = CacheKey {
widget_id: 1,
constraints_hash: 100,
};
cache.insert(key, Size::new(10.0, 10.0));
for _ in 0..100 {
let _ = cache.get(key);
cache.advance_frame();
}
assert_eq!(cache.get(key), Some(Size::new(10.0, 10.0)));
}
}