1
\$\begingroup\$

I've implemented Timer class for high accuracy timing (for example for update logic/game-loop, sending ping each 15 seconds by WebRTC etc.)

setTimeout/setInterval fires once per second when tab is inactive, so how to gain accuracy timing?

We could set up setTimeout/setInterval in WebWorker, because in WebWorkers when tab is inactive, setTimeout/setInterval still fires with it's own interval/delay.

We could then send message postmessage/onmessage from WebWorker to Main Thread on each setTimeout/setInterval fire calls as a tick.

For some time this idea was working and was brilliant, but when I'm looking now (latest chrome version) this isn't working anymore because of postmessage, now postmessage delivers event with delay when tab is inactive..

Looks like Chrome Dev Team noticed this feautre, and they've fixed it.. but it wasn't a bug? gaah.. or maybe I have something wrong in the code:

https://jsfiddle.net/mfzmotb1/16/

Timer = function Timer(callback, interval) {
    var id = Timer.prototype.__idCounter++;

    Timer.prototype.__hash[id] = callback;

    this.started = false;

    this.package = JSON.stringify({
        id: id,
        interval: interval
    });
}

Timer.prototype.__workerScript = function() {
    var __hash = {};

    onmessage = function(ev) {
        var data = JSON.parse(ev.data);

        if ( __hash[data.id] === undefined ) {
            __hash[data.id] = {
                id: data.id,
                lastTime: performance.now(),
                interval: data.interval,
                step: 0
            }
        }
        else delete __hash[data.id];
    }

    function loop(start) {
        if ( !start ) for ( var id in __hash ) step(__hash[id]);

        setTimeout(loop, 0);
    }

    loop(true);

    function step(unit) {
        while ( performance.now() - unit.lastTime >= unit.interval ) {
            unit.lastTime += unit.interval;
            unit.step++;
            var now = Date.now();
            console.log("ww", unit.step, "-", now/1000);
            postMessage(JSON.stringify({id: unit.id, time: now, step: unit.step}));
        }
    }
}

Timer.prototype.__idCounter = 0;
Timer.prototype.__hash = {};

Timer.prototype.__worker = new Worker(URL.createObjectURL(new Blob(['('+Timer.prototype.__workerScript.toString()+')()'], {type: 'application/javascript'})));

Timer.prototype.__worker.onmessage = function(ev) {
    var data = JSON.parse(ev.data);
    Timer.prototype.__hash[data.id](data.time, data.step);
}

Timer.prototype.start = function(){
    if ( !this.started ) {
        Timer.prototype.__worker.postMessage(this.package);
        this.started = true;
    }
}

Timer.prototype.stop = function(){
    if ( this.started ) {
        Timer.prototype.__worker.postMessage(this.package);
        this.started = false;
    }
}

var timer = new Timer(function(wwTime, step) {
    var now = Date.now();
    console.log("m_", step, "-", wwTime/1000, now/1000, (now-wwTime)/1000);
}, 100);

console.log("starting in 2sec, so you have time to switch tabs");

setTimeout(function() {
    timer.start();
}, 2000);

Basically this code produce new class called Timer, there is WebWorker based on one function body Timer.prototype.__workerScript <- this fn's body happens in WebWorker.

One WebWorker handles all instances of Timer, each instance is for one timer.

Any ideas how to fix it? Here goes output:

Some tips:

ww - console.log called from webworker
m_ - console.log called from main thread

1,2,3 - number of timer step

Tab Active:

starting in 2sec
ww 1 - 1485111848.697 //webworker tick time in seconds
m_ 1 - 1485111848.697 1485111848.707 0.01 //webworker tick time, main thread tick time, diff
ww 2 - 1485111848.798
m_ 2 - 1485111848.798 1485111848.799 0.001
ww 3 - 1485111848.898
m_ 3 - 1485111848.898 1485111848.898 0
...

Tab Inactive:

starting in 2sec
ww 1 - 1485110821.95 //webworker tick time in seconds
ww 2 - 1485110822.049
ww 3 - 1485110822.149
m_ 1 - 1485110821.95 1485110822.169 0.219 //webworker tick time, main thread tick time, diff
m_ 2 - 1485110822.049 1485110822.17 0.121
m_ 3 - 1485110822.149 1485110822.171 0.022
...

Do you know any ideas for measuring time for html5 gaming?

\$\endgroup\$
  • \$\begingroup\$ Are you sure you need to be doing this? Why do you need high-accurate ping measurements when the player isn't even looking at the tab your game is running in? \$\endgroup\$ – Philipp Jan 22 '17 at 22:10
  • \$\begingroup\$ @Philipp: it's not only about ping, it's about for example authoritative host (runned in browser through WebRTC), imagine that host will switch tab to facebook for a while, then all his clients will go crazy.. of course there are some hard methods, you could simulate whole physic on webworker, not just a timer, but then you have to send snapshot of data to main thread for rendering (OffscreenCanvas aren't ready yet) and all host-action (sending world snapshot to client each n-sec) will be delayed randomly.. - but on Firefox not.. \$\endgroup\$ – ElSajko Jan 22 '17 at 22:17
  • \$\begingroup\$ If you want the user's web browser to act as a gameserver through WebRTC (hope you don't mind if they cheat), then implementing all server logic in a web worker would actually not be the worst idea. The communication between local client and local server should then work via your network protocol. That means rendering should in no way be bound to when messages from the server arrive. \$\endgroup\$ – Philipp Jan 22 '17 at 22:22
  • \$\begingroup\$ Why you said about cheats? If one browser (host) will be authoritative, cheating won't be possible (excluding some helping overlay graphic, but I guess there are methods for it too) anyway, by "server logic" you mean communication by WebRTC? then I can't, WebRTC isn't avaliable in WebWorker. | Each browser (host and clients) simulate his own physic/world, but host sends snapshot for each n sec to clients for corrections. With chrome I guess it will be necessary to put physic simulation inside WebWorker too, but then you need to send positions snapshot to main thread for rendering. \$\endgroup\$ – ElSajko Jan 22 '17 at 22:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.