CocosCreator组件上的schedule

目录

1.首先看component.ts中schedule 函数,核心代码就是获取director.getScheduler(),并调用schedule方法,把callback等参数传递进去。

2.再看到scheduler.ts类中的schedule方法,只取一些主要代码,下面会分段详细拆解下面的代码:

2.1.根据组件的uuid或id,获取一个HashTimer。

2.1.1此中的_hashForTimers是什么?就是一个v8下的字典模式,主要方法就是Object.create(null),创建一个无原型链的对象,绕过一些检测机制,不需要用hasOwnProperty来判断对象内是否存在某元素,直接用_hashForTimers["anyString"],若不存在该值,则值为undefine

2.1.2.这里的HashTimerEntry类里包含了:

2.2.若无element,就新建一个,并push到一个数组中_arrayForTimers。另存数组里是为方便遍历调用,_hashForTimers是为了方便通过uuid直接取到对象。

2.3.创建timers数组,准备存入callback方法,若callback方法已经存在,只改变调用间隔。

2.4.从缓存池里pop出一个CallbackTimer对象,若无就新建一个,填充数据后push到timers中。

3.加入的全过程看完了,那么怎么触发callback呢?

4.再看到scheduler中的update函数:

5.跳转到 CallbackTimer 类:

6.trigger 和 cancel 函数:

7.看到scheduler 的 unschedule 函数:


1.首先看component.ts中schedule 函数,核心代码就是获取director.getScheduler(),并调用schedule方法,把callback等参数传递进去。

    public schedule (callback, interval = 0, repeat: number = legacyCC.macro.REPEAT_FOREVER, delay = 0) {
        assertID(callback, 1619);

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

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

        const scheduler = legacyCC.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.
        const paused = scheduler.isTargetPaused(this);

        scheduler.schedule(callback, this, interval, repeat, delay, paused);
    }

2.再看到scheduler.ts类中的schedule方法,只取一些主要代码,下面会分段详细拆解下面的代码:

    public schedule (callback: (dt?: number) => void, target: ISchedulable, interval: number, repeat?: number, delay?: number, paused?: boolean) {
        ...
        const targetId = target.uuid || target.id;
        ...
        let element =  this._hashForTimers[targetId];
        if (!element) {
            element = HashTimerEntry.get(null, target, 0, null, null, paused);
            this._arrayForTimers.push(element);
            this._hashForTimers[targetId] = element;
        } else if (element.paused !== paused) {
            warnID(1511);
        }

        let timer;
        let 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) {
                    logID(1507, timer.getInterval(), interval);
                    timer._interval = interval;
                    return;
                }
            }
        }

        timer = CallbackTimer.get();
        timer.initWithCallback(this, callback, target, interval, repeat, delay);
        element.timers.push(timer);

        if (this._currentTarget === element && this._currentTargetSalvaged) {
            this._currentTargetSalvaged = false;
        }
    }

2.1.根据组件的uuid或id,获取一个HashTimer。

const targetId = target.uuid || target.id;
let element =  this._hashForTimers[targetId];
2.1.1此中的_hashForTimers是什么?就是一个v8下的字典模式,主要方法就是Object.create(null),创建一个无原型链的对象,绕过一些检测机制,不需要用hasOwnProperty来判断对象内是否存在某元素,直接用_hashForTimers["anyString"],若不存在该值,则值为undefine
export function createMap (forceDictMode?: boolean): any {
    const map = Object.create(null);
    if (forceDictMode) {
        const INVALID_IDENTIFIER_1 = '.';
        const INVALID_IDENTIFIER_2 = '/';
        // assign dummy values on the object
        map[INVALID_IDENTIFIER_1] = 1;
        map[INVALID_IDENTIFIER_2] = 1;
        delete map[INVALID_IDENTIFIER_1];
        delete map[INVALID_IDENTIFIER_2];
    }
    return map;
}
this._hashForTimers = createMap(true);
2.1.2.这里的HashTimerEntry类里包含了:
    constructor (timers: any, target: ISchedulable, timerIndex: number, currentTimer: any, currentTimerSalvaged: any, paused: any) {
        this.timers = timers;
        this.target = target;
        this.timerIndex = timerIndex;
        this.currentTimer = currentTimer;
        this.currentTimerSalvaged = currentTimerSalvaged;
        this.paused = paused;
    }

2.2.若无element,就新建一个,并push到一个数组中_arrayForTimers。另存数组里是为方便遍历调用,_hashForTimers是为了方便通过uuid直接取到对象。

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);
            this._arrayForTimers.push(element);
            this._hashForTimers[targetId] = element;
        }

2.3.创建timers数组,准备存入callback方法,若callback方法已经存在,只改变调用间隔。

        if (element.timers == null) {
            element.timers = [];
        } else {
            for (i = 0; i < element.timers.length; ++i) {
                timer = element.timers[i];
                if (timer && callback === timer._callback) {
                    logID(1507, timer.getInterval(), interval);
                    timer._interval = interval;
                    return;
                }
            }
        }

2.4.从缓存池里pop出一个CallbackTimer对象,若无就新建一个,填充数据后push到timers中。

主要数据有:

  • _scheduler:scheduler自身,主要用来调用unschedule来取消。
  • _target :函数调用主体,主要用来在调用callback时传入调用.call传入调用主体。
  • _callback :调用函数。
  • _interval :触发间隔时间。
  • _elapsed:已经过去的时间。
  • _delay :延迟触发时间。
  • _useDelay :使用延迟触发标记。
  • _repeat :执行函数调用次数。
  • _runForever:一直循环调用标记。

这里还有一个put函数,把该回收的对象放入到缓存池中,看到这里的代码可以为我们提供一些编写代码的思路,对于缓存池需要有一个最大池数量和一个锁标记。

class CallbackTimer {
    public static get = () => CallbackTimer._timers.pop() || new CallbackTimer()
    public static put = (timer: CallbackTimer | any) => {
        if (CallbackTimer._timers.length < MAX_POOL_SIZE && !timer._lock) {
            timer._scheduler = timer._target = timer._callback = null;
            CallbackTimer._timers.push(timer);
        }
    }    
    public initWithCallback (scheduler: any, callback: any, target: ISchedulable, seconds: number, repeat: number, delay: number) {
        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 === legacyCC.macro.REPEAT_FOREVER);
        return true;
    }        
}

timer = CallbackTimer.get();
timer.initWithCallback(this, callback, target, interval, repeat, delay);
element.timers.push(timer);

3.加入的全过程看完了,那么怎么触发callback呢?

回到director类中,能看到在init类中,把 this._scheduler push到 this._systems 中,然后在tick(主循环)中遍历 this._systems 调用 scheduler.update 函数。

export class Director extends EventTarget {
    public init () {
        ...
        this.registerSystem(Scheduler.ID, this._scheduler, 200);
        ...
    }
    /**
     * @en Register a system.
     * @zh 注册一个系统。
     */
    public registerSystem (name: string, sys: System, priority: number) {
        sys.id = name;
        sys.priority = priority;
        this._systems.push(sys);
        this._systems.sort(System.sortByPriority);
    }
    public tick (dt: number) {
        ...
        for (let i = 0; i < this._systems.length; ++i) {
            this._systems[i].update(dt);
        }
        ...
    }
}

4.再看到scheduler中的update函数:

这里先说一个题外话,在update函数中,还包含了有优先级区分的 scheduleUpdate 函数push进来的调用处理,这里不细说。

遍历 this._arrayForTimers,再遍历 每一个元素中的 timers ,调用 timers[i] 的 updeate 函数,也就是 CallbackTimer 类中的 updeate 函数。

    public update (dt) {
       ...
        // Iterate over all the custom selectors
        let elt;
        const arr = this._arrayForTimers;
        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;
            }
        }
        ...
    }

5.跳转到 CallbackTimer 类:

首先 this._elapsed 加上 dt,再判断是否满足间隔触发时间,延迟时间,循环次数,若满足就调用 this.trigger() 函数,在触发后若不满足继续条件,就调用 this.cancel() 销毁自身。

    public update (dt: number) {
        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;
                }

                // @ts-expect-error Notes written for over eslint
                if (this._callback && !this._runForever && this._timesExecuted > this._repeat) {
                    this.cancel();
                }
            }
        }
    }

6.trigger 和 cancel 函数:

trigger函数就是调用callback函数,调用的方法是 this._callback.call(this._target, this._elapsed) 显式地设置函数的上下文;并且在调用过程中给 _lock 值加了锁,这样在函数调用过程中就不会被 清理掉。

cancel函数就是调用 scheduler 的 unschedule 函数。

    public trigger () {
        if (this._target && this._callback) {
            this._lock = true;
            this._callback.call(this._target, this._elapsed);
            this._lock = false;
        }
    }

    public cancel () {
        // override
        this._scheduler.unschedule(this._callback, this._target);
    }

7.看到scheduler 的 unschedule 函数:

通过uuid或id获取元素,遍历其 timers 对象,找到 callback 对应位置,把此对象 put 到缓存池中,等待后续有需要时重新拿出来用。

    public unschedule (callback, target: ISchedulable) {
        if (!target || !callback) {
            return;
        }
        const targetId = target.uuid || target.id;
        if (!targetId) {
            errorID(1510);
            return;
        }

        const element = this._hashForTimers[targetId];
        if (element) {
            const timers = element.timers;
            for (let i = 0, li = timers.length; i < li; i++) {
                const timer = timers[i];
                if (callback === timer._callback) {
                    if ((timer === element.currentTimer) && (!element.currentTimerSalvaged)) {
                        element.currentTimerSalvaged = true;
                    }
                    timers.splice(i, 1);
                    CallbackTimer.put(timer);
                    if (element.timerIndex >= i) {
                        element.timerIndex--;
                    }

                    if (timers.length === 0) {
                        if (this._currentTarget === element) {
                            this._currentTargetSalvaged = true;
                        } else {
                            this._removeHashElement(element);
                        }
                    }
                    return;
                }
            }
        }
    }

 如果觉得有帮助请给我点赞并收藏哦~您的支持是我最大的鼓励~

你可能感兴趣的:(cocoscreator,javascript,游戏引擎,CocosCreator)