use std::{cell::RefCell, collections::HashMap};
#[derive(Debug)]
pub struct HandleCache {
style_handles: RefCell<HashMap<u64, u64>>,
hits: RefCell<u64>,
misses: RefCell<u64>,
}
impl HandleCache {
pub fn new() -> Self {
Self {
style_handles: RefCell::new(HashMap::new()),
hits: RefCell::new(0),
misses: RefCell::new(0),
}
}
pub fn get_style_handle(&self, element: u64) -> Option<u64> {
if let Some(handle) = self.style_handles.borrow().get(&element) {
*self.hits.borrow_mut() += 1;
Some(*handle)
} else {
*self.misses.borrow_mut() += 1;
None
}
}
pub fn set_style_handle(&self, element: u64, style_handle: u64) {
self.style_handles
.borrow_mut()
.insert(element, style_handle);
}
pub fn invalidate_style_handle(&self, element: u64) {
self.style_handles.borrow_mut().remove(&element);
}
pub fn clear(&self) {
self.style_handles.borrow_mut().clear();
}
pub fn stats(&self) -> CacheStats {
let hits = *self.hits.borrow();
let misses = *self.misses.borrow();
let total = hits + misses;
let hit_rate = if total > 0 {
(hits as f64 / total as f64) * 100.0
} else {
0.0
};
CacheStats {
hits,
misses,
total,
hit_rate,
size: self.style_handles.borrow().len(),
}
}
}
impl Default for HandleCache {
fn default() -> Self {
Self::new()
}
}
impl Clone for HandleCache {
fn clone(&self) -> Self {
Self {
style_handles: RefCell::new(self.style_handles.borrow().clone()),
hits: RefCell::new(*self.hits.borrow()),
misses: RefCell::new(*self.misses.borrow()),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub total: u64,
pub hit_rate: f64,
pub size: usize,
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
thread_local! {
pub static HANDLE_CACHE: HandleCache = HandleCache::new();
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
impl HandleCache {
pub fn get() -> Self {
HANDLE_CACHE.with(|cache| cache.clone())
}
pub fn with<R>(f: impl FnOnce(&HandleCache) -> R) -> R {
HANDLE_CACHE.with(f)
}
}
#[cfg(not(all(feature = "wit-bindings", target_family = "wasm")))]
impl HandleCache {
pub fn get() -> Self {
Self::new()
}
pub fn with<R>(f: impl FnOnce(&HandleCache) -> R) -> R {
f(&Self::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_miss_returns_none() {
let cache = HandleCache::new();
assert_eq!(cache.get_style_handle(42), None);
}
#[test]
fn test_cache_hit_after_set() {
let cache = HandleCache::new();
cache.set_style_handle(42, 100);
assert_eq!(cache.get_style_handle(42), Some(100));
}
#[test]
fn test_cache_invalidate() {
let cache = HandleCache::new();
cache.set_style_handle(42, 100);
assert_eq!(cache.get_style_handle(42), Some(100));
cache.invalidate_style_handle(42);
assert_eq!(cache.get_style_handle(42), None);
}
#[test]
fn test_cache_clear() {
let cache = HandleCache::new();
cache.set_style_handle(42, 100);
cache.set_style_handle(43, 101);
cache.clear();
assert_eq!(cache.get_style_handle(42), None);
assert_eq!(cache.get_style_handle(43), None);
}
#[test]
fn test_cache_stats() {
let cache = HandleCache::new();
let stats = cache.stats();
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 0);
assert_eq!(stats.total, 0);
assert_eq!(stats.size, 0);
cache.set_style_handle(42, 100);
cache.get_style_handle(42);
cache.get_style_handle(99);
let stats = cache.stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
assert_eq!(stats.total, 2);
assert_eq!(stats.size, 1);
assert!((stats.hit_rate - 50.0).abs() < 0.01); }
#[test]
fn test_cache_multiple_elements() {
let cache = HandleCache::new();
cache.set_style_handle(1, 10);
cache.set_style_handle(2, 20);
cache.set_style_handle(3, 30);
assert_eq!(cache.get_style_handle(1), Some(10));
assert_eq!(cache.get_style_handle(2), Some(20));
assert_eq!(cache.get_style_handle(3), Some(30));
assert_eq!(cache.get_style_handle(4), None);
let stats = cache.stats();
assert_eq!(stats.size, 3);
assert_eq!(stats.hits, 3);
assert_eq!(stats.misses, 1);
}
#[test]
fn test_cache_overwrite() {
let cache = HandleCache::new();
cache.set_style_handle(42, 100);
assert_eq!(cache.get_style_handle(42), Some(100));
cache.set_style_handle(42, 200);
assert_eq!(cache.get_style_handle(42), Some(200));
let stats = cache.stats();
assert_eq!(stats.size, 1); }
}