git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitaff82a3ca779d4aaa81cc600da95cd1d8d05f48f
parent5cd22b0ab2
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 12:37
perf(scene): present the evaluated scene by reference

Every geometry rebuild cloned the whole evaluated Detail to keep it as
scene_base, and every revisualize (a visualizer edit, a light move with
smooth shading) cloned it again to hand present_scene an owned copy —
though the only thing that writes to it is the visualizers. present_scene
now borrows the scene and copies it only when a visualizer applies
(Visualizer::applies / visualizer::any_applies, shared with apply_all);
revisualize takes scene_base and puts it back. The copy was ~12 us on a
2,500-prim scene and ~0.9 ms at 100k prims, about 6% of presenting it.
Same-tree shadow A/B of a slider drag: pixel-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/render.rs     | 27 +++++++++++++++++++++------
 src/visualizer.rs | 13 ++++++++++++-
 2 files changed, 33 insertions(+), 7 deletions(-)

diff --git a/src/render.rs b/src/render.rs
index 4f4eb86..dc50509 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1315,8 +1315,8 @@ impl State {
         // What the visualizers are applied to, kept so an edit to one
         // re-presents this scene rather than running the graph again.
         self.scene_attributes = crate::visualizer::scene_attributes(&geom);
-        self.scene_base = Some(geom.clone());
-        self.present_scene(geom);
+        self.present_scene(&geom);
+        self.scene_base = Some(geom);
 
         // The pull arrows measure the selected node against this new
         // geometry version; a playing simnet reaches here every frame.
@@ -1350,15 +1350,30 @@ impl State {
     }
 
     pub(crate) fn revisualize(&mut self) {
-        if let Some(base) = self.scene_base.clone() {
-            self.present_scene(base);
+        // Taken and put back, not cloned: `present_scene` only reads it.
+        if let Some(base) = self.scene_base.take() {
+            self.present_scene(&base);
+            self.scene_base = Some(base);
         }
     }
 
     /// Everything the viewport draws of an evaluated scene: the visualizers
     /// applied to it, then its fill, its groups, its overlays and its edges.
-    fn present_scene(&mut self, mut geom: crate::detail::Detail) {
-        crate::visualizer::apply_all(&self.visualizers, &mut geom);
+    ///
+    /// Borrowed: the visualizers are the only thing that writes to the
+    /// scene, so only they cost a copy of it. Until 2026-10-06 every rebuild
+    /// and every revisualize cloned the whole Detail to keep `scene_base`,
+    /// visualizers or not (~1 ms at 100k prims).
+    fn present_scene(&mut self, base: &crate::detail::Detail) {
+        let visualized;
+        let geom = if crate::visualizer::any_applies(&self.visualizers) {
+            let mut g = base.clone();
+            crate::visualizer::apply_all(&self.visualizers, &mut g);
+            visualized = g;
+            &visualized
+        } else {
+            base
+        };
 
         let verts = crate::geometry::detail_vertices(&geom);
         self.vertex_count_spheres = verts.len() as u32;
diff --git a/src/visualizer.rs b/src/visualizer.rs
index 495d047..a4811f5 100644
--- a/src/visualizer.rs
+++ b/src/visualizer.rs
@@ -69,6 +69,11 @@ pub struct Visualizer {
 }
 
 impl Visualizer {
+    /// Whether this one does anything: on, and naming an attribute.
+    pub fn applies(&self) -> bool {
+        self.enabled && !self.attribute.trim().is_empty()
+    }
+
     /// A new visualizer on `attribute`, on, with the node's defaults.
     pub fn new(attribute: &str) -> Visualizer {
         Visualizer {
@@ -133,12 +138,18 @@ impl Visualizer {
 /// attribute the scene does not have does nothing: it is a display setting,
 /// and the scene it was made for may come back.
 pub fn apply_all(visualizers: &[Visualizer], geom: &mut Detail) {
-    for v in visualizers.iter().filter(|v| v.enabled && !v.attribute.trim().is_empty()) {
+    for v in visualizers.iter().filter(|v| v.applies()) {
         let mut ignored = None;
         crate::geometry::apply_visualize(geom, &v.as_node(), &mut ignored);
     }
 }
 
+/// Whether [`apply_all`] would change anything: some visualizer is on and
+/// names an attribute.
+pub fn any_applies(visualizers: &[Visualizer]) -> bool {
+    visualizers.iter().any(Visualizer::applies)
+}
+
 /// The visualizers as the settings hold them: one string, a visualizer per
 /// `;`, each `key=value` pairs joined by `|`, with `%`, `|`, `;`, `=`, `"`
 /// and `\` percent-escaped in the values. One string, as the marked groups