Skip to content

Commit

Permalink
Don't allocate new slices every animation tick
Browse files Browse the repository at this point in the history
  • Loading branch information
dweymouth committed Jan 27, 2024
1 parent 8d62571 commit 7d704a9
Showing 1 changed file with 24 additions and 28 deletions.
52 changes: 24 additions & 28 deletions internal/animation/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@ package animation

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

"fyne.io/fyne/v2"
)

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

runnerStarted bool
runnerStarted atomic.Bool
}

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

if !r.runnerStarted {
r.runnerStarted = true
if r.runnerStarted.CompareAndSwap(false, true) {
r.animations = append(r.animations, newAnim(a))
r.runAnimations()
go r.runAnimations()
} else {
r.pendingAnimations = append(r.pendingAnimations, newAnim(a))
}
Expand All @@ -35,7 +35,9 @@ func (r *Runner) Stop(a *fyne.Animation) {
r.animationMutex.Lock()
defer r.animationMutex.Unlock()

newList := make([]*anim, 0, len(r.animations))
// use technique from https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
// to filter the animation slice without allocating a new slice
newList := r.animations[:0]
stopped := false
for _, item := range r.animations {
if item.a != a {
Expand All @@ -50,7 +52,7 @@ func (r *Runner) Stop(a *fyne.Animation) {
return
}

newList = make([]*anim, 0, len(r.pendingAnimations))
newList = r.pendingAnimations[:0]
for _, item := range r.pendingAnimations {
if item.a != a {
newList = append(newList, item)
Expand All @@ -63,30 +65,24 @@ func (r *Runner) Stop(a *fyne.Animation) {

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)
}
for done := false; !done; {
<-draw.C
// 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
r.animationMutex.Lock()
newList := r.animations[:0]
for _, a := range r.animations {
if !a.isStopped() && r.tickAnimation(a) {
newList = append(newList, a)
}
r.animationMutex.Lock()
r.animations = append(newList, r.pendingAnimations...)
r.pendingAnimations = nil
done = len(r.animations) == 0
r.animationMutex.Unlock()
}
r.animationMutex.Lock()
r.runnerStarted = false
r.animations = append(newList, r.pendingAnimations...)
r.pendingAnimations = r.pendingAnimations[:0]
done = len(r.animations) == 0
r.animationMutex.Unlock()
draw.Stop()
}()
}
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 7d704a9

Please sign in to comment.