From 18517a20a3e4aacb1d799841eb5723910357cb38 Mon Sep 17 00:00:00 2001 From: Panos <25664832+mechpanos@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:23:06 +0300 Subject: [PATCH] perf: let the first edit after opening a file patch instead of rebuild MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first drawn point costs 946 ms against 17-39 ms for every one after it, and `build=587.67ms` is most of that — a full wire assembly where the following points patch in 1.2 ms. `try_resident_patch` refuses any resident set whose `layout` is `None`, and `install_prepared_open_geometry` inserted the loader's set with exactly that. So the set the file open hands over could never be patched, and the first edit rebuilt all 236 954 wires to add one. The layout is a pure function of the wire order, and the loader already built it for those very wires — `resident_wires_for` stores it in the temporary scene's resident set. It now travels in `PreparedOpenGeometry` like the tessellation memo and the draw-order map, taken by `Arc::ptr_eq` identity so it cannot be paired with a different assembly. `try_resident_patch` also reports why it declines. Each refusal costs a full assembly, and until now the only one that said anything was the shared `Arc`; `no-layout`, `no-entry` and `journal-too-old` were silent. **Not verified.** The headless harness opens through the synchronous path, so `install_prepared_open_geometry` never runs there, and `ZOOM E` does not reach `try_resident_patch` at all — no skip line is emitted for it. This needs a GUI run on the reproducer: the first drawn point should drop well below 946 ms, and if it does not, `resident-patch-skip reason=` now says what is still refusing. Co-Authored-By: Claude Opus 5 --- src/scene/mod.rs | 52 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 187b0d7a5..0cdbcde7b 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -411,7 +411,8 @@ struct ResidentWireSet { layout: Option, } -struct ResidentWireLayout { +#[derive(Clone)] +pub(crate) struct ResidentWireLayout { /// Entity handles in final submission order. A temporarily hidden entity /// remains here with no range so grip commit can restore it in place. order: Vec, @@ -435,6 +436,12 @@ pub struct PreparedOpenGeometry { /// Loader tessellations and their guard; a state mismatch clears the memo. pub tess_memo: HashMap>>, pub tess_guard: u64, + /// The loader's submission-order layout for `wires`. + /// + /// Without it the installed set has `layout: None`, and `try_resident_patch` + /// refuses any set that has none — so the first edit after opening a file + /// could never patch and rebuilt the whole wire assembly instead. + pub(crate) resident_layout: Option, /// Loader block definitions; the receiving scene supplies its own epoch. pub block_defns: Vec<(u64, Arc)>, /// The loader's draw-order map. Computing it walks all 1.17 M entities and @@ -1032,6 +1039,17 @@ pub fn prepare_open_geometry( let tess_memo = std::mem::take(&mut *scene.resident_tess_memo.borrow_mut()); let tess_guard = scene.resident_tess_guard.get(); let draw_depths = scene.draw_depth_cache.borrow_mut().take(); + // The layout the loader already built for exactly these wires. Taken by + // identity so it cannot be paired with a different assembly. + let resident_layout = scene + .resident_wire_sets + .borrow_mut() + .drain() + .find_map(|(_, set)| { + Arc::ptr_eq(&set.wires, &wires) + .then_some(set.layout) + .flatten() + }); let block_defns: Vec<(u64, Arc)> = std::mem::take(&mut *scene.block_defn_cache.borrow_mut()) .into_iter() @@ -1047,6 +1065,7 @@ pub fn prepare_open_geometry( tess_guard, block_defns, draw_depths, + resident_layout, }, ) } @@ -2248,7 +2267,7 @@ impl Scene { epoch: self.geometry_epoch, gen, wires: Arc::clone(&prepared.wires), - layout: None, + layout: prepared.resident_layout, }, ); // Adopt the loader thread's tessellation memo so the first edit patches @@ -5497,19 +5516,33 @@ impl Scene { frozen_layers: Option<&HashSet>, style_viewport: Option, ) -> Option>> { - if self.viewport_style_key(style_viewport) != 0 { - return None; - } let perf = crate::perf::enabled(); + // Each early exit here costs a full wire assembly, so say which one. + let declined = |reason: &str| -> Option>> { + if perf { + crate::perf_record!("[perf] resident-patch-skip reason={reason}"); + } + None + }; + if self.viewport_style_key(style_viewport) != 0 { + return declined("viewport-style"); + } let t_patch = iced::time::Instant::now(); // The entry must exist, be stale, and be uniquely held so we can move // its wires out rather than deep-clone them. let cached_epoch = { let sets = self.resident_wire_sets.borrow(); - let entry = sets.get(&key)?; - if entry.epoch == self.geometry_epoch || entry.layout.is_none() { + let Some(entry) = sets.get(&key) else { + drop(sets); + return declined("no-entry"); + }; + if entry.epoch == self.geometry_epoch { return None; } + if entry.layout.is_none() { + drop(sets); + return declined("no-layout"); + } let strong = Arc::strong_count(&entry.wires); if strong != 1 { if perf { @@ -5519,7 +5552,9 @@ impl Scene { } entry.epoch }; - let deltas = self.replay_since(cached_epoch)?; + let Some(deltas) = self.replay_since(cached_epoch) else { + return declined("journal-too-old"); + }; // Take ownership of the cached assembly (guaranteed unique above). // `prev_gen` is the content id the GPU currently holds for this set — the @@ -10827,6 +10862,7 @@ mod journal_tests { tess_guard: 0, block_defns: Vec::new(), draw_depths: Some(handed_over), + resident_layout: None, }); assert_eq!( *scene.draw_depth_map(),