From 4b25d12aac8cf13a85e6ddde7a100a73dcedc070 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Mon, 21 Aug 2023 17:26:48 +0200 Subject: [PATCH] Use glfw.WaitEvents() for more efficient run loop --- internal/driver/glfw/driver.go | 11 ++- internal/driver/glfw/driver_desktop.go | 2 +- internal/driver/glfw/loop.go | 117 +++++++++++++------------ internal/driver/glfw/loop_desktop.go | 10 ++- internal/driver/glfw/loop_goxjs.go | 10 ++- 5 files changed, 85 insertions(+), 65 deletions(-) diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index 2ac723286b..508601dc27 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -8,6 +8,7 @@ import ( "os" "runtime" "sync" + "sync/atomic" "github.com/fyne-io/image/ico" @@ -39,7 +40,7 @@ type gLDriver struct { windowLock sync.RWMutex windows []fyne.Window device *glDevice - done chan interface{} + mainDone uint32 drawDone chan interface{} animation *animation.Runner @@ -102,10 +103,9 @@ func (d *gLDriver) Quit() { } fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerExitedForeground() } - defer func() { - recover() // we could be called twice - no safe way to check if d.done is closed - }() - close(d.done) + + atomic.StoreUint32(&d.mainDone, 1) + PostEmptyEvent() } func (d *gLDriver) addWindow(w *window) { @@ -173,7 +173,6 @@ func NewGLDriver() fyne.Driver { repository.Register("file", intRepo.NewFileRepository()) return &gLDriver{ - done: make(chan interface{}), drawDone: make(chan interface{}), animation: &animation.Runner{}, } diff --git a/internal/driver/glfw/driver_desktop.go b/internal/driver/glfw/driver_desktop.go index 464863ffca..0079b53471 100644 --- a/internal/driver/glfw/driver_desktop.go +++ b/internal/driver/glfw/driver_desktop.go @@ -171,7 +171,7 @@ func (d *gLDriver) CurrentKeyModifiers() fyne.KeyModifier { func catchTerm(d *gLDriver) { terminateSignals := make(chan os.Signal, 1) - signal.Notify(terminateSignals, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(terminateSignals, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) for range terminateSignals { d.Quit() diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index d02346c683..78093d044e 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -3,6 +3,7 @@ package glfw import ( "runtime" "sync" + "sync/atomic" "time" "fyne.io/fyne/v2" @@ -58,6 +59,7 @@ func runOnMain(f func()) { defer donePool.Put(done) funcQueue <- funcData{f: f, done: done} + PostEmptyEvent() <-done } @@ -112,74 +114,81 @@ func (d *gLDriver) runGL() { if d.trayStart != nil { d.trayStart() } + fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStarted() - for { - select { - case <-d.done: - eventTick.Stop() - d.drawDone <- nil // wait for draw thread to stop + + // Post empty event to not get stuck in the loop on first iteration. + PostEmptyEvent() + + for range eventTick.C { + d.waitForEvents() + + // Check if we are done with execution. + if atomic.LoadUint32(&d.mainDone) == 1 { + d.drawDone <- nil // Wait for draw thread to stop. d.Terminate() fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStopped() return + } + + select { case f := <-funcQueue: f.f() - if f.done != nil { - f.done <- struct{}{} + f.done <- struct{}{} + default: // Don't get stuck waiting for event. + } + + newWindows := []fyne.Window{} + reassign := false + for _, win := range d.windowList() { + w := win.(*window) + if w.viewport == nil { + continue } - case <-eventTick.C: - d.tryPollEvents() - newWindows := []fyne.Window{} - reassign := false - for _, win := range d.windowList() { - w := win.(*window) - if w.viewport == nil { - continue - } - if w.viewport.ShouldClose() { - reassign = true - w.viewLock.Lock() - w.visible = false - v := w.viewport - w.viewLock.Unlock() - - // remove window from window list - v.Destroy() - w.destroy(d) - continue - } + if w.viewport.ShouldClose() { + reassign = true + w.viewLock.Lock() + w.visible = false + v := w.viewport + w.viewLock.Unlock() + + // remove window from window list + v.Destroy() + w.destroy(d) + continue + } - w.viewLock.RLock() - expand := w.shouldExpand - fullScreen := w.fullScreen - w.viewLock.RUnlock() - - if expand && !fullScreen { - w.fitContent() - w.viewLock.Lock() - shouldExpand := w.shouldExpand - w.shouldExpand = false - view := w.viewport - w.viewLock.Unlock() - if shouldExpand { - view.SetSize(w.shouldWidth, w.shouldHeight) - } + w.viewLock.RLock() + expand := w.shouldExpand + fullScreen := w.fullScreen + w.viewLock.RUnlock() + + if expand && !fullScreen { + w.fitContent() + w.viewLock.Lock() + shouldExpand := w.shouldExpand + w.shouldExpand = false + view := w.viewport + w.viewLock.Unlock() + if shouldExpand { + view.SetSize(w.shouldWidth, w.shouldHeight) } + } - newWindows = append(newWindows, win) + newWindows = append(newWindows, win) - if drawOnMainThread { - d.drawSingleFrame() - } + if drawOnMainThread { + d.drawSingleFrame() } - if reassign { - d.windowLock.Lock() - d.windows = newWindows - d.windowLock.Unlock() + } + if reassign { + d.windowLock.Lock() + d.windows = newWindows + d.windowLock.Unlock() - if len(newWindows) == 0 { - d.Quit() - } + if len(newWindows) == 0 { + d.Quit() } } } diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index 652e6cf023..2e2baf0c46 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -24,16 +24,22 @@ func (d *gLDriver) initGLFW() { }) } -func (d *gLDriver) tryPollEvents() { +func (d *gLDriver) waitForEvents() { defer func() { if r := recover(); r != nil { fyne.LogError(fmt.Sprint("GLFW poll event error: ", r), nil) } }() - glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called + glfw.WaitEvents() } func (d *gLDriver) Terminate() { glfw.Terminate() } + +// PostEmptyEvent posts an event to the GLFW event queue. +// This is used to signal execution to continue from glfw.WaitEvents(). +func PostEmptyEvent() { + glfw.PostEmptyEvent() +} diff --git a/internal/driver/glfw/loop_goxjs.go b/internal/driver/glfw/loop_goxjs.go index e5082800ac..16423452e6 100644 --- a/internal/driver/glfw/loop_goxjs.go +++ b/internal/driver/glfw/loop_goxjs.go @@ -24,16 +24,22 @@ func (d *gLDriver) initGLFW() { }) } -func (d *gLDriver) tryPollEvents() { +func (d *gLDriver) waitForEvents() { defer func() { if r := recover(); r != nil { fyne.LogError(fmt.Sprint("GLFW poll event error: ", r), nil) } }() - glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called + glfw.WaitEvents() } func (d *gLDriver) Terminate() { glfw.Terminate() } + +// PostEmptyEvent posts an event to the GLFW event queue. +// This is used to signal execution to continue from glfw.WaitEvents(). +func PostEmptyEvent() { + glfw.PostEmptyEvent() +}