Skip to main content

dais_document/
cache.rs

1use std::collections::HashMap;
2
3use crate::page::{RenderSize, RenderedPage};
4
5/// LRU cache for rendered pages, keyed on (`page_index`, `render_size`).
6///
7/// Each window gets pages rendered at its own resolution, so the same page
8/// may be cached at multiple sizes (e.g., presenter DPI and audience DPI).
9pub struct PageCache {
10    entries: HashMap<CacheKey, RenderedPage>,
11    order: Vec<CacheKey>,
12    capacity: usize,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16struct CacheKey {
17    page_index: usize,
18    size: RenderSize,
19}
20
21impl PageCache {
22    /// Create a new cache with the given capacity (number of entries).
23    pub fn new(capacity: usize) -> Self {
24        Self {
25            entries: HashMap::with_capacity(capacity),
26            order: Vec::with_capacity(capacity),
27            capacity,
28        }
29    }
30
31    /// Get a cached page, if available.
32    pub fn get(&mut self, page_index: usize, size: RenderSize) -> Option<&RenderedPage> {
33        let key = CacheKey { page_index, size };
34        if self.entries.contains_key(&key) {
35            // Move to end (most recently used)
36            self.order.retain(|k| *k != key);
37            self.order.push(key);
38            self.entries.get(&key)
39        } else {
40            None
41        }
42    }
43
44    /// Insert a rendered page into the cache, evicting the least recently used if full.
45    pub fn insert(&mut self, page_index: usize, size: RenderSize, page: RenderedPage) {
46        let key = CacheKey { page_index, size };
47
48        if self.entries.len() >= self.capacity && !self.entries.contains_key(&key) {
49            // Evict LRU
50            if let Some(evicted) = self.order.first().copied() {
51                self.order.remove(0);
52                self.entries.remove(&evicted);
53            }
54        }
55
56        self.order.retain(|k| *k != key);
57        self.order.push(key);
58        self.entries.insert(key, page);
59    }
60
61    /// Clear the entire cache.
62    pub fn clear(&mut self) {
63        self.entries.clear();
64        self.order.clear();
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    fn dummy_page(id: u8) -> RenderedPage {
73        RenderedPage { data: vec![id; 4], width: 1, height: 1 }
74    }
75
76    #[test]
77    fn cache_insert_and_get() {
78        let mut cache = PageCache::new(5);
79        let size = RenderSize { width: 1920, height: 1080 };
80        cache.insert(0, size, dummy_page(0));
81        assert!(cache.get(0, size).is_some());
82        assert!(cache.get(1, size).is_none());
83    }
84
85    #[test]
86    fn cache_evicts_lru() {
87        let mut cache = PageCache::new(2);
88        let size = RenderSize { width: 1920, height: 1080 };
89        cache.insert(0, size, dummy_page(0));
90        cache.insert(1, size, dummy_page(1));
91        cache.insert(2, size, dummy_page(2)); // should evict page 0
92        assert!(cache.get(0, size).is_none());
93        assert!(cache.get(1, size).is_some());
94        assert!(cache.get(2, size).is_some());
95    }
96}