📜 ⬆️ ⬇️

Javascript: autocorrection timers



In the post, in a narrative and not very manner, it tells about various implementations of the “exact” timers on JS. The material is designed for beginners ... Welcome under cat.

As many have noticed, the picture for the post depicts the clock from Dali's work “Time is flowing”, the choice is by no means accidental and metaphorical in nature. For, in the framework of programming on JS, time may not quite flow as we assume. JS is single-threaded in its essence, which creates a queue for performing functions, and a queue implies an indispensable order of succession. And if some of the computation steps turn out to be too resource-intensive, we have an obvious discrepancy between what is required and the result of the execution. This is especially critical in cases of controlling transients without the use of libraries. For example: performing a transition with a change in coordinate in time along a cubic curve ( easing ), or working with a rhythmic call of application logic to update the current state. A couple of months ago, as a weekend project, I chose to write a simple step sequencer (wiki) for myself, and faced the physical impossibility of precise timing on weak and weak systems using standard setTimeout () and setInterval () . The mismatch reached irreconcilable in this case half a second. In search of a solution, I came across an excellent article on this topic. And the post itself, in a certain way, is a free translation thereof.
')
As a result, the task of “accurate” timing is reduced to subtracting the delay of the previous execution of the function from the present. You can simply measure the difference in system time between iterations and subtract it on the next call. It sounds simple, and here is the code:
var start = new Date().getTime(), time = 0, elapsed = '0.0'; function instance() { time += 100; elapsed = Math.floor(time / 100) / 10; if(Math.round(elapsed) == elapsed) { elapsed += '.0'; } document.title = elapsed; var diff = (new Date().getTime() - start) - time; window.setTimeout(instance, (100 - diff)); } window.setTimeout(instance, 100); 


It's pretty simple, look at the results. Here is a demo on JSfiddle with comments in Russian, for comparing the work of ordinary timers and timers with autocorrection.
The best thing about this approach is that it does not really matter how inaccurate the timer is, since afterwards a slight constant delay (like 3-4ms in the last demo example) can be very easily compensated. While the inaccuracy of a simple timer is cumulative, it accumulates with each iteration, which in the end leads to a hell of a noticeable difference.

As mentioned above, I encountered this problem when writing an audio application. Applications, of course, are very loudly said, rather just another regular mini-project. After studying the material, this code was written here:

 //    "play/stop",     function preciousTimer (step) { //    ,  DateStamp   var start = new Date().getTime(), time = 0, /*            ,     (    )*/ it = 0; function instance () { //   time += step; //  var diff = (new Date().getTime()- start) - time; //    if (it == 4) { it = 0; /*     ,          . */ if (m == 8) { m = 0; }; for (var i = 0; i < 4; i++) { if (noteArr[i][m]) { sound[i].play(); }; }; m++; }; it++; //       , //     if (pause) { return; }; //  ,    window.setTimeout(instance, (step - diff)); }; //      instance(), //      setTimeout(instance, step); }; 


Someone has probably wondered: what about the overflow of the call stack. In this particular case, its size ranges from 10 to 17 positions, which is not enough for any modern browser. However, with an increase in pace, or together with an increase in the number of recalculated iterations, a choking attack may occur and you will need to think about implementing .tail () - like calls. But this is a completely different story.

We should also mention the window.performance.now () method, which returns a floating-point number, a significant number of milliseconds elapsed from the page load (not quite an exact definition) Therefore, after the decimal point, we will have submillisecond resolution, which is very good . Using this value, it is possible by a similar method to calculate the mismatch with an accuracy of one-tenth of a millisecond, and more accurately start the next iteration.

You can see the sequencer live here: stepograph.hol.es (webkit required)
Link to the original article, partially used in the post: reating accurate timers in JavaScript

Thanks for attention!

Source: https://habr.com/ru/post/212889/


All Articles