slight memory optimization and possible leak fixes
This commit is contained in:
parent
150329e5dd
commit
1247f9d48f
|
@ -1,8 +1,6 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
|
@ -33,8 +31,7 @@ type ListStore struct {
|
||||||
|
|
||||||
resetMe bool
|
resetMe bool
|
||||||
|
|
||||||
messages map[messageKey]*messageRow
|
messages map[messageKey]*messageRow
|
||||||
messageList *list.List
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListStore(constr Constructor, ctrl Controller) *ListStore {
|
func NewListStore(constr Constructor, ctrl Controller) *ListStore {
|
||||||
|
@ -44,11 +41,10 @@ func NewListStore(constr Constructor, ctrl Controller) *ListStore {
|
||||||
messageListCSS(listBox)
|
messageListCSS(listBox)
|
||||||
|
|
||||||
listStore := ListStore{
|
listStore := ListStore{
|
||||||
ListBox: listBox,
|
ListBox: listBox,
|
||||||
Construct: constr,
|
Construct: constr,
|
||||||
Controller: ctrl,
|
Controller: ctrl,
|
||||||
messages: make(map[messageKey]*messageRow, BacklogLimit+1),
|
messages: make(map[messageKey]*messageRow, BacklogLimit+1),
|
||||||
messageList: list.New(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var selected bool
|
var selected bool
|
||||||
|
@ -80,28 +76,10 @@ func NewListStore(constr Constructor, ctrl Controller) *ListStore {
|
||||||
func (c *ListStore) Reset() {
|
func (c *ListStore) Reset() {
|
||||||
primitives.RemoveChildren(c.ListBox)
|
primitives.RemoveChildren(c.ListBox)
|
||||||
c.messages = make(map[messageKey]*messageRow, BacklogLimit+1)
|
c.messages = make(map[messageKey]*messageRow, BacklogLimit+1)
|
||||||
c.messageList = list.New()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ListStore) MessagesLen() int {
|
func (c *ListStore) MessagesLen() int {
|
||||||
return c.messageList.Len()
|
return len(c.messages)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ListStore) findElement(id cchat.ID) (*list.Element, *messageRow, int) {
|
|
||||||
var index = c.messageList.Len() - 1
|
|
||||||
for elem := c.messageList.Back(); elem != nil; elem = elem.Prev() {
|
|
||||||
if gridMsg := elem.Value.(*messageRow); gridMsg.ID() == id {
|
|
||||||
return elem, gridMsg, index
|
|
||||||
}
|
|
||||||
index--
|
|
||||||
}
|
|
||||||
return nil, nil, -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// findIndex searches backwards for id.
|
|
||||||
func (c *ListStore) findIndex(id cchat.ID) (*messageRow, int) {
|
|
||||||
_, gridMsg, ix := c.findElement(id)
|
|
||||||
return gridMsg, ix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap changes the message with the ID to the given message. This provides a
|
// Swap changes the message with the ID to the given message. This provides a
|
||||||
|
@ -149,25 +127,26 @@ func (c *ListStore) Around(id cchat.ID) (before, after MessageRow) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ListStore) around(id cchat.ID) (before, after *messageRow) {
|
func (c *ListStore) around(aroundID cchat.ID) (before, after *messageRow) {
|
||||||
var last *messageRow
|
var last *messageRow
|
||||||
var next bool
|
var next bool
|
||||||
|
|
||||||
for elem := c.messageList.Front(); elem != nil; elem = elem.Next() {
|
primitives.ForeachChildBackwards(c.ListBox, func(v interface{}) (stop bool) {
|
||||||
message := elem.Value.(*messageRow)
|
id := primitives.GetName(v.(primitives.Namer))
|
||||||
if next {
|
if next {
|
||||||
after = message
|
after = c.message(id, "")
|
||||||
break
|
return true
|
||||||
}
|
}
|
||||||
if message.ID() == id {
|
if id == aroundID {
|
||||||
// The last message is the before.
|
|
||||||
before = last
|
before = last
|
||||||
next = true
|
next = true
|
||||||
continue
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
last = message
|
last = c.message(id, "")
|
||||||
}
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,52 +165,92 @@ func (c *ListStore) LatestMessageFrom(userID string) (msgID string, ok bool) {
|
||||||
return msg.ID(), true
|
return msg.ID(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMessage iterates backwards and returns the message if isMessage() returns
|
// findIndex searches backwards for id.
|
||||||
// true on that message.
|
func (c *ListStore) findIndex(findID cchat.ID) (found *messageRow, index int) {
|
||||||
func (c *ListStore) FindMessage(isMessage func(msg MessageRow) bool) MessageRow {
|
// Faster implementation of findMessage: no map lookup is done until an ID
|
||||||
for elem := c.messageList.Back(); elem != nil; elem = elem.Prev() {
|
// match, so the worst case is a single string hash.
|
||||||
gridMsg := elem.Value.(*messageRow)
|
index = c.MessagesLen() - 1
|
||||||
// Ignore sending messages.
|
|
||||||
if gridMsg.presend != nil {
|
primitives.ForeachChildBackwards(c.ListBox, func(v interface{}) (stop bool) {
|
||||||
continue
|
id := primitives.GetName(v.(primitives.Namer))
|
||||||
}
|
if id == findID {
|
||||||
if gridMsg := gridMsg.MessageRow; isMessage(gridMsg) {
|
found = c.message(findID, "")
|
||||||
return gridMsg
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index--
|
||||||
|
return index == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// Preserve old behavior.
|
||||||
|
if found == nil {
|
||||||
|
index = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ListStore) findMessage(presend bool, fn func(*messageRow) bool) (*messageRow, int) {
|
||||||
|
var r *messageRow
|
||||||
|
var i = c.MessagesLen() - 1
|
||||||
|
|
||||||
|
primitives.ForeachChildBackwards(c.ListBox, func(v interface{}) (stop bool) {
|
||||||
|
id := primitives.GetName(v.(primitives.Namer))
|
||||||
|
gridMsg := c.message(id, "")
|
||||||
|
|
||||||
|
// Ignore sending messages.
|
||||||
|
if (presend || gridMsg.presend == nil) && fn(gridMsg) {
|
||||||
|
r = gridMsg
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
i--
|
||||||
|
return i == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// Preserve old behavior.
|
||||||
|
if r == nil {
|
||||||
|
i = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, i
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindMessage iterates backwards and returns the message if isMessage() returns
|
||||||
|
// true on that message. It does not search presend messages.
|
||||||
|
func (c *ListStore) FindMessage(isMessage func(MessageRow) bool) MessageRow {
|
||||||
|
msg, _ := c.findMessage(false, func(row *messageRow) bool {
|
||||||
|
return isMessage(row.MessageRow)
|
||||||
|
})
|
||||||
|
if msg != nil {
|
||||||
|
return msg.MessageRow
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ListStore) nthMessage(n int) *messageRow {
|
||||||
|
v := primitives.NthChild(c.ListBox, n)
|
||||||
|
id := primitives.GetName(v.(primitives.Namer))
|
||||||
|
return c.message(id, "")
|
||||||
|
}
|
||||||
|
|
||||||
// NthMessage returns the nth message.
|
// NthMessage returns the nth message.
|
||||||
func (c *ListStore) NthMessage(n int) MessageRow {
|
func (c *ListStore) NthMessage(n int) MessageRow {
|
||||||
var index = 0
|
msg := c.nthMessage(n)
|
||||||
for elem := c.messageList.Front(); elem != nil; elem = elem.Next() {
|
if msg != nil {
|
||||||
if index == n {
|
return msg.MessageRow
|
||||||
return elem.Value.(*messageRow).MessageRow
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FirstMessage returns the first message.
|
// FirstMessage returns the first message.
|
||||||
func (c *ListStore) FirstMessage() MessageRow {
|
func (c *ListStore) FirstMessage() MessageRow {
|
||||||
if c.messageList.Len() == 0 {
|
return c.NthMessage(0)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Long unwrap.
|
|
||||||
return c.messageList.Front().Value.(*messageRow).MessageRow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastMessage returns the latest message.
|
// LastMessage returns the latest message.
|
||||||
func (c *ListStore) LastMessage() MessageRow {
|
func (c *ListStore) LastMessage() MessageRow {
|
||||||
if c.messageList.Len() == 0 {
|
return c.NthMessage(c.MessagesLen() - 1)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Long unwrap.
|
|
||||||
return c.messageList.Back().Value.(*messageRow).MessageRow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message finds the message state in the container. It is not thread-safe. This
|
// Message finds the message state in the container. It is not thread-safe. This
|
||||||
|
@ -283,8 +302,6 @@ func (c *ListStore) AddPresendMessage(msg input.PresendMessage) PresendMessageRo
|
||||||
|
|
||||||
// Set the message into the list.
|
// Set the message into the list.
|
||||||
c.ListBox.Insert(msgc.Row(), c.MessagesLen())
|
c.ListBox.Insert(msgc.Row(), c.MessagesLen())
|
||||||
// Append the message.
|
|
||||||
c.messageList.PushBack(msgc)
|
|
||||||
// Set the NONCE into the message map.
|
// Set the NONCE into the message map.
|
||||||
c.messages[nonceKey(msgc.Nonce())] = msgc
|
c.messages[nonceKey(msgc.Nonce())] = msgc
|
||||||
|
|
||||||
|
@ -321,31 +338,21 @@ func (c *ListStore) CreateMessageUnsafe(msg cchat.MessageCreate) {
|
||||||
}
|
}
|
||||||
msgTime := msg.Time()
|
msgTime := msg.Time()
|
||||||
|
|
||||||
var index = c.messageList.Len() - 1
|
|
||||||
var after = c.messageList.Back()
|
|
||||||
|
|
||||||
// Iterate and compare timestamp to find where to insert a message.
|
// Iterate and compare timestamp to find where to insert a message.
|
||||||
for after != nil {
|
after, index := c.findMessage(true, func(after *messageRow) bool {
|
||||||
if msgTime.After(after.Value.(*messageRow).Time()) {
|
return msgTime.After(after.Time())
|
||||||
break
|
})
|
||||||
}
|
|
||||||
index--
|
|
||||||
after = after.Prev()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the message. If after is nil, then that means the message is the
|
// Append the message. If after is nil, then that means the message is the
|
||||||
// oldest, so we add it to the front of the list.
|
// oldest, so we add it to the front of the list.
|
||||||
if after != nil {
|
if after != nil {
|
||||||
index++ // insert right after
|
index++ // insert right after
|
||||||
c.messageList.InsertAfter(msgc, after)
|
c.ListBox.Insert(msgc.Row(), index)
|
||||||
} else {
|
} else {
|
||||||
index = 0
|
index = 0
|
||||||
c.messageList.PushFront(msgc)
|
c.ListBox.Add(msgc.Row())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the message into the grid.
|
|
||||||
c.ListBox.Insert(msgc.Row(), index)
|
|
||||||
|
|
||||||
// Set the ID into the message map.
|
// Set the ID into the message map.
|
||||||
c.messages[idKey(msgc.ID())] = msgc
|
c.messages[idKey(msgc.ID())] = msgc
|
||||||
|
|
||||||
|
@ -375,16 +382,14 @@ func (c *ListStore) DeleteMessageUnsafe(msg cchat.MessageDelete) {
|
||||||
// PopMessage deletes a message off of the list and return the deleted message.
|
// PopMessage deletes a message off of the list and return the deleted message.
|
||||||
func (c *ListStore) PopMessage(id cchat.ID) (msg MessageRow) {
|
func (c *ListStore) PopMessage(id cchat.ID) (msg MessageRow) {
|
||||||
// Get the raw element to delete it off the list.
|
// Get the raw element to delete it off the list.
|
||||||
elem, gridMsg, _ := c.findElement(id)
|
gridMsg, _ := c.findIndex(id)
|
||||||
if elem == nil {
|
if gridMsg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
msg = gridMsg.MessageRow
|
msg = gridMsg.MessageRow
|
||||||
|
|
||||||
// Remove off of the Gtk grid.
|
// Remove off of the Gtk grid.
|
||||||
gridMsg.Row().Destroy()
|
gridMsg.Row().Destroy()
|
||||||
// Pop off the slice.
|
|
||||||
c.messageList.Remove(elem)
|
|
||||||
// Delete off the map.
|
// Delete off the map.
|
||||||
delete(c.messages, idKey(id))
|
delete(c.messages, idKey(id))
|
||||||
|
|
||||||
|
@ -400,8 +405,9 @@ func (c *ListStore) DeleteEarliest(n int) {
|
||||||
|
|
||||||
// Since container/list nils out the next element, we can't just call Next
|
// Since container/list nils out the next element, we can't just call Next
|
||||||
// after deleting, so we have to call Next manually before Removing.
|
// after deleting, so we have to call Next manually before Removing.
|
||||||
for elem := c.messageList.Front(); elem != nil && n != 0; n-- {
|
primitives.ForeachChildBackwards(c.ListBox, func(v interface{}) (stop bool) {
|
||||||
gridMsg := elem.Value.(*messageRow)
|
id := primitives.GetName(v.(primitives.Namer))
|
||||||
|
gridMsg := c.message(id, "")
|
||||||
|
|
||||||
if id := gridMsg.ID(); id != "" {
|
if id := gridMsg.ID(); id != "" {
|
||||||
delete(c.messages, idKey(id))
|
delete(c.messages, idKey(id))
|
||||||
|
@ -412,15 +418,13 @@ func (c *ListStore) DeleteEarliest(n int) {
|
||||||
|
|
||||||
gridMsg.Row().Destroy()
|
gridMsg.Row().Destroy()
|
||||||
|
|
||||||
next := elem.Next()
|
n--
|
||||||
c.messageList.Remove(elem)
|
return n == 0
|
||||||
elem = next
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ListStore) HighlightReference(ref markup.ReferenceSegment) {
|
func (c *ListStore) HighlightReference(ref markup.ReferenceSegment) {
|
||||||
msg := c.message(ref.MessageID(), "")
|
msg := c.message(ref.MessageID(), "")
|
||||||
log.Println("Highlighting", ref.MessageID())
|
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
c.Highlight(msg)
|
c.Highlight(msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input/attachment"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input/attachment"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/gotk3/gotk3/pango"
|
"github.com/gotk3/gotk3/pango"
|
||||||
)
|
)
|
||||||
|
@ -136,7 +137,8 @@ func (m *GenericPresendContainer) SetSentError(err error) {
|
||||||
|
|
||||||
// clearBox clears everything inside the content container.
|
// clearBox clears everything inside the content container.
|
||||||
func (m *GenericPresendContainer) clearBox() {
|
func (m *GenericPresendContainer) clearBox() {
|
||||||
m.contentBox.GetChildren().Foreach(func(v interface{}) {
|
primitives.ForeachChild(m.contentBox, func(v interface{}) (stop bool) {
|
||||||
m.contentBox.Remove(v.(gtk.IWidget))
|
m.contentBox.Remove(v.(gtk.IWidget))
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,14 +110,7 @@ func (c *Completer) Popdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Completer) Clear() {
|
func (c *Completer) Clear() {
|
||||||
var children = c.List.GetChildren()
|
primitives.RemoveChildren(c.List)
|
||||||
if children.Length() == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
children.Foreach(func(i interface{}) {
|
|
||||||
i.(primitives.WidgetDestroyer).Destroy()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Words returns the buffer content split into words.
|
// Words returns the buffer content split into words.
|
||||||
|
|
|
@ -32,7 +32,7 @@ type KeyDownHandlerFn = func(gtk.IWidget, *gdk.Event) bool
|
||||||
func KeyDownHandler(l *gtk.ListBox, focus func()) KeyDownHandlerFn {
|
func KeyDownHandler(l *gtk.ListBox, focus func()) KeyDownHandlerFn {
|
||||||
return func(w gtk.IWidget, ev *gdk.Event) bool {
|
return func(w gtk.IWidget, ev *gdk.Event) bool {
|
||||||
// Do we have any entries? If not, don't bother.
|
// Do we have any entries? If not, don't bother.
|
||||||
var length = int(l.GetChildren().Length())
|
var length = primitives.ChildrenLen(l)
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
// passthrough.
|
// passthrough.
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -30,9 +30,56 @@ func RemoveChildren(w Container) {
|
||||||
Destroy()
|
Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
w.GetChildren().Foreach(func(child interface{}) {
|
children := w.GetChildren()
|
||||||
child.(destroyer).Destroy()
|
children.Foreach(func(child interface{}) { child.(destroyer).Destroy() })
|
||||||
})
|
children.Free()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildrenLen gets the total count of children for the given container.
|
||||||
|
func ChildrenLen(w Container) int {
|
||||||
|
children := w.GetChildren()
|
||||||
|
defer children.Free()
|
||||||
|
|
||||||
|
return int(children.Length())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NthChild(w Container, n int) interface{} {
|
||||||
|
children := w.GetChildren()
|
||||||
|
defer children.Free()
|
||||||
|
|
||||||
|
return children.NthData(uint(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForeachChildBackwards iterates the list. If the callback returns true, then
|
||||||
|
// the loop is broken.
|
||||||
|
func ForeachChild(w Container, fn func(interface{}) (stop bool)) {
|
||||||
|
children := w.GetChildren()
|
||||||
|
defer children.Free()
|
||||||
|
|
||||||
|
for v := children; v != nil; v = v.Next() {
|
||||||
|
if fn(v.Data()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForeachChildBackwards iterates the list backwards. If the callback returns
|
||||||
|
// true, then the loop is broken.
|
||||||
|
func ForeachChildBackwards(w Container, fn func(interface{}) (stop bool)) {
|
||||||
|
children := w.GetChildren()
|
||||||
|
defer children.Free()
|
||||||
|
|
||||||
|
// Seek to last.
|
||||||
|
var last = children
|
||||||
|
for last != nil {
|
||||||
|
last = last.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
for v := last; v != nil; v = v.Previous() {
|
||||||
|
if fn(v.Data()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Namer interface {
|
type Namer interface {
|
||||||
|
|
Loading…
Reference in New Issue