alpha2_geometry/raster/
cache.rs1use super::TileCoverage;
4use crate::{IRect, StableHash};
5use alpha2_model::FillRule;
6use kurbo::BezPath;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10#[must_use]
12pub fn coverage_key(path: &BezPath, rule: FillRule, clip: IRect) -> u128 {
13 let mut hash = StableHash::new();
14 hash.bytes(b"coverage");
15 hash.path(path);
16 hash.u32(match rule {
17 FillRule::Nonzero => 0,
18 FillRule::Evenodd => 1,
19 });
20 for edge in [clip.x0, clip.y0, clip.x1, clip.y1] {
21 hash.u32(edge.cast_unsigned());
22 }
23 hash.finish()
24}
25
26#[derive(Debug)]
28pub struct CoverageCache {
29 budget: usize,
30 bytes: usize,
31 clock: u64,
32 entries: HashMap<u128, (Arc<TileCoverage>, u64)>,
33 hits: u64,
34 misses: u64,
35}
36
37pub const DEFAULT_COVERAGE_BUDGET: usize = 64 * 1024 * 1024;
39
40impl Default for CoverageCache {
41 fn default() -> Self {
42 Self::new(DEFAULT_COVERAGE_BUDGET)
43 }
44}
45
46impl CoverageCache {
47 #[must_use]
49 pub fn new(budget: usize) -> Self {
50 Self {
51 budget,
52 bytes: 0,
53 clock: 0,
54 entries: HashMap::new(),
55 hits: 0,
56 misses: 0,
57 }
58 }
59
60 pub fn get(&mut self, key: u128) -> Option<Arc<TileCoverage>> {
62 self.clock += 1;
63 let clock = self.clock;
64 if let Some((coverage, used)) = self.entries.get_mut(&key) {
65 *used = clock;
66 self.hits += 1;
67 Some(Arc::clone(coverage))
68 } else {
69 self.misses += 1;
70 None
71 }
72 }
73
74 pub fn insert(&mut self, key: u128, coverage: Arc<TileCoverage>) {
77 let size = coverage.heap_bytes() + size_of::<TileCoverage>();
78 if size > self.budget {
79 return;
80 }
81 self.clock += 1;
82 if let Some((old, _)) = self.entries.insert(key, (coverage, self.clock)) {
83 self.bytes -= old.heap_bytes() + size_of::<TileCoverage>();
84 }
85 self.bytes += size;
86 while self.bytes > self.budget {
87 let Some(oldest) = self
88 .entries
89 .iter()
90 .min_by_key(|(_, (_, used))| *used)
91 .map(|(key, _)| *key)
92 else {
93 break;
94 };
95 if let Some((evicted, _)) = self.entries.remove(&oldest) {
96 self.bytes -= evicted.heap_bytes() + size_of::<TileCoverage>();
97 }
98 }
99 }
100
101 pub fn clear(&mut self) {
103 self.entries.clear();
104 self.bytes = 0;
105 }
106
107 #[must_use]
109 pub const fn bytes(&self) -> usize {
110 self.bytes
111 }
112
113 #[must_use]
115 pub fn len(&self) -> usize {
116 self.entries.len()
117 }
118
119 #[must_use]
121 pub fn is_empty(&self) -> bool {
122 self.entries.is_empty()
123 }
124
125 #[must_use]
127 pub const fn hit_counts(&self) -> (u64, u64) {
128 (self.hits, self.misses)
129 }
130}