Source: schedulemanager.js

goog.provide('lime.scheduleManager');

goog.require('goog.array');
goog.require('goog.userAgent');
goog.require('lime');


/**
 * Unified timer provider class
 * Don't create instances of this class. Used the shared instance.
 * @this {lime.scheduleManager}
 * @constructor
 */
lime.scheduleManager = new(function() {

    /**
     * Array of registered functions
     * @type {Array.<lime.scheduleManager.Task>}
     * @private
     */
    this.taskStack_ = [];

    /**
     * ScheduleManager is active
     * @type {boolean}
     * @private
     */
    this.active_ = false;

    /**
     * Internal setInterval id
     * @type {number}
     * @private
     */
    this.intervalID_ = 0;

    /**
     * Maximum update rate in ms.
     * @type {number}
     * @private
     */
    this.displayRate_ = 1000 / 30;

    /**
     * Timer last fire timestamp
     * @type {number}
     * @private
     */
    this.lastRunTime_ = 0;

})();

/**
 * Scheduled task
 * @param {number} maxdelta Timer wait value after iteration.
 * @param {number=} opt_limit Number of calls.
 * @constructor
 */
lime.scheduleManager.Task = function(maxdelta, opt_limit) {
    this.delta = this.maxdelta = maxdelta;
    this.limit = goog.isDef(opt_limit) ? opt_limit : -1;
    this.functionStack_ = [];
};

/**
 * Handle iteration
 * @param {number} dt Delta time since last iteration.
 * @private
 */
lime.scheduleManager.Task.prototype.step_ = function(dt) {
    if (!this.functionStack_.length) return;
    if (this.delta > dt) {
        this.delta -= dt;
    } else {
        var delta = this.maxdelta + dt - this.delta;
        this.delta = this.maxdelta - (dt - this.delta);
        if (this.delta < 0) this.delta = 0;
        var f;
        var i = this.functionStack_.length;
        while (--i >= 0) {
            f = this.functionStack_[i];
            if (f && f[0] && goog.isFunction(f[1]))
                (f[1]).call(f[2], delta);
        }
        if (this.limit != -1) {
            this.limit--;
            if (this.limit == 0) {
                lime.scheduleManager.unschedule(f[1], f[2]);
            }
        }
    }
};

lime.scheduleManager.taskStack_.push(new lime.scheduleManager.Task(0));

(function() {
    var vendors = ['webkit', 'moz'];
    for (var x = 0; x < vendors.length && !goog.global.requestAnimationFrame; ++x) {
        goog.global.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        goog.global.cancelAnimationFrame =
            goog.global[vendors[x] + 'CancelAnimationFrame'] || goog.global[vendors[x] + 'CancelRequestAnimationFrame'];
    }
}());


/**
 * Whether to use requestAnimationFrame instead of timer events
 * Exposed here so it could be disabled if needed.
 * @type {boolean}
 */
lime.scheduleManager.USE_ANIMATION_FRAME = !! goog.global.requestAnimationFrame;

/**
 * Returns maximum fire rate in ms. If you need FPS then use 1000/x
 * @this {lime.scheduleManager}
 * @return {number} Display rate.
 */
lime.scheduleManager.getDisplayRate = function() {
    //todo: bad name
    return this.displayRate_;
};

/**
 * Sets maximum fire rate for the scheduler in ms.
 * If you have FPS then send 1000/x
 * Note that if animation frame methods are used browser chooses
 * max display rate and this value has no effect.
 * @this {lime.scheduleManager}
 * @param {number} value New display rate.
 */
lime.scheduleManager.setDisplayRate = function(value) {
    this.displayRate_ = value;
    if (this.active_) {
        lime.scheduleManager.disable_();
        lime.scheduleManager.activate_();
    }
};

/**
 * Schedule a function. Passed function will be called on every frame
 * with delta time from last run time
 * @this {lime.scheduleManager}
 * @param {function(number)} f Function to be called.
 * @param {Object} context The context used when calling function.
 * @param {lime.scheduleManager.Task=} opt_task Task object.
 */
lime.scheduleManager.schedule = function(f, context, opt_task) {
    var task = goog.isDef(opt_task) ? opt_task : this.taskStack_[0];
    goog.array.insert(task.functionStack_, [1, f, context]);
    goog.array.insert(this.taskStack_, task);
    if (!this.active_) {
        lime.scheduleManager.activate_();
    }
};

/**
 * Unschedule a function. For functions that have be previously scheduled
 * @this {lime.scheduleManager}
 * @param {function(number)} f Function to be unscheduled.
 * @param {Object} context Context used when scheduling.
 */
lime.scheduleManager.unschedule = function(f, context) {
    var j = this.taskStack_.length;
    while (--j >= 0) {
        var task = this.taskStack_[j],
            functionStack_ = task.functionStack_,
            fi, i = functionStack_.length;
        while (--i >= 0) {
            fi = functionStack_[i];
            if (fi[1] == f && fi[2] == context) {
                goog.array.remove(functionStack_, fi);

            }
        }
        if (functionStack_.length == 0 && j != 0) {
            goog.array.remove(this.taskStack_, task);
        }
    }
    // if no more functions: stop timers
    if (this.taskStack_.length == 1 &&
        this.taskStack_[0].functionStack_.length == 0) {
        lime.scheduleManager.disable_();
    }
};

/**
 * Start the internal timer functions
 * @this {lime.scheduleManager}
 * @private
 */
lime.scheduleManager.activate_ = function() {
    if (this.active_) return;

    this.lastRunTime_ = goog.now();

    if (lime.scheduleManager.USE_ANIMATION_FRAME && goog.global.requestAnimationFrame) {
        // old mozilla
        if (goog.global['mozRequestAnimationFrame'] && goog.userAgent.VERSION < 11) {
            goog.global['mozRequestAnimationFrame']();
            this.beforePaintHandlerBinded_ = goog.bind(lime.scheduleManager.beforePaintHandler_, this);
            goog.global.addEventListener('MozBeforePaint', this.beforePaintHandlerBinded_, false);
        } else {
            this.animationFrameHandlerBinded_ = goog.bind(lime.scheduleManager.animationFrameHandler_, this);
            goog.global.requestAnimationFrame(this.animationFrameHandlerBinded_);
        }
    } else {
        this.intervalID_ = setInterval(goog.bind(lime.scheduleManager.stepTimer_, this),
            lime.scheduleManager.getDisplayRate());
    }
    this.active_ = true;
};



/**
 * Stop interval timer functions
 * @this {lime.scheduleManager}
 * @private
 */
lime.scheduleManager.disable_ = function() {
    if (!this.active_) return;

    if (lime.scheduleManager.USE_ANIMATION_FRAME && goog.global.requestAnimationFrame) {
        // old mozilla
        if (goog.global['mozRequestAnimationFrame'] && goog.userAgent.VERSION < 11) {
            goog.global.removeEventListener('MozBeforePaint', this.beforePaintHandlerBinded_, false);
        } else {
            goog.global.cancelAnimationFrame(this.animationFrameHandlerBinded_);
        }
    } else {
        clearInterval(this.intervalID_);
    }
    this.active_ = false;
};

/**
 * Webkit implemtation of requestAnimationFrame handler.
 * @this {lime.scheduleManager}
 * @private
 */
lime.scheduleManager.animationFrameHandler_ = function(time) {
    var performance = goog.global['performance'],
        now;
    if (performance && (now = performance['now'] || performance['webkitNow'])) {
        time = performance['timing']['navigationStart'] + now.call(performance);
    } else if (!time) {
        time = goog.now();
    }
    var delta = time - this.lastRunTime_;
    if (delta < 0) { // i0S6 reports relative to the device restart time. So first is negative.
        delta = 1;
    }
    lime.scheduleManager.dispatch_(delta);
    this.lastRunTime_ = time;
    goog.global.requestAnimationFrame(this.animationFrameHandlerBinded_);
}

/**
 * Mozilla < 11 implementation of requestAnimationFrame handler.
 * @this {lime.scheduleManager}
 * @private
 */
lime.scheduleManager.beforePaintHandler_ = function(event) {
    var delta = event.timeStamp - this.lastRunTime_;
    lime.scheduleManager.dispatch_(delta);
    this.lastRunTime_ = event.timeStamp;
    goog.global['mozRequestAnimationFrame']();
}

/**
 * Timer events step function that delegates to other objects waiting
 * @this {lime.scheduleManager}
 * @private
 */
lime.scheduleManager.stepTimer_ = function() {
    var t;
    var curTime = goog.now();
    var delta = curTime - this.lastRunTime_;
    if (delta < 0) delta = 1;
    lime.scheduleManager.dispatch_(delta);
    this.lastRunTime_ = curTime;
};

/**
 * Call all scheduled tasks
 * @this {lime.scheduleManager}
 * @param {number} delta Milliseconds since last run.
 * @private
 */
lime.scheduleManager.dispatch_ = function(delta) {


    var stack = this.taskStack_.slice()
    var i = stack.length;
    while (--i >= 0) stack[i].step_(delta);
    //hack to deal with FF4 CSS transformation issue https://bugzilla.mozilla.org/show_bug.cgi?id=637597
    if (lime.transformSet_ == 1 && (/Firefox\/18./).test(goog.userAgent.getUserAgentString()) && !lime.FF4_USE_HW_ACCELERATION) {
        if (lime.scheduleManager.odd_) {
            document.body.style['MozTransform'] = '';
            lime.scheduleManager.odd_ = 0;
        } else {
            document.body.style['MozTransform'] = 'scale(1,1)';
            lime.scheduleManager.odd_ = 1;
        }
        lime.transformSet_ = 0;
    }
};

/**
 * Change director's activity. Used for pausing updates when director is paused
 * @this {lime.scheduleManager}
 * @param {lime.Director} director Director.
 * @param {boolean} value Active or inactive?
 */
lime.scheduleManager.changeDirectorActivity = function(director, value) {
    var t, context, f, d, i,
        j = this.taskStack_.length;
    while (--j >= 0) {

        t = this.taskStack_[j];
        i = t.functionStack_.length;
        while (--i >= 0) {
            f = t.functionStack_[i];
            context = f[2];
            if (goog.isFunction(context.getDirector)) {
                d = context.getDirector();
                if (d == director) {
                    f[0] = value;
                }
            }
        }
    }
};

/**
 * Set up function to be called once after a delay
 * @param {function(number)} f Function to be called.
 * @param {Object} context Context used when calling object.
 * @param {number} delay Delay before calling.
 */
lime.scheduleManager.callAfter = function(f, context, delay) {
    lime.scheduleManager.scheduleWithDelay(f, context, delay, 1);
};

/**
 * Set up function to be called repeatedly after a delay
 * @param {function(number)} f Function to be called.
 * @param {Object} context Context used when calling object.
 * @param {number} delay Delay before calling.
 * @param {number=} opt_limit Number of times to call.
 * @this {lime.scheduleManager}
 */
lime.scheduleManager.scheduleWithDelay = function(f, context,
    delay, opt_limit) {
    var task = new lime.scheduleManager.Task(delay, opt_limit);
    lime.scheduleManager.schedule(f, context, task);
};