Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating a version of fyne.Do that does not wait #5448

Merged
merged 5 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ type Driver interface {
// Since: 2.5
SetDisableScreenBlanking(bool)

// DoFromGoroutine provides a way to queue a function that is running on a goroutine back to
// the central thread for Fyne updates.
// DoFromGoroutine provides a way to queue a function `fn` that is running on a goroutine back to
// the central thread for Fyne updates, waiting for it to return if `wait` is true.
// The driver provides the implementation normally accessed through [fyne.Do].
// This is required when background tasks want to execute code safely in the graphical context.
//
// Since: 2.6
DoFromGoroutine(func())
DoFromGoroutine(fn func(), wait bool)
}
4 changes: 2 additions & 2 deletions internal/app/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ func (l *Lifecycle) QueueEvent(fn func()) {

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

Expand Down
2 changes: 1 addition & 1 deletion internal/app/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestLifecycle(t *testing.T) {

var entered, exited, start, stop, hookedStop, called bool
life.InitEventQueue()
go life.RunEventQueue(func(fn func()) {
go life.RunEventQueue(func(fn func(), _ bool) {
fn()
})
life.QueueEvent(func() { called = true })
Expand Down
4 changes: 2 additions & 2 deletions internal/driver/glfw/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ func toOSIcon(icon []byte) ([]byte, error) {
return buf.Bytes(), nil
}

func (d *gLDriver) DoFromGoroutine(f func()) {
func (d *gLDriver) DoFromGoroutine(f func(), wait bool) {
async.EnsureNotMain(func() {
runOnMain(f)
runOnMainWithWait(f, wait)
})
}

Expand Down
21 changes: 16 additions & 5 deletions internal/driver/glfw/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,28 @@ func init() {

// force a function f to run on the main thread
func runOnMain(f func()) {
runOnMainWithWait(f, true)
}

// force a function f to run on the main thread and specify if we should wait for it to return
func runOnMainWithWait(f func(), wait bool) {
// If we are on main just execute - otherwise add it to the main queue and wait.
// The "running" variable is normally false when we are on the main thread.
if !running.Load() {
f()
return
}

done := common.DonePool.Get()
defer common.DonePool.Put(done)
var done chan struct{}
if wait {
done = common.DonePool.Get()
defer common.DonePool.Put(done)
}

funcQueue <- funcData{f: f, done: done}

<-done
if wait {
<-done
}
}

// Preallocate to avoid allocations on every drawSingleFrame.
Expand Down Expand Up @@ -110,7 +119,9 @@ func (d *gLDriver) runGL() {
return
case f := <-funcQueue:
f.f()
f.done <- struct{}{}
if f.done != nil {
f.done <- struct{}{}
}
case <-eventTick.C:
d.pollEvents()
for i := 0; i < len(d.windows); i++ {
Expand Down
6 changes: 1 addition & 5 deletions internal/driver/glfw/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,11 +867,7 @@ func (w *window) Context() any {

func (w *window) runOnMainWhenCreated(fn func()) {
if w.view() != nil {
if async.IsMainGoroutine() {
fn()
return
}
fyne.Do(fn)
async.EnsureMain(fn)
return
}

Expand Down
2 changes: 1 addition & 1 deletion internal/driver/mobile/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func (c *canvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, tap
c.touchCancelFunc = nil
c.touchLastTapped = nil
c.touchCancelLock.Unlock()
})
}, true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this function call just be converted to fyne.DoAsync?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure - that is going out of the driver to a utility function which does thread checks then back into the driver code - feels inefficient. Either way wouldn't it be the Sync/wait/fyne.Do version?

}

func (c *canvas) windowHeadIsDisplacing() bool {
Expand Down
8 changes: 4 additions & 4 deletions internal/driver/mobile/canvas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func Test_canvas_Focusable(t *testing.T) {
c.tapUp(pos, 0, func(wid fyne.Tappable, ev *fyne.PointEvent) {
wid.Tapped(ev)
}, nil, nil, nil)
})
}, true)

waitAndCheck(tapDoubleDelay/time.Millisecond+150, func() {
assert.Equal(t, 1, content.focusedTimes)
Expand All @@ -154,7 +154,7 @@ func Test_canvas_Focusable(t *testing.T) {
c.tapUp(pos, 1, func(wid fyne.Tappable, ev *fyne.PointEvent) {
wid.Tapped(ev)
}, nil, nil, nil)
})
}, true)
waitAndCheck(tapDoubleDelay/time.Millisecond+150, func() {
assert.Equal(t, 1, content.focusedTimes)
assert.Equal(t, 0, content.unfocusedTimes)
Expand All @@ -177,7 +177,7 @@ func Test_canvas_Focusable(t *testing.T) {
c.tapDown(fyne.NewPos(10, 10), 2)
assert.Equal(t, 1, content.focusedTimes)
assert.Equal(t, 1, content.unfocusedTimes)
})
}, true)
}

func Test_canvas_InteractiveArea(t *testing.T) {
Expand Down Expand Up @@ -534,7 +534,7 @@ func waitAndCheck(msWait time.Duration, fn func()) {
fn()

waitForCheck <- struct{}{}
})
}, true)
}()
<-waitForCheck
}
21 changes: 14 additions & 7 deletions internal/driver/mobile/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,24 @@ func init() {
runtime.LockOSThread()
}

func (d *driver) DoFromGoroutine(fn func()) {
func (d *driver) DoFromGoroutine(fn func(), wait bool) {
async.EnsureNotMain(func() {
done := common.DonePool.Get()
defer common.DonePool.Put(done)
var done chan struct{}
if wait {
done = common.DonePool.Get()
defer common.DonePool.Put(done)
}

d.queuedFuncs.In() <- func() {
fn()
done <- struct{}{}
if wait {
done <- struct{}{}
}
}

<-done
if wait {
<-done
}
})
}

Expand Down Expand Up @@ -446,11 +453,11 @@ func (d *driver) tapUpCanvas(w *window, x, y float32, tapID touch.Sequence) {

d.DoFromGoroutine(func() {
wid.Dragged(ev)
})
}, true)
time.Sleep(time.Millisecond * 16)
}

d.DoFromGoroutine(wid.DragEnd)
d.DoFromGoroutine(wid.DragEnd, true)
}()
})
}
Expand Down
2 changes: 1 addition & 1 deletion test/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func (s *testSettings) apply() {
cache.ResetThemeCaches()
intapp.ApplySettings(s, s.app)
s.app.propertyLock.Unlock()
})
}, false)

s.app.propertyLock.Lock()
s.app.appliedTheme = s.Theme()
Expand Down
3 changes: 2 additions & 1 deletion test/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func NewDriverWithPainter(painter SoftwarePainter) fyne.Driver {
return &driver{painter: painter}
}

func (d *driver) DoFromGoroutine(f func()) {
// DoFromGoroutine on a test driver ignores the wait flag as our threading is simple
func (d *driver) DoFromGoroutine(f func(), _ bool) {
// Tests all run on a single (but potentially different per-test) thread
async.EnsureNotMain(f)
}
Expand Down
12 changes: 11 additions & 1 deletion thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,15 @@ package fyne
//
// Since: 2.6
func Do(fn func()) {
CurrentApp().Driver().DoFromGoroutine(fn)
CurrentApp().Driver().DoFromGoroutine(fn, true)
}

// DoAsync is used to execute a specified function in the main Fyne runtime context without waiting.
// This is required when a background process wishes to adjust graphical elements of a running app.
// Developers should use this only from within goroutines they have created and when the result does not have to
// be waited for.
//
// Since: 2.6
func DoAsync(fn func()) {
CurrentApp().Driver().DoFromGoroutine(fn, false)
}
Loading