From b407a2013279ea72bdd4af9e30e1f74330e878b5 Mon Sep 17 00:00:00 2001 From: "Brandon McGinty (chatgpt)" Date: Mon, 10 Aug 2026 10:27:12 -0400 Subject: [PATCH] Snapshot mutable tree display state --- barnard.go | 8 ++++-- ui_tree.go | 62 ++++++++++++++++++++++++++++------------ ui_tree_snapshot_test.go | 16 +++++++++++ 3 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 ui_tree_snapshot_test.go diff --git a/barnard.go b/barnard.go index 1bb105f..b57d300 100644 --- a/barnard.go +++ b/barnard.go @@ -14,8 +14,12 @@ import ( ) type TreeItem struct { - User *gumble.User - Channel *gumble.Channel + User *gumble.User + Channel *gumble.Channel + display string + userSession uint32 + channelID uint32 + snapshot bool } type Barnard struct { diff --git a/ui_tree.go b/ui_tree.go index a80053d..af824fc 100644 --- a/ui_tree.go +++ b/ui_tree.go @@ -8,6 +8,9 @@ import ( ) func (ti TreeItem) String() string { + if ti.display != "" { + return ti.display + } if ti.User != nil { if ti.User.LocallyMuted() { return "[MUTED] " + esc(ti.User.Name) @@ -94,7 +97,13 @@ func (b *Barnard) TreeItemBuild(item uiterm.TreeItem) []uiterm.TreeItem { if root == nil { return nil } - return []uiterm.TreeItem{TreeItem{Channel: root}} + var display string + var channelID uint32 + b.Client.Do(func() { + display = "#" + esc(root.Name) + channelID = root.ID + }) + return []uiterm.TreeItem{TreeItem{Channel: root, display: display, channelID: channelID, snapshot: true}} } else { treeItem = ti } @@ -104,41 +113,52 @@ func (b *Barnard) TreeItemBuild(item uiterm.TreeItem) []uiterm.TreeItem { } users := []uiterm.TreeItem{} - ul := []*gumble.User{} - cl := []*gumble.Channel{} + type userDisplay struct { + user *gumble.User + display string + name string + session uint32 + } + type channelDisplay struct { + channel *gumble.Channel + name string + id uint32 + } + ul := []userDisplay{} + cl := []channelDisplay{} // TCP handlers mutate both maps; snapshot them while Client.Do holds its // read lock, then sort/render outside the protocol critical section. b.Client.Do(func() { for _, user := range treeItem.Channel.Users { - ul = append(ul, user) + boostPercent := float32(user.Boost()-1) * 10 + totalVolume := user.Volume()*100 + boostPercent + display := fmt.Sprintf("%s [%.0f%%]", esc(user.Name), totalVolume) + if user.LocallyMuted() { + display = "[MUTED] " + display + } + ul = append(ul, userDisplay{user: user, name: user.Name, session: user.Session, display: display}) } for _, subchannel := range treeItem.Channel.Children { - cl = append(cl, subchannel) + cl = append(cl, channelDisplay{channel: subchannel, name: subchannel.Name, id: subchannel.ID}) } }) sort.Slice(ul, func(i, j int) bool { - return ul[i].Name < ul[j].Name + return ul[i].name < ul[j].name }) for _, user := range ul { - users = append(users, TreeItem{ - User: user, - }) + users = append(users, TreeItem{User: user.user, display: user.display, userSession: user.session, snapshot: true}) } channels := []uiterm.TreeItem{} sort.Slice(cl, func(i, j int) bool { - return cl[i].Name < cl[j].Name + return cl[i].name < cl[j].name }) for _, subchannel := range cl { - displayName := subchannel.Name - if b.isChannelMuted(subchannel.ID) { - displayName = "[MUTED] #" + displayName - } else { - displayName = "#" + displayName + displayName := "#" + esc(subchannel.name) + if b.isChannelMuted(subchannel.id) { + displayName = "[MUTED] " + displayName } - channels = append(channels, TreeItem{ - Channel: subchannel, - }) + channels = append(channels, TreeItem{Channel: subchannel.channel, display: displayName, channelID: subchannel.id, snapshot: true}) } return append(users, channels...) @@ -158,9 +178,15 @@ func sameUserChannelTreeItem(previous, current uiterm.TreeItem) bool { return false } if prev.User != nil && cur.User != nil { + if prev.snapshot && cur.snapshot { + return prev.userSession == cur.userSession + } return prev.User.Session == cur.User.Session } if prev.Channel != nil && cur.Channel != nil { + if prev.snapshot && cur.snapshot { + return prev.channelID == cur.channelID + } return prev.Channel.ID == cur.Channel.ID } return false diff --git a/ui_tree_snapshot_test.go b/ui_tree_snapshot_test.go new file mode 100644 index 0000000..219c2ad --- /dev/null +++ b/ui_tree_snapshot_test.go @@ -0,0 +1,16 @@ +package main + +import ( + "testing" + + "git.stormux.org/storm/barnard/gumble/gumble" +) + +func TestTreeItemUsesCapturedDisplaySnapshot(t *testing.T) { + user := &gumble.User{Name: "before"} + item := TreeItem{User: user, display: "before [100%]"} + user.Name = "after" + if got := item.String(); got != "before [100%]" { + t.Fatalf("tree display read mutable user state: %q", got) + } +}