Eric
Eric

Reputation: 2705

What's the fix for inaccurate setInterval?

Consider the following code:

var tick = 0;

setInterval(() => {
    console.log(tick);
}, 1000);

setInterval(() => {
  tick += 1;
}, 1);

Basically, I want my game to look smooth, so I update the position of the dot every 1 millisecond, which the function in the second setInterval does.

In 1 second, or 1000 millisecond, I expect the dot to have moved 1000 pixels. However, it moves roughly 250 pixels.

When I run the following jsfiddle, I see multiples of roughly 250 being printed to the console.

https://jsfiddle.net/58v74zw4/1/

I can fix it by multiplying 4, based on my observation, but it feels hacky.

What is causing this, and what is the correct fix for this?

Upvotes: 0

Views: 330

Answers (1)

user1636522
user1636522

Reputation:

Best article I found about JS timers : https://johnresig.com/blog/how-javascript-timers-work.

Since JavaScript can only ever execute one piece of code at a time [...] when an asynchronous event occurs [...] it gets queued up to be executed later [...]

This means that if there is a big chunk of code that is executing for 4ms, the interval handler is queued up for at most 4ms.

 ms | event
----|--------------------------------------------
  0 | bigChunk() starts
  1 |
  2 | interval event fires
  3 |
  4 | bigChunk() returns
  5 | interval handler starts 

[...] browsers [...] wait until no more interval handlers are queued [...] before queuing more.

This means that if there is a big chunk of code that is executing for 4ms, and if the interval is set to fire every 1ms, the browser executes the first interval handler after 4ms and drops 3 events.

 ms | event
----|--------------------------------------------
  0 | bigChunk() starts
  1 | interval event fires
  2 | interval event dropped
  3 | interval event dropped 
  4 | interval event dropped, bigChunk() returns
  5 | interval handler starts

In your case, 3/4 (750/1000) of interval events may be dropped for this reason, but there are also implementation related causes, like this minimum delay of 4ms between successive calls described here : https://stackoverflow.com/a/9647221/1636522 (credits : LGSon).

As you can see, setInterval is not reliable when used in a single thread, however, multi-threading does not necessarily make things easier. For example, if the execution time of your interval handler is 4ms, you will need 4 threads with a time shift of 1ms between each thread.

 Thread 1                      | Thread 2
-------------------------------|-------------------------------
 ms | event                    | ms | event
----|--------------------------|----|--------------------------
  0 | interval event fires     |  0 | 
  1 | interval handler starts  |  1 | interval event fires
  2 |                          |  2 | interval handler starts
  3 |                          |  3 |
  4 | interval handler returns |  4 |
  5 |                          |  5 | interval handler returns

And this is only one of the potential problems to solve. That said, if you still want to use multi-threading you could take a look at Web Workers. I can't help though, I've never used this feature :-| https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

Also keep in mind that "most browsers have 60fps [frame per second]" (see sunil), which means that you have almost 17ms (1000/60) to compute the next frame. Hence, updating the state of your game every millisecond is probably overkill :-)

Upvotes: 3

Related Questions