UNPKG

nanotimer

Version:

A much higher accuracy timer object that makes use of the node.js hrtime function call.

414 lines (332 loc) 11.3 kB
function NanoTimer(log){ var version = process.version; var major = version.split('.')[0]; major = major.split('v')[1]; var minor = version.split('.')[1]; if ((major == 0) && (minor < 10)){ console.log('Error: Please update to the latest version of node! This library requires 0.10.x or later'); process.exit(0); } //Time reference variables this.intervalT1 = null; this.timeOutT1 = null; this.intervalCount = 1; //Deferred reference indicator variables. Indicate whether the timer used/will use the deferred call. ie - delay/interval > 25ms this.deferredInterval = false; this.deferredTimeout = false; //Deferred reference variables. Used to clear the native js timeOut calls this.deferredTimeoutRef = null; this.deferredIntervalRef = null; //Callback reference variables. Used to be able to still successfully call callbacks when timeouts or intervals are cleared. this.timeoutCallbackRef = null; this.intervalCallbackRef = null; //Immediate reference variables. Used to clear functions scheduled with setImmediate from running in the event timeout/interval is cleared. this.timeoutImmediateRef = null; this.intervalImmediateRef = null; this.intervalErrorChecked = false; this.intervalType = ""; this.timeoutTriggered = false; if(log){ this.logging = true; } } NanoTimer.prototype.time = function(task, args, format, callback){ //Asynchronous task if(callback){ var t1 = process.hrtime(); if(args){ args.push(function(){ var time = process.hrtime(t1); if(format == 's'){ callback(time[0] + time[1]/1000000000); } else if (format == 'm'){ callback(time[0]*1000 + time[1]/1000000); } else if (format == 'u'){ callback(time[0]*1000000 + time[1]/1000); } else if (format == 'n'){ callback(time[0]*1000000000 + time[1]); } else { callback(time); } }); task.apply(null, args); } else { task(function(){ var time = process.hrtime(t1); if(format == 's'){ callback(time[0] + time[1]/1000000000); } else if (format == 'm'){ callback(time[0]*1000 + time[1]/1000000); } else if (format == 'u'){ callback(time[0]*1000000 + time[1]/1000); } else if (format == 'n'){ callback(time[0]*1000000000 + time[1]); } else { callback(time); } }); } //Synchronous task } else { var t1 = process.hrtime(); if(args){ task.apply(null, args); } else{ task(); } var t2 = process.hrtime(t1); if(format == 's'){ return t2[0] + t2[1]/1000000000; } else if (format == 'm'){ return t2[0]*1000 + t2[1]/1000000; } else if (format == 'u'){ return t2[0]*1000000 + t2[1]/1000; } else if (format == 'n'){ return t2[0]*1000000000 + t2[1]; } else { return process.hrtime(t1); } } }; NanoTimer.prototype.setInterval = function(task, args, interval, callback){ if(!this.intervalErrorChecked){ //Task error handling if(!task){ console.log("A task function must be specified to setInterval"); process.exit(1); } else { if(typeof(task) != "function"){ console.log("Task argument to setInterval must be a function reference"); process.exit(1); } } //Interval error handling if(!interval){ console.log("An interval argument must be specified"); process.exit(1); } else { if(typeof(interval) != "string"){ console.log("Interval argument to setInterval must be a string specified as an integer followed by 's' for seconds, 'm' for milli, 'u' for micro, and 'n' for nanoseconds. Ex. 2u"); process.exit(1); } } //This ref is used if deferred timeout is cleared, so the callback can still be accessed if(callback){ if(typeof(callback) != "function"){ console.log("Callback argument to setInterval must be a function reference"); process.exit(1); } else { this.intervalCallbackRef = callback; } } this.intervalType = interval[interval.length-1]; if(this.intervalType == 's'){ this.intervalTime = interval.slice(0, interval.length-1) * 1000000000; } else if(this.intervalType == 'm'){ this.intervalTime = interval.slice(0, interval.length-1) * 1000000; } else if(this.intervalType == 'u'){ this.intervalTime = interval.slice(0, interval.length-1) * 1000; } else if(this.intervalType == 'n'){ this.intervalTime = interval.slice(0, interval.length-1); } else { console.log('Error with argument: ' + interval + ': Incorrect interval format. Format is an integer followed by "s" for seconds, "m" for milli, "u" for micro, and "n" for nanoseconds. Ex. 2u'); process.exit(1); } this.intervalErrorChecked = true; } //Avoid dereferencing inside of function objects later //Must be performed on every execution var thisTimer = this; if(this.intervalTime > 0){ //Check and set constant t1 value. if(this.intervalT1 == null){ this.intervalT1 = process.hrtime(); } //Check for overflow. Every 8,000,000 seconds (92.6 days), this will overflow //and the reference time T1 will be re-acquired. This is the only case in which error will //propagate. if(this.intervalTime*this.intervalCount > 8000000000000000){ this.intervalT1 = process.hrtime(); this.intervalCount = 1; } //Get comparison time this.difArray = process.hrtime(this.intervalT1); this.difTime = (this.difArray[0] * 1000000000) + this.difArray[1]; //If updated time < expected time, continue //Otherwise, run task and update counter if(this.difTime < (this.intervalTime*this.intervalCount)){ //Can potentially defer to less accurate setTimeout if intervaltime > 25ms if(this.intervalTime > 25000000){ if(this.deferredInterval == false){ this.deferredInterval = true; var msDelay = (this.intervalTime - 25000000) / 1000000.0; this.deferredIntervalRef = setTimeout(function(){thisTimer.setInterval(task, args, interval, callback);}, msDelay); } else { this.deferredIntervalRef = null; this.intervalImmediateRef = setImmediate(function(){thisTimer.setInterval(task, args, interval, callback);}); } } else { this.intervalImmediateRef = setImmediate(function(){thisTimer.setInterval(task, args, interval, callback);}); } } else { this.intervalImmediateRef = null; if(this.logging){ console.log('nanotimer log: ' + 'cycle time at - ' + this.difTime); } if(args){ task.apply(null, args); } else { task(); } //Check if the intervalT1 is still not NULL. If it is, that means the task cleared the interval so it should not run again. if(this.intervalT1){ this.intervalCount++; this.deferredInterval = false; this.intervalImmediateRef = setImmediate(function(){thisTimer.setInterval(task, args, interval, callback);}); } } //If interval = 0, run as fast as possible. } else { //Check and set constant t1 value. if(this.intervalT1 == null){ this.intervalT1 = process.hrtime(); } if(args){ task.apply(null, args); } else { task(); } // This needs to be re-checked here incase calling task turned this off if(this.intervalT1){ this.intervalImmediateRef = setImmediate(function(){thisTimer.setInterval(task, args, interval, callback);}); } } }; NanoTimer.prototype.setTimeout = function(task, args, delay, callback){ //Task error handling if(!task){ console.log("A task function must be specified to setTimeout"); process.exit(1); } else { if(typeof(task) != "function"){ console.log("Task argument to setTimeout must be a function reference"); process.exit(1); } } //Delay error handling if(!delay){ console.log("A delay argument must be specified"); process.exit(1); } else { if(typeof(delay) != "string"){ console.log("Delay argument to setTimeout must be a string specified as an integer followed by 's' for seconds, 'm' for milli, 'u' for micro, and 'n' for nanoseconds. Ex. 2u"); process.exit(1); } } //This ref is used if deferred timeout is cleared, so the callback can still be accessed if(callback){ if(typeof(callback) != "function"){ console.log("Callback argument to setTimeout must be a function reference"); process.exit(1); } else { this.timeoutCallbackRef = callback; } } //Avoid dereferencing var thisTimer = this; if(this.timeoutTriggered){ this.timeoutTriggered = false; } var delayType = delay[delay.length-1]; if(delayType == 's'){ var delayTime = delay.slice(0, delay.length-1) * 1000000000; } else if(delayType == 'm'){ var delayTime = delay.slice(0, delay.length-1) * 1000000; } else if(delayType == 'u'){ var delayTime = delay.slice(0, delay.length-1) * 1000; } else if(delayType == 'n'){ var delayTime = delay.slice(0, delay.length-1); } else { console.log('Error with argument: ' + delay + ': Incorrect delay format. Format is an integer followed by "s" for seconds, "m" for milli, "u" for micro, and "n" for nanoseconds. Ex. 2u'); process.exit(1); } //Set marker if(this.timeOutT1 == null){ this.timeOutT1 = process.hrtime(); } var difArray = process.hrtime(this.timeOutT1); var difTime = (difArray[0] * 1000000000) + difArray[1]; if(difTime < delayTime){ //Can potentially defer to less accurate setTimeout if delayTime > 25ms if(delayTime > 25000000){ if(this.deferredTimeout == false){ this.deferredTimeout = true; var msDelay = (delayTime - 25000000) / 1000000.0; this.deferredTimeoutRef = setTimeout(function(){thisTimer.setTimeout(task, args, delay, callback);}, msDelay); } else { this.deferredTimeoutRef = null; this.timeoutImmediateRef = setImmediate(function(){thisTimer.setTimeout(task, args, delay, callback);}); } } else { this.timeoutImmediateRef = setImmediate(function(){thisTimer.setTimeout(task, args, delay, callback);}); } } else { this.timeoutTriggered = true; this.timeoutImmediateRef = null; this.timeOutT1 = null; this.deferredTimeout = false; if(this.logging == true){ console.log('nanotimer log: ' + 'actual wait - ' + difTime); } if(args){ task.apply(null, args); } else{ task(); } if(callback){ var data = {'waitTime':difTime}; callback(data); } } }; NanoTimer.prototype.clearInterval = function(){ if(this.deferredIntervalRef){ clearTimeout(this.deferredIntervalRef); this.deferredInterval = false; } if(this.intervalImmediateRef){ clearImmediate(this.intervalImmediateRef); } this.intervalT1 = null; this.intervalCount = 1; this.intervalErrorChecked = false; if(this.intervalCallbackRef){ this.intervalCallbackRef(); } }; NanoTimer.prototype.clearTimeout = function(){ // Only do something if this is not being called as a result // of the timeout triggering if(this.timeoutTriggered == false){ if(this.deferredTimeoutRef){ clearTimeout(this.deferredTimeoutRef); if(this.timeOutT1) { var difArray = process.hrtime(this.timeOutT1); var difTime = (difArray[0] * 1000000000) + difArray[1]; } this.deferredTimeout = false; } if(this.timeoutImmediateRef){ clearImmediate(this.timeoutImmediateRef); } this.timeOutT1 = null; if(this.timeoutCallbackRef){ var data = {'waitTime':difTime}; this.timeoutCallbackRef(data); } } }; NanoTimer.prototype.hasTimeout = function(){ return this.timeOutT1 != null; }; module.exports = NanoTimer;