Skip to content

Commit

Permalink
synchronize on runner started
Browse files Browse the repository at this point in the history
  • Loading branch information
dweymouth committed Jan 10, 2024
1 parent 5d3980b commit e47e3b2
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 42 deletions.
2 changes: 0 additions & 2 deletions internal/animation/animation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,5 @@ func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) {
wg.Wait()
// animations stopped inside tick are really stopped in the next runner cycle
time.Sleep(time.Second/60 + 100*time.Millisecond)
run.animationMutex.Lock()
assert.Zero(t, len(run.animations))
run.animationMutex.Unlock()
}
76 changes: 36 additions & 40 deletions internal/animation/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,67 @@ package animation

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

"fyne.io/fyne/v2"
)

// Runner is the main driver for animations package
type Runner struct {
animationMutex sync.Mutex
pendingAnimations []*anim
runnerStarted bool
pendingAnimationsMutex sync.Mutex
pendingAnimations []*anim
runnerStarted atomic.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()
r.pendingAnimationsMutex.Lock()
r.pendingAnimations = append(r.pendingAnimations, newAnim(a))
r.animationMutex.Unlock()
r.pendingAnimationsMutex.Unlock()

if !r.runnerStarted {
r.runnerStarted = true
r.runAnimations()
if r.runnerStarted.CompareAndSwap(false, true) {
go r.runAnimations()
}
}

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

go func() {
for done := false; !done; {
<-draw.C

// tick currently running animations
// use technique from https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
// to filter the still-running animations for the next iteration without allocating a new slice
newList := r.animations[:0]
for _, a := range r.animations {
if stopped := a.a.State() == fyne.AnimationStateStopped; !stopped && r.tickAnimation(a) {
newList = append(newList, a) // still running
} else if !stopped {
a.a.Stop() // mark as stopped (completed running)
}
for done := false; !done; {
<-draw.C

// tick currently running animations
// use technique from https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
// to filter the still-running animations for the next iteration without allocating a new slice
newList := r.animations[:0]
for _, a := range r.animations {
if stopped := a.a.State() == fyne.AnimationStateStopped; !stopped && r.tickAnimation(a) {
newList = append(newList, a) // still running
} else if !stopped {
a.a.Stop() // mark as stopped (completed running)
}
}

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

done = len(newList) == 0
for i := len(newList); i < len(r.animations); i++ {
r.animations[i] = nil // nil out extra slice capacity
}
r.animations = newList
done = len(newList) == 0
for i := len(newList); i < len(r.animations); i++ {
r.animations[i] = nil // nil out extra slice capacity
}
r.animationMutex.Lock()
r.runnerStarted = false
r.animationMutex.Unlock()
draw.Stop()
}()
r.animations = newList
}
r.runnerStarted.Store(false)
draw.Stop()
}

// tickAnimation will process a frame of animation and return true if this should continue animating
Expand Down

0 comments on commit e47e3b2

Please sign in to comment.