use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
const DEFAULT_CACHE_SIZE: usize = 10;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ScreenshotMetadata {
pub origin_x: f64,
pub origin_y: f64,
pub scale: f64,
pub window_id: Option<u32>,
pub pixel_width: u32,
pub pixel_height: u32,
}
#[derive(Debug, Clone)]
pub struct CachedScreenshot {
pub png_data: Vec<u8>,
pub metadata: ScreenshotMetadata,
access_order: u64,
}
pub struct ScreenshotCache {
entries: HashMap<String, CachedScreenshot>,
max_size: usize,
counter: AtomicU64,
}
impl Default for ScreenshotCache {
fn default() -> Self {
Self::new(DEFAULT_CACHE_SIZE)
}
}
#[allow(dead_code)]
impl ScreenshotCache {
pub fn new(max_size: usize) -> Self {
Self {
entries: HashMap::with_capacity(max_size),
max_size,
counter: AtomicU64::new(0),
}
}
pub fn store(&mut self, png_data: Vec<u8>, metadata: ScreenshotMetadata) -> String {
let id = self.next_id();
if self.entries.len() >= self.max_size {
self.evict_lru();
}
let access_order = self.counter.fetch_add(1, Ordering::Relaxed);
self.entries.insert(
id.clone(),
CachedScreenshot {
png_data,
metadata,
access_order,
},
);
id
}
pub fn get(&mut self, id: &str) -> Option<&CachedScreenshot> {
if let Some(entry) = self.entries.get_mut(id) {
entry.access_order = self.counter.fetch_add(1, Ordering::Relaxed);
}
self.entries.get(id)
}
pub fn peek(&self, id: &str) -> Option<&CachedScreenshot> {
self.entries.get(id)
}
pub fn contains(&self, id: &str) -> bool {
self.entries.contains_key(id)
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn next_id(&self) -> String {
let n = self.counter.fetch_add(1, Ordering::Relaxed);
format!("screenshot-{}", n)
}
fn evict_lru(&mut self) {
if let Some((lru_id, _)) = self
.entries
.iter()
.min_by_key(|(_, entry)| entry.access_order)
{
let lru_id = lru_id.clone();
self.entries.remove(&lru_id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_metadata() -> ScreenshotMetadata {
ScreenshotMetadata {
origin_x: 0.0,
origin_y: 0.0,
scale: 2.0,
window_id: None,
pixel_width: 100,
pixel_height: 100,
}
}
#[test]
fn test_store_and_get() {
let mut cache = ScreenshotCache::new(5);
let data = vec![1, 2, 3, 4];
let id = cache.store(data.clone(), make_metadata());
let retrieved = cache.get(&id).unwrap();
assert_eq!(retrieved.png_data, data);
}
#[test]
fn test_lru_eviction() {
let mut cache = ScreenshotCache::new(3);
let id1 = cache.store(vec![1], make_metadata());
let id2 = cache.store(vec![2], make_metadata());
let id3 = cache.store(vec![3], make_metadata());
assert_eq!(cache.len(), 3);
cache.get(&id1);
let _id4 = cache.store(vec![4], make_metadata());
assert_eq!(cache.len(), 3);
assert!(cache.contains(&id1));
assert!(!cache.contains(&id2)); assert!(cache.contains(&id3));
}
#[test]
fn test_clear() {
let mut cache = ScreenshotCache::new(5);
cache.store(vec![1], make_metadata());
cache.store(vec![2], make_metadata());
assert_eq!(cache.len(), 2);
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_unique_ids() {
let mut cache = ScreenshotCache::new(10);
let id1 = cache.store(vec![1], make_metadata());
let id2 = cache.store(vec![2], make_metadata());
let id3 = cache.store(vec![3], make_metadata());
assert_ne!(id1, id2);
assert_ne!(id2, id3);
assert_ne!(id1, id3);
}
#[test]
fn test_get_nonexistent_id() {
let mut cache = ScreenshotCache::new(5);
cache.store(vec![1], make_metadata());
assert!(cache.get("nonexistent").is_none());
}
#[test]
fn test_peek_does_not_update_lru() {
let mut cache = ScreenshotCache::new(3);
let id1 = cache.store(vec![1], make_metadata());
let id2 = cache.store(vec![2], make_metadata());
let id3 = cache.store(vec![3], make_metadata());
let _ = cache.peek(&id1);
let _id4 = cache.store(vec![4], make_metadata());
assert!(!cache.contains(&id1)); assert!(cache.contains(&id2));
assert!(cache.contains(&id3));
}
#[test]
fn test_cache_size_one() {
let mut cache = ScreenshotCache::new(1);
let id1 = cache.store(vec![1], make_metadata());
assert!(cache.contains(&id1));
let id2 = cache.store(vec![2], make_metadata());
assert!(!cache.contains(&id1)); assert!(cache.contains(&id2));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_metadata_preserved() {
let mut cache = ScreenshotCache::new(5);
let metadata = ScreenshotMetadata {
origin_x: 100.5,
origin_y: 200.5,
scale: 2.5,
window_id: Some(42),
pixel_width: 1920,
pixel_height: 1080,
};
let id = cache.store(vec![1, 2, 3], metadata);
let retrieved = cache.get(&id).unwrap();
assert!((retrieved.metadata.origin_x - 100.5).abs() < f64::EPSILON);
assert!((retrieved.metadata.origin_y - 200.5).abs() < f64::EPSILON);
assert!((retrieved.metadata.scale - 2.5).abs() < f64::EPSILON);
assert_eq!(retrieved.metadata.window_id, Some(42));
assert_eq!(retrieved.metadata.pixel_width, 1920);
assert_eq!(retrieved.metadata.pixel_height, 1080);
}
}