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

commit219b3ba9f7b99f8d65ef7c9d4fe9f8009a7cd71c
parent406ebe8388
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 14:58
perf(spreadsheet): read the colour and group columns once

geometry_to_spreadsheet_columns looked each point's colour and each
group's membership up by name; it reads the columns once now
(point_colors, AttribStore::group). With cce-ui's faster width pass, a
refill with a simnet selected at 57k points: 3.0 ms a frame to 1.6.

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

 CLAUDE.md     | 10 ++++++++++
 src/app.rs    |  7 +++++--
 src/detail.rs |  6 ++++++
 3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index baf9599..3151802 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -3552,6 +3552,16 @@ markers (1.0) and the visualizers' copy and apply (1.0) are what is left,
 with the point markers' duplicate check (0.8 of their 1.1). A fresh 8 MB
 buffer a frame costs 0.2 ms, so reusing buffers was not worth it.
 
+**The spreadsheet's refill, every frame of a replay with it shown**
+(the same day): `geometry_to_spreadsheet_columns` read each point's colour
+and each group's membership by name (`color(p)`, `in_group(g, p)`); it
+reads the columns once now (`point_colors`, `AttribStore::group`). And
+cce-ui's `SheetColumn::max_chars`, the width pass the widget runs over
+every value of every column, is branch-free and spread over the columns
+on several threads for a large table. A refill with a simnet selected at
+57k points: 3.0 ms a frame to 1.6 — the evaluation of the selected node,
+which copies its state out of the cache, the 0.6 left.
+
 **The markers are instanced** (the same day, cce-ui's
 `SceneDraw::instances`): every kind — Show Point Markers, Show Vertex
 Markers, the selected group's, the marked groups' and the spreadsheet
diff --git a/src/app.rs b/src/app.rs
index 385f125..0f2b992 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -7392,12 +7392,15 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
     let mut columns = Vec::with_capacity(headers.len());
     columns.push(SheetColumn::Int((0..n as i64).collect()));
     for g in &groups {
-        columns.push(SheetColumn::Int((0..n).map(|p| geom.points().in_group(g, p) as i64).collect()));
+        // The membership found once: `in_group` at every point looked the
+        // group up by name each time, most of what the table cost a frame.
+        let members = geom.points().group(g).unwrap_or(&[]);
+        columns.push(SheetColumn::Int((0..n).map(|p| members.get(p).copied().unwrap_or(false) as i64).collect()));
     }
     for k in 0..3 {
         columns.push(float(geom.positions().iter().map(|p| p[k]).collect()));
     }
-    let colors: Vec<[f32; 3]> = (0..n).map(|p| geom.color(p)).collect();
+    let colors = geom.point_colors();
     for k in 0..3 {
         columns.push(float(colors.iter().map(|c| c[k]).collect()));
     }
diff --git a/src/detail.rs b/src/detail.rs
index 6a4dc2c..738d807 100644
--- a/src/detail.rs
+++ b/src/detail.rs
@@ -567,6 +567,12 @@ impl AttribStore {
         }
     }
 
+    /// A group's membership, an element a flag: what [`in_group`](Self::in_group)
+    /// reads, the group found once — for a reader walking every element.
+    pub fn group(&self, name: &str) -> Option<&[bool]> {
+        self.groups.get(name).map(Vec::as_slice)
+    }
+
     pub fn in_group(&self, name: &str, i: usize) -> bool {
         self.groups.get(name).and_then(|m| m.get(i)).copied().unwrap_or(false)
     }