2

We all know how difficult it is to make a proper update algorithm if certain fps is important or something like this.

Anyway, I just came up with this infinite-ish while cycle hack, which just freezes the program until the next frame, and it seems to work flawlessly.

var then = Date.now()
var fps = 40;
var interval = 1000 / fps;
function mainloop() {
     while (Date.now() - then < interval) {}  // freezes program until next frame
     requestAnimationFrame(mainloop);
     then = Date.now();
     // update logic goes here
}
mainloop();

I haven't seen this solution anywhere, so I wanted to ask whether it is clean and correct. I know it is bad freezing the program just to wait for something and that piece of code looks terrible, but it seems to work. Is there a cleaner solution that would work similarly to my code?

BoltKey
  • 1,994
  • 1
  • 14
  • 26
  • 2
    Well, freezing would rarely be considered correct. I think that targeting a certain fps is a bad approach to begin with, and instead rather render as many fps as possible, and adjust animation acccordingly. Meaning, instead of targeting a certain interval, you identify how much time has elapsed since last frame rendered and animate accordingly. – jishi Jan 16 '16 at 14:56
  • The problem is that I need each frame to be the same, I can't make any "longer" or "shorter" frames and I also need the program to run at certain rate because of things like music sync. – BoltKey Jan 16 '16 at 14:58
  • I think the terminology I'm talking about is called delta based animation. Instead of having fixed frame changes, you have a start and end and able to calculate the changes that should have occured at certain points in time. – jishi Jan 16 '16 at 15:02
  • [Related](https://stackoverflow.com/a/3048834/271917). Note how violently the community rejects stopping program execution within a `while` loop. In any case, even if you do what you're proposing you still are not guaranteed an accurate frame rate. – Robusto Jan 16 '16 at 15:08

3 Answers3

0

In your specific scenario, it would probably be better to delay the mainloop execution using setTimeout, like this:

var nextExecution = Date.now() - then + interval;
if (nextExecution < 0) nextExecution = 0;
setTimeout(mainloop, nextExecution);

This will allow it to do other stuff while waiting for the next frame rendering.

jishi
  • 24,126
  • 6
  • 49
  • 75
0

Using a while loop to waste time is a bad idea. It just wastes processor time that could be doing something else.

Using SetTimeout as suggested by jishi, is one possible solution. However, that only controls when your code runs. You have no real control over when the browser actually paints. Bascially, the browser will paint the last frame that your code updated.

Thus, another possible solution is to use requestAnimation. In your drawing code, determine the last frame that would have occurred at your preferred rate. Draw that frame. For example...

var start = null;
var fps = 40;
var interval = 1000 / fps;
function mainloop(timeStamp) {
    if (!start) {
        start = timeStamp;
    }
    var n = (timeStamp - start) / interval;
    // update logic goes here to paint nth frame
    requestAnimationFrame(mainloop);
}
mainloop();
Bobby Orndorff
  • 3,265
  • 1
  • 9
  • 7
0

You can use setTimeout to wait for a certain time but that will not be very precise. However by changing interval all the time you can get the average delay precise enough.

var startTime = Date.now();
var fps = 40;
var frames = 0;
var interval = 1000 / fps;

function mainloop() {
    frames++;
    var timeElapsed = Date.now() - startTime,
        averageFps = 1000 * frames / timeElapsed;
    if(averageFps < fps && interval > 0) interval -= 0.1;
    if(averageFps > fps) interval += 0.1;
    setTimeout(mainloop, interval);

    // update logic goes here
}
setTimeout(mainloop, interval);

But there is still the risk that the computer isn't able to meet the requested fps if it's too slow.

Tesseract
  • 8,049
  • 2
  • 20
  • 37
  • That's and interesting solution. The problem will emerge if in the application, there are some parts that are heavy on CPU, where interval gets set to low value, and then on the simple parts of the code the program will run too fast. It is probably better to calculate average fps from last 20 frames or something. – BoltKey Jan 16 '16 at 16:56