Skip to content

Commit 43f9b03

Browse files
committed
Fix sycnchronous animation queues
rwaldron#1376 only fixed the problem when animation segments were enqueued in seperate passes through the event loop. When two animation segments were enqueued on the same pass through the even loop we were starting both segments because the isRunning state was not being set until nextTick. This should fix all that. I've updated some of the tests which I believe were written in a non-intuitive manner because of this bug. Instead of really understanding the problem I had just written the tests in a way that made them pass. There's probably a name for this sort of behavior. If not, we should invent a name.
1 parent f36463c commit 43f9b03

File tree

3 files changed

+103
-158
lines changed

3 files changed

+103
-158
lines changed

lib/animation.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ function Animation(target) {
9191

9292
Animation.Segment.call(this);
9393

94-
this.defaultTarget = target;
94+
this.defaultTarget = target || {};
9595
}
9696

9797
util.inherits(Animation, Emitter);
@@ -152,8 +152,7 @@ Animation.prototype.enqueue = function(opts) {
152152
opts.target = this.defaultTarget;
153153
}
154154

155-
this.segments.push(new Animation.Segment(opts));
156-
155+
this.segments.push(opts);
157156

158157
/* istanbul ignore if */
159158
if (!this.paused && !this.isRunning) {
@@ -170,10 +169,16 @@ Animation.prototype.enqueue = function(opts) {
170169
*/
171170
Animation.prototype.next = function() {
172171

173-
if (this.segments.length > 0) {
174-
175-
Object.assign(this, this.segments.shift());
172+
if (this.isRunning) {
173+
return this;
174+
} else {
175+
this.isRunning = true;
176+
}
176177

178+
if (this.segments.length > 0) {
179+
var segment = new Animation.Segment(this.segments.shift());
180+
181+
Object.assign(this, segment);
177182
this.paused = this.currentSpeed === 0 ? true : false;
178183

179184
if (this.onstart) {
@@ -281,7 +286,6 @@ Animation.prototype.loopFunction = function(loop) {
281286
// Find the current timeline progress
282287
var progress = this.calculateProgress(loop.calledAt);
283288

284-
285289
// Find the left and right cuePoints/keyFrames;
286290
var indices = this.findIndices(progress);
287291

@@ -320,14 +324,16 @@ Animation.prototype.loopFunction = function(loop) {
320324
this.endTime = this.startTime + this.scaledDuration;
321325
} else {
322326

323-
this.stop();
327+
this.isRunning = false;
324328

325329
if (this.oncomplete) {
326330
process.nextTick(this.oncomplete.bind(this));
327331
}
328332

329333
if (this.segments.length > 0) {
330-
this.next();
334+
process.nextTick(() => { this.next(); });
335+
} else {
336+
this.stop();
331337
}
332338
}
333339
}

test/animation.js

+40-77
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,32 @@ exports["Animation -- Servo"] = {
360360
);
361361

362362
test.done();
363-
}
363+
},
364+
365+
enqueueOnSameTick: function(test) {
366+
367+
test.expect(7);
368+
369+
this.animation = new Animation(this.a);
370+
371+
this.normalizeKeyframes = this.sandbox.spy(this.animation, "normalizeKeyframes");
372+
373+
test.equal(this.animation.isRunning, false);
374+
test.equal(this.animation.segments.length, 0);
375+
376+
// This is the first segment so it should be immediately shifted off the queue
377+
test.equal(this.animation.enqueue(this.segment.single), this.animation);
378+
test.equal(this.animation.segments.length, 0);
379+
test.equal(this.animation.isRunning, true);
380+
381+
// This is the second segment so it should stay in the queue
382+
test.equal(this.animation.enqueue(this.segment.single), this.animation);
383+
test.equal(this.animation.segments.length, 1);
384+
385+
this.normalizeKeyframes.restore();
386+
test.done();
387+
388+
}
364389

365390
};
366391

@@ -436,6 +461,7 @@ exports["Animation"] = {
436461
test.equal(this.animation.segments.length, 1);
437462
test.done();
438463
},
464+
439465
next: function(test) {
440466
test.expect(12);
441467

@@ -687,78 +713,6 @@ exports["Animation"] = {
687713
test.done();
688714
},
689715

690-
loopFunctiononcomplete: function(test) {
691-
test.expect(2);
692-
693-
this.clock = this.sandbox.useFakeTimers();
694-
this.tfb = this.sandbox.stub(Animation, "TemporalFallback", function() {});
695-
696-
var startTime = Date.now();
697-
var loop = {
698-
calledAt: startTime + 1000
699-
};
700-
701-
this.animation.startTime = startTime;
702-
this.animation.fallBackTime = 500;
703-
this.animation.speed(1);
704-
705-
706-
this.animation.normalizedKeyFrames = [];
707-
this.animation.target = {};
708-
this.animation.target[Animation.render] = function() {};
709-
this.animation.playLoop = null;
710-
this.animation.onloop = this.sandbox.stub();
711-
this.animation.next = this.sandbox.stub();
712-
713-
var animation = this.animation;
714-
715-
this.sandbox.stub(Animation.prototype, "stop");
716-
this.animation.oncomplete = function() {
717-
test.equal(Animation.TemporalFallback.callCount, 1);
718-
test.equal(this, animation);
719-
720-
animation.oncomplete = null;
721-
test.done();
722-
};
723-
724-
this.animation.loopFunction(loop);
725-
},
726-
727-
loopFunctionNOoncompleteSegmentsRemaining: function(test) {
728-
test.expect(1);
729-
730-
this.clock = this.sandbox.useFakeTimers();
731-
this.tfb = this.sandbox.stub(Animation, "TemporalFallback", function() {});
732-
this.stop = this.sandbox.stub(Animation.prototype, "stop");
733-
734-
var startTime = Date.now();
735-
var loop = {
736-
calledAt: startTime + 1000
737-
};
738-
739-
this.animation.startTime = startTime;
740-
this.animation.fallBackTime = 500;
741-
this.animation.speed(1);
742-
743-
744-
this.animation.normalizedKeyFrames = [];
745-
this.animation.target = {};
746-
this.animation.target[Animation.render] = function() {};
747-
this.animation.playLoop = null;
748-
this.animation.onloop = this.sandbox.stub();
749-
this.animation.next = this.sandbox.stub();
750-
751-
this.nextTick = this.sandbox.stub(process, "nextTick");
752-
753-
this.animation.next = function() {
754-
test.equal(this.stop.callCount, 1);
755-
test.done();
756-
}.bind(this);
757-
758-
this.animation.segments = [1, 2, 3, 4];
759-
this.animation.loopFunction(loop);
760-
},
761-
762716
loopFunctionfallBackTimeWithPlayLoop: function(test) {
763717
test.expect(1);
764718

@@ -794,8 +748,14 @@ exports["Animation"] = {
794748
test.expect(1);
795749

796750
this.clock = this.sandbox.useFakeTimers();
797-
this.normalizeKeyframes = this.sandbox.stub(Animation.prototype, "normalizeKeyframes", function() {
798-
this.loopback = 1;
751+
this.animation.normalizeKeyframes = this.sandbox.stub(Animation.prototype, "normalizeKeyframes", function() {
752+
this.animation.loopback = 1;
753+
this.animation.normalizedKeyFrames = [[
754+
{ value: 90, easing: "linear" },
755+
{ step: false, easing: "linear", value: 90 },
756+
{ value: 45, easing: "linear" },
757+
{ step: 33, easing: "linear", value: 78 }
758+
]];
799759
}.bind(this));
800760

801761
var startTime = Date.now();
@@ -804,18 +764,20 @@ exports["Animation"] = {
804764
};
805765

806766
this.animation.startTime = startTime;
807-
this.animation.normalizedKeyFrames = [];
808767
this.animation.target = {};
809768
this.animation.target[Animation.render] = function() {};
810769
this.animation.speed(1);
811770

812771
this.animation.metronomic = true;
813772
this.animation.reverse = false;
814773
this.animation.loop = true;
774+
this.animation.fps = 10;
815775
this.animation.onloop = this.sandbox.spy();
776+
this.animation.normalizeKeyframes();
816777
this.animation.loopFunction(loop);
817778

818779
test.equal(this.animation.onloop.callCount, 1);
780+
this.animation.stop();
819781
test.done();
820782
},
821783

@@ -1021,8 +983,9 @@ exports["Animation.Segment"] = {
1021983
},
1022984

1023985
instanceof: function(test) {
1024-
test.expect(1);
986+
test.expect(2);
1025987
test.equal(Animation({}) instanceof Animation, true);
988+
test.equal(Animation() instanceof Animation, true);
1026989
test.done();
1027990
},
1028991

0 commit comments

Comments
 (0)