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 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-05 09:10:48 +03:00
1 parent 2d2fdacdfd
commit d5d9ceec45
1 file changed
+23
+23
View File
@@ -432,6 +432,17 @@ struct ResidentWireLayout {
pub struct PreparedOpenGeometry {
pub wires: Arc<Vec<WireModel>>,
pub interaction_index: Option<Arc<crate::scene::pick::interaction_index::InteractionIndex>>,
/// 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<Handle, Arc<Vec<WireModel>>>,
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);
}