Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stopwatch with breakpoints not adding up correctly

I have a main stopwatch with 4 mini-stopwatches for each step. After a finished time, here is an example of how the timers should look:

MAIN:  00 : 14 : 57
-------------------
MINI1: 00 : 04 . 17
MINI2: 00 : 06 . 40
MINI3: 00 : 02 . 54
MINI4: 00 : 01 . 46

The mini-timers should add up to the main timer, as they do in this case. With my current timer, it always seems to be .02 milliseconds off, so they would add up to 00 : 14 . 55 in this case instead of 00 : 14 . 57.

Here is a JSFiddle of my current timers. I think the issue is most likely in the stopwatch.js file, but I'm not sure why that would be the case since I'm using Date.now() to calculate how much time has passed. Here is the stopwatch.js file which is the code for an individual stopwatch:

class Stopwatch {
  constructor (opts) {
    this.isOn = false;
    this.time = 0;
    this.elem = opts.elem;
  }

  start () {
    this.offset = Date.now();
    this.interval = setInterval(() => this._update(), 10);
    this.isOn = true;
  }

  stop () {
    clearInterval(this.interval);
    this.offset = null;
    this.interval = null;
    this.isOn = false;
  }

  reset () {
    this.time = 0;
    this._render();
  }

  _update () {
    this.time += this._getTimePassed();
    this._render();
  }

  _getTimePassed () {
    const now = Date.now();
    const timePassed = now - this.offset;
    this.offset = now;
    return timePassed;
  }

  _timeFormatter (milliseconds) {
    const padZero = (time) => `0${time}`.slice(-2);

    const minutes = padZero(milliseconds / 60000 | 0);
    const seconds = padZero((milliseconds / 1000 | 0) % 60);
    const centiseconds = padZero((milliseconds / 10 | 0) % 100);

    return `${minutes} : ${seconds} . ${centiseconds}`;
  }

  _render () {
    this.elem.textContent = this._timeFormatter(this.time);
  }
}

I have everything altogether inside the JSFiddle I mentioned above and also in this gist if that's easier to read. Any guidance would be appreciated.

like image 931
Saad Avatar asked Jul 10 '16 01:07

Saad


3 Answers

You're talking about a 20ms delay, which can be caused by a number of things.

  • As Flynn1179 suggested, there maybe between the calls to Date.now()
  • JavaScript's setInterval drifts! And here's a bin to prove it. You can't really expect for a language that runs on a single thread to schedule tasks every 10ms with perfect accuracy, and your _update method demands just that.
  • You're ignoring the 0-9ms in the centiseconds counter, which is being accounted for in the main timer. This is likely the largest contributing factor. For example, say miniwatch A is at 15ms and miniwatch B is at 15ms. Miniwatch A would display '01', miniwatch B would display '01', but the main watch would display '03' since 30ms have passed.

To solve this correctly, I'd recommend redesigning your solution so that you wrap all your stopwatches in a StopwatchManager, which renders all your stopwatches at once and computes the total time for your main watch by adding the times of the miniwatches. You might also want to look into using requestAnimationFrame for rendering instead of setInterval.

like image 153
fny Avatar answered Nov 19 '22 07:11

fny


You're stopping one timer, and in the next line, starting the next. Your problem is partially that time is passing between those two method calls.

Also, your 'stop' method doesn't even use the current time, it just retroactively stops it from the last time it was updated, it doesn't do a final _update.

If you really want it to add up precisely, take Date.now() in your updateMiniTimers method, and pass that to both calls to make sure they stop/start at the same point in time, and do a final render after a stop call.

In general, within the method:

method() {
  var a = Date.now();
  var b = Date.now();
}

a and b are absolutely not guaranteed to be the same, no method call is instantaneous.

like image 1
Flynn1179 Avatar answered Nov 19 '22 09:11

Flynn1179


I concur with Faraz about possible causes. I would also check for rounding errors in the divisions. Anyway, if you want to make it a bit more robust and scalable, you could think about your time points as elements in a List that keeps expanding. Whenever a Stopwatch starts, you record the index of the last element in the List as Starting Point, whenever one ends you record the the index of the last Element as the End Point. That would allow you to have accurate hierarchies of timers.

like image 1
Milton Hernandez Avatar answered Nov 19 '22 07:11

Milton Hernandez