mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
app: [API] replace events channel with an iterator interface
The goroutine started by Window.run runs concurrently with the user goroutine receiving from Window.Events, leading to races such as #543. This change replaces the Window.run goroutine and the Window.Events channel with an iterator API driven by the user goroutine directly. Fixes: https://todo.sr.ht/~eliasnaur/gio/543 Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+6
-5
@@ -12,16 +12,16 @@ Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
|||||||
is embedded in another project, NewWindow merely connects with a previously
|
is embedded in another project, NewWindow merely connects with a previously
|
||||||
created window.
|
created window.
|
||||||
|
|
||||||
A Window is run by receiving events from its Events channel. The most
|
A Window is run by calling NextEvent in a loop. The most important event is
|
||||||
important event is FrameEvent that prompts an update of the window
|
FrameEvent that prompts an update of the window contents.
|
||||||
contents and state.
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
import "gioui.org/unit"
|
import "gioui.org/unit"
|
||||||
|
|
||||||
w := app.NewWindow()
|
w := app.NewWindow()
|
||||||
for e := range w.Events() {
|
for {
|
||||||
|
e := w.NextEvent()
|
||||||
if e, ok := e.(system.FrameEvent); ok {
|
if e, ok := e.(system.FrameEvent); ok {
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
// Add operations to ops.
|
// Add operations to ops.
|
||||||
@@ -50,7 +50,8 @@ For example, to display a blank but otherwise functional window:
|
|||||||
func main() {
|
func main() {
|
||||||
go func() {
|
go func() {
|
||||||
w := app.NewWindow()
|
w := app.NewWindow()
|
||||||
for range w.Events() {
|
for {
|
||||||
|
w.NextEvent()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
app.Main()
|
app.Main()
|
||||||
|
|||||||
+53
-29
@@ -61,6 +61,8 @@ type Window struct {
|
|||||||
// actions are the actions waiting to be performed.
|
// actions are the actions waiting to be performed.
|
||||||
actions chan system.Action
|
actions chan system.Action
|
||||||
|
|
||||||
|
// out is where the platform backend delivers events bound for the
|
||||||
|
// user program.
|
||||||
out chan event.Event
|
out chan event.Event
|
||||||
frames chan *op.Ops
|
frames chan *op.Ops
|
||||||
frameAck chan struct{}
|
frameAck chan struct{}
|
||||||
@@ -107,6 +109,16 @@ type Window struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
imeState editorState
|
imeState editorState
|
||||||
|
|
||||||
|
// event stores the state required for processing and delivering events
|
||||||
|
// from NextEvent. If we had support for range over func, this would
|
||||||
|
// be the iterator state.
|
||||||
|
eventState struct {
|
||||||
|
created bool
|
||||||
|
initialOpts []Option
|
||||||
|
wakeup func()
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type editorState struct {
|
type editorState struct {
|
||||||
@@ -185,7 +197,7 @@ func NewWindow(options ...Option) *Window {
|
|||||||
w.imeState.compose = key.Range{Start: -1, End: -1}
|
w.imeState.compose = key.Range{Start: -1, End: -1}
|
||||||
w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
|
w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
|
||||||
w.callbacks.w = w
|
w.callbacks.w = w
|
||||||
go w.run(options)
|
w.eventState.initialOpts = options
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,11 +207,6 @@ func decoHeightOpt(h unit.Dp) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events returns the channel where events are delivered.
|
|
||||||
func (w *Window) Events() <-chan event.Event {
|
|
||||||
return w.out
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the window contents, input operations declare input handlers,
|
// update the window contents, input operations declare input handlers,
|
||||||
// and so on. The supplied operations list completely replaces the window state
|
// and so on. The supplied operations list completely replaces the window state
|
||||||
// from previous calls.
|
// from previous calls.
|
||||||
@@ -713,7 +720,7 @@ func (w *Window) waitAck(d driver) {
|
|||||||
select {
|
select {
|
||||||
case f := <-w.driverFuncs:
|
case f := <-w.driverFuncs:
|
||||||
f(d)
|
f(d)
|
||||||
case w.out <- event.Event(nil):
|
case w.out <- theFlushEvent:
|
||||||
// A dummy event went through, so we know the application has processed the previous event.
|
// A dummy event went through, so we know the application has processed the previous event.
|
||||||
return
|
return
|
||||||
case <-w.immediateRedraws:
|
case <-w.immediateRedraws:
|
||||||
@@ -747,7 +754,7 @@ func (w *Window) waitFrame(d driver) *op.Ops {
|
|||||||
case frame := <-w.frames:
|
case frame := <-w.frames:
|
||||||
// The client called FrameEvent.Frame.
|
// The client called FrameEvent.Frame.
|
||||||
return frame
|
return frame
|
||||||
case w.out <- event.Event(nil):
|
case w.out <- theFlushEvent:
|
||||||
// The client ignored FrameEvent and continued processing
|
// The client ignored FrameEvent and continued processing
|
||||||
// events.
|
// events.
|
||||||
return nil
|
return nil
|
||||||
@@ -881,7 +888,6 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil {
|
if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil {
|
||||||
w.destroyGPU()
|
w.destroyGPU()
|
||||||
w.out <- system.DestroyEvent{Err: err}
|
w.out <- system.DestroyEvent{Err: err}
|
||||||
close(w.out)
|
|
||||||
close(w.destroy)
|
close(w.destroy)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -890,7 +896,6 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
case system.DestroyEvent:
|
case system.DestroyEvent:
|
||||||
w.destroyGPU()
|
w.destroyGPU()
|
||||||
w.out <- e2
|
w.out <- e2
|
||||||
close(w.out)
|
|
||||||
close(w.destroy)
|
close(w.destroy)
|
||||||
case ViewEvent:
|
case ViewEvent:
|
||||||
w.out <- e2
|
w.out <- e2
|
||||||
@@ -938,43 +943,51 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) run(options []Option) {
|
// NextEvent blocks until an event is received from the window, such as
|
||||||
if err := newWindow(&w.callbacks, options); err != nil {
|
// [io/system.FrameEvent]. It blocks forever if called after [io/system.DestroyEvent]
|
||||||
w.out <- system.DestroyEvent{Err: err}
|
// has been returned.
|
||||||
close(w.out)
|
func (w *Window) NextEvent() event.Event {
|
||||||
close(w.destroy)
|
state := &w.eventState
|
||||||
return
|
if !state.created {
|
||||||
|
state.created = true
|
||||||
|
if err := newWindow(&w.callbacks, state.initialOpts); err != nil {
|
||||||
|
close(w.destroy)
|
||||||
|
return system.DestroyEvent{Err: err}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var wakeup func()
|
|
||||||
var timer *time.Timer
|
|
||||||
for {
|
for {
|
||||||
var (
|
var (
|
||||||
wakeups <-chan struct{}
|
wakeups <-chan struct{}
|
||||||
timeC <-chan time.Time
|
timeC <-chan time.Time
|
||||||
)
|
)
|
||||||
if wakeup != nil {
|
if state.wakeup != nil {
|
||||||
wakeups = w.wakeups
|
wakeups = w.wakeups
|
||||||
if timer != nil {
|
if state.timer != nil {
|
||||||
timeC = timer.C
|
timeC = state.timer.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case t := <-w.scheduledRedraws:
|
case t := <-w.scheduledRedraws:
|
||||||
if timer != nil {
|
if state.timer != nil {
|
||||||
timer.Stop()
|
state.timer.Stop()
|
||||||
}
|
}
|
||||||
timer = time.NewTimer(time.Until(t))
|
state.timer = time.NewTimer(time.Until(t))
|
||||||
case <-w.destroy:
|
case e := <-w.out:
|
||||||
return
|
// Receiving a flushEvent indicates to the platform backend that
|
||||||
|
// all previous events have been processed by the user program.
|
||||||
|
if _, ok := e.(flushEvent); ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return e
|
||||||
case <-timeC:
|
case <-timeC:
|
||||||
select {
|
select {
|
||||||
case w.redraws <- struct{}{}:
|
case w.redraws <- struct{}{}:
|
||||||
wakeup()
|
state.wakeup()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
case <-wakeups:
|
case <-wakeups:
|
||||||
wakeup()
|
state.wakeup()
|
||||||
case wakeup = <-w.wakeupFuncs:
|
case state.wakeup = <-w.wakeupFuncs:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1165,3 +1178,14 @@ func Decorated(enabled bool) Option {
|
|||||||
cnf.Decorated = enabled
|
cnf.Decorated = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flushEvent is sent to detect when the user program
|
||||||
|
// has completed processing of all prior events. Its an
|
||||||
|
// [io/event.Event] but only for internal use.
|
||||||
|
type flushEvent struct{}
|
||||||
|
|
||||||
|
func (t flushEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
|
// theFlushEvent avoids allocating garbage when sending
|
||||||
|
// flushEvents.
|
||||||
|
var theFlushEvent flushEvent
|
||||||
|
|||||||
Reference in New Issue
Block a user