From 44eea23c3f0d1b1a01c7882964dde4ab2fb7d9d8 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 5 Jun 2026 08:52:16 +0300 Subject: [PATCH] perf(scene): parallelize block AABB post-pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ROADMAP.md | 5 ++++- src/scene/block_cache.rs | 21 ++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 526c36b9f..5464d30bc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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) diff --git a/src/scene/block_cache.rs b/src/scene/block_cache.rs index d354f2264..0419fa482 100644 --- a/src/scene/block_cache.rs +++ b/src/scene/block_cache.rs @@ -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 = names.to_vec(); - for name in &names { - let mut visited: Vec = 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 = 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;