Re: Semantics of the time argument on rAF's FrameRequestCallback

So, presentation time is without a doubt an important value to report
(though it sounds like not everyone is reporting this). Passing a
frameBeginTime to callbacks allows disparate callbacks to agree with
one another about when the frame began --- which is useful for
animation state transitions, javascript-frame rate timing, etc.

Here's an example where frameBeginTime versus presentTime is important:

function Animation(duration) {
   this.startTime = window.performance.now();
   this.duration = duration;
   requestAnimationFrame(this.tick.bind(this));
}
Animation.prototype.tick = function(presentTime) {
  this.updatePositionTo(presentTime);
  if(presentTime > this.startTime + this.duration)
    this.dispatchEvent('animationEnded');
}

Now, we do something like
   var a= new Animation(1000.0);
   a.addEventListener('animationEnded', function() {
      assert(window.performance.now() - a.startTime >= a.duration);
   });

This assert is going to fail --- this will end 16-30ms early ---
somewhere around 990ms, we're going to get a tick for present for
+16ms in the future, and thus fire the ended callback. Whoops!


The naieve fix here is to check window.performance.now:
Animation.prototype.tick = function(presentTime) {
  this.updatePositionTo(presentTime);
  // Check performance.now so that we fire events in a way that seems sane.
  if(window.performance.now() > this.startTime + this.duration)
    this.dispatchEvent('animationEnded');
}

Ok, we're good, right?

But, now, lets say I have two animations:

   var a= new Animation(1000.0);
   a.addEventListener('animationEnded', function() {
      assert(window.performance.now() - a.startTime >= a.duration);
      aEnded = true;
      checkBothEnded();
   });
   var b= new Animation(1000.0);
   b.addEventListener('animationEnded', function() {
      assert(window.performance.now() - b.startTime >= b.duration);
      bEnded = true;
      checkBothEnded();
   });
   checkBothEnded() {
     if (!checkTaskEnqueued) window.postTask(checkBothEnded);
     assert (aEnded && bEnded);
   }

If we're lucky, this will just work. But, if we run this test on a bot
somewhere, every once in a while, we will get a tick at
window.performance.now()=999.99999ms AND then between the a callback
and the b callback, now() becomes 1001 and so one ends but the other
doesn't.

This then forces animation systems to write their own "master raf
code" which works fine if you have one framework across the entire
page, but ~ugh~!

I think that this is why most animation systems pass a frame begin
time as well as a present time. But, I could be wrong. :)

(CVDisplayLink is documented here, though it doesn't explain the
rationale for the design:
http://developer.apple.com/library/mac/documentation/QuartzCore/Reference/CVDisplayLinkRef/Reference/reference.html#//apple_ref/c/tdef/CVDisplayLinkOutputCallback)



On Wed, May 9, 2012 at 11:20 AM, Yehuda Katz <yehuda.katz@jquery.com> wrote:
> In this case, shouldn't the time be "the time that the animations will be
> presented on
> the screen".
>
> Yehuda Katz
> (ph) 718.877.1325
>
>
>
> On Wed, May 9, 2012 at 11:17 AM, Boris Zbarsky <bzbarsky@mit.edu> wrote:
>>
>> On 5/9/12 2:05 PM, Yehuda Katz wrote:
>>>
>>> What are authors expected to do with this timestamp?
>>
>>
>> Decide what exactly to draw; it tells them what time the animation is to
>> be drawn for.  Especially for animations that are not inhertently
>> frame-based (e.g. moving an object from point X to point Y).
>>
>>> What is the use-case?
>>
>>
>> Synchronizing animations with each other.
>>
>> -Boris
>
>

Received on Wednesday, 9 May 2012 22:23:59 UTC