CocosCreator コンポーネントのスケジュール

目次

1. まず、component.ts のスケジュール関数を見てください。中心となるコードは、director.getScheduler() を取得し、schedule メソッドを呼び出し、それにコールバックなどのパラメーターを渡すことです。

2. 次に、scheduler.ts クラスのスケジュール メソッドを見て、いくつかの主要なコードのみを取り上げます。次のコードはセクションに分けて詳細に逆アセンブルされます。

2.1. コンポーネントの uuid または ID に基づいて HashTimer を取得します。

2.1.1 _hashForTimers とは何ですか? v8 では辞書モードです。主なメソッドは Object.create(null) で、プロトタイプ チェーンなしでオブジェクトを作成し、一部の検出メカニズムをバイパスします。オブジェクト内に要素が存在するかどうかを判断するために hasOwnProperty を使用する必要はありません。 _hashForTimers["anyString"] を直接使用します。値が存在しない場合、値は未定義です

2.1.2. ここでの HashTimerEntry クラスには次のものが含まれます。

2.2. 要素がない場合は、新しい要素を作成し、配列 _arrayForTimers にプッシュします。配列に保存する目的は、トラバーサル呼び出しを容易にすることであり、_hashForTimers は、uuid を介して直接オブジェクトを取得しやすくすることです。

2.3. タイマー配列を作成し、コールバック メソッドに格納する準備をする コールバック メソッドがすでに存在する場合は、呼び出し間隔のみが変更されます。

2.4. キャッシュ プールから CallbackTimer オブジェクトをポップします。そうでない場合は、新しいオブジェクトを作成し、データを入力してタイマーにプッシュします。

3. 参加プロセス全体を読んだところで、コールバックをどのようにトリガーしますか?

4. スケジューラの更新機能を再度確認します。

5. CallbackTimer クラスにジャンプします。

6.トリガーおよびキャンセル機能:

7. スケジューラのスケジュール解除機能を参照してください。


1. まず、component.ts のスケジュール関数を見てください。中心となるコードは、director.getScheduler() を取得し、schedule メソッドを呼び出し、それにコールバックなどのパラメーターを渡すことです。

    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 クラスのスケジュール メソッドを見て、いくつかの主要なコードのみを取り上げます。次のコードはセクションに分けて詳細に逆アセンブルされます。

    public schedule (callback: (dt?: number) => void, target: ISchedulable, interval: number, repeat?: number, delay?: number, paused?: boolean) {
        ...
        const targetId = target.uuid || target.id;
        ...
        let element = <HashTimerEntry> 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 = <HashTimerEntry> this._hashForTimers[targetId];
2.1.1 _hashForTimers とは何ですか? v8 では辞書モードです。主なメソッドは Object.create(null) で、プロトタイプ チェーンなしでオブジェクトを作成し、一部の検出メカニズムをバイパスします。オブジェクト内に要素が存在するかどうかを判断するために hasOwnProperty を使用する必要はありません。 _hashForTimers["anyString"] を直接使用します。値が存在しない場合、値は未定義です
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. 要素がない場合は、新しい要素を作成し、配列 _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. タイマー配列を作成し、コールバック メソッドに格納する準備をする コールバック メソッドがすでに存在する場合は、呼び出し間隔のみが変更されます。
        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. キャッシュ プールから CallbackTimer オブジェクトをポップします。そうでない場合は、新しいオブジェクトを作成し、データを入力してタイマーにプッシュします。

主なデータは次のとおりです。

  • _scheduler: スケジューラ自体。主に unschedule を呼び出してキャンセルするために使用されます。
  • _target: 関数呼び出し本体、主にコールバック呼び出し時に呼び出しを渡すために使用されます。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. 参加プロセス全体を読んだところで、コールバックをどのようにトリガーしますか?

Director クラスに戻ると、init クラスで this._scheduler を this._systems にプッシュし、ティック (メイン ループ) で 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. スケジューラの更新機能を再度確認します。

余談ですが、アップデート関数には優先度を区別したscheduleUpdate関数のプッシュ呼び出し処理も含まれますが、ここでは詳細は述べません。

this._arrayForTimers を走査し、各要素のタイマーを走査して、timers[i] の更新関数 (CallbackTimer クラスの更新関数) を呼び出します。

    public update (dt) {
       ...
        // Iterate over all the custom selectors
        let elt;
        const arr = this._arrayForTimers;
        for (i = 0; i < arr.length; i++) {
            elt = <HashTimerEntry> 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 クラスにジャンプします。

まず dt に this._elapsed を追加し、インターバルのトリガー時間、遅延時間、サイクル数を満たしているかどうかを判断し、満たしている場合は this.trigger() 関数を呼び出し、トリガー後に継続条件が満たされていない場合は 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.トリガーおよびキャンセル機能:

トリガー関数はコールバック関数を呼び出すことです。呼び出しメソッドは this._callback.call(this._target, this._elapsed) で関数のコンテキストを明示的に設定し、呼び出しプロセス中に _lock 値にロックを追加します。そのため、関数が呼び出されたときに、プロセス中にクリーンアップされません。

cancel関数は、スケジューラの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. スケジューラのスケジュール解除機能を参照してください。

uuid または id を通じて要素を取得し、そのタイマー オブジェクトを走査し、対応するコールバックの場所を見つけて、このオブジェクトをキャッシュ プールに入れ、必要に応じて再度使用されるのを待ちます。

    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;
                }
            }
        }
    }

 役に立ったと思ったら、「いいね!」を押して保存してください〜あなたのサポートが私の最大の励みです〜

おすすめ

転載: blog.csdn.net/m0_37609239/article/details/132501223