Browse Source

perf(ui/chat): cache canCollapseParent result in guilds tree

Cache the canCollapseParent tree-walk result so ShortHelp() and
FullHelp() reuse it within the same render frame instead of walking
the tree 3+ times per draw cycle. The cache key is the current node
pointer, which naturally invalidates on cursor change, and is
explicitly cleared on tree rebuild in resetNodeIndex().

Fixes COMP #16.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
claude 1 month ago
parent
commit
1c6c599a65
1 changed files with 16 additions and 2 deletions
  1. 16 2
      internal/ui/chat/guilds_tree.go

+ 16 - 2
internal/ui/chat/guilds_tree.go

@@ -32,6 +32,9 @@ type guildsTree struct {
 	dmRootNode      *tview.TreeNode
 	dmRootNode      *tview.TreeNode
 
 
 	guildState *guildState
 	guildState *guildState
+
+	cachedCollapseNode   *tview.TreeNode
+	cachedCollapseResult bool
 }
 }
 
 
 var _ help.KeyMap = (*guildsTree)(nil)
 var _ help.KeyMap = (*guildsTree)(nil)
@@ -93,7 +96,7 @@ func (gt *guildsTree) ShortHelp() []keybind.Keybind {
 	collapseParent.SetHelp(collapseHelp.Key, "collapse parent")
 	collapseParent.SetHelp(collapseHelp.Key, "collapse parent")
 
 
 	shortHelp := []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, selectCurrent}
 	shortHelp := []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, selectCurrent}
-	if gt.canCollapseParent(gt.GetCurrentNode()) {
+	if gt.canCollapseParentCached() {
 		shortHelp = append(shortHelp, collapseParent)
 		shortHelp = append(shortHelp, collapseParent)
 	}
 	}
 	return shortHelp
 	return shortHelp
@@ -109,7 +112,7 @@ func (gt *guildsTree) FullHelp() [][]keybind.Keybind {
 	collapseParent.SetHelp(collapseHelp.Key, "collapse parent")
 	collapseParent.SetHelp(collapseHelp.Key, "collapse parent")
 
 
 	actions := []keybind.Keybind{selectCurrent, cfg.MoveToParentNode.Keybind}
 	actions := []keybind.Keybind{selectCurrent, cfg.MoveToParentNode.Keybind}
-	if gt.canCollapseParent(gt.GetCurrentNode()) {
+	if gt.canCollapseParentCached() {
 		actions = append(actions, collapseParent)
 		actions = append(actions, collapseParent)
 	}
 	}
 
 
@@ -133,11 +136,22 @@ func (gt *guildsTree) canCollapseParent(node *tview.TreeNode) bool {
 	return parent != nil && parent.GetLevel() != 0
 	return parent != nil && parent.GetLevel() != 0
 }
 }
 
 
+func (gt *guildsTree) canCollapseParentCached() bool {
+	node := gt.GetCurrentNode()
+	if node == gt.cachedCollapseNode {
+		return gt.cachedCollapseResult
+	}
+	gt.cachedCollapseNode = node
+	gt.cachedCollapseResult = gt.canCollapseParent(node)
+	return gt.cachedCollapseResult
+}
+
 func (gt *guildsTree) resetNodeIndex() {
 func (gt *guildsTree) resetNodeIndex() {
 	// Keep allocated map capacity; READY can rebuild often during reconnects.
 	// Keep allocated map capacity; READY can rebuild often during reconnects.
 	clear(gt.guildNodeByID)
 	clear(gt.guildNodeByID)
 	clear(gt.channelNodeByID)
 	clear(gt.channelNodeByID)
 	gt.dmRootNode = nil
 	gt.dmRootNode = nil
+	gt.cachedCollapseNode = nil
 }
 }
 
 
 func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder, guildsByID map[discord.GuildID]*gateway.GuildCreateEvent) {
 func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder, guildsByID map[discord.GuildID]*gateway.GuildCreateEvent) {