Javascript System Timer / Clock using Delta Timing with a Pause Button

Ryuzaki

お前はもう死んでいる
Moderator
BuSo Pro
Digital Strategist
Joined
Sep 3, 2014
Messages
6,246
Likes
13,132
Degree
9
I need help! I'm not any kind of master scripter as it's been at least a decade since I've done anything of the sort, so I'm sure there are better ways of getting this done, probably with objects or classes and methods, but here's where I'm at, then I'll explain what's wrong and where I need to go. Hopefully we can supply a full answer for the internet, because by golly I can't find one that's not tucked away in an engine that's overly convoluted.

The Current State

Code:
var startTime = Date.now();

function updateTimer() {
    newTime = Date.now();
    var diff = Math.round((newTime-startTime)/1000);
    var d = Math.floor(diff/(24*60*60));
    diff = diff-(d*24*60*60);
    var h = Math.floor(diff/(60*60));
    diff = diff-(h*60*60);
    var m = Math.floor(diff/60);
    diff = diff-(m*60);
    var s = diff;
    d = prettyTimes(d);
    h = prettyTimes(h);
    m = prettyTimes(m);
    s = prettyTimes(s);
    $my.timer.text(d+':'+h+':'+m+':'+s);
}

function prettyTimes( num ) {
    return (num <= 9 ? "0" : "" ) + num;
}

Here's the current situation in word form. In Javascript you can't use setInterval or setTimeout because they drift by a matter of milliseconds each loop. So what you do is take snapshots of the system time and do some math to get the true interval of time that has passed, called "delta time".

So each cycle I'm grabbing the "current time" and subtracting the global variable "startTime" to get the total amount of time that has passed. At that point, it's a matter of converting units around to days, hours, minutes, and seconds, all from milliseconds, and subtracting them out of the delta, called "diff" here. Explaining how it works in words might be too wordy here. I'm willing to if someone needs the explanation.

I'm also calling "prettyTimes()" on each segment of time in order to put a placeholder zero in front of it if the digit is less than 10. So what you end up with is something like....

01:13:07:32 - 1day, 13 hours, 7 minutes, 32 seconds, where the 0 in front of 7 is the prettyTimes result.
So that's all good. It works. I've also cooked up a Pause button that controls an if loop like...

Code:
var notPaused = true;

    function timerPause() {
        if ($(this).hasClass('Timer-Active')) {
            $(this).addClass('Timer-Pause');
            $(this).removeClass('Timer-Active');
            notPaused = false;
        } else {
            $(this).removeClass('Timer-Pause');
            $(this).addClass('Timer-Active');
            notPaused = true;
        }
    }

I yanked a lot of code out of there so it's strictly relevant to this. So what's going on is I have a Pause button designed and adding the 'Timer-Active' and 'Timer-Pause' classes switch colors among other things. I can simplify that, I just realized, to just one class that's either there or not. But you'll notice they also change the boolean state of notPaused.

Elsewhere, I have a recursive requestAnimationFrame() being paused using an if loop for notPaused being true or false. So this pauses everything.

The Problem

So the problem here is that when I unpause, the delta time is still calculating based off the current system time minus the startTime. So while the program stops running, the timer jumps forward in time. So if I pause at 10 seconds, wait 5 seconds and unpause, the timer will jump to 15 seconds instead of starting back at 11 seconds.

I understand the issue and but I'm having a hard time thinking it through. Here's a diagram of what I'm going to try to explain in words:

JNpcsyP.png


So the first section called "save" is your first batch of running time. I recognize this will need to be saved, thus the name. The 2nd batch is called "paused" and is time that shouldn't be accounted for inside the timer. So somehow I'll need to use (new - stop) to make that time go missing.

The 3rd batch is the next period of unpaused running time that'll have to be added to the "save" variable, which is keeping track of the total amount of passed time while unpaused.

So somewhere and somehow I need to create a variable called stopTime to keep track of the last time the game was paused, create a savedTime to track the total unpassed time, and somehow do a flip flop of saving with startTime and newTime to keep track of the current iteration of being unpaused.

If this is absolutely an easy thing for someone smarter and more well-greased in scripting, if you have time can you help a builder out?
 
Figured it out. It wasn't too bad. Writing the opening post definitely helped. Here's the missing pieces:
Code:
    function timerPause() {
        $this = $(this);
        if ($this.hasClass('Paused')) {
            $this.removeClass('Paused');
            $this.css('background-color', '#80e080');
            time.notPaused = true;
            time.startTime = Date.now();
            frame();
        } else {
            $this.addClass('Paused');
            $this.css('background-color', '#e08080');
            time.notPaused = false;
            time.savedTime = time.currentTime - time.startTime;
            time.totalSavedTime += time.savedTime;
        }
    }

And this one line on the above timer code:
Code:
var diff = Math.round((newTime-startTime)/1000);

Needs to be changed to this:
Code:
var diff = Math.round(((time.currentTime-time.startTime)+time.totalSavedTime)/1000);

It's kind of scattered with different labels than the drawing above, but anyone trying to pull this off should be able to follow the concepts.

Since the timerPause() and the updateTimer() functions are in different scopes, I created an object in the global namespace to hold all of this info, like this:
Code:
var time = {
    notPaused : true,
    startTime : Date.now(),
    currentTime : Date.now(),
    savedTime : 0,
    totalSavedTime : 0
}

The only thing that happened to the timerPause() function is that I added "+ time.totalTimeSaved". This new variable is tracking all of the time the game is unpaused. But each batch of being unpaused is first saved into time.timeSaved. This only saves the current period of being unpaused, but then gets added to time.totalTimeSaved, which counts up all of the periods of time unpaused added together.

So the timer is now only tracking the most recent and currently active period of time unpaused, but then it adds the totalTimeSaved to that.

The entire trick was to BEGIN with time.startTime as the very first moment the timer started tracking time, which is Date.now(). That's WAS the current system time. But after that, you have to change the startTime again to Date.now() right when you unpause the timer. So you're getting a new current system time that coincides with unpausing and starting the timer again.

currentTime remains a snapshot of the most recent system time. It's constantly refreshing as Date.now(), but startTime isn't. startTime begins as the moment you load, then the moment you unpause for the first time, then the moment you unpause the 2nd time, and so forth. And "currentTime minus startTime" keeps writing to savedTime, which is then accumulated in totalSavedTime.

And finally, you're left with (( currentTime - startTime ) + totalSavedTime ). That's the current period of being unpaused plus all the previous periods. This chops out all of the time spent paused. You can track that too if you save a different variable for the moment you load the timer.
 
Back