Skip to content

Commit c05c8c1

Browse files
mike-wardclaude
andcommitted
fix: inspector perf — pass Layout by ref, reduce allocs
- Pass &Layout in recursive pick/tree/snapshot fns (avoid struct copy) - Build ancestor paths incrementally (avoid slice+join per level) - Iterative flat-row search with early return (avoid array + linear scan) - Clamp wireframe content rect to non-negative - Bump cap_inspector 5→8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7ec8d2e commit c05c8c1

1 file changed

Lines changed: 41 additions & 20 deletions

File tree

inspector.v

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module gui
77

88
const ns_inspector = 'gui.inspector'
99
const ns_inspector_width = 'gui.inspector.w'
10-
const cap_inspector = 5
10+
const cap_inspector = 8
1111
const inspector_id_focus = u32(0xFFF00000)
1212
const inspector_id_scroll_panel = u32(0xFFF00001)
1313
const inspector_tree_id = '__inspector_tree__'
@@ -24,6 +24,13 @@ const inspector_icon_style = TextStyle{
2424
color: Color{220, 220, 220, 255}
2525
}
2626

27+
// InspectorStackFrame is a stack entry for iterative tree walk.
28+
struct InspectorStackFrame {
29+
nodes []TreeNodeCfg
30+
mut:
31+
pos int
32+
}
33+
2734
// InspectorNodeProps snapshots shape properties as values
2835
// so the properties panel can display them after layout_clear.
2936
struct InspectorNodeProps {
@@ -182,8 +189,11 @@ fn inspector_select(path string, mut w Window) {
182189
// Expand selected node and all ancestors.
183190
tree_map[path] = true
184191
parts := path.split('.')
192+
mut prefix := parts[0]
193+
tree_map[prefix] = true
185194
for i in 1 .. parts.len {
186-
tree_map[parts[..i].join('.')] = true
195+
prefix += '.${parts[i]}'
196+
tree_map[prefix] = true
187197
}
188198
w.view_state.tree_state.set(inspector_tree_id, tree_map)
189199
}
@@ -202,7 +212,7 @@ fn inspector_pick_path(layout &Layout, x f32, y f32) string {
202212
}
203213

204214
// inspector_pick_recurse depth-first reverse-child walk.
205-
fn inspector_pick_recurse(layout Layout, path string, x f32, y f32) string {
215+
fn inspector_pick_recurse(layout &Layout, path string, x f32, y f32) string {
206216
if layout.shape == unsafe { nil } {
207217
return ''
208218
}
@@ -242,7 +252,7 @@ fn inspector_build_tree_nodes(layout &Layout, selected string, mut props map[str
242252
// subtree into tree nodes, caching props for each node.
243253
// When path matches selected, property leaf nodes are
244254
// appended as children.
245-
fn inspector_layout_to_tree(layout Layout, path string, selected string, mut props map[string]InspectorNodeProps) []TreeNodeCfg {
255+
fn inspector_layout_to_tree(layout &Layout, path string, selected string, mut props map[string]InspectorNodeProps) []TreeNodeCfg {
246256
label := inspector_node_label(layout.shape)
247257
p := inspector_snapshot_props(layout)
248258
props[path] = p
@@ -412,7 +422,7 @@ fn inspector_props_nodes(p InspectorNodeProps) []TreeNodeCfg {
412422

413423
// inspector_snapshot_props captures shape properties as
414424
// plain values for the properties panel.
415-
fn inspector_snapshot_props(layout Layout) InspectorNodeProps {
425+
fn inspector_snapshot_props(layout &Layout) InspectorNodeProps {
416426
shape := layout.shape
417427
if shape == unsafe { nil } {
418428
return InspectorNodeProps{}
@@ -541,8 +551,8 @@ fn inspector_inject_wireframe(mut w Window) {
541551
w.renderers << Renderer(DrawStrokeRect{
542552
x: shape.x + shape.padding.left
543553
y: shape.y + shape.padding.top
544-
w: shape.width - shape.padding.left - shape.padding.right
545-
h: shape.height - shape.padding.top - shape.padding.bottom
554+
w: f32_max(0, shape.width - shape.padding.left - shape.padding.right)
555+
h: f32_max(0, shape.height - shape.padding.top - shape.padding.bottom)
546556
radius: 0
547557
color: rgba(0, 200, 0, 150).to_gx_color()
548558
thickness: 1
@@ -619,22 +629,33 @@ fn inspector_apply_scroll_to(panel_h f32, mut w Window) {
619629
}
620630

621631
// inspector_flat_row_index returns the flat row index of
622-
// target in the tree, respecting expanded state. Returns -1
623-
// if not found.
632+
// target in the visible tree. Returns -1 if not found.
633+
// Walks depth-first, counting visible rows until target
634+
// is found, avoiding array allocation.
624635
fn inspector_flat_row_index(nodes []TreeNodeCfg, tree_map map[string]bool, target string) int {
625-
mut ids := []string{cap: 64}
626-
inspector_collect_flat_ids(nodes, tree_map, mut ids)
627-
return ids.index(target)
628-
}
629-
630-
// inspector_collect_flat_ids walks tree nodes depth-first,
631-
// collecting visible node IDs in display order.
632-
fn inspector_collect_flat_ids(nodes []TreeNodeCfg, tree_map map[string]bool, mut ids []string) {
633-
for node in nodes {
636+
mut stack := []InspectorStackFrame{cap: 16}
637+
stack << InspectorStackFrame{
638+
nodes: nodes
639+
}
640+
mut idx := 0
641+
for stack.len > 0 {
642+
si := stack.len - 1
643+
if stack[si].pos >= stack[si].nodes.len {
644+
stack.delete_last()
645+
continue
646+
}
647+
node := stack[si].nodes[stack[si].pos]
648+
stack[si].pos++
634649
id := if node.id.len == 0 { node.text } else { node.id }
635-
ids << id
650+
if id == target {
651+
return idx
652+
}
653+
idx++
636654
if tree_map[id] && node.nodes.len > 0 {
637-
inspector_collect_flat_ids(node.nodes, tree_map, mut ids)
655+
stack << InspectorStackFrame{
656+
nodes: node.nodes
657+
}
638658
}
639659
}
660+
return -1
640661
}

0 commit comments

Comments
 (0)