Source: utils/Ticker.js

import Class from '../core/Class';

/**
 * Ticker是一个定时器类。它可以按指定帧率重复运行,从而按计划执行代码。
 * @class Ticker
 * @param {Number} [fps] 指定定时器的运行帧率。默认60。
 */
const Ticker = Class.create(/** @lends Ticker.prototype */ {
    constructor(fps) {
        this.targetFPS = fps || 60;
        this._tickers = [];
    },

    _paused: false,
    _targetFPS: 0,
    _interval: 0,
    _intervalId: null,
    _tickers: null,
    _lastTime: 0,
    _tickCount: 0,
    _tickTime: 0,
    _measuredFPS: 0,

    /**
     * 定时器的目标帧率
     * @type {number}
     */
    targetFPS: {
        set(value) {
            this._targetFPS = value;
            this._interval = 1000 / this._targetFPS;
            if (this._intervalId) {
                this._cancelRunLoop();
                this._startRunLoop();
            }
        },
        get() {
            return this._targetFPS;
        }
    },

    /**
     * 启动定时器。
     */
    start() {
        this._paused = false;
        this._startRunLoop();
    },

    /**
     * 停止定时器。
     */
    stop() {
        this._paused = true;
        this._cancelRunLoop();
        this._lastTime = 0;
    },

    /**
     * 暂停定时器。
     */
    pause() {
        this._paused = true;
    },

    /**
     * 恢复定时器。
     */
    resume() {
        this._paused = false;
    },

    /**
     * @private
     */
    _startRunLoop() {
        if (this._intervalId) {
            return;
        }

        this._lastTime = +new Date();

        const self = this;
        const interval = this._interval;
        const raf = window.requestAnimationFrame;

        let runLoop;
        if (raf && interval < 17) {
            this._useRAF = true;
            runLoop = function() {
                self._intervalId = raf(runLoop);
                self._tick();
            };
        } else {
            this._useRAF = false;
            runLoop = function() {
                self._intervalId = setTimeout(runLoop, interval);
                self._tick();
            };
        }

        runLoop();
    },

    /**
     * @private
     */
    _cancelRunLoop() {
        if (this._useRAF) {
            const cancelRAF = window.cancelAnimationFrame;
            cancelRAF(this._intervalId);
        } else {
            clearTimeout(this._intervalId);
        }
        this._intervalId = null;
    },

    /**
     * @private
     */
    _tick() {
        if (this._paused) return;
        const startTime = +new Date();
        const deltaTime = startTime - this._lastTime;
        const tickers = this._tickers;

        // calculates the real fps
        if (++this._tickCount >= this._targetFPS) {
            this._measuredFPS = (1000 / (this._tickTime / this._tickCount) + 0.5) >> 0;
            this._tickCount = 0;
            this._tickTime = 0;
        } else {
            this._tickTime += startTime - this._lastTime;
        }
        this._lastTime = startTime;

        const tickersCopy = tickers.slice(0);
        for (let i = 0, len = tickersCopy.length; i < len; i++) {
            tickersCopy[i].tick(deltaTime);
        }
    },

    /**
     * 获得测定的运行时帧率。
     * @return {number}
     */
    getMeasuredFPS() {
        return Math.min(this._measuredFPS, this._targetFPS);
    },

    /**
     * 添加定时器对象。定时器对象必须实现 tick 方法。
     * @param {Object}  ticker 对象
     */
    addTick(tickObject) {
        if (!tickObject || typeof tickObject.tick !== 'function') {
            throw new Error(
                'Ticker: The tick object must implement the tick method.'
            );
        }
        this._tickers.push(tickObject);
    },

    /**
     * 删除定时器对象。
     * @param {Object} tickObject 要删除的定时器对象。
     */
    removeTick(tickObject) {
        const tickers = this._tickers;
        const index = tickers.indexOf(tickObject);
        if (index >= 0) {
            tickers.splice(index, 1);
        }
    },
    /**
     * 下次tick时回调
     * @param  {Function} callback
     * @return {Object} tickObject 定时器对象
     */
    nextTick(callback) {
        const that = this;
        const tickObj = {
            tick(dt) {
                that.removeTick(tickObj);
                callback(dt);
            },
        };

        that.addTick(tickObj);
        return tickObj;
    },
    /**
     * 延迟指定的时间后调用回调, 类似setTimeout
     * @param  {Function} callback
     * @param  {Number}   duration 延迟的毫秒数
     * @return {Object} tickObject 定时器对象
     */
    timeout(callback, duration) {
        const that = this;
        const targetTime = new Date().getTime() + duration;
        const tickObj = {
            tick() {
                const nowTime = new Date().getTime();
                const dt = nowTime - targetTime;
                if (dt >= 0) {
                    that.removeTick(tickObj);
                    callback();
                }
            },
        };
        that.addTick(tickObj);
        return tickObj;
    },
    /**
     * 指定的时间周期来调用函数, 类似setInterval
     * @param  {Function} callback
     * @param  {Number}   duration 时间周期,单位毫秒
     * @return {Object} tickObject 定时器对象
     */
    interval(callback, duration) {
        const that = this;
        let targetTime = new Date().getTime() + duration;
        const tickObj = {
            tick() {
                let nowTime = new Date().getTime();
                const dt = nowTime - targetTime;
                if (dt >= 0) {
                    if (dt < duration) {
                        nowTime -= dt;
                    }
                    targetTime = nowTime + duration;
                    callback();
                }
            },
        };
        that.addTick(tickObj);
        return tickObj;
    },
});

export default Ticker;