Last active
August 29, 2015 14:00
-
-
Save jeffreytgilbert/11202429 to your computer and use it in GitHub Desktop.
CommonJS module for managing a timeline of timers with an optional execution time limit. The timers can all be paused and resumed independent of the execution timer. This was tested to within 1/100th of a second accuracy in Chrome, but uses Date.now() which is not compatible with some older browsers (IE 9 for instance). Easy to replace with new …
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
// ****** BEGIN SOLUTION TIMER ******* // | |
module.exports = (function () { | |
// all times will be measured in seconds by default | |
return function (_arrayOfTimes, _scriptTimeLimit, _timerCallback, _timeLimitCallback, _useMilliseconds) { // _useSeconds - add this later to add the ability to measure in milliseconds | |
var _startTime = 0, // the time this script started | |
_initialPlayTime = 0, // the time the playhead initially started | |
_endTime = 0, // the time that this script ended | |
_isRunning = false, // indicator of if the timer playhead is recording or if it is cleared | |
_lastResumedStreakTime = 0, // save point for tracking streaks | |
_lastResumedTime = 0, // save point for cumulative time (start point) | |
_lastPausedTime = 0, // save point for cumulative time (end point) | |
_cumulativeTimeTracked = 0, // do not read from this directly. it is only accurate/updated when it is called from the method | |
_longestStreak = 0, // long time spent waiting for a timer to fire | |
_baseIncrement = 1, // if using seconds (default) this will be 1000, otherwise this will be 1 | |
_clocks = [], // array of setTimeout instances that will go off eventually if left playing | |
_scriptTimeoutClock,// the setTimeout instance for the script timelimit, serving as the master clock | |
// internal object for keeping track of which timers have hit. Has length property to mimic an array, but is not an array. | |
_finishedTimers = { | |
times: {}, | |
findByIndex: function (index) { | |
// console.log(_finishedTimers.times); | |
// console.log(_finishedTimers.times[index]); | |
if (!!_finishedTimers.times[index]) { // this should be either the time or null, so force it to be true/false | |
// console.log('Found the timer in this check for index ' + index); | |
return _finishedTimers.times[index]; | |
} else { | |
// console.log('Did not find the timer in this check for index ' + index); | |
return -1; | |
} | |
}, | |
addTimer: function (index) { | |
_finishedTimers.times[index] = _arrayOfTimes[index]; | |
_finishedTimers.length += 1; | |
}, | |
resetTimers: function () { | |
_finishedTimers.times = {}; | |
_finishedTimers.length = 0; | |
}, | |
length: 0 | |
}, | |
_self = this; | |
var now = function (){ | |
return new Date().getTime(); | |
}; | |
if (typeof _arrayOfTimes !== 'object' || !_arrayOfTimes.length) { | |
_arrayOfTimes = []; | |
} | |
if (_scriptTimeLimit <= 0) { | |
_scriptTimeLimit = Infinity; | |
} | |
if (typeof _timerCallback !== 'function') { | |
_timerCallback = function (self, timeHit, timesLeft, cumulativeTimeTracked, cumulativeRunTime, longestStreak, scriptTimeRemaining) { | |
console.log('Time hit: ' + timeHit); | |
console.log('Timers left: ' + timesLeft); | |
console.log('Cumulative time tracked ' + cumulativeTimeTracked); | |
console.log('Cumulative run time: ' + cumulativeRunTime); | |
console.log('Longest timing streak: ' + longestStreak); | |
console.log('Remainder before script times out: ' + scriptTimeRemaining); | |
console.log(self); | |
}; | |
} | |
if (typeof _timeLimitCallback !== 'function') { | |
_timeLimitCallback = function (self, timesLeft, cumulativeTimeTracked, cumulativeRunTime, longestStreak) { | |
console.log('Script timeout hit!'); | |
console.log('Timers left: ' + timesLeft); | |
console.log('Cumulative time tracked ' + cumulativeTimeTracked); | |
console.log('Cumulative run time: ' + cumulativeRunTime); | |
console.log('Longest timing streak: ' + longestStreak); | |
console.log(self); | |
}; | |
} | |
if (_useMilliseconds) { | |
_baseIncrement = 1; | |
} else { | |
_baseIncrement = 1000; | |
} | |
(function () { | |
var x = 0; | |
while (x < _arrayOfTimes.length) { | |
if (_scriptTimeLimit !== Infinity && _arrayOfTimes[x] > _scriptTimeLimit) { | |
console.log('!Warning! Some timers which have been declared will never fire because the script will timeout first.'); | |
} | |
x++; | |
} | |
})(); | |
var saveEndScriptTime = function () { | |
_endTime = now(); | |
}; | |
var startScriptTimeoutClock = function () { | |
if (_scriptTimeLimit !== Infinity && _scriptTimeLimit > 0) { | |
_scriptTimeoutClock = setTimeout(function () { | |
clearScriptTimeout(_scriptTimeoutClock); | |
clearTimeouts(); | |
saveEndScriptTime(); | |
_isRunning = false; | |
_timeLimitCallback( | |
_self, | |
_self.timersLeft(), | |
_self.cumulativeTimeTracked() / _self.getBaseIncrement(), | |
_self.cumulativeRunTime() / _self.getBaseIncrement(), | |
_self.longestStreak() / _self.getBaseIncrement() | |
); | |
return _self; | |
// don't reset everything here because we may want some of the info after the script timeout has fired | |
}, (_scriptTimeLimit * _baseIncrement)); | |
} | |
}; | |
var clearScriptTimeout = function () { | |
if (_scriptTimeLimit !== Infinity && _scriptTimeLimit > 0) { | |
clearTimeout(_scriptTimeoutClock); | |
} | |
}; | |
var timeoutWrapper = function (index) { | |
return function () { | |
_finishedTimers.addTimer(index); | |
var timersLeft = _self.timersLeft(); | |
if (timersLeft < 1) { | |
clearTimeouts(); | |
clearScriptTimeout(); | |
saveEndScriptTime(); | |
_isRunning = false; | |
// } else { | |
// saveCumulativeTimeTracked(); | |
} | |
var _timeTrackedSoFar = _self.cumulativeTimeTracked(); | |
console.log('The timer just went off for ' + _arrayOfTimes[index] + ' with ' + _timeTrackedSoFar + ' spent so far.'); | |
_timerCallback( | |
_self, | |
_arrayOfTimes[index], // time hit | |
timersLeft, | |
_timeTrackedSoFar / _self.getBaseIncrement(), | |
_self.cumulativeRunTime() / _self.getBaseIncrement(), | |
_self.longestStreak() / _self.getBaseIncrement(), | |
(_self.timeRemaining() !== Infinity ? _self.timeRemaining() / _self.getBaseIncrement(): Infinity) | |
); | |
return _self; | |
}; | |
}; | |
var setTimer = function (index) { | |
var newTime = (_arrayOfTimes[index] * _baseIncrement) - _cumulativeTimeTracked; | |
console.log('New time is being set for ' + newTime + ' milliseconds'); | |
_clocks[index] = setTimeout(timeoutWrapper(index), newTime); // if you use the getter here it will run the whole update algo X number of times, which is slow and potentially buggy. | |
}; | |
var setTimeouts = function () { | |
console.log('Setting all the timeouts'); | |
if (_startTime) { | |
_lastResumedStreakTime = now(); | |
} | |
// initially the playhead was not moving, just the run time was. | |
if (!_initialPlayTime) { | |
_initialPlayTime = now(); | |
} | |
saveCumulativeTimeTracked(); | |
var x = 0, t = 0; | |
while (x < _arrayOfTimes.length) { | |
t = _finishedTimers.findByIndex(x); | |
if (t === -1) { | |
console.log('Timer number ' + x + '(' + t + ') is being set.'); | |
setTimer(x); | |
} | |
x++; | |
} | |
}; | |
var clearTimeouts = function () { | |
_lastPausedTime = now(); | |
saveCumulativeTimeTracked(); | |
var x = 0, t = 0; | |
while (x < _clocks.length) { | |
t = _finishedTimers.findByIndex(x); | |
if (t === -1) { | |
console.log('Timer number ' + x + '(' + t + ') is still active and was running, so it is being removed.'); | |
clearTimeout(_clocks[x]); | |
} | |
x++; | |
} | |
}; | |
this.timeRemaining = function () { | |
if (_scriptTimeLimit !== Infinity) { | |
return (_scriptTimeLimit * _baseIncrement) - this.cumulativeRunTime(); // the difference between then and now minus the bigger limit number | |
} else { | |
return Infinity; | |
} | |
}; | |
// The time when the clock started (read only) | |
this.startTime = function () { | |
return _startTime; | |
}; | |
// The time when the clock stopped running (read only) | |
this.endTime = function () { | |
return _endTime; | |
}; | |
// The cumulative time limit this script will run for before quiting. Set to 0 or Infinity for no limit (read only) | |
this.scriptTimeLimit = function () { | |
return _scriptTimeLimit; | |
}; | |
var saveCumulativeTimeTracked = function () { | |
var rightNow = now(); | |
// console.log('****************************************************'); | |
// console.log('(::) _startTime: ' + _startTime); | |
// console.log('(::) _endTime: ' + _endTime); | |
// console.log('**------------------------------------------------**'); | |
// console.log('(::) _lastResumedTime: ' + _lastResumedTime); | |
// console.log('(::) _lastPausedTime: ' + _lastPausedTime); | |
if (_initialPlayTime && _initialPlayTime > _lastPausedTime) { _lastPausedTime = _initialPlayTime; } | |
if (_endTime !== 0 && _endTime < _lastPausedTime) { _lastPausedTime = _endTime; } | |
if (_initialPlayTime && _initialPlayTime > _lastResumedTime) { _lastResumedTime = _initialPlayTime; } | |
// console.log('**------------------------------------------------**'); | |
// console.log('(::) _lastResumedTime: ' + _lastResumedTime); | |
// console.log('(::) _lastPausedTime: ' + _lastPausedTime); | |
// console.log('**------------------------------------------------**'); | |
var difference; | |
if (_lastPausedTime > _lastResumedTime) { | |
difference = _lastPausedTime - _lastResumedTime; | |
_lastPausedTime = _lastResumedTime; | |
// console.log('(+++) Adding the difference: ' + difference); | |
_cumulativeTimeTracked += difference; | |
} else { | |
if (_isRunning) { | |
// console.log('The timer is still running, so we\'re using NOW as the last pause, then we\'ll move the playhead accordingly.'); | |
difference = rightNow - _lastResumedTime; | |
_lastPausedTime = rightNow; | |
_lastResumedTime = rightNow; | |
// console.log('(+++) Adding the difference: ' + difference); | |
_cumulativeTimeTracked += difference; | |
// } else { | |
// console.log('Not updating the cumulative time because last resumed time is bigger than the last pause time and the thread isnt running.'); | |
// difference = _lastPausedTime - _lastResumedTime; | |
// console.log('(---) Ignoring the difference: ' + difference); | |
} | |
} | |
// console.log('(::) _cumulativeTimeTracked: ' + _cumulativeTimeTracked); | |
// console.log('****************************************************'); | |
}; | |
_self.useMilliseconds = function () { | |
return !!_useMilliseconds; | |
}; | |
_self.baseIncrement = function () { | |
return _baseIncrement; | |
}; | |
// Total time in view according to this timer | |
_self.cumulativeTimeTracked = function () { | |
saveCumulativeTimeTracked(); | |
return _cumulativeTimeTracked; | |
}; | |
_self.cumulativeRunTime = function () { | |
return now() - _startTime; | |
}; | |
// Longest in view streak | |
_self.longestStreak = function (val) { | |
if (val) { | |
if (val > _longestStreak) { _longestStreak = val; } | |
} else { | |
if (_isRunning) { | |
var currentStreak = now() - _lastResumedStreakTime; | |
if (currentStreak > _longestStreak) { _longestStreak = currentStreak; } | |
return _longestStreak; | |
} else { | |
return _longestStreak; | |
} | |
} | |
}; | |
// get the number of timers yet to run | |
_self.timersLeft = function () { | |
return _arrayOfTimes.length - _finishedTimers.length; | |
}; | |
_self.startTimers = function (andStartScriptTimer) { | |
if (!_startTime && !_isRunning) { | |
_startTime = now(); | |
if (andStartScriptTimer) { | |
_initialPlayTime = _startTime; | |
setTimeouts(); | |
_isRunning = true; | |
} | |
startScriptTimeoutClock(); | |
} | |
}; | |
_self.resumeTimers = function () { | |
console.log('Trying to resume timers'); | |
if (!_isRunning) { | |
console.log('None were running, so we are ok to do our magic'); | |
_lastResumedTime = now(); | |
setTimeouts(); | |
_isRunning = true; | |
console.log('All the timers should be resumed'); | |
} | |
}; | |
_self.pauseTimers = function () { | |
if (_isRunning) { | |
_self.longestStreak(); | |
clearTimeouts(); | |
_isRunning = false; | |
} | |
}; | |
_self.fullReset = function () { | |
clearTimeouts(); | |
clearScriptTimeout(); | |
_isRunning = false; | |
_startTime = 0; | |
_endTime = 0; | |
_lastResumedStreakTime = 0; | |
_lastResumedTime = 0; | |
_lastPausedTime = 0; | |
_cumulativeTimeTracked = 0; | |
_longestStreak = 0; | |
_baseIncrement = 1; | |
_finishedTimers.resetTimers(); | |
}; | |
_self.isRunning = function () { | |
return _isRunning; | |
}; | |
_self.skipToIndex = function (index, andStartTimers){ | |
var targetTimestamp = _startTime + (_arrayOfTimes[index] * _baseIncrement); | |
_self.movePlayheadToTime(targetTimestamp, andStartTimers); | |
}; | |
_self.skipToNow = function (andStartTimers){ | |
_self.movePlayheadToTime(now(), andStartTimers); | |
}; | |
_self.movePlayheadToTime = function (targetTimestamp, andStartTimers) { | |
console.log('Pausing the timers so we can move the playhead with known streak times.'); | |
// sure, we're pausing, but we have to catch the last known good time before skipping forward and restarting | |
_self.pauseTimers(); | |
andStartTimers = !!andStartTimers; | |
console.log('We ' + (andStartTimers ? 'WILL' : 'WONT') + ' be starting timers after the playhead moves.'); | |
// now what happens when we start back up? | |
// normally this sets the new time: | |
// var newTime = (_arrayOfTimes[index] * _baseIncrement) - _cumulativeTimeTracked; | |
// so we need to first know how much time has passed since the start of the script | |
_cumulativeTimeTracked = targetTimestamp - _startTime; | |
console.log('calculating the time tracked as: ' + _cumulativeTimeTracked); | |
var index = 0, targetTime = 0; | |
while (index < _arrayOfTimes.length) { | |
targetTime = (_arrayOfTimes[index] * _baseIncrement); | |
console.log('The target time is: ' + targetTime); | |
if (targetTime <= _cumulativeTimeTracked && _finishedTimers.findByIndex(index) === -1) { | |
console.log('It has not expired, and it is at or beyond the time where it should be, so we will attempt to fire it now.'); | |
timeoutWrapper(index)(); // if this should have fired by now (with the fast forward) fire the timeout instantly as it would be normally | |
} else if (targetTime > _cumulativeTimeTracked && andStartTimers) { // && _finishedTimers.findByIndex(index) !== -1 | |
console.log('It has not expired, so we will start the timer for it.'); | |
setTimer(index); // else if this has yet to fire because it is in the future, set another timeout | |
} | |
index++; | |
} | |
}; | |
_self.getLongestStreak = function () { | |
return _self.getUseMilliseconds() ? _self.longestStreak() : (_self.longestStreak() / 1000).toFixed(3); | |
}; | |
_self.play = function (andStartScriptTimer) { | |
console.log('Intercepted a request to play/resume the in view timer. It ' + (andStartScriptTimer?'should':'should NOT') + 'also start the script timer.'); | |
if (_self.startTime()) { | |
console.log('It has been detected that times have started before, so we are going to resume.'); | |
_self.resumeTimers(); | |
} else { | |
console.log('It has been detected that times have NOT started before, so we are going to initialize them.'); | |
_self.startTimers(andStartScriptTimer); | |
} | |
}; | |
// Aliases for public easy public access | |
_self.pause = _self.pauseTimers; | |
_self.kill = _self.fullReset; | |
_self.getScriptTimeLimit = _self.scriptTimeLimit; | |
_self.getTimersLeft = _self.timersLeft; | |
_self.getUseMilliseconds = _self.useMilliseconds; | |
_self.getBaseIncrement = _self.baseIncrement; | |
_self.getEndTime = _self.endTime; | |
_self.getStartTime = _self.startTime; | |
_self.isPlaying = _self.isRunning; | |
}; | |
})(); | |
// ****** END SOLUTION TIMER ******* // |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment