requestAnimationFrame at beginning or end of function?
Asked Answered
C

4

24

If I have a loop using requestAnimationFrame like this:

function render() {
    // Rendering code

    requestAnimationFrame(render);
}

Will there be any difference if I put the requestAnimationFrame in the beginning of the function, like this:

function render() {
    requestAnimationFrame(render);

    // Rendering code
}

I haven't noticed any difference, but I have seen both implementations, is one of them better in any way, or are they the same?

Edit: One thing I have thought about is, if I put it in the beginning, and the render code takes quite long time to run, say 10ms, wouldn't putting it in the end make the frame rate drop with 10ms?

Cockatrice answered 6/7, 2016 at 16:47 Comment(8)
it looks like you're going to get a stack overflow hereHaha
@self asynchronously queued functions don't push onto the call stack.Actiniform
@self what is being recursive here? The event loop calls render, render calls RAF, RAF modifies the event loop. No more render can be called by the event loop before the previous one returns. Learn the difference between calling a function and passing it somewhere to be called later, please.Hillegass
@self you're completely wrong. It's easily apparent in this demo: jsfiddle.net/patrob10114/76b6k6nxActiniform
Lol user got removed that downvoted xDCockatrice
Assuming the code runs easily within the 1000/60ms typically allotted to a single rAF loop then you might occasionally catch an extra execution of the loop by putting the request at the beginning of the loop. But it shouldn't matter much. If the code doesn't finish before the end of the allotted time, this code continues until finished and any further scheduled loops are queued (but the queue will always execute in scheduled order) See this previous related Q&A.Leyva
@markE, In the book "Multiplayer Game Development With Html5", it has a special section for this question. It says: "By scheduling the method early on, we can be sure that it will be called again as soon as it can after it completes its current execution, even if other events are triggered during the current execution, and other code is placed on the event loop." and "if we wait until the end of the method(raf) to schedule it again with the event loop, we may find the method (raf) waiting in line (so that it can have a turn at the CPU again) before other callbacks are handled."Typescript
@newguy, You might squeeze out an extra execution if you request the rAF early, but the key to success is ensuring that your code routinely executes within the 1/60th second allowed. Then the positioning of rAF is irrelevant.Leyva
A
21

requestAnimationFrame does always call its callback asynchronously, so as long as your rendering code is synchronous and does not throw exceptions it doesn't make any difference.

It's essentially a style choice, choose yourself which approach is cleaner. Putting it at the top may emphasise that render is scheduling itself, and does so even in the presence of errors in the rendering. Putting it in the bottom allows to conditionally break out of the rendering loop (e.g. when you want to pause your game).

Arsonist answered 6/7, 2016 at 16:59 Comment(3)
Nice answer, see my edit, I have an additional thoughtCockatrice
There is a difference in speed if your render function takes some time\Hillegass
@JanDvorak Really? Afaik, requestAnimationFrame tries to keep a steady interval between frames, regardless when it is called. If rendering does take longer, the next one will be scheduled immediately.Arsonist
H
6

It likely won't make a diference. The requestAnimationFrame method is asynchronous, so either way, your render function will work as expected. But... there's a catch when it comes to halting. Say you have the following code:

function render() {
    requestAnimationFrame(render);
    // Rendering code
}

In order to stop the next render, a call to the cancelAnimationFrame method is needed, like so:

function render() {
    requestAnimationFrame(render);
    // Rendering code
    if (noLongerInterested) {
        cancelAnimationFrame();
    }
}

Otherwise, the render method will just run indefinitely. Alternatively, you could do:

function render() {
    // Rendering code
    if (stillInterested) {
        requestAnimationFrame(render);
    }
}

As for frame dropping, you could look at requestAnimationFrame as being on a fixed schedule (at 60 frames-per-second, it would be approximately 16ms intervals). If your code takes longer than that, the browser will begin to drop frames. Look at Patrick Roberts's answer for instructions on how to take charge of your frames, and use that for more consistent rendering.

I hope that helps!

Hexahydrate answered 6/7, 2016 at 17:19 Comment(0)
A
5

To answer your question, those two functions will make a difference in the amount of time the asynchronous callback takes to occur only if your rendering code is longer than the animation frame speed (typically around 16 - 33ms depending on browser implementation). However, if you were using this API as intended, even that shouldn't make a difference.

Note that you are opting out of using the optional parameter passed from requestAnimationFrame -- the timestamp.

Make sure to calculate your deltas if you have any delta-time-dependent animations to render. Typically you multiply an animation "velocity" with the timestamp delta (current timestamp minus previous timestamp) in order to get an effective distance an object should travel across the screen. Its effect is particularly noticeable when your rendering code does not consistently take the same amount of time to execute each frame.

Demo

var untimed = 20;
var timed = 20;

function untimedRender() {
  var then = performance.now() + Math.random() * 100;

  while (performance.now() < then) {}
  
  // estimated velocity
  untimed += 50 / 30;
  
  document.querySelector('#untimed').style.left = Math.min(Math.floor(untimed), 200) + 'px';
  
  if (untimed < 200) {
    requestAnimationFrame(untimedRender);
  } else {
    last = performance.now();
    requestAnimationFrame(timedRender);
  }
}

var last;

function timedRender(timestamp) {
  var delta = timestamp - last;
  var then = timestamp + Math.random() * 100;
  
  last = timestamp;

  while (performance.now() < then) {}
  
  // calculated velocity
  timed += delta / 30;
  
  document.querySelector('#timed').style.left = Math.min(Math.floor(timed), 200) + 'px';
  
  if (timed < 200) {
    requestAnimationFrame(timedRender);
  }
}

requestAnimationFrame(untimedRender);
div {
  position: absolute;
  left: 20px;
  width: 10px;
  height: 10px;
}

#untimed {
  background-color: #F00;
  top: 20px;
}

#timed {
  background-color: #00F;
  top: 50px;
}
<div id="untimed"></div>
<div id="timed"></div>

Notice how the blue square appears to maintain a more consistent velocity overall. That is the intention.

Actiniform answered 6/7, 2016 at 17:23 Comment(1)
Interesting. In the book "Multiplayer Game Development With Html5", it has a special section for this question. It says: "By scheduling the method early on, we can be sure that it will be called again as soon as it can after it completes its current execution, even if other events are triggered during the current execution, and other code is placed on the event loop."Typescript
W
1

The MDN description states that:

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.

When that repaint occurs is largely up to the browser. There shouldn't be any difference in behavior unless your JS is still running when the repaint would have occurred.

The WhatWG spec does not mention waiting for the JS call stack to clear or anything of the sort, although an exceptionally long-running function will block the UI thread and therefore should prevent animation frames from being called.

Werbel answered 6/7, 2016 at 16:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.