Skip to main content

sequoia_draw/
cache.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use sequoia_core::{Point, Rect};
6
7use crate::path::{FillRule, Path};
8use crate::raster::{self, FillStyle, RasterOptions};
9use crate::stroke::{self, StrokeStyle};
10use crate::{Span, union_rect};
11
12#[derive(Clone, Debug, PartialEq)]
13pub struct Shape {
14    pub spans: Rc<[Span]>,
15
16    pub bounds: Rect,
17}
18
19impl Shape {
20    pub fn new(spans: Vec<Span>) -> Self {
21        let mut bounds = Rect::ZERO;
22        for span in &spans {
23            bounds = union_rect(bounds, span.rect);
24        }
25        Self {
26            spans: spans.into(),
27            bounds,
28        }
29    }
30
31    pub fn empty() -> Self {
32        Self {
33            spans: Rc::from(Vec::new()),
34            bounds: Rect::ZERO,
35        }
36    }
37
38    pub fn len(&self) -> usize {
39        self.spans.len()
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.spans.is_empty()
44    }
45
46    pub fn iter(&self) -> std::slice::Iter<'_, Span> {
47        self.spans.iter()
48    }
49}
50
51impl<'a> IntoIterator for &'a Shape {
52    type Item = &'a Span;
53    type IntoIter = std::slice::Iter<'a, Span>;
54
55    fn into_iter(self) -> Self::IntoIter {
56        self.spans.iter()
57    }
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
61pub struct ShapeStats {
62    pub hits: u64,
63
64    pub misses: u64,
65
66    pub evictions: u64,
67}
68
69impl ShapeStats {
70    pub fn hit_rate(&self) -> f32 {
71        let total = self.hits + self.misses;
72        if total == 0 {
73            0.0
74        } else {
75            self.hits as f32 / total as f32
76        }
77    }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
81struct Key {
82    kind: u8,
83    fingerprint: u64,
84    style: u64,
85    opts: u64,
86}
87
88#[derive(Debug)]
89struct Entry {
90    shape: Rc<Shape>,
91    used: u64,
92}
93
94pub const DEFAULT_CAPACITY: usize = 256;
95
96#[derive(Debug)]
97pub struct ShapeCache {
98    inner: RefCell<Inner>,
99}
100
101#[derive(Debug)]
102struct Inner {
103    entries: HashMap<Key, Entry>,
104    capacity: usize,
105    tick: u64,
106    stats: ShapeStats,
107}
108
109impl Default for ShapeCache {
110    fn default() -> Self {
111        Self::new(DEFAULT_CAPACITY)
112    }
113}
114
115impl ShapeCache {
116    pub fn new(capacity: usize) -> Self {
117        Self {
118            inner: RefCell::new(Inner {
119                entries: HashMap::new(),
120                capacity,
121                tick: 0,
122                stats: ShapeStats::default(),
123            }),
124        }
125    }
126
127    pub fn capacity(&self) -> usize {
128        self.inner.borrow().capacity
129    }
130
131    pub fn len(&self) -> usize {
132        self.inner.borrow().entries.len()
133    }
134
135    pub fn is_empty(&self) -> bool {
136        self.inner.borrow().entries.is_empty()
137    }
138
139    pub fn stats(&self) -> ShapeStats {
140        self.inner.borrow().stats
141    }
142
143    pub fn reset_stats(&self) {
144        self.inner.borrow_mut().stats = ShapeStats::default();
145    }
146
147    pub fn clear(&self) {
148        self.inner.borrow_mut().entries.clear();
149    }
150
151    pub fn fill(&self, path: &Path, rule: FillRule, opts: RasterOptions) -> Rc<Shape> {
152        self.fill_styled(path, FillStyle::new(rule, opts))
153    }
154
155    pub fn fill_styled(&self, path: &Path, style: FillStyle) -> Rc<Shape> {
156        let key = Key {
157            kind: 0,
158            fingerprint: path.fingerprint(),
159            style: style.rule as u64,
160            opts: opts_key(style.opts),
161        };
162        self.get_or_build(key, |out| {
163            raster::fill(path, style.rule, style.opts, out);
164        })
165    }
166
167    pub fn stroke(&self, path: &Path, style: &StrokeStyle, opts: RasterOptions) -> Rc<Shape> {
168        let key = Key {
169            kind: 1,
170            fingerprint: path.fingerprint(),
171            style: style.fingerprint(),
172            opts: opts_key(opts),
173        };
174        self.get_or_build(key, |out| {
175            stroke::stroke_path(path, style, opts, out);
176        })
177    }
178
179    pub fn fill_contours(
180        &self,
181        contours: &[crate::Contour],
182        rule: FillRule,
183        opts: RasterOptions,
184    ) -> Rc<Shape> {
185        let key = Key {
186            kind: 2,
187            fingerprint: contours_fingerprint(contours),
188            style: rule as u64,
189            opts: opts_key(opts),
190        };
191        self.get_or_build(key, |out| {
192            raster::fill_contours(contours, rule, opts, out);
193        })
194    }
195
196    pub fn disk(&self, center: Point, radius: f32) -> Rc<Shape> {
197        if !(radius > 0.0) || !radius.is_finite() {
198            return Rc::new(Shape::empty());
199        }
200        Rc::new(Shape::new(vec![Span::rounded(
201            Rect::from_xywh(
202                center.x - radius,
203                center.y - radius,
204                radius * 2.0,
205                radius * 2.0,
206            ),
207            radius,
208        )]))
209    }
210
211    pub fn rounded_rect(&self, rect: Rect, radius: f32) -> Rc<Shape> {
212        let radius = radius
213            .clamp(0.0, rect.size.width.min(rect.size.height) * 0.5)
214            .max(0.0);
215        Rc::new(Shape::new(vec![Span::rounded(rect, radius)]))
216    }
217
218    pub fn rect(&self, rect: Rect) -> Rc<Shape> {
219        Rc::new(Shape::new(vec![Span::rect(rect)]))
220    }
221
222    fn get_or_build(&self, key: Key, build: impl FnOnce(&mut Vec<Span>)) -> Rc<Shape> {
223        let mut inner = self.inner.borrow_mut();
224        inner.tick += 1;
225        let tick = inner.tick;
226        if let Some(entry) = inner.entries.get_mut(&key) {
227            entry.used = tick;
228            let shape = entry.shape.clone();
229            inner.stats.hits += 1;
230            return shape;
231        }
232        inner.stats.misses += 1;
233        if inner.capacity == 0 {
234            drop(inner);
235            let mut spans = Vec::new();
236            build(&mut spans);
237            return Rc::new(Shape::new(spans));
238        }
239
240        let mut spans = Vec::new();
241        build(&mut spans);
242        let shape = Rc::new(Shape::new(spans));
243        if inner.entries.len() >= inner.capacity
244            && let Some(oldest) = inner
245                .entries
246                .iter()
247                .min_by_key(|(_, entry)| entry.used)
248                .map(|(key, _)| *key)
249        {
250            inner.entries.remove(&oldest);
251            inner.stats.evictions += 1;
252        }
253        inner.entries.insert(
254            key,
255            Entry {
256                shape: shape.clone(),
257                used: tick,
258            },
259        );
260        shape
261    }
262}
263
264fn opts_key(opts: RasterOptions) -> u64 {
265    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
266    for v in [opts.tolerance, opts.row_height, opts.merge_tol] {
267        hash ^= u64::from(v.to_bits());
268        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
269    }
270    hash ^= u64::from(opts.aa_samples);
271    hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
272    hash ^= u64::from(opts.analytic);
273    hash.wrapping_mul(0x0000_0100_0000_01b3)
274}
275
276fn contours_fingerprint(contours: &[crate::Contour]) -> u64 {
277    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
278    let mut eat = |v: u64| {
279        hash ^= v;
280        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
281    };
282    for c in contours {
283        eat(c.points.len() as u64);
284        eat(u64::from(c.closed));
285        for p in &c.points {
286            eat(u64::from(p.x.to_bits()));
287            eat(u64::from(p.y.to_bits()));
288        }
289    }
290    hash
291}