admin管理员组

文章数量:1530847

creator 里面的计时器相信大家不陌生,但是了解它的原理是必要的,它的运行机制和setInterval有什么不同呢,

先简单说说setInterval的原理:setInterval是每隔一段时间向事件队列里面添加回调函数,如果主线程忙的话调用时间不确定特别容易出问题,由于setInterval只负责定时向队列中添加函数,而不考虑函数的执行,setInterval有一个原则:在向队列中添加回调函数时,如果队列中存在之前由其添加的回调函数,就放弃本次添加(不会影响之后的计时),如果主线程忙的话,之前添加的计时器回调没有触发紧接着又添加一个这个时候就会放弃本次添加,是很容易出问题的。另外计时是不准确的。

但是提前说好schedule的计时也是不准确的

首先看看schedule是什么时候出生的:

cc.Director = function () {
    EventTarget.call(this);

    // paused?
    this._paused = false;
    // purge?
    this._purgeDirectorInNextLoop = false;

    this._winSizeInPoints = null;

    // scenes
    this._scene = null;
    this._loadingScene = '';

    // FPS
    this._totalFrames = 0;
    this._lastUpdate = 0;
    this._deltaTime = 0.0;
    this._startTime = 0.0;

    // ParticleSystem max step delta time
    this._maxParticleDeltaTime = 0.0;

    // Scheduler for user registration update
    this._scheduler = null;
    // Scheduler for life-cycle methods in component
    this._compScheduler = null;
    // Node activator
    this._nodeActivator = null;
    // Action manager
    this._actionManager = null;

    var self = this;
    game.on(game.EVENT_SHOW, function () {
        self._lastUpdate = performance.now();
    });
    // 监听引擎初始化完毕,初始化定时器
    game.once(game.EVENT_ENGINE_INITED, this.init, this);
};
cc.Director.prototype = {
    constructor: cc.Director,
    init: function () {
        this._totalFrames = 0;
        this._lastUpdate = performance.now();
        this._startTime = this._lastUpdate;
        this._paused = false;
        this._purgeDirectorInNextLoop = false;
        this._winSizeInPoints = cc.size(0, 0);
        // 引擎初始化完毕之后初始化了一个Schedule实例
        this._scheduler = new Scheduler();

        if (cc.ActionManager) {
            // action的优先级比较高系统级别的优先级
            this._actionManager = new cc.ActionManager();
            this._scheduler.scheduleUpdate(this._actionManager, Scheduler.PRIORITY_SYSTEM, false);
        } else {
            this._actionManager = null;
        }

        this.sharedInit();
        return true;
    },
cc.Scheduler = function () {
    this._timeScale = 1.0;
    // 优先级 < 0的定时器列表 有ActionMangaer,CollisionManager,physicsManager,physics3dManager
    this._updatesNegList = [];  // list of priority < 0
    // 优先级 = 0 的定时器列表
    this._updates0List = [];    // list of priority == 0
    // 优先级 > 0 的定时器列表
    this._updatesPosList = [];  // list of priority > 0

    this._hashForUpdates = js.createMap(true);  // hash used to fetch quickly the list entries for pause, delete, etc
    this._hashForTimers = js.createMap(true);   // Used for "selectors with interval"
    this._currentTarget = null;
    this._currentTargetSalvaged = false;
    this._updateHashLocked = false; // If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.

    this._arrayForTimers = [];  // Speed up indexing
    //this._arrayForUpdates = [];   // Speed up indexing
};

在CCDirector.js中的mainLoop每帧调用schedule,所以schedule是帧驱动的方式运行的,跟setInterval不用:

mainLoop: CC_EDITOR ? function (deltaTime, updateAnimate) {
        this._deltaTime = deltaTime;

        // Update
        if (!this._paused) {
            this.emit(cc.Director.EVENT_BEFORE_UPDATE);

            this._compScheduler.startPhase();
            this._compScheduler.updatePhase(deltaTime);

            if (updateAnimate) {
                this._scheduler.update(deltaTime);
            }

            this._compScheduler.lateUpdatePhase(deltaTime);

            this.emit(cc.Director.EVENT_AFTER_UPDATE);
        }

        // Render
        this.emit(cc.Director.EVENT_BEFORE_DRAW);
        renderer.render(this._scene, deltaTime);
        
        // After draw
        this.emit(cc.Director.EVENT_AFTER_DRAW);

        this._totalFrames++;

    } : function (now) {
        if (this._purgeDirectorInNextLoop) {
            this._purgeDirectorInNextLoop = false;
            this.purgeDirector();
        }
        else {
            // calculate "global" dt
            this.calculateDeltaTime(now);

            // Update
            if (!this._paused) {
                // before update
                this.emit(cc.Director.EVENT_BEFORE_UPDATE);

                // Call start for new added components
                this._compScheduler.startPhase();

                // Update for components
                this._compScheduler.updatePhase(this._deltaTime);
                // Engine update with scheduler 这里更新schedule
                this._scheduler.update(this._deltaTime);

                // Late update for components
                this._compScheduler.lateUpdatePhase(this._deltaTime);

                // User can use this event to do things after update
                this.emit(cc.Director.EVENT_AFTER_UPDATE);
                
                // Destroy entities that have been removed recently
                Obj._deferredDestroy();
            }

            // Render
            this.emit(cc.Director.EVENT_BEFORE_DRAW);
            renderer.render(this._scene, this._deltaTime);

            // After draw
            this.emit(cc.Director.EVENT_AFTER_DRAW);

            eventManager.frameUpdateListeners();
            this._totalFrames++;
        }

在看看组件里面使用schedule的具体流程:

schedule (callback, interval, repeat, delay) {
        cc.assertID(callback, 1619);

        interval = interval || 0;
        cc.assertID(interval >= 0, 1620);

        repeat = isNaN(repeat) ? cc.macro.REPEAT_FOREVER : repeat;
        delay = delay || 0;

        var scheduler = cc.director.getScheduler();

        // should not use enabledInHierarchy to judge whether paused,
        // because enabledInHierarchy is assigned after onEnable.
        // Actually, if not yet scheduled, resumeTarget/pauseTarget has no effect on component,
        // therefore there is no way to guarantee the paused state other than isTargetPaused.
        var paused = scheduler.isTargetPaused(this);
        // 在该组件上注册了一个计时器
        scheduler.schedule(callback, this, interval, repeat, delay, paused);
    },

接着看看CCSchedule.js中的schedule方法干了什么:

// target是Component对象
schedule: function (callback, target, interval, repeat, delay, paused) {
        
        'use strict';
        if (typeof callback !== 'function') {
            var tmp = callback;
            callback = target;
            target = tmp;
        }
        //selector, target, interval, repeat, delay, paused
        //selector, target, interval, paused
        if (arguments.length === 4 || arguments.length === 5) {
            paused = !!repeat;
            repeat = cc.macro.REPEAT_FOREVER;
            delay = 0;
        }

        cc.assertID(target, 1502);

        var targetId = target._id;
        if (!targetId) {
            if (target.__instanceId) {
                cc.warnID(1513);
                targetId = target._id = target.__instanceId;
            }
            else {
                cc.errorID(1510);
            }
        }
        // 通过targetId获得对应的定时器实例(HashTimerEntry),有点拗口的感觉,里面保存了timers target paused等属性
        var element = this._hashForTimers[targetId];
        if (!element) {
            // Is this the 1st element ? Then set the pause level to all the callback_fns of this target
            element = HashTimerEntry.get(null, target, 0, null, null, paused);
            // 注意看这里的arrayForTimes push 了一个CallbackTimer对象
            this._arrayForTimers.push(element);
            this._hashForTimers[targetId] = element;
        } else if (element.paused !== paused) {
            cc.warnID(1511);
        }

        var timer, i;
        if (element.timers == null) {
            element.timers = [];
        }
        else {
            for (i = 0; i < element.timers.length; ++i) {
                timer = element.timers[i];
                if (timer && callback === timer._callback) {
                    // 回调已存在,将更新interval属性
                    cc.logID(1507, timer.getInterval(), interval);
                    timer._interval = interval;
                    return;
                }
            }
        }

        // 获得一个定时器实例(CallbackTimer),这里也有一个对象池(_timers)
        timer = CallbackTimer.get();
        timer.initWithCallback(this, callback, target, interval, repeat, delay);
        // 自定义的计时器被push到timers里面了
        element.timers.push(timer);

        // 修改_currentTargetSalvaged防止当前的HashTimerEntry被删除 这个在update函数中会有相关解释
        if (this._currentTarget === element && this._currentTargetSalvaged) {
            this._currentTargetSalvaged = false;
        }
    },

回过头来看Schedule.update干了什么怎么一步一步驱动定时器运作的:

 update: function (dt) {
        this._updateHashLocked = true;
        if(this._timeScale !== 1)
            dt *= this._timeScale;

        var i, list, len, entry;
        // 先遍历优先级比较高的
        for(i=0,list=this._updatesNegList, len = list.length; i<len; i++){
            entry = list[i];
            if (!entry.paused && !entry.markedForDeletion)
                entry.target.update(dt);
        }

        for(i=0, list=this._updates0List, len=list.length; i<len; i++){
            entry = list[i];
            if (!entry.paused && !entry.markedForDeletion)
                entry.target.update(dt);
        }

        for(i=0, list=this._updatesPosList, len=list.length; i<len; i++){
            entry = list[i];
            if (!entry.paused && !entry.markedForDeletion)
                entry.target.update(dt);
        }

        // Iterate over all the custom selectors 遍历所有自定义的计时器
        
        var elt, arr = this._arrayForTimers;
        // arr HashTimerEntry : currentTimer,paused: boolean,target: cc.Component,timerIndex: number,timers: CallbackTimer[]
        for(i=0; i<arr.length; i++){
            elt = arr[i];
            this._currentTarget = elt;
            this._currentTargetSalvaged = false;

            if (!elt.paused){
                // The 'timers' array may change while inside this loop
                for (elt.timerIndex = 0; elt.timerIndex < elt.timers.length; ++(elt.timerIndex)){
                    elt.currentTimer = elt.timers[elt.timerIndex];
                    elt.currentTimerSalvaged = false;
                    // 更新计时器
                    elt.currentTimer.update(dt);
                    elt.currentTimer = null;
                }
            }

            // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
            if (this._currentTargetSalvaged && this._currentTarget.timers.length === 0) {
                this._removeHashElement(this._currentTarget);
                --i;
            }
        }

        // delete all updates that are marked for deletion
        // updates with priority < 0
        for(i=0,list=this._updatesNegList; i<list.length; ){
            entry = list[i];
            if(entry.markedForDeletion)
                this._removeUpdateFromHash(entry);
            else
                i++;
        }

        for(i=0, list=this._updates0List; i<list.length; ){
            entry = list[i];
            if (entry.markedForDeletion)
                this._removeUpdateFromHash(entry);
            else
                i++;
        }

        for(i=0, list=this._updatesPosList; i<list.length; ){
            entry = list[i];
            if (entry.markedForDeletion)
                this._removeUpdateFromHash(entry);
            else
                i++;
        }

        this._updateHashLocked = false;
        this._currentTarget = null;
    },

最后就是主角 CallbackTimer了:它是一个池子方便复用,提升性能

function CallbackTimer () {
    // 是否锁定
    this._lock = false;
    // 计时器对象
    this._scheduler = null;
    // 流逝的时间
    this._elapsed = -1;
    // 是否是一直运行
    this._runForever = false;
    // 是否使用延时
    this._useDelay = false;
    // 执行的次数
    this._timesExecuted = 0;
    // 重复次数
    this._repeat = 0;
    // 延时时间
    this._delay = 0;
    // 间隔时间
    this._interval = 0;
    // 目标组件
    this._target = null;
    // 绑定到计时器身上的回调函数
    this._callback = null;
}

var proto = CallbackTimer.prototype;

proto.initWithCallback = function (scheduler, callback, target, seconds, repeat, delay) {
    this._lock = false;
    this._scheduler = scheduler;
    this._target = target;
    this._callback = callback;

    this._elapsed = -1;
    this._interval = seconds;
    this._delay = delay;
    this._useDelay = (this._delay > 0);
    this._repeat = repeat;
    this._runForever = (this._repeat === cc.macro.REPEAT_FOREVER);
    return true;
};
/**
 * @return {Number} returns interval of timer
 */
proto.getInterval = function(){return this._interval;};
/**
 * @param {Number} interval set interval in seconds
 */
proto.setInterval = function(interval){this._interval = interval;};

/**
 * triggers the timer
 * @param {Number} dt delta time
 */
proto.update = function (dt) {
    if (this._elapsed === -1) {
        this._elapsed = 0;
        this._timesExecuted = 0;
    } else {
        /** 流逝的时间 */
        this._elapsed += dt;
        if (this._runForever && !this._useDelay) {//standard timer usage
            if (this._elapsed >= this._interval) {
                this.trigger();
                this._elapsed = 0;
            }
        } else {//advanced usage
            if (this._useDelay) {
                if (this._elapsed >= this._delay) {
                    // 触发组件定时器绑定的回调函数
                    this.trigger();

                    this._elapsed -= this._delay;
                    // 定时器执行的次数
                    this._timesExecuted += 1;
                    this._useDelay = false;
                }
            } else {
                if (this._elapsed >= this._interval) {
                    this.trigger();

                    this._elapsed = 0;
                    this._timesExecuted += 1;
                }
            }

            if (this._callback && !this._runForever && this._timesExecuted > this._repeat)
                this.cancel();
        }
    }
};

proto.getCallback = function(){
    return this._callback;
};

proto.trigger = function () {
    if (this._target && this._callback) {
        this._lock = true;
        // 触发回调函数传了个参数流逝的时间 往往是不准确的
        this._callback.call(this._target, this._elapsed);
        this._lock = false;
    }
};

proto.cancel = function () {
    //override
    this._scheduler.unschedule(this._callback, this._target);
};

var _timers = [];
CallbackTimer.get = function () {
    return _timers.pop() || new CallbackTimer();
};
CallbackTimer.put = function (timer) {
    if (_timers.length < MAX_POOL_SIZE && !timer._lock) {
        timer._scheduler = timer._target = timer._callback = null;
        _timers.push(timer);
    }
};

到此为止分析了schedule的出生到消亡的过程,

1:schedule是帧驱动的

2:schedule((t) => {},1.0) ,t时间是不准确的,自定义的计时器优先级比较低

本文标签: 源码CocosCreatorschedule