目次
2. 次に、scheduler.ts クラスのスケジュール メソッドを見て、いくつかの主要なコードのみを取り上げます。次のコードはセクションに分けて詳細に逆アセンブルされます。
2.1. コンポーネントの uuid または ID に基づいて HashTimer を取得します。
2.1.2. ここでの HashTimerEntry クラスには次のものが含まれます。
2.3. タイマー配列を作成し、コールバック メソッドに格納する準備をする コールバック メソッドがすでに存在する場合は、呼び出し間隔のみが変更されます。
2.4. キャッシュ プールから CallbackTimer オブジェクトをポップします。そうでない場合は、新しいオブジェクトを作成し、データを入力してタイマーにプッシュします。
3. 参加プロセス全体を読んだところで、コールバックをどのようにトリガーしますか?
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;
}
}
}
}
役に立ったと思ったら、「いいね!」を押して保存してください〜あなたのサポートが私の最大の励みです〜