Commit Graph

2 Commits

Author SHA1 Message Date
Tim Culverhouse 049c72393a invalidatable: always mark ui as dirty OnInvalidate
The Invalidatable struct is designed so that a widget can have a
callback function ran when it is Invalidated. This is used to cascade up
the widget tree, marking things as Invalid along the way so that only
Invalid widgets are drawn. However, this is only implemented at the grid
cell level for checks if the cell is invalidated -- and the grid cells
are never set back to a "valid" state. The effect of this is that no
matter what is invalidated, the entire UI gets drawn again.

The calling through the Invalidate callbacks creates *several* race
conditions, as Invalidate is called from several different goroutines,
and many widgets call invalidate on their parent or children.

Tcell has optimizations to only rerender screen cells that have changed
their rune and style. The only performance penalty by redrawing the
entire screen for aerc is the operations *within the aerc draw methods*.
Most of these are not expensive and have relatively no impact on
performance.

Skip all of the OnInvalidates, and directly invalidate the UI when
DoInvalidate is called by a widget. This reduces data races, and
simplifies the widget redraw logic signficantly.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
2022-10-07 10:51:53 +02:00
Simon Ser 5685a17674 lib/ui: introduce Invalidatable
Many Drawable implementations have their own Invalidate and OnInvalidate
functions, with an unexported onInvalidate field. However OnInvalidate and
Invalidate are usually not called in the same goroutine. This results in a race
on this field, e.g.:

    Read at 0x00c000094748 by goroutine 7:
      git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
          /home/simon/src/aerc2/widgets/dirlist.go:85 +0x56
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
          /home/simon/src/aerc2/widgets/spinner.go:93 +0x1bb

    Previous write at 0x00c000094748 by main goroutine:
      [failed to restore the stack]

    Goroutine 7 (running) created at:
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start()
          /home/simon/src/aerc2/widgets/spinner.go:46 +0x8f
      git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
          /home/simon/src/aerc2/widgets/dirlist.go:37 +0x286
      git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
          /home/simon/src/aerc2/widgets/account.go:50 +0x5ca
      git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
          /home/simon/src/aerc2/widgets/aerc.go:60 +0x800
      main.main()
          /home/simon/src/aerc2/aerc.go:65 +0x33e

To fix this, introduce a new type, Invalidatable, which protects the field.
Unfortunately the Drawable must be passed to the callback function in
Invalidate, so we still need to re-implement this in each Invalidatable user.
2019-04-27 14:30:28 -04:00