Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/develop' into efficient-runloop
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacalz committed Jan 17, 2025
2 parents 4a917f0 + e7068b7 commit 9c98e2c
Show file tree
Hide file tree
Showing 37 changed files with 241 additions and 229 deletions.
File renamed without changes.
File renamed without changes.
12 changes: 7 additions & 5 deletions dialog/color_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package dialog

import (
"strconv"
"sync/atomic"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
Expand Down Expand Up @@ -152,7 +151,7 @@ func (e *colorChannelEntry) MinSize() fyne.Size {

type userChangeEntry struct {
widget.Entry
userTyped atomic.Bool
userTyped bool
}

func newUserChangeEntry(text string) *userChangeEntry {
Expand All @@ -164,9 +163,12 @@ func newUserChangeEntry(text string) *userChangeEntry {

func (e *userChangeEntry) setOnChanged(onChanged func(s string)) {
e.Entry.OnChanged = func(text string) {
if !e.userTyped.CompareAndSwap(true, false) {
if !e.userTyped {
return
}

e.userTyped = false

if onChanged != nil {
onChanged(text)
}
Expand All @@ -175,11 +177,11 @@ func (e *userChangeEntry) setOnChanged(onChanged func(s string)) {
}

func (e *userChangeEntry) TypedRune(r rune) {
e.userTyped.Store(true)
e.userTyped = true
e.Entry.TypedRune(r)
}

func (e *userChangeEntry) TypedKey(ev *fyne.KeyEvent) {
e.userTyped.Store(true)
e.userTyped = true
e.Entry.TypedKey(ev)
}
File renamed without changes.
7 changes: 3 additions & 4 deletions internal/animation/animation.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package animation

import (
"sync/atomic"
"time"

"fyne.io/fyne/v2"
Expand All @@ -14,7 +13,7 @@ type anim struct {
reverse bool
start time.Time
total int64
stopped atomic.Bool
stopped bool
}

func newAnim(a *fyne.Animation) *anim {
Expand All @@ -25,9 +24,9 @@ func newAnim(a *fyne.Animation) *anim {
}

func (a *anim) setStopped() {
a.stopped.Store(true)
a.stopped = true
}

func (a *anim) isStopped() bool {
return a.stopped.Load()
return a.stopped
}
File renamed without changes.
49 changes: 16 additions & 33 deletions internal/app/lifecycle.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package app

import (
"sync/atomic"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/async"
)
Expand All @@ -13,10 +11,10 @@ var _ fyne.Lifecycle = (*Lifecycle)(nil)
//
// Since: 2.1
type Lifecycle struct {
onForeground atomic.Pointer[func()]
onBackground atomic.Pointer[func()]
onStarted atomic.Pointer[func()]
onStopped atomic.Pointer[func()]
onForeground func()
onBackground func()
onStarted func()
onStopped func()

onStoppedHookExecuted func()

Expand All @@ -31,75 +29,60 @@ func (l *Lifecycle) SetOnStoppedHookExecuted(f func()) {

// SetOnEnteredForeground hooks into the app becoming foreground.
func (l *Lifecycle) SetOnEnteredForeground(f func()) {
l.onForeground.Store(&f)
l.onForeground = f
}

// SetOnExitedForeground hooks into the app having moved to the background.
// Depending on the platform it may still be visible but will not receive keyboard events.
// On some systems hover or desktop mouse move events may still occur.
func (l *Lifecycle) SetOnExitedForeground(f func()) {
l.onBackground.Store(&f)
l.onBackground = f
}

// SetOnStarted hooks into an event that says the app is now running.
func (l *Lifecycle) SetOnStarted(f func()) {
l.onStarted.Store(&f)
l.onStarted = f
}

// SetOnStopped hooks into an event that says the app is no longer running.
func (l *Lifecycle) SetOnStopped(f func()) {
l.onStopped.Store(&f)
l.onStopped = f
}

// OnEnteredForeground returns the focus gained hook, if one is registered.
func (l *Lifecycle) OnEnteredForeground() func() {
f := l.onForeground.Load()
if f == nil {
return nil
}

return *f
return l.onForeground
}

// OnExitedForeground returns the focus lost hook, if one is registered.
func (l *Lifecycle) OnExitedForeground() func() {
f := l.onBackground.Load()
if f == nil {
return nil
}

return *f
return l.onBackground
}

// OnStarted returns the started hook, if one is registered.
func (l *Lifecycle) OnStarted() func() {
f := l.onStarted.Load()
if f == nil {
return nil
}

return *f
return l.onStarted
}

// OnStopped returns the stopped hook, if one is registered.
func (l *Lifecycle) OnStopped() func() {
stopped := l.onStopped.Load()
stopped := l.onStopped
stopHook := l.onStoppedHookExecuted
if (stopped == nil || *stopped == nil) && stopHook == nil {
if stopped == nil && stopHook == nil {
return nil
}

if stopHook == nil {
return *stopped
return stopped
}

if stopped == nil || *stopped == nil {
if stopped == nil {
return stopHook
}

// we have a stopped handle and the onStoppedHook
return func() {
(*stopped)()
stopped()
stopHook()
}
}
Expand Down
58 changes: 58 additions & 0 deletions internal/async/goroutine.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package async

import (
"log"
"runtime"
"strings"

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

// mainGoroutineID stores the main goroutine ID.
// This ID must be initialized in main.init because
// a main goroutine may not equal to 1 due to the
Expand All @@ -10,6 +19,55 @@ func init() {
mainGoroutineID = goroutineID()
}

// IsMainGoroutine returns true if it is called from the main goroutine, false otherwise.
func IsMainGoroutine() bool {
return goroutineID() == mainGoroutineID
}

// EnsureNotMain is part of our thread transition and makes sure that the passed function runs off main.
// If the context is running on a goroutine or the transition has been disabled this will blindly run.
// Otherwise, an error will be logged and the function will be called on a new goroutine.
//
// This will be removed later and should never be public
func EnsureNotMain(fn func()) {
if build.DisableThreadChecks || !IsMainGoroutine() {
fn()
return
}

log.Println("*** Error in Fyne call thread, fyne.Do called from main goroutine ***")

logStackTop(2)
go fn()
}

// EnsureMain is part of our thread transition and makes sure that the passed function runs on main.
// If the context is main or the transition has been disabled this will blindly run.
// Otherwise, an error will be logged and the function will be called on the main goroutine.
//
// This will be removed later and should never be public
func EnsureMain(fn func()) {
if build.DisableThreadChecks || IsMainGoroutine() {
fn()
return
}

log.Println("*** Error in Fyne call thread, this should have been called in fyne.Do ***")

logStackTop(1)
fyne.Do(fn)
}

func logStackTop(skip int) {
pc := make([]uintptr, 2)
count := runtime.Callers(2+skip, pc)
frames := runtime.CallersFrames(pc)
frame, more := frames.Next()
if more && count > 1 {
nextFrame, _ := frames.Next() // skip an occasional driver call to itself
if !strings.Contains(nextFrame.File, "runtime") { // don't descend into Go
frame = nextFrame
}
}
log.Printf(" From: %s:%d", frame.File, frame.Line)
}
File renamed without changes.
2 changes: 2 additions & 0 deletions internal/build/build.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
// Package build contains information about they type of build currently running.
package build

const DisableThreadChecks = false
6 changes: 6 additions & 0 deletions internal/build/menu_integrated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build !darwin || no_native_menus

package build

// HasNativeMenu is true if the app is built with support for native menu.
const HasNativeMenu = false
6 changes: 6 additions & 0 deletions internal/build/menu_native.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build darwin && !no_native_menus

package build

// HasNativeMenu is true if the app is built with support for native menu.
const HasNativeMenu = true
27 changes: 3 additions & 24 deletions internal/cache/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cache

import (
"os"
"sync/atomic"
"time"

"fyne.io/fyne/v2"
Expand Down Expand Up @@ -175,35 +174,15 @@ func matchesACanvas(cinfo *canvasInfo, canvases []fyne.Canvas) bool {
}

type expiringCache struct {
expires atomic.Pointer[time.Time]
}

// isExpired check if the cache data is expired.
func (c *expiringCache) isExpired(now time.Time) bool {
t := c.expires.Load()
if t == nil {
return (time.Time{}).Before(now)
}
return (*t).Before(now)
}

// setAlive updates expiration time.
func (c *expiringCache) setAlive() {
time := timeNow().Add(cacheDuration)
c.expires.Store(&time)
}

type expiringCacheNoLock struct {
expires time.Time
}

// isExpired check if the cache data is expired.
func (c *expiringCacheNoLock) isExpired(now time.Time) bool {
func (c *expiringCache) isExpired(now time.Time) bool {
return c.expires.Before(now)
}

// setAlive updates expiration time.
func (c *expiringCacheNoLock) setAlive() {
t := timeNow().Add(cacheDuration)
c.expires = t
func (c *expiringCache) setAlive() {
c.expires = timeNow().Add(cacheDuration)
}
22 changes: 1 addition & 21 deletions internal/cache/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,23 +192,6 @@ func Test_expiringCache(t *testing.T) {
assert.True(t, c.isExpired(tm.now))
}

func Test_expiringCacheNoLock(t *testing.T) {
tm := &timeMock{}
tm.setTime(10, 10)

c := &expiringCacheNoLock{}
assert.True(t, c.isExpired(tm.now))

c.setAlive()

tm.setTime(10, 20)
assert.False(t, c.isExpired(tm.now))

tm.setTime(10, 11)
tm.now = tm.now.Add(cacheDuration)
assert.True(t, c.isExpired(tm.now))
}

type dummyCanvas struct {
fyne.Canvas
}
Expand Down Expand Up @@ -265,10 +248,7 @@ func (t *timeMock) setTime(min, sec int) {
func testClearAll() {
skippedCleanWithCanvasRefresh = false
canvases = make(map[fyne.CanvasObject]*canvasInfo, 1024)
svgs.Range(func(key string, _ *svgInfo) bool {
svgs.Delete(key)
return true
})
svgs.Clear()
textTextures.Clear()
objectTextures.Clear()
renderers.Clear()
Expand Down
2 changes: 0 additions & 2 deletions internal/cache/svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ func SetSvg(name string, o fyne.CanvasObject, pix *image.NRGBA, w int, h int) {
}

type svgInfo struct {
// An svgInfo can be accessed from different goroutines, e.g., systray.
// Use expiringCache instead of expiringCacheNoLock.
expiringCache
pix *image.NRGBA
w, h int
Expand Down
8 changes: 4 additions & 4 deletions internal/cache/texture_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,17 @@ func DeleteTextTexturesFor(canvas fyne.Canvas) {

// SetTextTexture sets cached texture for a text run.
func SetTextTexture(ent FontCacheEntry, texture TextureType, canvas fyne.Canvas, free func()) {
tinfo := prepareTexture(ent, texture, canvas, free)
tinfo := prepareTexture(texture, canvas, free)
textTextures.Store(ent, tinfo)
}

// SetTexture sets cached texture.
func SetTexture(obj fyne.CanvasObject, texture TextureType, canvas fyne.Canvas) {
tinfo := prepareTexture(obj, texture, canvas, nil)
tinfo := prepareTexture(texture, canvas, nil)
objectTextures.Store(obj, tinfo)
}

func prepareTexture(obj any, texture TextureType, canvas fyne.Canvas, free func()) *textureInfo {
func prepareTexture(texture TextureType, canvas fyne.Canvas, free func()) *textureInfo {
tinfo := &textureInfo{texture: texture, textFree: free}
tinfo.canvas = canvas
tinfo.setAlive()
Expand All @@ -106,6 +106,6 @@ func prepareTexture(obj any, texture TextureType, canvas fyne.Canvas, free func(

// textureCacheBase defines base texture cache object.
type textureCacheBase struct {
expiringCacheNoLock
expiringCache
canvas fyne.Canvas
}
File renamed without changes.
Loading

0 comments on commit 9c98e2c

Please sign in to comment.