perf(ui/chat): index guild tree nodes for read updates (#738)
This commit is contained in:
parent
1428b58f68
commit
56337067fa
3 changed files with 63 additions and 51 deletions
|
|
@ -20,6 +20,13 @@ type guildsTree struct {
|
|||
*tview.TreeView
|
||||
cfg *config.Config
|
||||
chatView *View
|
||||
|
||||
// Fast-path indexes for frequent event handlers (read updates, picker
|
||||
// navigation). They mirror the current rendered tree and are rebuilt on
|
||||
// READY before nodes are added.
|
||||
guildNodeByID map[discord.GuildID]*tview.TreeNode
|
||||
channelNodeByID map[discord.ChannelID]*tview.TreeNode
|
||||
dmRootNode *tview.TreeNode
|
||||
}
|
||||
|
||||
func newGuildsTree(cfg *config.Config, chatView *View) *guildsTree {
|
||||
|
|
@ -27,6 +34,9 @@ func newGuildsTree(cfg *config.Config, chatView *View) *guildsTree {
|
|||
TreeView: tview.NewTreeView(),
|
||||
cfg: cfg,
|
||||
chatView: chatView,
|
||||
|
||||
guildNodeByID: make(map[discord.GuildID]*tview.TreeNode),
|
||||
channelNodeByID: make(map[discord.ChannelID]*tview.TreeNode),
|
||||
}
|
||||
|
||||
gt.Box = ui.ConfigureBox(gt.Box, &cfg.Theme)
|
||||
|
|
@ -42,6 +52,13 @@ func newGuildsTree(cfg *config.Config, chatView *View) *guildsTree {
|
|||
return gt
|
||||
}
|
||||
|
||||
func (gt *guildsTree) resetNodeIndex() {
|
||||
// Keep allocated map capacity; READY can rebuild often during reconnects.
|
||||
clear(gt.guildNodeByID)
|
||||
clear(gt.channelNodeByID)
|
||||
gt.dmRootNode = nil
|
||||
}
|
||||
|
||||
func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder) {
|
||||
name := "Folder"
|
||||
if folder.Name != "" {
|
||||
|
|
@ -92,6 +109,7 @@ func (gt *guildsTree) createGuildNode(n *tview.TreeNode, guild discord.Guild) {
|
|||
SetReference(guild.ID).
|
||||
SetTextStyle(gt.getGuildNodeStyle(guild.ID))
|
||||
n.AddChild(guildNode)
|
||||
gt.guildNodeByID[guild.ID] = guildNode
|
||||
}
|
||||
|
||||
func (gt *guildsTree) createChannelNode(node *tview.TreeNode, channel discord.Channel) {
|
||||
|
|
@ -103,6 +121,7 @@ func (gt *guildsTree) createChannelNode(node *tview.TreeNode, channel discord.Ch
|
|||
SetReference(channel.ID).
|
||||
SetTextStyle(gt.getChannelNodeStyle(channel.ID))
|
||||
node.AddChild(channelNode)
|
||||
gt.channelNodeByID[channel.ID] = channelNode
|
||||
}
|
||||
|
||||
func (gt *guildsTree) createChannelNodes(node *tview.TreeNode, channels []discord.Channel) {
|
||||
|
|
@ -126,16 +145,9 @@ PARENT_CHANNELS:
|
|||
|
||||
for _, channel := range channels {
|
||||
if channel.ParentID.IsValid() {
|
||||
var parent *tview.TreeNode
|
||||
node.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
if node.GetReference() == channel.ParentID {
|
||||
parent = node
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// Parent categories are inserted earlier in this function, so this
|
||||
// lookup is O(1) and avoids per-channel subtree walks.
|
||||
parent := gt.channelNodeByID[channel.ParentID]
|
||||
if parent != nil {
|
||||
gt.createChannelNode(parent, channel)
|
||||
}
|
||||
|
|
@ -297,16 +309,25 @@ func (gt *guildsTree) yankID() {
|
|||
}
|
||||
|
||||
func (gt *guildsTree) findNodeByReference(reference any) *tview.TreeNode {
|
||||
var found *tview.TreeNode
|
||||
gt.GetRoot().Walk(func(node, _ *tview.TreeNode) bool {
|
||||
if node.GetReference() == reference {
|
||||
found = node
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
return found
|
||||
switch ref := reference.(type) {
|
||||
case discord.GuildID:
|
||||
return gt.guildNodeByID[ref]
|
||||
case discord.ChannelID:
|
||||
return gt.channelNodeByID[ref]
|
||||
case dmNode:
|
||||
return gt.dmRootNode
|
||||
default:
|
||||
// Fallback keeps this helper safe for non-indexed custom references.
|
||||
var found *tview.TreeNode
|
||||
gt.GetRoot().Walk(func(node, _ *tview.TreeNode) bool {
|
||||
if node.GetReference() == reference {
|
||||
found = node
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
}
|
||||
|
||||
func (gt *guildsTree) findNodeByChannelID(channelID discord.ChannelID) *tview.TreeNode {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ func (v *View) onRaw(event *ws.RawEvent) {
|
|||
|
||||
func (v *View) onReady(r *gateway.ReadyEvent) {
|
||||
dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{})
|
||||
// Rebuild indexes from scratch so reconnects and account switches do not
|
||||
// retain stale pointers to detached tree nodes.
|
||||
v.guildsTree.resetNodeIndex()
|
||||
v.guildsTree.dmRootNode = dmNode
|
||||
root := v.guildsTree.
|
||||
GetRoot().
|
||||
ClearChildren().
|
||||
|
|
|
|||
|
|
@ -263,39 +263,26 @@ func (v *View) showConfirmModal(prompt string, buttons []string, onDone func(lab
|
|||
}
|
||||
|
||||
func (v *View) onReadUpdate(event *read.UpdateEvent) {
|
||||
var guildNode *tview.TreeNode
|
||||
v.guildsTree.
|
||||
GetRoot().
|
||||
Walk(func(node, parent *tview.TreeNode) bool {
|
||||
switch node.GetReference() {
|
||||
case event.GuildID:
|
||||
node.SetTextStyle(v.guildsTree.getGuildNodeStyle(event.GuildID))
|
||||
guildNode = node
|
||||
return false
|
||||
case event.ChannelID:
|
||||
// private channel
|
||||
if !event.GuildID.IsValid() {
|
||||
style := v.guildsTree.getChannelNodeStyle(event.ChannelID)
|
||||
node.SetTextStyle(style)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if guildNode != nil {
|
||||
guildNode.Walk(func(node, parent *tview.TreeNode) bool {
|
||||
if node.GetReference() == event.ChannelID {
|
||||
node.SetTextStyle(v.guildsTree.getChannelNodeStyle(event.ChannelID))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
// Use indexed node lookup to avoid walking the whole tree on every read
|
||||
// event. This runs frequently while reading/typing across channels.
|
||||
var updated bool
|
||||
if event.GuildID.IsValid() {
|
||||
if guildNode := v.guildsTree.findNodeByReference(event.GuildID); guildNode != nil {
|
||||
guildNode.SetTextStyle(v.guildsTree.getGuildNodeStyle(event.GuildID))
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
v.app.Draw()
|
||||
// Channel style is always updated for the target channel regardless of
|
||||
// whether it's in a guild or DM.
|
||||
if channelNode := v.guildsTree.findNodeByReference(event.ChannelID); channelNode != nil {
|
||||
channelNode.SetTextStyle(v.guildsTree.getChannelNodeStyle(event.ChannelID))
|
||||
updated = true
|
||||
}
|
||||
|
||||
if updated {
|
||||
v.app.Draw()
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) clearTypers() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue