Skip to content

Commit

Permalink
WIP- optimize animation runner
Browse files Browse the repository at this point in the history
  • Loading branch information
dweymouth committed Dec 10, 2023
1 parent 0ebdaf0 commit 7344f9a
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 72 deletions.
16 changes: 14 additions & 2 deletions animation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fyne

import "time"
import (
"time"
)

// AnimationCurve represents an animation algorithm for calculating the progress through a timeline.
// Custom animations can be provided by implementing the "func(float32) float32" definition.
Expand Down Expand Up @@ -42,6 +44,8 @@ type Animation struct {
Duration time.Duration
RepeatCount int
Tick func(float32)

stopped bool
}

// NewAnimation creates a very basic animation where the callback function will be called for every
Expand All @@ -55,12 +59,20 @@ func NewAnimation(d time.Duration, fn func(float32)) *Animation {

// Start registers the animation with the application run-loop and starts its execution.
func (a *Animation) Start() {
a.stopped = false
CurrentApp().Driver().StartAnimation(a)
}

// Stop will end this animation and remove it from the run-loop.
func (a *Animation) Stop() {
CurrentApp().Driver().StopAnimation(a)
a.stopped = true
}

// Stopped returns true if this animation has been stopped or has completed running.
//
// Since: 2.5
func (a *Animation) Stopped() bool {
return a.stopped
}

func animationEaseIn(val float32) float32 {
Expand Down
9 changes: 7 additions & 2 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ type Driver interface {
Quit()

// StartAnimation registers a new animation with this driver and requests it be started.
StartAnimation(*Animation)
//
// Deprecated: Use a.Start() instead.
StartAnimation(a *Animation)

// StopAnimation stops an animation and unregisters from this driver.
StopAnimation(*Animation)
//
// Deprecated: Use a.Stop() instead.
StopAnimation(a *Animation)
}
10 changes: 0 additions & 10 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,6 @@ type anim struct {
reverse bool
start time.Time
total int64
stopped uint32 // atomic, 0 == false 1 == true
}

func newAnim(a *fyne.Animation) *anim {
Expand All @@ -23,11 +21,3 @@ func newAnim(a *fyne.Animation) *anim {
animate.repeatsLeft = a.RepeatCount
return animate
}

func (a *anim) setStopped() {
atomic.StoreUint32(&a.stopped, 1)
}

func (a *anim) isStopped() bool {
return atomic.LoadUint32(&a.stopped) == 1
}
18 changes: 9 additions & 9 deletions internal/animation/animation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ func TestGLDriver_StopAnimation(t *testing.T) {
case <-time.After(time.Second):
t.Error("animation was not ticked")
}
run.Stop(a)
run.animationMutex.RLock()
assert.Zero(t, len(run.animations))
run.animationMutex.RUnlock()
a.Stop()
run.animationMutex.Lock()
assert.True(t, a.Stopped(), "animation was not stopped")
run.animationMutex.Unlock()
}

func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) {
Expand All @@ -64,7 +64,7 @@ func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) {
Tick: func(f float32) {},
}
run.Start(a)
run.Stop(a)
a.Stop()

// stopping animation inside tick function
for i := 0; i < 10; i++ {
Expand All @@ -73,7 +73,7 @@ func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) {
b = &fyne.Animation{
Duration: time.Second,
Tick: func(d float32) {
run.Stop(b)
b.Stop()
wg.Done()
}}
run.Start(b)
Expand All @@ -86,12 +86,12 @@ func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) {
Tick: func(f float32) {},
}
run.Start(c)
run.Stop(c)
c.Stop()

wg.Wait()
// animations stopped inside tick are really stopped in the next runner cycle
time.Sleep(time.Second/60 + 100*time.Millisecond)
run.animationMutex.RLock()
run.animationMutex.Lock()
assert.Zero(t, len(run.animations))
run.animationMutex.RUnlock()
run.animationMutex.Unlock()
}
72 changes: 25 additions & 47 deletions internal/animation/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,78 +9,56 @@ import (

// Runner is the main driver for animations package
type Runner struct {
animationMutex sync.RWMutex
animations []*anim
animationMutex sync.Mutex
pendingAnimations []*anim
runnerStarted bool

runnerStarted bool
animations []*anim // accessed only by runAnimations
}

// Start will register the passed application and initiate its ticking.
func (r *Runner) Start(a *fyne.Animation) {
r.animationMutex.Lock()
defer r.animationMutex.Unlock()

r.pendingAnimations = append(r.pendingAnimations, newAnim(a))
if !r.runnerStarted {
r.runnerStarted = true
r.animations = append(r.animations, newAnim(a))
r.runAnimations()
} else {
r.pendingAnimations = append(r.pendingAnimations, newAnim(a))
}
}

// Stop causes an animation to stop ticking (if it was still running) and removes it from the runner.
func (r *Runner) Stop(a *fyne.Animation) {
r.animationMutex.Lock()
defer r.animationMutex.Unlock()

newList := make([]*anim, 0, len(r.animations))
stopped := false
for _, item := range r.animations {
if item.a != a {
newList = append(newList, item)
} else {
item.setStopped()
stopped = true
}
}
r.animations = newList
if stopped {
return
}

newList = make([]*anim, 0, len(r.pendingAnimations))
for _, item := range r.pendingAnimations {
if item.a != a {
newList = append(newList, item)
} else {
item.setStopped()
}
}
r.pendingAnimations = newList
}

func (r *Runner) runAnimations() {
draw := time.NewTicker(time.Second / 60)

go func() {
for done := false; !done; {
<-draw.C
r.animationMutex.Lock()
oldList := r.animations
r.animationMutex.Unlock()
newList := make([]*anim, 0, len(oldList))
for _, a := range oldList {
if !a.isStopped() && r.tickAnimation(a) {
newList = append(newList, a)

// tick currently running animations
newList := r.animations[:0] // references same underlying backing array
for _, a := range r.animations {
if s := a.a.Stopped(); !s && r.tickAnimation(a) {
newList = append(newList, a) // still running
} else if !s {
a.a.Stop() // mark as stopped (completed running)
}
}

// bring in all pending animations
r.animationMutex.Lock()
r.animations = append(newList, r.pendingAnimations...)
r.pendingAnimations = nil
done = len(r.animations) == 0
for i, a := range r.pendingAnimations {
newList = append(newList, a)
r.pendingAnimations[i] = nil
}
r.pendingAnimations = r.pendingAnimations[:0]
r.animationMutex.Unlock()

done = len(newList) == 0
for i := len(newList); i < len(r.animations); i++ {
r.animations[i] = nil
}
r.animations = newList
}
r.animationMutex.Lock()
r.runnerStarted = false
Expand Down
2 changes: 1 addition & 1 deletion internal/driver/glfw/animation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ func (d *gLDriver) StartAnimation(a *fyne.Animation) {
}

func (d *gLDriver) StopAnimation(a *fyne.Animation) {
d.animation.Stop(a)
a.Stop()
}
2 changes: 1 addition & 1 deletion internal/driver/mobile/animation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ func (d *mobileDriver) StartAnimation(a *fyne.Animation) {
}

func (d *mobileDriver) StopAnimation(a *fyne.Animation) {
d.animation.Stop(a)
a.Stop()
}

0 comments on commit 7344f9a

Please sign in to comment.