Skip to content

Commit

Permalink
Merge pull request fyne-io#4878 from sdassow/lifecycle-event-queue
Browse files Browse the repository at this point in the history
Add lifecycle event queue to ensure callbacks run before exit
  • Loading branch information
andydotxyz authored Jun 8, 2024
2 parents fb00e6c + de250cc commit 44fa9f6
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 5 deletions.
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (a *fyneApp) NewWindow(title string) fyne.Window {
}

func (a *fyneApp) Run() {
go a.lifecycle.RunEventQueue()
a.driver.Run()
}

Expand Down Expand Up @@ -138,6 +139,7 @@ func newAppWithDriver(d fyne.Driver, id string) fyne.App {
fyne.SetCurrentApp(newApp)

newApp.prefs = newApp.newDefaultPreferences()
newApp.lifecycle.InitEventQueue()
newApp.lifecycle.SetOnStoppedHookExecuted(func() {
if prefs, ok := newApp.prefs.(*preferences); ok {
prefs.forceImmediateSave()
Expand Down
36 changes: 36 additions & 0 deletions internal/app/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"sync/atomic"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/async"
)

var _ fyne.Lifecycle = (*Lifecycle)(nil)
Expand All @@ -18,6 +19,8 @@ type Lifecycle struct {
onStopped atomic.Pointer[func()]

onStoppedHookExecuted func()

eventQueue *async.UnboundedFuncChan
}

// SetOnStoppedHookExecuted is an internal function that lets Fyne schedule a clean-up after
Expand Down Expand Up @@ -100,3 +103,36 @@ func (l *Lifecycle) OnStopped() func() {
stopHook()
}
}

// DestroyEventQueue destroys the event queue.
func (l *Lifecycle) DestroyEventQueue() {
l.eventQueue.Close()
}

// InitEventQueue initializes the event queue.
func (l *Lifecycle) InitEventQueue() {
// This channel should be closed when the window is closed.
l.eventQueue = async.NewUnboundedFuncChan()
}

// QueueEvent uses this method to queue up a callback that handles an event. This ensures
// user interaction events for a given window are processed in order.
func (l *Lifecycle) QueueEvent(fn func()) {
l.eventQueue.In() <- fn
}

// RunEventQueue runs the event queue. This should called inside a go routine.
// This function blocks.
func (l *Lifecycle) RunEventQueue() {
for fn := range l.eventQueue.Out() {
fn()
}
}

// WaitForEvents wait for all the events.
func (l *Lifecycle) WaitForEvents() {
done := make(chan struct{})

l.eventQueue.In() <- func() { done <- struct{}{} }
<-done
}
9 changes: 8 additions & 1 deletion internal/app/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ func TestLifecycle(t *testing.T) {
assert.Nil(t, life.OnStarted())
assert.Nil(t, life.OnStopped())

var entered, exited, start, stop, hookedStop bool
var entered, exited, start, stop, hookedStop, called bool
life.InitEventQueue()
go life.RunEventQueue()
life.QueueEvent(func() { called = true })
life.SetOnEnteredForeground(func() { entered = true })
life.OnEnteredForeground()()
assert.True(t, entered)
Expand Down Expand Up @@ -48,4 +51,8 @@ func TestLifecycle(t *testing.T) {
assert.Nil(t, life.OnExitedForeground())
assert.Nil(t, life.OnStarted())
assert.Nil(t, life.OnStopped())

life.WaitForEvents()
life.DestroyEventQueue()
assert.True(t, called)
}
5 changes: 5 additions & 0 deletions internal/driver/glfw/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ func (d *gLDriver) Run() {

go d.catchTerm()
d.runGL()

// Ensure lifecycle events run to completion before the app exits
l := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle)
l.WaitForEvents()
l.DestroyEventQueue()
}

func (d *gLDriver) DoubleTapDelay() time.Duration {
Expand Down
5 changes: 3 additions & 2 deletions internal/driver/glfw/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ func (d *gLDriver) runGL() {
eventTick.Stop()
d.drawDone <- struct{}{} // wait for draw thread to stop
d.Terminate()
if f := fyne.CurrentApp().Lifecycle().(*app.Lifecycle).OnStopped(); f != nil {
go f() // don't block main, we don't have window event queue
l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle)
if f := l.OnStopped(); f != nil {
l.QueueEvent(f)
}
return
case f := <-funcQueue:
Expand Down
10 changes: 8 additions & 2 deletions internal/driver/mobile/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ func (d *driver) Run() {
settingsChange := make(chan fyne.Settings)
fyne.CurrentApp().Settings().AddChangeListener(settingsChange)
draw := time.NewTicker(time.Second / 60)
defer func() {
l := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle)
l.WaitForEvents()
l.DestroyEventQueue()
}()

for {
select {
Expand Down Expand Up @@ -292,8 +297,9 @@ func (d *driver) onStart() {
}

func (d *driver) onStop() {
if f := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).OnStopped(); f != nil {
go f() // don't block main, we don't have window event queue
l := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle)
if f := l.OnStopped(); f != nil {
l.QueueEvent(f)
}
}

Expand Down

0 comments on commit 44fa9f6

Please sign in to comment.