aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Culverhouse <tim@timculverhouse.com>2022-10-06 11:46:41 -0500
committerRobin Jarry <robin@jarry.cc>2022-10-07 10:51:53 +0200
commitbb1249164d8de6d821dcf3293f5ff2650be95481 (patch)
tree8dd0194a877a226a976baeb253cb1fe9d77fa70e
parentd847073bdf67a2fd8c8695dbacbe010bcbfd27c8 (diff)
downloadaerc-bb1249164d8de6d821dcf3293f5ff2650be95481.tar.gz
aerc: use single event loop
Combine tcell events with WorkerMessages to better synchronize state with IO and UI. Remove Tick loop for rendering. Use events to trigger renders. Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--CHANGELOG.md2
-rw-r--r--aerc.go20
-rw-r--r--lib/msgstore.go2
-rw-r--r--lib/ui/textinput.go1
-rw-r--r--lib/ui/ui.go34
-rw-r--r--widgets/account.go17
-rw-r--r--widgets/aerc.go34
-rw-r--r--widgets/spinner.go1
-rw-r--r--widgets/terminal.go1
-rw-r--r--worker/types/messages.go11
-rw-r--r--worker/types/worker.go14
-rw-r--r--worker/worker.go4
12 files changed, 75 insertions, 66 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4602f94f..0739dcd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Running the same command multiple times only adds one entry to the command
history.
- Embedded terminal backend (libvterm was replaced by a pure go implementation).
+- Use event driven loop instead of Tick based
### Fixed
@@ -35,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
can be disabled by setting `outgoing-cred-cmd-cache=false` in
`accounts.conf`.
- Mouse support for embedded editors when `mouse-enabled=true`.
+- Numerous race conditions related to event handling order
## [0.12.0](https://git.sr.ht/~rjarry/aerc/refs/0.12.0) - 2022-09-01
diff --git a/aerc.go b/aerc.go
index e1a1345b..e5801a0c 100644
--- a/aerc.go
+++ b/aerc.go
@@ -9,9 +9,9 @@ import (
"runtime"
"sort"
"strings"
- "time"
"git.sr.ht/~sircmpwn/getopt"
+ "github.com/gdamore/tcell/v2"
"github.com/mattn/go-isatty"
"github.com/xo/terminfo"
@@ -28,6 +28,7 @@ import (
libui "git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/aerc/logging"
"git.sr.ht/~rjarry/aerc/widgets"
+ "git.sr.ht/~rjarry/aerc/worker/types"
)
func getCommands(selected libui.Drawable) []*commands.Commands {
@@ -241,15 +242,18 @@ func main() {
setWindowTitle()
}
- go ui.ProcessEvents()
- for !ui.ShouldExit() {
- for aerc.Tick() {
- // Continue updating our internal state
+ ui.ChannelEvents()
+ for event := range libui.MsgChannel {
+ switch event := event.(type) {
+ case tcell.Event:
+ ui.HandleEvent(event)
+ case types.WorkerMessage:
+ aerc.HandleMessage(event)
}
- if !ui.Render() {
- // ~60 FPS
- time.Sleep(16 * time.Millisecond)
+ if ui.ShouldExit() {
+ break
}
+ ui.Render()
}
err = aerc.CloseBackends()
if err != nil {
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 74a021a7..23fbb8f8 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -7,6 +7,7 @@ import (
"git.sr.ht/~rjarry/aerc/lib/marker"
"git.sr.ht/~rjarry/aerc/lib/sort"
+ "git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/aerc/logging"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/worker/types"
@@ -394,6 +395,7 @@ func (store *MessageStore) runThreadBuilder() {
}
store.threadBuilderDebounce = time.AfterFunc(store.threadBuilderDelay, func() {
store.runThreadBuilderNow()
+ ui.QueueRedraw()
})
}
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index ce8ccc55..d99871ce 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -314,6 +314,7 @@ func (ti *TextInput) showCompletions() {
ti.completions, ti.prefix = ti.tabcomplete(ti.StringLeft())
ti.completeIndex = -1
ti.Invalidate()
+ QueueRedraw()
}
func (ti *TextInput) OnChange(onChange func(ti *TextInput)) {
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index d477242e..e29ab13c 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -3,7 +3,6 @@ package ui
import (
"sync/atomic"
- "git.sr.ht/~rjarry/aerc/logging"
"github.com/gdamore/tcell/v2"
)
@@ -108,21 +107,24 @@ func (state *UI) EnableMouse() {
state.screen.EnableMouse()
}
-func (state *UI) ProcessEvents() {
- defer logging.PanicHandler()
-
- for !state.ShouldExit() {
- event := state.screen.PollEvent()
- if event, ok := event.(*tcell.EventResize); ok {
- state.screen.Clear()
- width, height := event.Size()
- state.ctx = NewContext(width, height, state.screen, state.onPopover)
- state.Content.Invalidate()
- }
- // if we have a popover, and it can handle the event, it does so
- if state.popover == nil || !state.popover.Event(event) {
- // otherwise, we send the event to the main content
- state.Content.Event(event)
+func (state *UI) ChannelEvents() {
+ go func() {
+ for {
+ MsgChannel <- state.screen.PollEvent()
}
+ }()
+}
+
+func (state *UI) HandleEvent(event tcell.Event) {
+ if event, ok := event.(*tcell.EventResize); ok {
+ state.screen.Clear()
+ width, height := event.Size()
+ state.ctx = NewContext(width, height, state.screen, state.onPopover)
+ state.Content.Invalidate()
+ }
+ // if we have a popover, and it can handle the event, it does so
+ if state.popover == nil || !state.popover.Event(event) {
+ // otherwise, we send the event to the main content
+ state.Content.Event(event)
}
}
diff --git a/widgets/account.go b/widgets/account.go
index c131f335..93a75973 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -74,7 +74,7 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
})
- worker, err := worker.NewWorker(acct.Source)
+ worker, err := worker.NewWorker(acct.Source, acct.Name)
if err != nil {
host.SetError(fmt.Sprintf("%s: %s", acct.Name, err))
logging.Errorf("%s: %v", acct.Name, err)
@@ -110,20 +110,6 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
return view, nil
}
-func (acct *AccountView) Tick() bool {
- if acct.worker == nil {
- return false
- }
- select {
- case msg := <-acct.worker.Messages:
- msg = acct.worker.ProcessMessage(msg)
- acct.onMessage(msg)
- return true
- default:
- return false
- }
-}
-
func (acct *AccountView) SetStatus(setters ...statusline.SetStateFunc) {
for _, fn := range setters {
fn(acct.state, acct.SelectedDirectory())
@@ -236,6 +222,7 @@ func (acct *AccountView) isSelected() bool {
}
func (acct *AccountView) onMessage(msg types.WorkerMessage) {
+ msg = acct.worker.ProcessMessage(msg)
switch msg := msg.(type) {
case *types.Done:
switch msg.InResponseTo().(type) {
diff --git a/widgets/aerc.go b/widgets/aerc.go
index bdd18dd5..b396cf5e 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -20,6 +20,7 @@ import (
"git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/aerc/logging"
"git.sr.ht/~rjarry/aerc/models"
+ "git.sr.ht/~rjarry/aerc/worker/types"
)
type Aerc struct {
@@ -145,26 +146,10 @@ func (aerc *Aerc) Beep() {
}
}
-func (aerc *Aerc) Tick() bool {
- more := false
- for _, acct := range aerc.accounts {
- more = acct.Tick() || more
- }
-
- if len(aerc.prompts.Children()) > 0 {
- more = true
- previous := aerc.focused
- prompt := aerc.prompts.Pop().(*ExLine)
- prompt.finish = func() {
- aerc.statusbar.Pop()
- aerc.focus(previous)
- }
-
- aerc.statusbar.Push(prompt)
- aerc.focus(prompt)
+func (aerc *Aerc) HandleMessage(msg types.WorkerMessage) {
+ if acct, ok := aerc.accounts[msg.Account()]; ok {
+ acct.onMessage(msg)
}
-
- return more
}
func (aerc *Aerc) OnInvalidate(onInvalidate func(d ui.Drawable)) {
@@ -182,6 +167,17 @@ func (aerc *Aerc) Focus(focus bool) {
}
func (aerc *Aerc) Draw(ctx *ui.Context) {
+ if len(aerc.prompts.Children()) > 0 {
+ previous := aerc.focused
+ prompt := aerc.prompts.Pop().(*ExLine)
+ prompt.finish = func() {
+ aerc.statusbar.Pop()
+ aerc.focus(previous)
+ }
+
+ aerc.statusbar.Push(prompt)
+ aerc.focus(prompt)
+ }
aerc.grid.Draw(ctx)
if aerc.dialog != nil {
if w, h := ctx.Width(), ctx.Height(); w > 8 && h > 4 {
diff --git a/widgets/spinner.go b/widgets/spinner.go
index f2607727..0e7c9005 100644
--- a/widgets/spinner.go
+++ b/widgets/spinner.go
@@ -49,6 +49,7 @@ func (s *Spinner) Start() {
case <-time.After(200 * time.Millisecond):
atomic.AddInt64(&s.frame, 1)
s.Invalidate()
+ ui.QueueRedraw()
}
}
}()
diff --git a/widgets/terminal.go b/widgets/terminal.go
index b854a288..82954a70 100644
--- a/widgets/terminal.go
+++ b/widgets/terminal.go
@@ -169,6 +169,7 @@ func (term *Terminal) HandleEvent(ev tcell.Event) bool {
}
case *tcellterm.EventClosed:
term.Close(nil)
+ ui.QueueRedraw()
}
return false
}
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 00a1a781..033450bb 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -12,11 +12,14 @@ type WorkerMessage interface {
InResponseTo() WorkerMessage
getId() int64
setId(id int64)
+ Account() string
+ setAccount(string)
}
type Message struct {
inResponseTo WorkerMessage
id int64
+ acct string
}
func RespondTo(msg WorkerMessage) Message {
@@ -37,6 +40,14 @@ func (m *Message) setId(id int64) {
m.id = id
}
+func (m *Message) Account() string {
+ return m.acct
+}
+
+func (m *Message) setAccount(name string) {
+ m.acct = name
+}
+
// Meta-messages
type Done struct {
diff --git a/worker/types/worker.go b/worker/types/worker.go
index b5f51496..d21a46a5 100644
--- a/worker/types/worker.go
+++ b/worker/types/worker.go
@@ -5,6 +5,7 @@ import (
"sync"
"sync/atomic"
+ "git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/aerc/logging"
"git.sr.ht/~rjarry/aerc/models"
)
@@ -16,9 +17,9 @@ type Backend interface {
}
type Worker struct {
- Backend Backend
- Actions chan WorkerMessage
- Messages chan WorkerMessage
+ Backend Backend
+ Actions chan WorkerMessage
+ Name string
actionCallbacks map[int64]func(msg WorkerMessage)
messageCallbacks map[int64]func(msg WorkerMessage)
@@ -28,10 +29,10 @@ type Worker struct {
sync.Mutex
}
-func NewWorker() *Worker {
+func NewWorker(name string) *Worker {
return &Worker{
Actions: make(chan WorkerMessage),
- Messages: make(chan WorkerMessage, 50),
+ Name: name,
actionCallbacks: make(map[int64]func(msg WorkerMessage)),
messageCallbacks: make(map[int64]func(msg WorkerMessage)),
actionQueue: list.New(),
@@ -103,13 +104,14 @@ func (worker *Worker) PostMessage(msg WorkerMessage,
cb func(msg WorkerMessage),
) {
worker.setId(msg)
+ msg.setAccount(worker.Name)
if resp := msg.InResponseTo(); resp != nil {
logging.Debugf("PostMessage %T:%T", msg, resp)
} else {
logging.Debugf("PostMessage %T", msg)
}
- worker.Messages <- msg
+ ui.MsgChannel <- msg
if cb != nil {
worker.Lock()
diff --git a/worker/worker.go b/worker/worker.go
index 2af892c4..bef5b727 100644
--- a/worker/worker.go
+++ b/worker/worker.go
@@ -9,12 +9,12 @@ import (
)
// Guesses the appropriate worker type based on the given source string
-func NewWorker(source string) (*types.Worker, error) {
+func NewWorker(source string, name string) (*types.Worker, error) {
u, err := url.Parse(source)
if err != nil {
return nil, err
}
- worker := types.NewWorker()
+ worker := types.NewWorker(name)
scheme := u.Scheme
if strings.ContainsRune(scheme, '+') {
scheme = scheme[:strings.IndexRune(scheme, '+')]