From 5a0eead96508404b16d0967319126c8cb738fcde Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 18 Nov 2023 13:41:37 +0100 Subject: [PATCH 1/2] Process events in runloop only when needed --- internal/driver/glfw/driver.go | 9 +- internal/driver/glfw/driver_desktop.go | 2 +- internal/driver/glfw/loop.go | 139 +++++++++++++------------ internal/driver/glfw/loop_desktop.go | 11 +- internal/driver/glfw/loop_goxjs.go | 10 +- 5 files changed, 94 insertions(+), 77 deletions(-) diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index 241b5e09e1..dc1ab76787 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -40,7 +40,7 @@ type gLDriver struct { windowLock sync.RWMutex windows []fyne.Window device *glDevice - done chan struct{} + mainDone uint32 drawDone chan struct{} waitForStart chan struct{} @@ -105,10 +105,8 @@ func (d *gLDriver) Quit() { fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerExitedForeground() } - // Only call close once to avoid panic. - if atomic.CompareAndSwapUint32(&running, 1, 0) { - close(d.done) - } + atomic.StoreUint32(&d.mainDone, 1) + postEmptyEvent() } func (d *gLDriver) addWindow(w *window) { @@ -173,7 +171,6 @@ func NewGLDriver() *gLDriver { repository.Register("file", intRepo.NewFileRepository()) return &gLDriver{ - done: make(chan struct{}), drawDone: make(chan struct{}), waitForStart: make(chan struct{}), animation: &animation.Runner{}, diff --git a/internal/driver/glfw/driver_desktop.go b/internal/driver/glfw/driver_desktop.go index 5660d745ed..ce3e645d0c 100644 --- a/internal/driver/glfw/driver_desktop.go +++ b/internal/driver/glfw/driver_desktop.go @@ -174,7 +174,7 @@ func (d *gLDriver) CurrentKeyModifiers() fyne.KeyModifier { func (d *gLDriver) catchTerm() { terminateSignal := make(chan os.Signal, 1) - signal.Notify(terminateSignal, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(terminateSignal, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-terminateSignal d.Quit() diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 7d2fab3228..2f0e04fe03 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -50,6 +50,7 @@ func runOnMain(f func()) { defer common.DonePool.Put(done) funcQueue <- funcData{f: f, done: done} + postEmptyEvent() <-done } @@ -115,87 +116,97 @@ func (d *gLDriver) runGL() { d.trayStart() } fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStarted() - eventTick := time.NewTicker(time.Second / 60) + + postEmptyEvent() + for { - select { - case <-d.done: - eventTick.Stop() - d.drawDone <- struct{}{} // wait for draw thread to stop - d.Terminate() + d.waitForEvents() + + if atomic.LoadUint32(&d.mainDone) == 1 { + d.drawDone <- struct{}{} // Tell draw thread to stop. + d.terminate() fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStopped() return + } + + select { case f := <-funcQueue: f.f() f.done <- struct{}{} - case <-eventTick.C: - d.tryPollEvents() - windowsToRemove := 0 - for _, win := range d.windowList() { - w := win.(*window) - if w.viewport == nil { - continue - } + default: // Do not wait for items to populate in the queue. + } - if w.viewport.ShouldClose() { - windowsToRemove++ - continue - } + windowsToRemove := 0 + for _, win := range d.windowList() { + w := win.(*window) + if w.viewport == nil { + 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) - } - } + if w.viewport.ShouldClose() { + windowsToRemove++ + continue + } - if drawOnMainThread { - d.drawSingleFrame() + 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) } } - if windowsToRemove > 0 { - oldWindows := d.windowList() - newWindows := make([]fyne.Window, 0, len(oldWindows)-windowsToRemove) - - for _, win := range oldWindows { - w := win.(*window) - if w.viewport == nil { - continue - } - if w.viewport.ShouldClose() { - w.viewLock.Lock() - w.visible = false - v := w.viewport - w.viewLock.Unlock() + if drawOnMainThread { + d.drawSingleFrame() + } + } + + if windowsToRemove > 0 { + d.removeWindows(windowsToRemove) + } + } +} - // remove window from window list - v.Destroy() - w.destroy(d) - continue - } +func (d *gLDriver) removeWindows(windowsToRemove int) { + oldWindows := d.windowList() + newWindows := make([]fyne.Window, 0, len(oldWindows)-windowsToRemove) - newWindows = append(newWindows, win) - } + for _, win := range oldWindows { + w := win.(*window) + if w.viewport == nil { + continue + } - d.windowLock.Lock() - d.windows = newWindows - d.windowLock.Unlock() + if w.viewport.ShouldClose() { + w.viewLock.Lock() + w.visible = false + v := w.viewport + w.viewLock.Unlock() - if len(newWindows) == 0 { - d.Quit() - } - } + // remove window from window list + v.Destroy() + w.destroy(d) + continue } + + newWindows = append(newWindows, win) + } + + d.windowLock.Lock() + d.windows = newWindows + d.windowLock.Unlock() + + if len(newWindows) == 0 { + d.Quit() } } diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index 652e6cf023..6d809470c0 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -24,16 +24,21 @@ func (d *gLDriver) initGLFW() { }) } -func (d *gLDriver) tryPollEvents() { +// waitForEvents() will block until one or more events occur. +func (*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() { +func postEmptyEvent() { + glfw.PostEmptyEvent() +} + +func (*gLDriver) terminate() { glfw.Terminate() } diff --git a/internal/driver/glfw/loop_goxjs.go b/internal/driver/glfw/loop_goxjs.go index e5082800ac..0bbb5d46ca 100644 --- a/internal/driver/glfw/loop_goxjs.go +++ b/internal/driver/glfw/loop_goxjs.go @@ -24,16 +24,20 @@ func (d *gLDriver) initGLFW() { }) } -func (d *gLDriver) tryPollEvents() { +func (*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() { +func postEmptyEvent() { + glfw.PostEmptyEvent() +} + +func (*gLDriver) terminate() { glfw.Terminate() } From 38cb822e4b94856f86d4dcdef0428de26a7a9cdb Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 18 Nov 2023 13:58:35 +0100 Subject: [PATCH 2/2] Add back ticker to make it all work correctly --- internal/driver/glfw/loop.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 2f0e04fe03..c4147a651a 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -119,7 +119,8 @@ func (d *gLDriver) runGL() { postEmptyEvent() - for { + eventTick := time.NewTicker(time.Second / 60) // TODO: Why does it not work without this? + for range eventTick.C { d.waitForEvents() if atomic.LoadUint32(&d.mainDone) == 1 {