From bc7e2d9a1c871e3fbce932f9695fc24083bc2cc4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 27 Apr 2019 15:02:21 +0300 Subject: Add locks and other sync stuff --- ui/message-view.go | 213 ++++++++++++++++++++++++++++++++++++---------------- ui/room-list.go | 46 ++++++++++-- ui/tag-room-list.go | 2 +- ui/view-main.go | 111 ++++++++++++++++----------- 4 files changed, 254 insertions(+), 118 deletions(-) (limited to 'ui') diff --git a/ui/message-view.go b/ui/message-view.go index 1d9519e..43c757f 100644 --- a/ui/message-view.go +++ b/ui/message-view.go @@ -20,6 +20,8 @@ import ( "fmt" "math" "strings" + "sync" + "sync/atomic" "github.com/mattn/go-runewidth" @@ -43,20 +45,24 @@ type MessageView struct { DateFormat string TimestampFormat string TimestampWidth int - LoadingMessages bool - widestSender int - width int - height int - prevWidth int - prevHeight int - prevMsgCount int - prevPrefs config.UserPreferences - - messageIDs map[string]messages.UIMessage - messages []messages.UIMessage - - msgBuffer []messages.UIMessage + // Used for locking + loadingMessages int32 + + _widestSender uint32 + _width uint32 + _height uint32 + _prevWidth uint32 + _prevHeight uint32 + prevMsgCount int + prevPrefs config.UserPreferences + + messageIDLock sync.RWMutex + messageIDs map[string]messages.UIMessage + messagesLock sync.RWMutex + messages []messages.UIMessage + msgBufferLock sync.RWMutex + msgBuffer []messages.UIMessage } func NewMessageView(parent *RoomView) *MessageView { @@ -72,19 +78,20 @@ func NewMessageView(parent *RoomView) *MessageView { messageIDs: make(map[string]messages.UIMessage), msgBuffer: make([]messages.UIMessage, 0), - width: 80, - widestSender: 5, - prevWidth: -1, - prevHeight: -1, - prevMsgCount: -1, + _width: 80, + _widestSender: 5, + _prevWidth: 0, + _prevHeight: 0, + prevMsgCount: -1, } } func (view *MessageView) updateWidestSender(sender string) { - if len(sender) > view.widestSender { - view.widestSender = len(sender) - if view.widestSender > view.MaxSenderWidth { - view.widestSender = view.MaxSenderWidth + if len(sender) > int(view._widestSender) { + if len(sender) > view.MaxSenderWidth { + atomic.StoreUint32(&view._widestSender, uint32(view.MaxSenderWidth)) + } else { + atomic.StoreUint32(&view._widestSender, uint32(len(sender))) } } } @@ -109,22 +116,21 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir } var oldMsg messages.UIMessage - var messageExists bool - if oldMsg, messageExists = view.messageIDs[message.ID()]; messageExists { + if oldMsg = view.getMessageByID(message.ID()); oldMsg != nil { view.replaceMessage(oldMsg, message) direction = IgnoreMessage - } else if oldMsg, messageExists = view.messageIDs[message.TxnID()]; messageExists { + } else if oldMsg = view.getMessageByID(message.TxnID()); oldMsg != nil { view.replaceMessage(oldMsg, message) - delete(view.messageIDs, message.TxnID()) + view.deleteMessageID(message.TxnID()) direction = IgnoreMessage } view.updateWidestSender(message.Sender()) - width := view.width + width := view.width() bare := view.config.Preferences.BareMessageView if !bare { - width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap + width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap } message.CalculateBuffer(view.config.Preferences, width) @@ -140,18 +146,22 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir if view.ScrollOffset > 0 { view.ScrollOffset += message.Height() } + view.messagesLock.Lock() if len(view.messages) > 0 && !view.messages[len(view.messages)-1].SameDate(message) { view.messages = append(view.messages, makeDateChange(), message) } else { view.messages = append(view.messages, message) } + view.messagesLock.Unlock() view.appendBuffer(message) } else if direction == PrependMessage { + view.messagesLock.Lock() if len(view.messages) > 0 && !view.messages[0].SameDate(message) { view.messages = append([]messages.UIMessage{message, makeDateChange()}, view.messages...) } else { view.messages = append([]messages.UIMessage{message}, view.messages...) } + view.messagesLock.Unlock() } else if oldMsg != nil { view.replaceBuffer(oldMsg, message) } else { @@ -160,31 +170,62 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir } if len(message.ID()) > 0 { - view.messageIDs[message.ID()] = message - } -} - -func (view *MessageView) appendBuffer(message messages.UIMessage) { - for i := 0; i < message.Height(); i++ { - view.msgBuffer = append(view.msgBuffer, message) + view.setMessageID(message) } - view.prevMsgCount++ } func (view *MessageView) replaceMessage(original messages.UIMessage, new messages.UIMessage) { if len(new.ID()) > 0 { - view.messageIDs[new.ID()] = new + view.setMessageID(new) } + view.messagesLock.Lock() for index, msg := range view.messages { if msg == original { view.messages[index] = new } } + view.messagesLock.Unlock() +} + +func (view *MessageView) getMessageByID(id string) messages.UIMessage { + view.messageIDLock.RLock() + defer view.messageIDLock.RUnlock() + msg, ok := view.messageIDs[id] + if !ok { + return nil + } + return msg +} + +func (view *MessageView) deleteMessageID(id string) { + view.messageIDLock.Lock() + delete(view.messageIDs, id) + view.messageIDLock.Unlock() +} + +func (view *MessageView) setMessageID(message messages.UIMessage) { + view.messageIDLock.Lock() + view.messageIDs[message.ID()] = message + view.messageIDLock.Unlock() +} + +func (view *MessageView) appendBuffer(message messages.UIMessage) { + view.msgBufferLock.Lock() + view.appendBufferUnlocked(message) + view.msgBufferLock.Unlock() +} + +func (view *MessageView) appendBufferUnlocked(message messages.UIMessage) { + for i := 0; i < message.Height(); i++ { + view.msgBuffer = append(view.msgBuffer, message) + } + view.prevMsgCount++ } func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) { start := -1 end := -1 + view.msgBufferLock.RLock() for index, meta := range view.msgBuffer { if meta == original { if start == -1 { @@ -195,6 +236,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages break } } + view.msgBufferLock.RUnlock() if start == -1 { debug.Print("Called replaceBuffer() with message that was not in the buffer:", original) @@ -208,9 +250,10 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages } if new.Height() == 0 { - new.CalculateBuffer(view.prevPrefs, view.prevWidth) + new.CalculateBuffer(view.prevPrefs, view.prevWidth()) } + view.msgBufferLock.Lock() if new.Height() != end-start { metaBuffer := view.msgBuffer[0:start] for i := 0; i < new.Height(); i++ { @@ -222,17 +265,20 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages view.msgBuffer[i] = new } } + view.msgBufferLock.Unlock() } func (view *MessageView) recalculateBuffers() { prefs := view.config.Preferences - recalculateMessageBuffers := view.width != view.prevWidth || + recalculateMessageBuffers := view.width() != view.prevWidth() || view.prevPrefs.BareMessageView != prefs.BareMessageView || view.prevPrefs.DisableImages != prefs.DisableImages + view.messagesLock.RLock() + view.msgBufferLock.Lock() if recalculateMessageBuffers || len(view.messages) != view.prevMsgCount { - width := view.width + width := view.width() if !prefs.BareMessageView { - width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap + width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap } view.msgBuffer = []messages.UIMessage{} view.prevMsgCount = 0 @@ -244,11 +290,12 @@ func (view *MessageView) recalculateBuffers() { if recalculateMessageBuffers { message.CalculateBuffer(prefs, width) } - view.appendBuffer(message) + view.appendBufferUnlocked(message) } } - view.prevHeight = view.height - view.prevWidth = view.width + view.msgBufferLock.Unlock() + view.messagesLock.RUnlock() + view.updatePrevSize() view.prevPrefs = prefs } @@ -309,19 +356,21 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool { return true case tcell.Button1: x, y := event.Position() - line := view.TotalHeight() - view.ScrollOffset - view.height + y + line := view.TotalHeight() - view.ScrollOffset - view.Height() + y if line < 0 || line >= view.TotalHeight() { return false } + view.msgBufferLock.RLock() message := view.msgBuffer[line] var prevMessage messages.UIMessage if y != 0 && line > 0 { prevMessage = view.msgBuffer[line-1] } + view.msgBufferLock.RUnlock() usernameX := view.TimestampWidth + TimestampSenderGap - messageX := usernameX + view.widestSender + SenderMessageGap + messageX := usernameX + view.widestSender() + SenderMessageGap if x >= messageX { return view.handleMessageClick(message) @@ -336,30 +385,59 @@ const PaddingAtTop = 5 func (view *MessageView) AddScrollOffset(diff int) { totalHeight := view.TotalHeight() - if diff >= 0 && view.ScrollOffset+diff >= totalHeight-view.height+PaddingAtTop { - view.ScrollOffset = totalHeight - view.height + PaddingAtTop + height := view.Height() + if diff >= 0 && view.ScrollOffset+diff >= totalHeight-height+PaddingAtTop { + view.ScrollOffset = totalHeight - height + PaddingAtTop } else { view.ScrollOffset += diff } - if view.ScrollOffset > totalHeight-view.height+PaddingAtTop { - view.ScrollOffset = totalHeight - view.height + PaddingAtTop + if view.ScrollOffset > totalHeight-height+PaddingAtTop { + view.ScrollOffset = totalHeight - height + PaddingAtTop } if view.ScrollOffset < 0 { view.ScrollOffset = 0 } } +func (view *MessageView) setSize(width, height int) { + atomic.StoreUint32(&view._width, uint32(width)) + atomic.StoreUint32(&view._height, uint32(height)) +} + +func (view *MessageView) updatePrevSize() { + atomic.StoreUint32(&view._prevWidth, atomic.LoadUint32(&view._width)) + atomic.StoreUint32(&view._prevHeight, atomic.LoadUint32(&view._height)) +} + +func (view *MessageView) prevHeight() int { + return int(atomic.LoadUint32(&view._prevHeight)) +} + +func (view *MessageView) prevWidth() int { + return int(atomic.LoadUint32(&view._prevWidth)) +} + +func (view *MessageView) widestSender() int { + return int(atomic.LoadUint32(&view._widestSender)) +} + func (view *MessageView) Height() int { - return view.height + return int(atomic.LoadUint32(&view._height)) +} + +func (view *MessageView) width() int { + return int(atomic.LoadUint32(&view._width)) } func (view *MessageView) TotalHeight() int { + view.msgBufferLock.RLock() + defer view.msgBufferLock.RUnlock() return len(view.msgBuffer) } func (view *MessageView) IsAtTop() bool { - return view.ScrollOffset >= len(view.msgBuffer)-view.height+PaddingAtTop + return view.ScrollOffset >= view.TotalHeight()-view.Height()+PaddingAtTop } const ( @@ -407,7 +485,7 @@ func (view *MessageView) getIndexOffset(screen mauview.Screen, height, messageX indexOffset = view.TotalHeight() - view.ScrollOffset - height if indexOffset <= -PaddingAtTop { message := "Scroll up to load more messages." - if view.LoadingMessages { + if atomic.LoadInt32(&view.loadingMessages) == 1 { message = "Loading more messages..." } widget.WriteLineSimpleColor(screen, message, messageX, 0, tcell.ColorGreen) @@ -419,6 +497,7 @@ func (view *MessageView) CapturePlaintext(height int) string { var buf strings.Builder indexOffset := view.TotalHeight() - view.ScrollOffset - height var prevMessage messages.UIMessage + view.msgBufferLock.RLock() for line := 0; line < height; line++ { index := indexOffset + line if index < 0 { @@ -438,27 +517,29 @@ func (view *MessageView) CapturePlaintext(height int) string { prevMessage = message } } + view.msgBufferLock.RUnlock() return buf.String() } func (view *MessageView) Draw(screen mauview.Screen) { - view.width, view.height = screen.Size() + view.setSize(screen.Size()) view.recalculateBuffers() + height := view.Height() if view.TotalHeight() == 0 { - widget.WriteLineSimple(screen, "It's quite empty in here.", 0, view.height) + widget.WriteLineSimple(screen, "It's quite empty in here.", 0, height) return } usernameX := view.TimestampWidth + TimestampSenderGap - messageX := usernameX + view.widestSender + SenderMessageGap + messageX := usernameX + view.widestSender() + SenderMessageGap bareMode := view.config.Preferences.BareMessageView if bareMode { messageX = 0 } - indexOffset := view.getIndexOffset(screen, view.height, messageX) + indexOffset := view.getIndexOffset(screen, height, messageX) viewStart := 0 if indexOffset < 0 { @@ -466,13 +547,13 @@ func (view *MessageView) Draw(screen mauview.Screen) { } if !bareMode { - separatorX := usernameX + view.widestSender + SenderSeparatorGap - scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height) + separatorX := usernameX + view.widestSender() + SenderSeparatorGap + scrollBarHeight, scrollBarPos := view.calculateScrollBar(height) - for line := viewStart; line < view.height; line++ { + for line := viewStart; line < height; line++ { showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos - isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight() - isBottom := line == view.height-1 && view.ScrollOffset == 0 + isTop := line == viewStart && view.ScrollOffset+height >= view.TotalHeight() + isBottom := line == height-1 && view.ScrollOffset == 0 borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom) @@ -481,7 +562,8 @@ func (view *MessageView) Draw(screen mauview.Screen) { } var prevMsg messages.UIMessage - for line := viewStart; line < view.height && indexOffset+line < view.TotalHeight(); line++ { + view.msgBufferLock.RLock() + for line := viewStart; line < height && indexOffset+line < len(view.msgBuffer); line++ { index := indexOffset + line msg := view.msgBuffer[index] @@ -493,7 +575,7 @@ func (view *MessageView) Draw(screen mauview.Screen) { //if !bareMode && (prevMsg == nil || meta.Sender() != prevMsg.Sender()) { widget.WriteLineColor( screen, mauview.AlignRight, msg.Sender(), - usernameX, line, view.widestSender, + usernameX, line, view.widestSender(), msg.SenderColor()) //} prevMsg = msg @@ -502,7 +584,8 @@ func (view *MessageView) Draw(screen mauview.Screen) { for i := index - 1; i >= 0 && view.msgBuffer[i] == msg; i-- { line-- } - msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, msg.Height())) + msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width()-messageX, msg.Height())) line += msg.Height() - 1 } + view.msgBufferLock.RUnlock() } diff --git a/ui/room-list.go b/ui/room-list.go index c8e43a6..47ac182 100644 --- a/ui/room-list.go +++ b/ui/room-list.go @@ -20,6 +20,7 @@ import ( "math" "regexp" "strings" + "sync" "maunium.net/go/mauview" "maunium.net/go/tcell" @@ -29,6 +30,8 @@ import ( ) type RoomList struct { + sync.RWMutex + parent *MainView // The list of tags in display order. @@ -71,6 +74,8 @@ func NewRoomList(parent *MainView) *RoomList { } func (list *RoomList) Contains(roomID string) bool { + list.RLock() + defer list.RUnlock() for _, trl := range list.items { for _, room := range trl.All() { if room.ID == roomID { @@ -88,8 +93,8 @@ func (list *RoomList) Add(room *rooms.Room) { } } -func (list *RoomList) CheckTag(tag string) { - index := list.IndexTag(tag) +func (list *RoomList) checkTag(tag string) { + index := list.indexTag(tag) trl, ok := list.items[tag] @@ -107,6 +112,8 @@ func (list *RoomList) CheckTag(tag string) { } func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) { + list.Lock() + defer list.Unlock() trl, ok := list.items[tag.Tag] if !ok { list.items[tag.Tag] = NewTagRoomList(list, tag.Tag, NewDefaultOrderedRoom(room)) @@ -114,7 +121,7 @@ func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) { } trl.Insert(tag.Order, room) - list.CheckTag(tag.Tag) + list.checkTag(tag.Tag) } func (list *RoomList) Remove(room *rooms.Room) { @@ -124,6 +131,8 @@ func (list *RoomList) Remove(room *rooms.Room) { } func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) { + list.Lock() + defer list.Unlock() trl, ok := list.items[tag] if !ok { return @@ -158,10 +167,12 @@ func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) { list.selectedTag = "" } } - list.CheckTag(tag) + list.checkTag(tag) } func (list *RoomList) Bump(room *rooms.Room) { + list.RLock() + defer list.RUnlock() for _, tag := range room.Tags() { trl, ok := list.items[tag.Tag] if !ok { @@ -172,6 +183,8 @@ func (list *RoomList) Bump(room *rooms.Room) { } func (list *RoomList) Clear() { + list.Lock() + defer list.Unlock() list.items = make(map[string]*TagRoomList) list.tags = []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"} for _, tag := range list.tags { @@ -220,6 +233,8 @@ func (list *RoomList) AddScrollOffset(offset int) { } func (list *RoomList) First() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() for _, tag := range list.tags { trl := list.items[tag] if trl.HasVisibleRooms() { @@ -230,6 +245,8 @@ func (list *RoomList) First() (string, *rooms.Room) { } func (list *RoomList) Last() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- { tag := list.tags[tagIndex] trl := list.items[tag] @@ -240,7 +257,7 @@ func (list *RoomList) Last() (string, *rooms.Room) { return "", nil } -func (list *RoomList) IndexTag(tag string) int { +func (list *RoomList) indexTag(tag string) int { for index, entry := range list.tags { if tag == entry { return index @@ -250,6 +267,8 @@ func (list *RoomList) IndexTag(tag string) int { } func (list *RoomList) Previous() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() if len(list.items) == 0 { return "", nil } else if list.selected == nil { @@ -266,7 +285,7 @@ func (list *RoomList) Previous() (string, *rooms.Room) { } if index == trl.Length()-1 { - tagIndex := list.IndexTag(list.selectedTag) + tagIndex := list.indexTag(list.selectedTag) tagIndex-- for ; tagIndex >= 0; tagIndex-- { prevTag := list.tags[tagIndex] @@ -283,6 +302,8 @@ func (list *RoomList) Previous() (string, *rooms.Room) { } func (list *RoomList) Next() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() if len(list.items) == 0 { return "", nil } else if list.selected == nil { @@ -299,7 +320,7 @@ func (list *RoomList) Next() (string, *rooms.Room) { } if index == 0 { - tagIndex := list.IndexTag(list.selectedTag) + tagIndex := list.indexTag(list.selectedTag) tagIndex++ for ; tagIndex < len(list.tags); tagIndex++ { nextTag := list.tags[tagIndex] @@ -325,6 +346,8 @@ func (list *RoomList) Next() (string, *rooms.Room) { // // TODO: Sorting. Now just finds first room with new messages. func (list *RoomList) NextWithActivity() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() for tag, trl := range list.items { for _, room := range trl.All() { if room.HasNewMessages() { @@ -337,7 +360,7 @@ func (list *RoomList) NextWithActivity() (string, *rooms.Room) { } func (list *RoomList) index(tag string, room *rooms.Room) int { - tagIndex := list.IndexTag(tag) + tagIndex := list.indexTag(tag) if tagIndex == -1 { return -1 } @@ -358,6 +381,7 @@ func (list *RoomList) index(tag string, room *rooms.Room) int { if tagIndex > 0 { for i := 0; i < tagIndex; i++ { prevTag := list.tags[i] + prevTRL := list.items[prevTag] localIndex += prevTRL.RenderHeight() } @@ -367,9 +391,11 @@ func (list *RoomList) index(tag string, room *rooms.Room) int { } func (list *RoomList) ContentHeight() (height int) { + list.RLock() for _, tag := range list.tags { height += list.items[tag].RenderHeight() } + list.RUnlock() return } @@ -410,6 +436,8 @@ func (list *RoomList) clickRoom(line, column int, mod bool) bool { if line < 0 { return false } + list.RLock() + defer list.RUnlock() for _, tag := range list.tags { trl := list.items[tag] if line--; line == -1 { @@ -484,6 +512,7 @@ func (list *RoomList) Draw(screen mauview.Screen) { y -= list.scrollOffset // Draw the list items. + list.RLock() for _, tag := range list.tags { trl := list.items[tag] tagDisplayName := list.GetTagDisplayName(tag) @@ -501,4 +530,5 @@ func (list *RoomList) Draw(screen mauview.Screen) { break } } + list.RUnlock() } diff --git a/ui/tag-room-list.go b/ui/tag-room-list.go index c965f10..dfc7cb2 100644 --- a/ui/tag-room-list.go +++ b/ui/tag-room-list.go @@ -277,7 +277,7 @@ func (trl *TagRoomList) Draw(screen mauview.Screen) { screen.SetCell(width-1, 0, tcell.StyleDefault, '▼') y := 1 - for i := trl.Length() - 1; i >= 0; i-- { + for i := len(items) - 1; i >= 0; i-- { if y >= height { return } diff --git a/ui/view-main.go b/ui/view-main.go index bda53ad..2d2afdf 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -20,6 +20,8 @@ import ( "bufio" "fmt" "os" + "sync" + "sync/atomic" "time" "unicode" @@ -42,6 +44,7 @@ type MainView struct { roomView *mauview.Box currentRoom *RoomView rooms map[string]*RoomView + roomsLock sync.RWMutex cmdProcessor *CommandProcessor focused mauview.Focusable @@ -245,13 +248,17 @@ func (view *MainView) Blur() { } func (view *MainView) SwitchRoom(tag string, room *rooms.Room) { + view.switchRoom(tag, room, true) +} + +func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) { if room == nil { return } - roomView := view.rooms[room.ID] - if roomView == nil { - debug.Print("Tried to switch to non-nil room with nil roomView!") + roomView, ok := view.getRoomView(room.ID, lock) + if !ok { + debug.Print("Tried to switch to room with nonexistent roomView!") debug.Print(tag, room) return } @@ -265,7 +272,7 @@ func (view *MainView) SwitchRoom(tag string, room *rooms.Room) { } } -func (view *MainView) addRoomPage(room *rooms.Room) { +func (view *MainView) addRoomPage(room *rooms.Room) *RoomView { if _, ok := view.rooms[room.ID]; !ok { roomView := NewRoomView(view, room). SetInputChangedFunc(view.InputChanged) @@ -276,53 +283,73 @@ func (view *MainView) addRoomPage(room *rooms.Room) { if len(roomView.MessageView().messages) == 0 { go view.LoadHistory(room.ID) } + return roomView } + return nil } func (view *MainView) GetRoom(roomID string) ifc.RoomView { - room, ok := view.rooms[roomID] + room, ok := view.getRoomView(roomID, true) if !ok { - view.AddRoom(view.matrix.GetRoom(roomID)) - room, ok := view.rooms[roomID] - if !ok { - return nil - } - return room + return view.addRoom(view.matrix.GetRoom(roomID)) } return room } -func (view *MainView) AddRoom(room *rooms.Room) { - if view.roomList.Contains(room.ID) { - debug.Print("Add aborted (room exists)", room.ID, room.GetTitle()) - return - } - debug.Print("Adding", room.ID, room.GetTitle()) - view.roomList.Add(room) - view.addRoomPage(room) - if !view.roomList.HasSelected() { - view.SwitchRoom(view.roomList.First()) +func (view *MainView) getRoomView(roomID string, lock bool) (room *RoomView, ok bool) { + if lock { + view.roomsLock.RLock() + room, ok = view.rooms[roomID] + view.roomsLock.RUnlock() + } else { + room, ok = view.rooms[roomID] } + return room, ok +} + +func (view *MainView) AddRoom(room *rooms.Room) { + view.addRoom(room) } func (view *MainView) RemoveRoom(room *rooms.Room) { - roomView := view.GetRoom(room.ID) - if roomView == nil { + view.roomsLock.Lock() + _, ok := view.getRoomView(room.ID, false) + if !ok { + view.roomsLock.Unlock() debug.Print("Remove aborted (not found)", room.ID, room.GetTitle()) return } debug.Print("Removing", room.ID, room.GetTitle()) view.roomList.Remove(room) - view.SwitchRoom(view.roomList.Selected()) - + t, r := view.roomList.Selected() + view.switchRoom(t, r, false) delete(view.rooms, room.ID) + view.roomsLock.Unlock() view.parent.Render() } +func (view *MainView) addRoom(room *rooms.Room) *RoomView { + if view.roomList.Contains(room.ID) { + debug.Print("Add aborted (room exists)", room.ID, room.GetTitle()) + return nil + } + debug.Print("Adding", room.ID, room.GetTitle()) + view.roomList.Add(room) + view.roomsLock.Lock() + roomView := view.addRoomPage(room) + if !view.roomList.HasSelected() { + t, r := view.roomList.First() + view.switchRoom(t, r, false) + } + view.roomsLock.Unlock() + return roomView +} + func (view *MainView) SetRooms(rooms map[string]*rooms.Room) { view.roomList.Clear() + view.roomsLock.Lock() view.rooms = make(map[string]*RoomView) for _, room := range rooms { if room.HasLeft { @@ -331,7 +358,9 @@ func (view *MainView) SetRooms(rooms map[string]*rooms.Room) { view.roomList.Add(room) view.addRoomPage(room) } - view.SwitchRoom(view.roomList.First()) + t, r := view.roomList.First() + view.switchRoom(t, r, false) + view.roomsLock.Unlock() } func (view *MainView) UpdateTags(room *rooms.Room) { @@ -342,8 +371,8 @@ func (view *MainView) UpdateTags(room *rooms.Room) { view.roomList.Add(room) } -func (view *MainView) SetTyping(room string, users []string) { - roomView, ok := view.rooms[room] +func (view *MainView) SetTyping(roomID string, users []string) { + roomView, ok := view.getRoomView(roomID, true) if ok { roomView.SetTyping(users) view.parent.Render() @@ -392,33 +421,27 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul func (view *MainView) InitialSyncDone() { view.roomList.Clear() + view.roomsLock.RLock() for _, room := range view.rooms { view.roomList.Add(room.Room) room.UpdateUserList() } + view.roomsLock.RUnlock() } -func (view *MainView) LoadHistory(room string) { +func (view *MainView) LoadHistory(roomID string) { defer debug.Recover() - roomView := view.rooms[room] + roomView, ok := view.getRoomView(roomID, true) + if !ok { + return + } msgView := roomView.MessageView() - batch := roomView.Room.PrevBatch - lockTime := time.Now().Unix() + 1 - - roomView.Room.LockHistory() - msgView.LoadingMessages = true - defer func() { - roomView.Room.UnlockHistory() - msgView.LoadingMessages = false - }() - - // There's no clean way to try to lock a mutex, so we just check if we still - // want to continue after we get the lock. This function should always be ran - // in a goroutine, so the blocking doesn't matter. - if time.Now().Unix() >= lockTime || batch != roomView.Room.PrevBatch { + if !atomic.CompareAndSwapInt32(&msgView.loadingMessages, 0, 1) { + // Locked return } + defer atomic.StoreInt32(&msgView.loadingMessages, 0) history, err := view.matrix.GetHistory(roomView.Room, 50) if err != nil { -- cgit v1.2.3