|
/** |
|
* Instantiates a highly flexible interval object that provides consistent and |
|
* clean utilities to manage complex interval timer needs. |
|
* |
|
* By default, this creates a standard interval that refreshes every minute. |
|
* |
|
* @param args (object) |
|
* minInterval (int) : (default: 15) the minimum refresh interval. If curve not set, this will be ignored |
|
* maxInterval (int) : (default: 60) the max refresh interval. If curve not set, this will be used. |
|
* rateOfChange (float): (default: 0.05) a fractional percentage used in some functions. |
|
* callback (function) : (default: fn) function to execute when timer resets. By default, this does nothing. |
|
* NOTE: caller must define the scope of the callback fn, typically by wrapping it in an anonymous fn and |
|
* calling it with call() or apply() and passing in the scope, e.g.: callback: function() {thisObj.myCallbackFn.call(thisObj)} |
|
* curve (string) : (default: 'flat') any of the defined curveModels: |
|
* "flat" : a simple interval with a constant time, |
|
* "exponentialGrowth" : the interval increases exponentially by the rateOfChange, starting with the minInterval and increasing |
|
* to maxInterval. Once maxInterval is reached, it flattens out. |
|
* "exponentialDecay" : interval decreases exponentially by rateOfChange, starting with maxInterval and decreasing to |
|
* minInterval, where it then flattens out. |
|
* "sGrowth" : interval increases on an s-curve by the rateOfChange, starting at the minInterval and flattening |
|
* out at the maxInterval. |
|
* "linearGrowth" : interval increases linearly by the rateOfChange, starting at minInterval, and increasing to maxInterval. |
|
* "linearDecay" : interval decreases linearly by the rateOfChange, starting at maxInterval, and decreasing to minInterval |
|
*/ |
|
function MorphInterval(args) |
|
{ |
|
assert(arguments.length <= 1); |
|
assert(arguments.length === 1 && isset(args) ? is_strict_object(args) : false, "if passed in, args must be an object"); |
|
|
|
this.minInterval = args.minInterval || 15; |
|
this.maxInterval = args.maxInterval || 60; |
|
|
|
assert(this.minInterval > 0); |
|
assert(this.maxInterval >= this.minInterval); |
|
|
|
var thisInterval = this; |
|
|
|
this.curveModels = { |
|
flat : function(base) { return base }, |
|
exponentialGrowth : function(base) { return base * Math.pow((1 + thisInterval.rateOfChange), ++thisInterval.count) }, |
|
exponentialDecay : function(base) { return base * Math.pow((1 - thisInterval.rateOfChange), ++thisInterval.count) }, |
|
sGrowth : function(base) { return thisInterval.maxInterval / (1 + ((thisInterval.maxInterval - base) / base) * Math.pow(Math.E, (-thisInterval.rateOfChange * ++thisInterval.count))) }, |
|
linearGrowth : function(base) { return base + thisInterval.rateOfChange }, |
|
linearDecay : function(base) { return base - thisInterval.rateOfChange } |
|
} |
|
|
|
this.callback = args.hasOwnProperty('callback') && $.isFunction(args.callback) ? args.callback : function(){assert(false, "callback invalid, interval has no effect.")}; |
|
|
|
this.getNewTime = isset(args.curve) ? this.curveModels[args.curve] : this.curveModels.flat; |
|
|
|
this.id; |
|
this.initialInterval = (isset(args.curve) && args.curve.search(/growth/i) !== -1) ? this.minInterval : this.maxInterval; // if growth, min, else max |
|
this.rateOfChange = args.hasOwnProperty('rateOfChange') && is_number(args.rateOfChange) ? args.rateOfChange : 0.05; // 5% |
|
this.count; // how many times interval has grown or decayed |
|
} |
|
|
|
window.MorphInterval = MorphInterval; |
|
|
|
/** |
|
* wrapper for the reset fn (more intuitive). Should only be called once at instantiation. |
|
* |
|
* @return (MorphInterval) returns the instance of MorphInterval. This is simply for |
|
* convenience in chaining. For example: |
|
* |
|
* var myInterval = new MorphInterval({parameters:values}).start(); |
|
* |
|
* vs. |
|
* |
|
* var myInterval = new MorphInterval({parameters:values}); |
|
* myInterval.start(); |
|
*/ |
|
MorphInterval.prototype.start = function() |
|
{ |
|
var thisInterval = this; |
|
|
|
thisInterval.reset(); |
|
|
|
return thisInterval; |
|
} |
|
|
|
/** |
|
* kills the interval. |
|
* |
|
* After killed, call myMorphIntervalInstance.reset() with no |
|
* parameters if need to restart. |
|
*/ |
|
MorphInterval.prototype.kill = function() |
|
{ |
|
var thisInterval = this; |
|
|
|
thisInterval.id = window.clearInterval(thisInterval.id); |
|
} |
|
|
|
/** |
|
* recursive function that sets up the interval. |
|
* |
|
* IMPORTANT: Should never be explicitly passed the lastTime parameter. This is |
|
* done in the 2nd+ iteration(s). |
|
*/ |
|
MorphInterval.prototype.reset = function(lastTime) // newInterval will be the value of rateOfChange when called recursively |
|
{ |
|
assert(arguments.length <= 1); |
|
|
|
var thisInterval = this; |
|
|
|
// must kill the existing interval before making any change to it. |
|
thisInterval.kill(); |
|
|
|
// decay: action == -1, growth: action == 1 |
|
// will only be set on the second and greater iterations |
|
var newTime; |
|
|
|
if (is_number(lastTime)) |
|
{ |
|
//thisInterval.count++; |
|
|
|
//newTime = lastTime * Math.pow((1 - thisInterval.rateOfChange), ++thisInterval.count); // exponential decay |
|
newTime = thisInterval.getNewTime(lastTime); |
|
|
|
// if this is a growth model, we want to make sure that the new time is not larger than the max |
|
if (thisInterval.initialInterval === thisInterval.minInterval) // growth |
|
{ |
|
newTime = (newTime > thisInterval.maxInterval) ? thisInterval.maxInterval : newTime; |
|
} |
|
else |
|
{ |
|
newTime = (newTime < thisInterval.minInterval) ? thisInterval.minInterval : newTime; |
|
} |
|
} |
|
else |
|
{ |
|
// if lastTime not given, is is the first call, so reset the timer at the beginning, |
|
// which in this case is the max. Also reset the global counter |
|
newTime = thisInterval.initialInterval; |
|
thisInterval.count = 0; |
|
} |
|
|
|
var normalizedTime = Math.floor(Math.round(newTime * 1000000)/1000); // converts time to microtime by rounding to the nearest thousanth |
|
|
|
//console.log('morphInterval time', newTime, normalizedTime, thisInterval.count); |
|
|
|
thisInterval.id = setInterval(function(){ thisInterval.reset(newTime); thisInterval.callback(); }, normalizedTime); |
|
} |