0

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?

Eric
  • 2,635
  • 6
  • 26
  • 66
  • 3
    1 seems to be too small value for setInterval – brk Mar 18 '18 at 05:16
  • 2
    Possible duplicate of [What is minimum millisecond value of setTimeout?](https://stackoverflow.com/questions/9647215/what-is-minimum-millisecond-value-of-settimeout) – Asons Mar 18 '18 at 05:58
  • 4
    A better interval can be (1000/60) milliseconds since most browsers have 60fps but how about using requestAnimationFrame instead of setInterval? requestAnimationFrame seems better suited for you. Read here https://stackoverflow.com/questions/28256197/get-a-smooth-animation-for-a-canvas-game – Sunil Chaudhary Mar 18 '18 at 06:14
  • 1
    `setInterval` (and other timer APIs in the browser) only guarantee a **minimum** delay, for a [number of reasons](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified) the delay can be longer than what you specify. Instead of assuming the time since the last tick, you need to measure it and update things according to how much time has actually passed. – Useless Code Mar 18 '18 at 08:10
  • @UselessCode I'm jealous of people like you who are able to summarize a big post in a tiny comment :-D –  Mar 18 '18 at 08:20
  • An old stuff that you may find interesting : https://stackoverflow.com/a/32416036/1636522 :-) –  Mar 18 '18 at 15:20
  • Thank you! I ended up using `requestAnimationFrame` and now the animation looks smooth and it's fast! – Eric Mar 21 '18 at 04:18

1 Answers1

3

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 :-)