Why does calling requestAnimationFrame at the beginning of a loop not cause infinite recursion?
Asked Answered
A

4

12

What is going on that allows the rest of the loop to execute, and then for requestAnimationFrame to execute next frame?

I am misunderstanding how this method works, and can't see a clear explanation anywhere. I tried reading the timing specification here http://www.w3.org/TR/animation-timing/ but I couldn't make out how it worked.

For example, this code is taken from the threejs documentation.

var render = function () { 
  requestAnimationFrame(render); 
  cube.rotation.x += 0.1; 
  cube.rotation.y += 0.1;
  renderer.render(scene, camera); 
};
Adkinson answered 18/2, 2014 at 21:57 Comment(3)
Can you post some code please?Hartill
Request Animation Frame for Better Performance, requestAnimationFrame for Smart AnimatingCharest
requestAnimationFrame is not recursive ,it's async(just like a short setTimeout but more efficient) and get executed after the body of the function in which it is called.It makes little sense however to put it in a for loop. The recursion happens in the render function , not in requestAnimationFrame by the way, You are not calling render in render.Bise
H
18

Please let me know if I am completely off-base; I haven't used the animation stuff before. An example I saw for using requestAnimationFrame is:

(function animloop(){
  requestAnimFrame(animloop);
  render();
})();

Are you wondering why animloop as it is passed into requestAnimFrame doesn't cause an infinite loop when it is subsequently called?

This is because this function isn't truly recursive. You might be thinking that animloop is immediately called when you call requestAnimFrame. Not so! requestAnimFrame is asynchronous. So the statements are executed in the order that you see. What this means is that the main thread does not wait for the call to requestAnimFrame to return, before the call to render(). So render() is called almost immediately. However the callback (which in this case is animloop) is not called immediately. It may be called at some point in the future when you have already exited from the first call to animloop. This new call to animloop has its own context and stack since it hasn't been actually called from within the execution context of the first animloop call. This is why you don't end up with infinite recursion and a stack overflow.

Hartill answered 18/2, 2014 at 22:6 Comment(2)
OK this makes more sense now, my understanding of using callbacks is still shaky so I'll have to do some research. Thanks :)Adkinson
This is a very concise answer that cleared up my confusion surrounding the same question. I'd say it should be the accepted answer :)Nuzzle
R
6

This is what happens:

you declare a function definition, call requestAnimationFrame function.

Which schedule your function to be called and executed again when the time is right, which is the Next frame that is usually after 16ms. Also, this scheduling is async. It won't stop the execution of code below it. So it's not like code below this line won't work until 16ms has passed.

However, in most cases function executes within 3-4 ms.

But if function were to take longer to finish the next frame would be delayed thus not executing the scheduled task which is to call the same function over again.

In a sense animation is infinite loop. Which requestAnimationFrame aims to be. However, this nonblocking infinite loop is limited by frames/fps.

Roxannaroxanne answered 4/5, 2014 at 0:15 Comment(2)
Based on my tests and limited understanding, raf does not necessarily wait for the next frame the first time it's called. The reason it usually runs almost immediately (0.4ms just now when i tested in chrome) the first time is because it's running as fast as it can before the next frame. But when it gets called in a recursive-ish loop, then the second and all subsequent calls within the loop will be debounced to the framerate. I could be totally wrong, just my best guess as to what's going on. I just have a hard time the browser is rendering a frame in 0.4ms, esp. when it doesn't have to.Hagar
raf tries to not waste resources by running code that isn't going to be displayed. suppose frames are getting updated at 10ms. and your code is looping around 5 ms. then your code tells browser to do paint something twice but it only does the last thing, this is what raf prevents..if browser can update frames faster then the code gets run faster. Inversely say your code takes 20ms and browser can update at 10ms. then there is no point in updating if nothing has changed browser will delay the update. Thus saving computing resources.Roxannaroxanne
J
4

For the same reason that scheduling a callback with setTimeout in a loop wont cause infinite recursion, it schedules the next call on the JS event loop instead of executing it immediately.

The call is not made in the current context, so it's technically not recursion in the strict sense of the word, and it wont cause stack limit exceeded errors.

Event loop diagram
(source: dartlang.org)

This diagram is for Dart, but the concept is the same in JS. If you're interested in reading more about event loops, scheduling timers, and the difference between microtasks and macrotasks, check out this question.

Joyous answered 28/6, 2017 at 6:17 Comment(0)
S
2

I myself had got similar question and I didn't find any satisfactory answers. This answer is for that question but it relates here as well.

  1. Initially, window.rAF is passed to call stack.

  2. By definition, rAF waits till next repaint of window which is 16ms for 60fps screen. It's like setTimeOut but better.

img1

  1. window.rAF() is executed, so it is removed from the call stack. Call stack is empty. Meanwhile at the same time, after 16ms has been passed, main() has been put into callback queue.

img2

Now here is where the event loop comes in. The event loop is a continuous running process that constantly checks if the call stack is empty or not. If the call stack is empty, it will move the function from the callback queue into the call stack and it gets executed. So, main() gets executed.

img3

  1. Again, it calls window.rAF(main).

img4

window.rAF() has now executed.

  1. Now, the code under window.rAF() runs(the if condition and so on and so forth). After, 16ms, main() is passed to callback queue.

img5

main() gets executed.

img6

Source for images: https://nainacodes.com/blog/understand-the-event-loop-in-javascript

Scholiast answered 9/1, 2023 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.