Skip to main content

sequoia_view/
thumbnail.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::hash::Hash;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use sequoia_core::{ImageSource, Pixels};
8
9static NEXT_ID: AtomicU64 = AtomicU64::new(1);
10
11#[derive(Clone, Default)]
12pub struct ThumbnailCache {
13    inner: Rc<RefCell<HashMap<(String, u32, u32), ImageSource>>>,
14}
15
16impl ThumbnailCache {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    pub fn get_or_insert<K>(
22        &self,
23        key: K,
24        width: u32,
25        height: u32,
26        build: impl FnOnce(Pixels) -> Pixels,
27    ) -> ImageSource
28    where
29        K: Hash + Eq + ToString,
30    {
31        let w = width.max(1);
32        let h = height.max(1);
33        let k = (key.to_string(), w, h);
34        let mut map = self.inner.borrow_mut();
35        if let Some(src) = map.get(&k) {
36            return src.clone();
37        }
38        let buf = build(Pixels::new(w, h));
39        let src = buf.into_source(NEXT_ID.fetch_add(1, Ordering::Relaxed), 0);
40        map.insert(k, src.clone());
41        src
42    }
43
44    pub fn len(&self) -> usize {
45        self.inner.borrow().len()
46    }
47
48    pub fn is_empty(&self) -> bool {
49        self.inner.borrow().is_empty()
50    }
51
52    pub fn clear(&self) {
53        self.inner.borrow_mut().clear();
54    }
55}
56
57pub fn shared_cache() -> ThumbnailCache {
58    thread_local! {
59        static SHARED: ThumbnailCache = ThumbnailCache::new();
60    }
61    SHARED.with(|c| c.clone())
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use sequoia_core::Color;
68
69    #[test]
70    fn build_runs_once_per_key_and_size() {
71        let cache = ThumbnailCache::new();
72        let calls = Rc::new(std::cell::Cell::new(0));
73        let build = {
74            let calls = calls.clone();
75            move |mut px: Pixels| {
76                calls.set(calls.get() + 1);
77                px.set(0, 0, Color::WHITE);
78                px
79            }
80        };
81        let a = cache.get_or_insert("n", 4, 4, build);
82        let b = cache.get_or_insert("n", 4, 4, |px| px);
83        assert_eq!(calls.get(), 1, "drugi raz to hit, nie build");
84        assert_eq!(a.cache_key(), b.cache_key());
85        let _ = cache.get_or_insert("n", 8, 8, |px| px);
86        assert_eq!(cache.len(), 2, "inny rozmiar = osobny wpis");
87    }
88
89    #[test]
90    fn degenerate_size_clamps_to_one() {
91        let cache = ThumbnailCache::new();
92        let src = cache.get_or_insert("z", 0, 0, |px| px);
93        let _ = src;
94        assert_eq!(cache.len(), 1);
95    }
96}