From 909b7f337904f6287c092943392c3bd38870db6d Mon Sep 17 00:00:00 2001 From: Panos <25664832+mechpanos@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:12:44 +0300 Subject: [PATCH] perf: hand the loader thread's block definitions to the scene The first edit on a large drawing spent 1147 ms rebuilding the block definition cache: wires-build 2723.3ms ... blk_cache=1147.2 ... memo_miss=186455 Not because the edit invalidated it -- `add_entity_internal` already routes a non-block entity through `bump_entities`, so drawing a line leaves `block_epoch` alone. The cache was empty because `prepare_open_geometry` built it on a throwaway `Scene`, exactly as it did the tessellation memo, and it went the same way. `PreparedOpenGeometry` now carries the definitions across, moved rather than cloned, and `install_prepared_open_geometry` stamps them with the receiving scene's `block_epoch`. That stamp is sound: the document is not touched between the loader finishing and this call, so the definitions describe it exactly. A key that does not match what the scene later asks for simply misses, which is the old behaviour. Co-Authored-By: Claude Opus 5 --- src/scene/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 228cfc276..67a3ed77c 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -443,6 +443,14 @@ pub struct PreparedOpenGeometry { /// exactly the old behaviour. pub tess_memo: HashMap>>, pub tess_guard: u64, + /// Block definitions the loader thread built while tessellating, keyed as + /// `block_defn_cache` keys them but without an epoch — the receiving scene + /// stamps its own. + /// + /// Dropped with the throwaway scene before, which is why the first edit + /// spent 1147 ms rebuilding definitions the loader had just built from the + /// same, unmodified document. + pub block_defns: Vec<(u64, Arc)>, } impl std::fmt::Debug for PreparedOpenGeometry { @@ -451,6 +459,7 @@ impl std::fmt::Debug for PreparedOpenGeometry { .field("wires", &self.wires.len()) .field("interaction_index", &self.interaction_index.is_some()) .field("tess_memo", &self.tess_memo.len()) + .field("block_defns", &self.block_defns.len()) .finish() } } @@ -1031,6 +1040,11 @@ pub fn prepare_open_geometry( // Taken, not cloned: the scene is discarded on the next line. let tess_memo = std::mem::take(&mut *scene.resident_tess_memo.borrow_mut()); let tess_guard = scene.resident_tess_guard.get(); + let block_defns: Vec<(u64, Arc)> = + std::mem::take(&mut *scene.block_defn_cache.borrow_mut()) + .into_iter() + .map(|(key, (_epoch, cache))| (key, cache)) + .collect(); let doc = std::mem::replace(&mut scene.document, CadDocument::new()); ( doc, @@ -1039,6 +1053,7 @@ pub fn prepare_open_geometry( interaction_index, tess_memo, tess_guard, + block_defns, }, ) } @@ -2229,6 +2244,16 @@ impl Scene { *self.resident_tess_memo.borrow_mut() = prepared.tess_memo; self.resident_tess_guard.set(prepared.tess_guard); } + // Same for the block definitions, stamped with this scene's block + // epoch: the document has not been touched between the loader + // finishing and this call, so they describe it exactly. + if !prepared.block_defns.is_empty() { + let epoch = self.block_epoch; + let mut cache = self.block_defn_cache.borrow_mut(); + for (key, defns) in prepared.block_defns { + cache.insert(key, (epoch, defns)); + } + } if let Some(index) = prepared.interaction_index { self.cache_interaction_index(self.geometry_epoch, prepared.wires, index); }