sequoia_view/
thumbnail.rs1use 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}