I was recently coding a game and wanted a fixed frame rate. For 60fps, I needed a game tick to occur every 1000ms / 60 = ~16ms, and the easiest way to achieve that in Javascript was by using setInterval.

The setInterval method is used to execute a function repeatedly at a fixed interval. When called, it provides an ID that should eventually be passed to clearInterval, which cancels further execution of the function. (It's especially important that this is called in single-page applications to avoid memory leaks.)

This method is comparable to the setTimeout method, which executes a function after some user-specified amount of time has passed, but it does so only once. Here, too, the clearTimeout function exists to cancel a function from running.

While coding my game, one of the issues I ran into was that the amount of work that needed to be done within a single game tick could take highly variable amounts of time to complete. It depended heavily on how much of the game's canvas needed to be repainted. Sometimes, the work for one tick took longer than 16ms and overlapped with the work started for the next tick by setInterval. The canvas painting function was written to be idempotent, but the long execution time was still a problem because the function calls could pile up and slow the game's frame rate. With enough pileup, the game could even potentially crash, though I never ran into that issue when testing.

For testing, I found it easiest to use a ridiculously low period for setInterval to guarantee that work overlapped between every tick. At first, I set a period of 1ms, but then I learned that you can't do that! The minimum setInterval period allowed in modern browsers is 4ms, but not too long ago, it used to be 10ms. I guess that it'd be very difficult to produce a Javascript game that ran much higher than 100fps.

The easiest solution was to switch from setInterval to a recursive setTimeout. The function call would do the repainting work and then call setTimeout with the appropriate amount of delay to give the appearance of 60fps. Of course, the 60fps was no longer guaranteed if repainting took over 16ms, and the time needed to paint the canvas had to be tracked to calculate the proper amount of time to pass to setTimeout. But it prevented pileup!

I also spent time figuring out how to optimize the  canvas painting work. I considered these strategies:

  1. Changing how much of the canvas I repainted by only repainting pixels that had changed instead of repainting the whole canvas
  2. Repainting earlier in the tick by making predictions about the future canvas state instead of calculating the exact canvas state
  3. Minimizing overhead by reducing the total number of functions that needed to be called even though it sacrificed some code modularity and readability

I sunk a lot of time into each of these strategies and now have a newfound appreciation for video game developers. I'll write more about that in a future post.