1use std::collections::{BTreeMap, HashMap, HashSet};
4use std::rc::Rc;
5
6use kurbo::{Affine, BezPath, Rect, Shape};
7
8#[derive(Clone, Default)]
14pub struct CanvasGeometry {
15 pub local_bounds: Option<Rect>,
17 pub path: Option<Rc<BezPath>>,
19}
20
21impl CanvasGeometry {
22 pub fn for_layout(bounds: (f64, f64)) -> Self {
24 Self {
25 local_bounds: Some(Rect::new(0.0, 0.0, bounds.0, bounds.1)),
26 path: None,
27 }
28 }
29}
30
31pub struct PreparedCanvasGeometry {
39 pub world_transform: Affine,
41 pub(crate) is_canvas: bool,
42 pub(crate) is_native: bool,
43 pub layer: usize,
45 pub bounds: (f64, f64),
47 pub surface_transform: Affine,
49 pub coverage_bounds: Option<Rect>,
51 pub local: CanvasGeometry,
53}
54
55impl PreparedCanvasGeometry {
56 pub(crate) fn new(
57 layer: usize,
58 bounds: (f64, f64),
59 surface_transform: Affine,
60 local: CanvasGeometry,
61 ) -> Self {
62 const TILE_CULL_BOUNDS_PAD: f64 = 64.0;
65 let coverage_bounds = local.local_bounds.map(|bounds| {
66 (surface_transform * bounds.to_path(0.1))
67 .bounding_box()
68 .inflate(TILE_CULL_BOUNDS_PAD, TILE_CULL_BOUNDS_PAD)
69 });
70 Self {
71 world_transform: surface_transform,
72 is_canvas: true,
73 is_native: false,
74 layer,
75 bounds,
76 surface_transform,
77 coverage_bounds,
78 local,
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
85pub struct SceneGeometryStats {
86 pub preparations: u64,
88 pub index_updates: u64,
90 pub queries: u64,
92 pub candidate_tests: u64,
94}
95
96#[derive(Default)]
97pub(crate) struct SceneGeometry {
98 records: HashMap<u32, Rc<PreparedCanvasGeometry>>,
99 dirty: HashSet<u32>,
100 pending: Vec<u32>,
101 layers: HashMap<usize, SpatialIndex>,
102 native_layers: HashMap<usize, SpatialIndex>,
103 pub stats: SceneGeometryStats,
104}
105
106impl SceneGeometry {
107 pub fn invalidate(&mut self, id: u32) {
108 if self.dirty.insert(id) {
109 self.pending.push(id);
110 }
111 }
112
113 pub(crate) fn invalidate_existing(&mut self, id: u32) {
114 if self.records.contains_key(&id) {
115 self.invalidate(id);
116 }
117 }
118
119 pub fn take_dirty(&mut self) -> Vec<u32> {
120 let dirty = &self.dirty;
122 self.pending
123 .drain(..)
124 .filter(|id| dirty.contains(id))
125 .collect()
126 }
127
128 pub fn get(&self, id: u32) -> Option<Rc<PreparedCanvasGeometry>> {
129 (!self.dirty.contains(&id))
130 .then(|| self.records.get(&id).cloned())
131 .flatten()
132 }
133
134 pub fn insert(
135 &mut self,
136 id: u32,
137 geometry: PreparedCanvasGeometry,
138 ) -> Rc<PreparedCanvasGeometry> {
139 self.dirty.remove(&id);
140 self.stats.preparations += 1;
141 let changed = self.records.get(&id).is_none_or(|old| {
142 old.layer != geometry.layer
143 || old.coverage_bounds != geometry.coverage_bounds
144 || old.is_canvas != geometry.is_canvas
145 || old.is_native != geometry.is_native
146 || (geometry.is_native
147 && (old.bounds != geometry.bounds
148 || old.surface_transform != geometry.surface_transform))
149 });
150 if changed {
151 if let Some(old) = self.records.get(&id) {
152 if let Some(index) = self.native_layers.get_mut(&old.layer) {
153 index.remove(id);
154 if index.is_empty() {
155 self.native_layers.remove(&old.layer);
156 }
157 }
158 if let Some(index) = self.layers.get_mut(&old.layer) {
159 index.remove(id);
160 if index.is_empty() {
161 self.layers.remove(&old.layer);
162 }
163 }
164 }
165 if geometry.is_canvas {
166 self.layers
167 .entry(geometry.layer)
168 .or_default()
169 .insert(id, geometry.coverage_bounds);
170 }
171 if geometry.is_native {
172 self.native_layers
173 .entry(geometry.layer)
174 .or_default()
175 .insert(
176 id,
177 Some(geometry.surface_transform.transform_rect_bbox(Rect::new(
178 0.0,
179 0.0,
180 geometry.bounds.0,
181 geometry.bounds.1,
182 ))),
183 );
184 }
185 self.stats.index_updates += 1;
186 }
187 let geometry = Rc::new(geometry);
188 self.records.insert(id, geometry.clone());
189 geometry
190 }
191
192 pub fn remove(&mut self, id: u32) {
193 self.dirty.remove(&id);
194 if let Some(old) = self.records.remove(&id) {
195 if let Some(index) = self.native_layers.get_mut(&old.layer) {
196 index.remove(id);
197 if index.is_empty() {
198 self.native_layers.remove(&old.layer);
199 }
200 }
201 if let Some(index) = self.layers.get_mut(&old.layer) {
202 index.remove(id);
203 if index.is_empty() {
204 self.layers.remove(&old.layer);
205 }
206 }
207 }
208 }
209
210 pub fn query(&mut self, layer: usize, regions: &[Rect]) -> Option<Vec<u32>> {
211 let ids = self.query_exact(layer, regions, false);
212 (!ids.is_empty()).then_some(ids)
215 }
216
217 pub(crate) fn query_exact(&mut self, layer: usize, regions: &[Rect], native: bool) -> Vec<u32> {
218 self.stats.queries += 1;
219 let indices = if native {
220 &self.native_layers
221 } else {
222 &self.layers
223 };
224 let Some(index) = indices.get(&layer) else {
225 return Vec::new();
226 };
227 let mut candidates = HashSet::new();
228 for region in regions {
229 index.candidates(*region, &mut candidates);
230 }
231 self.stats.candidate_tests += candidates.len() as u64;
232 let mut ids: Vec<_> = candidates
233 .into_iter()
234 .filter(|id| {
235 regions
236 .iter()
237 .any(|region| intersects(index.bounds[id], *region))
238 })
239 .collect();
240 ids.sort_unstable();
241 ids
242 }
243}
244
245fn finite(rect: Rect) -> bool {
246 [rect.x0, rect.y0, rect.x1, rect.y1]
247 .iter()
248 .all(|x| x.is_finite())
249}
250
251fn intersects(bounds: Option<Rect>, region: Rect) -> bool {
252 let Some(bounds) = bounds.filter(|bounds| finite(*bounds)) else {
253 return true;
254 };
255 !finite(region)
256 || (bounds.x1 >= region.x0
257 && bounds.x0 <= region.x1
258 && bounds.y1 >= region.y0
259 && bounds.y0 <= region.y1)
260}
261
262type Columns = BTreeMap<i64, BTreeMap<i64, HashSet<u32>>>;
266
267#[derive(Clone, Copy)]
268struct CellRange {
269 level: u32,
270 x0: i64,
271 x1: i64,
272 y0: i64,
273 y1: i64,
274}
275
276impl CellRange {
277 fn for_bounds(bounds: Rect) -> Option<Self> {
278 if !finite(bounds) || bounds.width() < 0.0 || bounds.height() < 0.0 {
279 return None;
280 }
281 let span = bounds.width().max(bounds.height()).max(256.0);
282 if !span.is_finite() {
283 return None;
284 }
285 let level = (span / 256.0).log2().ceil().max(0.0) as u32;
286 let range = Self::at_level(bounds, level);
287 ((range.x1 as i128 - range.x0 as i128) <= 1 && (range.y1 as i128 - range.y0 as i128) <= 1)
290 .then_some(range)
291 }
292
293 fn at_level(bounds: Rect, level: u32) -> Self {
294 let size = 256.0 * 2.0f64.powi(level as i32);
295 Self {
296 level,
297 x0: (bounds.x0 / size).floor() as i64,
298 x1: (bounds.x1 / size).floor() as i64,
299 y0: (bounds.y0 / size).floor() as i64,
300 y1: (bounds.y1 / size).floor() as i64,
301 }
302 }
303}
304
305#[derive(Default)]
306pub(crate) struct SpatialIndex {
307 bounds: HashMap<u32, Option<Rect>>,
308 levels: BTreeMap<u32, Columns>,
309 unbounded: HashSet<u32>,
310}
311
312impl SpatialIndex {
313 fn is_empty(&self) -> bool {
314 self.bounds.is_empty()
315 }
316
317 pub(crate) fn insert(&mut self, id: u32, bounds: Option<Rect>) {
318 self.bounds.insert(id, bounds);
319 let Some(range) = bounds.and_then(CellRange::for_bounds) else {
320 self.unbounded.insert(id);
321 return;
322 };
323 let columns = self.levels.entry(range.level).or_default();
324 for x in range.x0..=range.x1 {
325 let rows = columns.entry(x).or_default();
326 for y in range.y0..=range.y1 {
327 rows.entry(y).or_default().insert(id);
328 }
329 }
330 }
331
332 pub(crate) fn remove(&mut self, id: u32) {
333 let Some(bounds) = self.bounds.remove(&id) else {
334 return;
335 };
336 self.unbounded.remove(&id);
337 let Some(range) = bounds.and_then(CellRange::for_bounds) else {
338 return;
339 };
340 let Some(columns) = self.levels.get_mut(&range.level) else {
341 return;
342 };
343 for x in range.x0..=range.x1 {
344 if let Some(rows) = columns.get_mut(&x) {
345 for y in range.y0..=range.y1 {
346 if let Some(ids) = rows.get_mut(&y) {
347 ids.remove(&id);
348 if ids.is_empty() {
349 rows.remove(&y);
350 }
351 }
352 }
353 if rows.is_empty() {
354 columns.remove(&x);
355 }
356 }
357 }
358 if columns.is_empty() {
359 self.levels.remove(&range.level);
360 }
361 }
362
363 pub(crate) fn candidates(&self, region: Rect, result: &mut HashSet<u32>) {
364 if !finite(region) {
365 result.extend(self.bounds.keys().copied());
366 return;
367 }
368 if region.width() < 0.0 || region.height() < 0.0 {
369 return;
370 }
371 result.extend(self.unbounded.iter().copied());
372 for (&level, columns) in &self.levels {
373 let range = CellRange::at_level(region, level);
374 for (_, rows) in columns.range(range.x0..=range.x1) {
375 for (_, ids) in rows.range(range.y0..=range.y1) {
376 result.extend(ids);
377 }
378 }
379 }
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 fn geometry(layer: usize, bounds: Option<Rect>) -> PreparedCanvasGeometry {
388 PreparedCanvasGeometry {
389 world_transform: Affine::IDENTITY,
390 is_canvas: true,
391 is_native: false,
392 layer,
393 bounds: (10.0, 10.0),
394 surface_transform: Affine::IDENTITY,
395 coverage_bounds: bounds,
396 local: CanvasGeometry::default(),
397 }
398 }
399
400 #[test]
401 fn updates_moves_layer_changes_and_removal_replace_old_coverage() {
402 let mut scene = SceneGeometry::default();
403 let a = Rect::new(0.0, 0.0, 100.0, 100.0);
404 let b = Rect::new(4000.0, 0.0, 4100.0, 100.0);
405 scene.insert(1, geometry(0, Some(a)));
406 scene.insert(2, geometry(0, Some(b)));
407 assert_eq!(scene.query(0, &[a]), Some(vec![1]));
408 scene.insert(1, geometry(0, Some(a)));
409 assert_eq!(
410 scene.stats.index_updates, 2,
411 "unchanged coverage keeps its index entries"
412 );
413 scene.insert(1, geometry(0, Some(b)));
414 assert_eq!(scene.query(0, &[a]), None);
415 assert_eq!(scene.query(0, &[b, b]), Some(vec![1, 2]));
416 scene.insert(1, geometry(5, Some(a)));
417 assert_eq!(scene.query(0, &[b]), Some(vec![2]));
418 assert_eq!(scene.query(5, &[a]), Some(vec![1]));
419 scene.invalidate(1);
420 assert!(scene.get(1).is_none());
421 scene.remove(1);
422 assert!(scene.take_dirty().is_empty());
423 assert!(!scene.layers.contains_key(&5));
424 scene.remove(2);
425 assert!(scene.layers.is_empty());
426 }
427
428 #[test]
429 fn spatial_queries_match_linear_oracle_across_sizes_and_mutations() {
430 let mut scene = SceneGeometry::default();
431 let mut seed = 13891u64;
432 let mut next = || {
433 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
434 (seed >> 32) as u32
435 };
436 for id in 0..2000 {
437 let x = next() as f64 % 40000.0 - 20000.0;
438 let y = next() as f64 % 40000.0 - 20000.0;
439 let size = 2.0f64.powi((next() % 18) as i32);
440 scene.insert(
441 id,
442 geometry(
443 id as usize % 3,
444 Some(Rect::new(x, y, x + size, y + size / 3.0)),
445 ),
446 );
447 }
448 scene.insert(2000, geometry(0, None));
449 scene.insert(
450 2001,
451 geometry(0, Some(Rect::new(f64::NAN, 0.0, 10.0, 10.0))),
452 );
453 scene.insert(
454 2002,
455 geometry(0, Some(Rect::new(-f64::MAX, -f64::MAX, f64::MAX, f64::MAX))),
456 );
457 for phase in 0..2 {
458 for _ in 0..150 {
459 let x = next() as f64 % 50000.0 - 25000.0;
460 let y = next() as f64 % 50000.0 - 25000.0;
461 let regions = [
462 Rect::new(x, y, x + 800.0, y + 900.0),
463 Rect::new(-50.0, -50.0, 0.0, 0.0),
464 ];
465 for layer in 0..3 {
466 let mut expected: Vec<_> = scene
467 .records
468 .iter()
469 .filter_map(|(&id, g)| {
470 (g.layer == layer
471 && regions.iter().any(|r| intersects(g.coverage_bounds, *r)))
472 .then_some(id)
473 })
474 .collect();
475 expected.sort_unstable();
476 assert_eq!(scene.query(layer, ®ions).unwrap_or_default(), expected);
477 }
478 }
479 if phase == 0 {
480 for id in (0..2000).step_by(3) {
481 scene.remove(id);
482 }
483 for id in (1..2000).step_by(3) {
484 scene.insert(id, geometry(2, Some(Rect::new(-256.0, -256.0, 0.0, 0.0))));
485 }
486 }
487 }
488 }
489
490 #[test]
491 fn edge_contact_unknown_bounds_and_extreme_queries_are_conservative() {
492 let mut scene = SceneGeometry::default();
493 scene.insert(1, geometry(0, Some(Rect::new(-256.0, -256.0, 0.0, 0.0))));
494 scene.insert(2, geometry(0, None));
495 scene.insert(
496 3,
497 geometry(0, Some(Rect::new(1.0e200, 1.0e200, 1.0e200, 1.0e200))),
498 );
499 assert_eq!(
500 scene.query(0, &[Rect::new(0.0, 0.0, 1.0, 1.0)]),
501 Some(vec![1, 2])
502 );
503 assert_eq!(
504 scene.query(0, &[Rect::new(-f64::MAX, -f64::MAX, f64::MAX, f64::MAX)]),
505 Some(vec![1, 2, 3])
506 );
507 assert_eq!(
508 scene.query(0, &[Rect::new(f64::NEG_INFINITY, 0.0, f64::INFINITY, 1.0)]),
509 Some(vec![1, 2, 3])
510 );
511 }
512
513 #[test]
514 fn fixed_density_queries_do_not_scan_the_collection() {
515 for horizontal in [false, true] {
516 let mut costs = Vec::new();
517 for count in [1000, 10000] {
518 let mut scene = SceneGeometry::default();
519 for id in 0..count {
520 let offset = id as f64 * 200.0;
521 let bounds = if horizontal {
522 Rect::new(offset, 0.0, offset + 100.0, 100.0)
523 } else {
524 Rect::new(0.0, offset, 100.0, offset + 100.0)
525 };
526 scene.insert(id, geometry(0, Some(bounds)));
527 }
528 let region = if horizontal {
529 Rect::new(100000.0, 0.0, 101000.0, 100.0)
530 } else {
531 Rect::new(0.0, 100000.0, 100.0, 101000.0)
532 };
533 assert_eq!(scene.query(0, &[region]).unwrap().len(), 6);
534 costs.push(scene.stats.candidate_tests);
535 assert_eq!(scene.stats.preparations, count as u64);
536 }
537 assert_eq!(costs[0], costs[1]);
538 assert!(
539 costs[1] < 12,
540 "only nearby buckets should reach detailed tests: {costs:?}"
541 );
542 }
543 }
544
545 #[test]
546 fn preparation_preserves_canvas_coverage_padding_and_overflow() {
547 let local = CanvasGeometry {
548 local_bounds: Some(Rect::new(-25.0, -10.0, 125.0, 110.0)),
549 path: None,
550 };
551 let prepared =
552 PreparedCanvasGeometry::new(2, (100.0, 100.0), Affine::translate((10.0, 20.0)), local);
553 assert_eq!(prepared.bounds, (100.0, 100.0));
554 assert_eq!(
555 prepared.coverage_bounds,
556 Some(Rect::new(-79.0, -54.0, 199.0, 194.0))
557 );
558 }
559}