diff --git a/cocos/core/event/callbacks-invoker.ts b/cocos/core/event/callbacks-invoker.ts index a5f137f734c..ff30edc2c7a 100644 --- a/cocos/core/event/callbacks-invoker.ts +++ b/cocos/core/event/callbacks-invoker.ts @@ -25,32 +25,42 @@ import { TEST } from 'internal:constants'; import { Pool } from '../memop'; -import { array, createMap } from '../utils/js'; +import { createMap } from '../utils/js'; import { isCCObject, isValid } from '../data/object'; import { legacyCC } from '../global-exports'; -const fastRemoveAt = array.fastRemoveAt; - -function empty (): void { } +function empty(): void {} class CallbackInfo { - public callback: AnyFunction = empty; - public target: unknown = undefined; - public once = false; + callback: AnyFunction = empty; + target: unknown = null; + once = false; + + valid = false;// 是否有效 + next: CallbackInfo | null = null;// 链表下一个节点 - public set (callback: AnyFunction, target?: unknown, once?: boolean): void { - this.callback = callback || empty; + set (callback: AnyFunction, target?: unknown, once?: boolean) { + this.callback = callback; this.target = target; this.once = !!once; + this.valid = true; + return this; } - public reset (): void { - this.target = undefined; + reset () { this.callback = empty; + this.target = null; this.once = false; + this.valid = false; + } + + clear () { + this.reset(); + this.next = null; + return this; } - public check (): boolean { + check (): boolean { // Validation if (isCCObject(this.target) && !isValid(this.target, true)) { return false; @@ -61,123 +71,167 @@ class CallbackInfo { } const callbackInfoPool = new Pool(() => new CallbackInfo(), 32); + /** * @zh 事件监听器列表的简单封装。 * @en A simple list of event callbacks */ -export class CallbackList { - public callbackInfos: Array = []; - public isInvoking = false; - public containCanceled = false; +class CallbackList { + isInvoking = false; + containCanceled = false; - /** - * @zh 从列表中移除与指定目标相同回调函数的事件。 - * @en Remove the event listeners with the given callback from the list - * - * @param cb - The callback to be removed - */ - public removeByCallback (cb: AnyFunction): void { - for (let i = 0; i < this.callbackInfos.length; ++i) { - const info = this.callbackInfos[i]; - if (info && info.callback === cb) { - info.reset(); - callbackInfoPool.free(info); - fastRemoveAt(this.callbackInfos, i); - --i; + head: CallbackInfo | null = null;// 链表头节点 + + find (callback: AnyFunction, target?: unknown) { + let info = this.head; + while (info) { + if (info.callback === callback && info.target === target) { + return info; } + info = info.next; } + return null; } - /** - * @zh 从列表中移除与指定目标相同调用者的事件。 - * @en Remove the event listeners with the given target from the list - * @param target - */ - public removeByTarget (target: unknown): void { - for (let i = 0; i < this.callbackInfos.length; ++i) { - const info = this.callbackInfos[i]; - if (info && info.target === target) { - info.reset(); - callbackInfoPool.free(info); - fastRemoveAt(this.callbackInfos, i); - --i; + + add (callback: AnyFunction, target?: unknown, once?: boolean) { + let tail: CallbackInfo | null = null; + let info = this.head; + while (info) { + if (info.callback === callback && info.target === target) { + return callback; + } + tail = info; + info = info.next; + } + info = callbackInfoPool.alloc().set(callback, target, once); + if (tail) { + tail.next = info; + } else { + this.head = info; + } + return callback; + } + + remove (callback: AnyFunction, target?: unknown) { + let tail: CallbackInfo | null = null; + let info = this.head; + while (info) { + if (info.callback === callback && info.target === target) { + this.erase(info, tail); + break; } + tail = info; + info = info.next; } } - /** - * @zh 移除指定编号事件。 - * @en Remove the event listener at the given index - * @param index - */ - public cancel (index: number): void { - const info = this.callbackInfos[index]; - if (info) { - info.reset(); - if (this.isInvoking) { - this.callbackInfos[index] = null; + removeByTarget (target: unknown) { + let tail: CallbackInfo | null = null; + let info = this.head; + while (info) { + if (info.target === target) { + info = this.erase(info, tail); } else { - fastRemoveAt(this.callbackInfos, index); + tail = info; + info = info.next; } - callbackInfoPool.free(info); } + } + + + cancel(info: CallbackInfo) { + info.reset(); this.containCanceled = true; } - /** - * @zh 注销所有事件。 - * @en Cancel all event listeners - */ - public cancelAll (): void { - for (let i = 0; i < this.callbackInfos.length; i++) { - const info = this.callbackInfos[i]; - if (info) { - info.reset(); - callbackInfoPool.free(info); - this.callbackInfos[i] = null; + cancelByCallback (callback: AnyFunction, target?: unknown) { + let info = this.head; + while (info) { + if (info.callback === callback && info.target === target) { + this.cancel(info); + break; } + info = info.next; } - this.containCanceled = true; } - /** - * @zh 立即删除所有取消的回调。(在移除过程中会更加紧凑的排列数组) - * @en Delete all canceled callbacks and compact array - */ - public purgeCanceled (): void { - for (let i = this.callbackInfos.length - 1; i >= 0; --i) { - const info = this.callbackInfos[i]; - if (!info) { - fastRemoveAt(this.callbackInfos, i); + cancelByTarget (target: unknown) { + let info = this.head; + while (info) { + if (info.target === target) { + this.cancel(info); + } + info = info.next; + } + } + + cancelAll () { + let info = this.head; + while (info) { + this.cancel(info); + info = info.next; + } + } + + purgeCanceled (): void { + let tail: CallbackInfo | null = null; + let info = this.head; + while (info) { + if (!info.valid) { + info = this.erase(info, tail); + } else { + tail = info; + info = info.next; } } this.containCanceled = false; } - /** - * @zh 清除并重置所有数据。 - * @en Clear all data - */ - public clear (): void { - this.cancelAll(); - this.callbackInfos.length = 0; + private erase (info: CallbackInfo, tail: CallbackInfo | null) { + const next = info.next; + if (next) { + if (tail) { + tail.next = next; + } else { + this.head = next; + } + } else { + if (tail) { + tail.next = null; + } else { + this.head = null;// current info is head + } + } + callbackInfoPool.free(info.clear()); + return next; + } + + clear (): this { + let temp: CallbackInfo | null = null; + let info = this.head; + while (info) { + temp = info; + info = info.next; + callbackInfoPool.free(temp.clear()); + } + this.head = null; this.isInvoking = false; this.containCanceled = false; + return this; } } -const MAX_SIZE = 16; -const callbackListPool = new Pool(() => new CallbackList(), MAX_SIZE); +const callbackListPool = new Pool(() => new CallbackList(), 16); export interface ICallbackTable { [x: string]: CallbackList | undefined; } type EventType = string | number; + /** * @zh CallbacksInvoker 用来根据事件名(Key)管理事件监听器列表并调用回调方法。 - * @en CallbacksInvoker is used to manager and invoke event listeners with different event keys, - * each key is mapped to a CallbackList. - * @engineInternal + * @en CallbacksInvoker is used to manager and invoke event listeners with different event keys, each key is mapped to a CallbackList. */ export class CallbacksInvoker { /** @@ -194,56 +248,53 @@ export class CallbacksInvoker { * @param callback - Callback function when event triggered * @param target - Callback callee * @param once - Whether invoke the callback only once (and remove it) + * + * @returns callback */ - public on (key: EventTypeClass, callback: AnyFunction, target?: unknown, once?: boolean): AnyFunction { - if (!this.hasEventListener(key, callback, target)) { - let list = this._callbackTable[key]; - if (!list) { - list = this._callbackTable[key] = callbackListPool.alloc(); - } - const info = callbackInfoPool.alloc(); - info.set(callback, target, once); - list.callbackInfos.push(info); + on (key: EventTypeClass, callback: AnyFunction, target?: unknown, once?: boolean): typeof callback { + let list = this._callbackTable[key]; + if (!list) { + list = callbackListPool.alloc(); + this._callbackTable[key] = list; } - return callback; + return list!.add(callback, target, once); } /** - * @zh 检查指定事件是否已注册回调。 - * @en Checks whether there is correspond event listener registered on the given event + * @zh 向一个事件名注册一个新的事件监听器,包含回调函数和调用者,该监听器只执行一次。 + * @en Register an event listener to a given event key with callback and target. + * * @param key - Event type * @param callback - Callback function when event triggered * @param target - Callback callee + * + * @returns callback */ - public hasEventListener (key: EventTypeClass, callback?: AnyFunction, target?: unknown): boolean { - const list = this._callbackTable && this._callbackTable[key]; - if (!list) { - return false; - } + once (key: EventTypeClass, callback: AnyFunction, target?: unknown) { + return this.on(key, callback, target, true); + } - // check any valid callback - const infos = list.callbackInfos; - if (!callback) { - // Make sure no cancelled callbacks - if (list.isInvoking) { - for (let i = 0; i < infos.length; ++i) { - if (infos[i]) { - return true; - } + /** + * @zh 删除以指定事件,回调函数,目标注册的回调。 + * @en Remove event listeners registered with the given event key, callback and target + * @param key - Event type + * @param callback - The callback function of the event listener, if absent all event listeners for the given type will be removed + * @param target - The callback callee of the event listener + */ + off (key: EventTypeClass, callback: AnyFunction, target?: unknown): void { + const list = this._callbackTable[key]; + if (list) { + if (!list.isInvoking) { + list.remove(callback, target); + if (!list.head) { + callbackListPool.free(list.clear()); + delete this._callbackTable[key]; } - return false; } else { - return infos.length > 0; - } - } - - for (let i = 0; i < infos.length; ++i) { - const info = infos[i]; - if (info && info.check() && info.callback === callback && info.target === target) { - return true; + list.cancelByCallback(callback, target); } } - return false; + this._offCallback?.(); } /** @@ -254,118 +305,131 @@ export class CallbacksInvoker { public removeAll (keyOrTarget: unknown): void { const type = typeof keyOrTarget; if (type === 'string' || type === 'number') { - // remove by key - const list = this._callbackTable && this._callbackTable[keyOrTarget as string|number]; + const list = this._callbackTable[type]; if (list) { - if (list.isInvoking) { - list.cancelAll(); + if (!list.isInvoking) { + callbackListPool.free(list.clear()); + delete this._callbackTable[type]; } else { - list.clear(); - callbackListPool.free(list); - delete this._callbackTable[keyOrTarget as string|number]; + list.cancelAll(); } } } else if (keyOrTarget) { - // remove by target - for (const key in this._callbackTable) { - const list = this._callbackTable[key]!; - if (list.isInvoking) { - const infos = list.callbackInfos; - for (let i = 0; i < infos.length; ++i) { - const info = infos[i]; - if (info && info.target === keyOrTarget) { - list.cancel(i); - } + for (const k in this._callbackTable) { + const list = this._callbackTable[k]!; + if (!list.isInvoking) { + list.removeByTarget(keyOrTarget); + if (!list.head) { + callbackListPool.free(list.clear()); + delete this._callbackTable[k]; } } else { - list.removeByTarget(keyOrTarget); + list.cancelByTarget(keyOrTarget); } } } } + hasTarget(target: unknown) { + let list: CallbackList, info: CallbackInfo | null; + for (const k in this._callbackTable) { + list = this._callbackTable[k]!; + info = list.head; + while (info) { + if (info.valid && info.target === target) { + return true; + } + info = info.next; + } + } + return false; + } + /** - * @zh 删除以指定事件,回调函数,目标注册的回调。 - * @en Remove event listeners registered with the given event key, callback and target + * @zh 检查指定事件是否已注册回调。 + * @en Checks whether there is correspond event listener registered on the given event + * * @param key - Event type - * @param callback - The callback function of the event listener, if absent all event listeners for the given type will be removed - * @param target - The callback callee of the event listener + * @param callback - Callback function when event triggered + * @param target - Callback callee */ - public off (key: EventTypeClass, callback?: AnyFunction, target?: unknown): void { - const list = this._callbackTable && this._callbackTable[key]; - if (list) { - const infos = list.callbackInfos; - if (callback) { - for (let i = 0; i < infos.length; ++i) { - const info = infos[i]; - if (info && info.callback === callback && info.target === target) { - list.cancel(i); - break; + hasEventListener (key: EventTypeClass, callback?: AnyFunction, target?: unknown): boolean { + const list = this._callbackTable[key]; + if (!list) { + return false; + } + // check any valid callback + if (!callback) { + if (list.isInvoking) { + let info = list.head; + while (info) { + if (info.valid) { + return true;// make sure no cancelled callbacks } + info = info.next; } + return false; } else { - this.removeAll(key); + return !!list.head; } } - this._offCallback?.(); + return !!list.find(callback, target)?.check(); } /** * @zh 派发一个指定事件,并传递需要的参数 * @en Trigger an event directly with the event name and necessary arguments. - * @param key - event type + * @param key - Event type * @param arg0 - The first argument to be passed to the callback * @param arg1 - The second argument to be passed to the callback * @param arg2 - The third argument to be passed to the callback * @param arg3 - The fourth argument to be passed to the callback * @param arg4 - The fifth argument to be passed to the callback */ - public emit (key: EventTypeClass, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void { - const list: CallbackList = this._callbackTable && this._callbackTable[key]!; - if (list) { - const rootInvoker = !list.isInvoking; - list.isInvoking = true; - - const infos = list.callbackInfos; - for (let i = 0, len = infos.length; i < len; ++i) { - const info = infos[i]; - if (info) { - const callback = info.callback; - const target = info.target; - // Pre off once callbacks to avoid influence on logic in callback - if (info.once) { - this.off(key, callback, target); - } - // Lazy check validity of callback target, - // if target is CCObject and is no longer valid, then remove the callback info directly - if (!info.check()) { - this.off(key, callback, target); - } else if (target) { - callback.call(target, arg0, arg1, arg2, arg3, arg4); - } else { - callback(arg0, arg1, arg2, arg3, arg4); - } - } + emit (key: EventTypeClass, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void { + const list = this._callbackTable[key]; + if (!list) { + return; + } + const rootInvoker = !list.isInvoking; + list.isInvoking = true; + let callback: AnyFunction, target: unknown; + let info = list.head; + while (info) { + if (!info.check()) { + list.cancel(info); + info = info.next; + continue; } - - if (rootInvoker) { - list.isInvoking = false; - if (list.containCanceled) { - list.purgeCanceled(); + callback = info.callback; + target = info.target; + if (info.once) { + list.cancel(info); + } + info = info.next; + if (target) { + callback.call(target, arg0, arg1, arg2, arg3, arg4); + } else { + callback(arg0, arg1, arg2, arg3, arg4); + } + } + if (rootInvoker) { + list.isInvoking = false; + if (list.containCanceled) { + list.purgeCanceled(); + if (!list.head) { + callbackListPool.free(list.clear()); + delete this._callbackTable[key]; } } } } - /** - * 移除所有回调。 - */ - public clear (): void { + clear (): void { for (const key in this._callbackTable) { const list = this._callbackTable[key]; if (list) { - list.clear(); - callbackListPool.free(list); + callbackListPool.free(list.clear()); delete this._callbackTable[key]; } } @@ -381,4 +445,4 @@ export class CallbacksInvoker { if (TEST) { legacyCC._Test.CallbacksInvoker = CallbacksInvoker; -} +} \ No newline at end of file