From d5d9ceec457eb1c8f66b3859934fc97a7e3798ed Mon Sep 17 00:00:00 2001 From: Panos <25664832+mechpanos@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:06:21 +0300 Subject: [PATCH] perf: hand the loader thread's tessellation memo to the scene `prepare_open_geometry` builds the first Model wire set on a throwaway `Scene`, filling its per-entity tessellation memo with an entry for every one of the drawing's 186 455 entities. `install_prepared_open_geometry` then transferred the wire set and the interaction index but not the memo, so the temporary scene took it to the grave. The first geometry edit therefore rebuilt from cold: wires-build 2723.3ms memo_hit=0 memo_miss=186455 against 760 ms once the memo is warm -- 1.3 s of work the loader thread had already done, repeated on the user's first action. `PreparedOpenGeometry` now carries the memo, moved rather than cloned since the scene is dropped immediately afterwards. The guard hash travels with it: the memo is only usable under the state it was built for, and a mismatch on the receiving side clears it, which is what happened unconditionally before. Co-Authored-By: Claude Opus 5 --- src/scene/mod.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index f012cd2e6..f1b8cf7df 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -432,6 +432,17 @@ struct ResidentWireLayout { pub struct PreparedOpenGeometry { pub wires: Arc>, pub interaction_index: Option>, + /// The per-entity tessellation memo the loader thread filled while building + /// `wires`, with the guard hash it was valid under. + /// + /// `prepare_open_geometry` works on a throwaway `Scene`, so this used to be + /// dropped with it and the first geometry edit re-tessellated all 186 455 + /// entities from cold — 1.3 s of work the loader had already done. The + /// guard travels with it because the memo is only usable under the state it + /// was built for; a mismatch on the receiving side clears it, which is + /// exactly the old behaviour. + pub tess_memo: HashMap>>, + pub tess_guard: u64, } impl std::fmt::Debug for PreparedOpenGeometry { @@ -439,6 +450,7 @@ impl std::fmt::Debug for PreparedOpenGeometry { f.debug_struct("PreparedOpenGeometry") .field("wires", &self.wires.len()) .field("interaction_index", &self.interaction_index.is_some()) + .field("tess_memo", &self.tess_memo.len()) .finish() } } @@ -1016,12 +1028,17 @@ pub fn prepare_open_geometry( wires.len(), ); } + // 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 doc = std::mem::replace(&mut scene.document, CadDocument::new()); ( doc, PreparedOpenGeometry { wires, interaction_index, + tess_memo, + tess_guard, }, ) } @@ -2206,6 +2223,12 @@ impl Scene { layout: None, }, ); + // Adopt the loader thread's tessellation memo so the first edit patches + // the entities it touched instead of rebuilding every one of them. + if !prepared.tess_memo.is_empty() { + *self.resident_tess_memo.borrow_mut() = prepared.tess_memo; + self.resident_tess_guard.set(prepared.tess_guard); + } if let Some(index) = prepared.interaction_index { self.cache_interaction_index(self.geometry_epoch, prepared.wires, index); }