graphic design tool
git clone https://git.lucas.co/cce-designer.git
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