mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
app: introduce Window.driverDefer for blocking functions
Window.driverRun is designed to run functions on the driver event goroutine, to avoid race conditions with internal driver state and, more importantly, to run on the designated main or UI thread for platforms that require that. However, driverRun runs functions during the processing of a driver event, so if a function in turn triggers another driver event, deadlock occurs. This change introduces Window.driverDefer for functions that don't need to block event processing. Functions passed to driverDefer may themselves trigger new events. A few callers of driverRun remain; they need the result of their functions but are guaranteed not to trigger new events. Fixes gio#263 Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+45
-25
@@ -33,6 +33,9 @@ type Window struct {
|
||||
// driverFuncs is a channel of functions to run when
|
||||
// the Window has a valid driver.
|
||||
driverFuncs chan func(d driver)
|
||||
// driverDefers is like driverFuncs for functions that may
|
||||
// block and shouldn't be waited for.
|
||||
driverDefers chan func(d driver)
|
||||
// wakeups wakes up the native event loop to send a
|
||||
// WakeupEvent that flushes driverFuncs.
|
||||
wakeups chan struct{}
|
||||
@@ -108,6 +111,7 @@ func NewWindow(options ...Option) *Window {
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(d driver), 1),
|
||||
driverDefers: make(chan func(d driver), 1),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
dead: make(chan struct{}),
|
||||
notifyAnimate: make(chan struct{}, 1),
|
||||
@@ -207,12 +211,12 @@ func (w *Window) processFrame(frameStart time.Time, frame *op.Ops) {
|
||||
w.queue.q.Frame(frame)
|
||||
switch w.queue.q.TextInputState() {
|
||||
case router.TextInputOpen:
|
||||
w.driverRun(func(d driver) { d.ShowTextInput(true) })
|
||||
w.driverDefer(func(d driver) { d.ShowTextInput(true) })
|
||||
case router.TextInputClose:
|
||||
w.driverRun(func(d driver) { d.ShowTextInput(false) })
|
||||
w.driverDefer(func(d driver) { d.ShowTextInput(false) })
|
||||
}
|
||||
if hint, ok := w.queue.q.TextInputHint(); ok {
|
||||
w.driverRun(func(d driver) { d.SetInputHint(hint) })
|
||||
w.driverDefer(func(d driver) { d.SetInputHint(hint) })
|
||||
}
|
||||
if txt, ok := w.queue.q.WriteClipboard(); ok {
|
||||
w.WriteClipboard(txt)
|
||||
@@ -257,7 +261,7 @@ func (w *Window) Invalidate() {
|
||||
|
||||
// Option applies the options to the window.
|
||||
func (w *Window) Option(opts ...Option) {
|
||||
go w.driverRun(func(d driver) {
|
||||
go w.driverDefer(func(d driver) {
|
||||
c := new(config)
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
@@ -270,21 +274,21 @@ func (w *Window) Option(opts ...Option) {
|
||||
// of a clipboard.Event. Multiple reads may be coalesced
|
||||
// to a single event.
|
||||
func (w *Window) ReadClipboard() {
|
||||
go w.driverRun(func(d driver) {
|
||||
go w.driverDefer(func(d driver) {
|
||||
d.ReadClipboard()
|
||||
})
|
||||
}
|
||||
|
||||
// WriteClipboard writes a string to the clipboard.
|
||||
func (w *Window) WriteClipboard(s string) {
|
||||
go w.driverRun(func(d driver) {
|
||||
go w.driverDefer(func(d driver) {
|
||||
d.WriteClipboard(s)
|
||||
})
|
||||
}
|
||||
|
||||
// SetCursorName changes the current window cursor to name.
|
||||
func (w *Window) SetCursorName(name pointer.CursorName) {
|
||||
go w.driverRun(func(d driver) {
|
||||
go w.driverDefer(func(d driver) {
|
||||
d.SetCursor(name)
|
||||
})
|
||||
}
|
||||
@@ -295,7 +299,7 @@ func (w *Window) SetCursorName(name pointer.CursorName) {
|
||||
// Currently, only macOS, Windows and X11 drivers implement this functionality,
|
||||
// all others are stubbed.
|
||||
func (w *Window) Close() {
|
||||
go w.driverRun(func(d driver) {
|
||||
go w.driverDefer(func(d driver) {
|
||||
d.Close()
|
||||
})
|
||||
}
|
||||
@@ -314,6 +318,9 @@ func (w *Window) Run(f func()) {
|
||||
})
|
||||
}
|
||||
|
||||
// driverRun runs f on the driver event goroutine and returns when f has
|
||||
// completed. It can only be called during the processing of an event from
|
||||
// w.in.
|
||||
func (w *Window) driverRun(f func(d driver)) {
|
||||
done := make(chan struct{})
|
||||
wrapper := func(d driver) {
|
||||
@@ -322,7 +329,6 @@ func (w *Window) driverRun(f func(d driver)) {
|
||||
}
|
||||
select {
|
||||
case w.driverFuncs <- wrapper:
|
||||
w.wakeup()
|
||||
select {
|
||||
case <-done:
|
||||
case <-w.dead:
|
||||
@@ -331,6 +337,16 @@ func (w *Window) driverRun(f func(d driver)) {
|
||||
}
|
||||
}
|
||||
|
||||
// driverDefer is like driverRun but can be run from any context. It doesn't wait
|
||||
// for f to return.
|
||||
func (w *Window) driverDefer(f func(d driver)) {
|
||||
select {
|
||||
case w.driverDefers <- f:
|
||||
w.wakeup()
|
||||
case <-w.dead:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) updateAnimation() {
|
||||
animate := false
|
||||
if w.delayedDraw != nil {
|
||||
@@ -378,10 +394,20 @@ func (c *callbacks) SetDriver(d driver) {
|
||||
}
|
||||
|
||||
func (c *callbacks) Event(e event.Event) {
|
||||
select {
|
||||
case c.w.in <- e:
|
||||
c.w.runFuncs(c.d)
|
||||
case <-c.w.dead:
|
||||
deferChan := c.w.driverDefers
|
||||
if c.d == nil {
|
||||
deferChan = nil
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case f := <-deferChan:
|
||||
f(c.d)
|
||||
case c.w.in <- e:
|
||||
c.w.runFuncs(c.d)
|
||||
return
|
||||
case <-c.w.dead:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,18 +417,7 @@ func (w *Window) runFuncs(d driver) {
|
||||
<-w.ack
|
||||
return
|
||||
}
|
||||
// Flush pending runnnables.
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-w.notifyAnimate:
|
||||
d.SetAnimating(w.animating)
|
||||
case f := <-w.driverFuncs:
|
||||
f(d)
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
var defers []func(d driver)
|
||||
// Wait for ack while running incoming runnables.
|
||||
for {
|
||||
select {
|
||||
@@ -410,7 +425,12 @@ loop:
|
||||
d.SetAnimating(w.animating)
|
||||
case f := <-w.driverFuncs:
|
||||
f(d)
|
||||
case f := <-w.driverDefers:
|
||||
defers = append(defers, f)
|
||||
case <-w.ack:
|
||||
for _, f := range defers {
|
||||
f(d)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user