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