perf(scene): parallelize block AABB post-pass

Extends Phase 2.1. compute_block_aabbs resolved each defn's union AABB
serially via defn_aabb_recursive, which only reads the finished defns map
and re-walks shared nested defns with no memoization — real cost on
block-heavy drawings. Split into a parallel read phase (per-name resolves
fan out over rayon) and a serial write-back phase. BlockCache is Sync, so
sharing &self across the read phase is sound.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-05 08:52:16 +03:00
1 parent 9b112ce12c
commit 44eea23c3f
2 files changed
+20 -6

No files matched your search

+4 -1
View File
@@ -114,7 +114,10 @@ No topological stratification was needed after all: `build_defn` stores
nested INSERTs as by-name references (`LocalSub::Nested`) and never expands
them at build time, so each defn depends only on the read-only `doc` — the
builds are embarrassingly parallel. Now a plain rayon `par_iter().collect()`.
`compute_block_aabbs` stays a serial post-pass (resolves nested refs, cheap).
`compute_block_aabbs` is also parallelized: its `defn_aabb_recursive` walk is
read-only over the finished `defns` map and re-walks shared nested defns
(no memo), so the per-name resolves fan out across rayon (read phase), then a
serial phase stores each AABB back.
### 2.2 Incremental wire cache (delta tessellation)
+16 -5
View File
@@ -151,11 +151,22 @@ impl BlockCache {
/// called in. Cycle guard: a self-referential block keeps an empty AABB
/// (will fail every frustum test → not emitted, which is correct).
fn compute_block_aabbs(&mut self, names: &[String]) {
// Snapshot defn pointers up front — we mutate the map below.
let names: Vec<String> = names.to_vec();
for name in &names {
let mut visited: Vec<String> = Vec::new();
let aabb = self.defn_aabb_recursive(name, &mut visited);
use rayon::prelude::*;
// Phase 1 (parallel, read-only): each defn's union AABB is resolved
// by `defn_aabb_recursive`, which only *reads* `self.defns` (the map
// is fully built by now). There's no memoization, so a defn shared by
// many parents is re-walked per parent — real work on block-heavy
// drawings — and the per-name walks are independent, so they fan out.
let this: &Self = self;
let resolved: Vec<(&String, [f32; 4])> = names
.par_iter()
.map(|name| {
let mut visited: Vec<String> = Vec::new();
(name, this.defn_aabb_recursive(name, &mut visited))
})
.collect();
// Phase 2 (serial): store the AABB back into each defn.
for (name, aabb) in resolved {
if let Some(defn_arc) = self.defns.get_mut(name) {
let mut defn = (**defn_arc).clone();
defn.aabb_local = aabb;