From 0738856794faba09b05eb0474b3e30315150953a Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 17 Dec 2024 15:03:34 +0800 Subject: [PATCH] [v3.8.6] Fix pointerEventProcessorList may not be re-sorted. (#18046) * [v3.8.6] Fix pointerEventProcessorList may not be re-sorted. This bug was reported at https://forum.cocos.org/t/topic/163982/10 * legacyCC -> cclegacy, fix Node.isNode compilation error in node-event-processor.ts * Fix cclegacy in node.jsb.ts and fix _onPostActivated. * Revert * Update root.jsb.ts --- cocos/2d/event/pointer-event-dispatcher.ts | 8 ++-- cocos/root.jsb.ts | 26 +++++----- cocos/scene-graph/node-event-processor.ts | 24 +++++----- cocos/scene-graph/node.jsb.ts | 31 +++++++----- cocos/scene-graph/node.ts | 55 +++++++++++++++------- 5 files changed, 87 insertions(+), 57 deletions(-) diff --git a/cocos/2d/event/pointer-event-dispatcher.ts b/cocos/2d/event/pointer-event-dispatcher.ts index ec4e08f3354..b113538cf38 100644 --- a/cocos/2d/event/pointer-event-dispatcher.ts +++ b/cocos/2d/event/pointer-event-dispatcher.ts @@ -56,10 +56,10 @@ class PointerEventDispatcher implements IEventDispatcher { constructor () { input._registerEventDispatcher(this); - - NodeEventProcessor.callbacksInvoker.on(DispatcherEventType.ADD_POINTER_EVENT_PROCESSOR, this.addPointerEventProcessor, this); - NodeEventProcessor.callbacksInvoker.on(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this.removePointerEventProcessor, this); - NodeEventProcessor.callbacksInvoker.on(DispatcherEventType.MARK_LIST_DIRTY, this._markListDirty, this); + const callbacksInvoker = NodeEventProcessor.callbacksInvoker; + callbacksInvoker.on(DispatcherEventType.ADD_POINTER_EVENT_PROCESSOR, this.addPointerEventProcessor, this); + callbacksInvoker.on(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this.removePointerEventProcessor, this); + callbacksInvoker.on(DispatcherEventType.MARK_LIST_DIRTY, this._markListDirty, this); } onThrowException (): void { diff --git a/cocos/root.jsb.ts b/cocos/root.jsb.ts index 95f388bb2cd..182af483fac 100644 --- a/cocos/root.jsb.ts +++ b/cocos/root.jsb.ts @@ -22,10 +22,10 @@ THE SOFTWARE. */ -import { legacyCC } from './core/global-exports'; +import { cclegacy } from './core/global-exports'; import { DataPoolManager } from './3d/skeletal-animation/data-pool-manager'; import { Device, deviceManager } from './gfx'; -import { settings, Settings, warnID, Pool, macro, log, cclegacy } from './core'; +import { settings, Settings, warnID, Pool, macro, log } from './core'; import { PipelineEventProcessor } from './rendering/pipeline-event'; import type { Root as JsbRoot } from './root'; @@ -55,8 +55,8 @@ export interface IRootInfo { const rootProto: any = Root.prototype; rootProto._createBatcher2D = function () { - if (!this._batcher && legacyCC.internal.Batcher2D) { - this._batcher = new legacyCC.internal.Batcher2D(this); + if (!this._batcher && cclegacy.internal.Batcher2D) { + this._batcher = new cclegacy.internal.Batcher2D(this); if (!this._batcher!.initialize()) { this._batcher = null; this.destroy(); @@ -92,7 +92,7 @@ Object.defineProperty(rootProto, 'pipelineEvent', { rootProto._ctor = function (device: Device) { this._device = device; - this._dataPoolMgr = legacyCC.internal.DataPoolManager && new legacyCC.internal.DataPoolManager(device) as DataPoolManager; + this._dataPoolMgr = cclegacy.internal.DataPoolManager && new cclegacy.internal.DataPoolManager(device) as DataPoolManager; this._modelPools = new Map(); this._lightPools = new Map(); this._batcher = null; @@ -196,19 +196,19 @@ rootProto.recycleLight = function (l) { }; rootProto._onDirectorBeforeCommit = function () { - legacyCC.director.emit(legacyCC.Director.EVENT_BEFORE_COMMIT); + cclegacy.director.emit(cclegacy.Director.EVENT_BEFORE_COMMIT); }; rootProto._onDirectorBeforeRender = function () { - legacyCC.director.emit(legacyCC.Director.EVENT_BEFORE_RENDER); + cclegacy.director.emit(cclegacy.Director.EVENT_BEFORE_RENDER); }; rootProto._onDirectorAfterRender = function () { - legacyCC.director.emit(legacyCC.Director.EVENT_AFTER_RENDER); + cclegacy.director.emit(cclegacy.Director.EVENT_AFTER_RENDER); }; rootProto._onDirectorPipelineChanged = function () { - const scene = legacyCC.director.getScene(); + const scene = cclegacy.director.getScene(); if (scene) { scene._activate(); } @@ -217,25 +217,25 @@ rootProto._onDirectorPipelineChanged = function () { const oldOnGlobalPipelineStateChanged = rootProto.onGlobalPipelineStateChanged; rootProto.onGlobalPipelineStateChanged = function() { oldOnGlobalPipelineStateChanged.call(this); - const builder = legacyCC.rendering.getCustomPipeline(macro.CUSTOM_PIPELINE_NAME); + const builder = cclegacy.rendering.getCustomPipeline(macro.CUSTOM_PIPELINE_NAME); if (builder) { if (typeof builder.onGlobalPipelineStateChanged === 'function') { builder.onGlobalPipelineStateChanged(); } - legacyCC.rendering.forceResizeAllWindows(); + cclegacy.rendering.forceResizeAllWindows(); } } const oldFrameMove = rootProto.frameMove; rootProto.frameMove = function (deltaTime: number) { - oldFrameMove.call(this, deltaTime, legacyCC.director.getTotalFrames()); + oldFrameMove.call(this, deltaTime, cclegacy.director.getTotalFrames()); }; const oldSetPipeline = rootProto.setRenderPipeline; rootProto.setRenderPipeline = function (customPipeline: boolean) { let ppl; if (customPipeline) { - legacyCC.rendering.createCustomPipeline(); + cclegacy.rendering.createCustomPipeline(); ppl = oldSetPipeline.call(this, null); log(`Using custom pipeline: ${macro.CUSTOM_PIPELINE_NAME}`); } else { diff --git a/cocos/scene-graph/node-event-processor.ts b/cocos/scene-graph/node-event-processor.ts index 5dae5eb9ec3..e4b7bb464d6 100644 --- a/cocos/scene-graph/node-event-processor.ts +++ b/cocos/scene-graph/node-event-processor.ts @@ -25,8 +25,8 @@ import { CallbacksInvoker } from '../core/event/callbacks-invoker'; import { Event, EventMouse, EventTouch, Touch } from '../input/types'; import { Vec2 } from '../core/math/vec2'; -import { Node } from './node'; -import { legacyCC } from '../core/global-exports'; +import type { Node } from './node'; +import { cclegacy } from '../core/global-exports'; import { Component } from './component'; import { NodeEventType } from './node-event'; import { InputEventType, SystemEventTypeUnion } from '../input/types/event-enum'; @@ -63,6 +63,8 @@ export enum DispatcherEventType { MARK_LIST_DIRTY, } +const globalCallbacksInvoker = new CallbacksInvoker(); + /** * @en The event processor for Node * @zh 节点事件类。 @@ -75,7 +77,7 @@ export class NodeEventProcessor { /** * @internal */ - public static callbacksInvoker = new CallbacksInvoker(); + public static callbacksInvoker = globalCallbacksInvoker; /** * Whether the node event is enabled @@ -159,7 +161,7 @@ export class NodeEventProcessor { if (value) { this._attachMask(); } - NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.MARK_LIST_DIRTY); + globalCallbacksInvoker.emit(DispatcherEventType.MARK_LIST_DIRTY); if (recursive && children.length > 0) { for (let i = 0; i < children.length; ++i) { const child = children[i]; @@ -184,7 +186,7 @@ export class NodeEventProcessor { if (this.capturingTarget) this.capturingTarget.clear(); if (this.bubblingTarget) this.bubblingTarget.clear(); - NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this); + globalCallbacksInvoker.emit(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this); if (this._dispatchingTouch) { // Dispatch touch cancel event when node is destroyed. const cancelEvent = new EventTouch([this._dispatchingTouch], true, InputEventType.TOUCH_CANCEL); @@ -244,7 +246,7 @@ export class NodeEventProcessor { this.shouldHandleEventMouse = false; } if (!this._hasPointerListeners()) { - NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this); + globalCallbacksInvoker.emit(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this); } } @@ -363,7 +365,7 @@ export class NodeEventProcessor { } public onUpdatingSiblingIndex (): void { - NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.MARK_LIST_DIRTY); + globalCallbacksInvoker.emit(DispatcherEventType.MARK_LIST_DIRTY); } private _searchComponentsInParent (ctor: Constructor | null): IMask[] | null { @@ -371,7 +373,7 @@ export class NodeEventProcessor { if (ctor) { let index = 0; let list: IMask[] = []; - for (let curr: Node | null = node; curr && Node.isNode(curr); curr = curr.parent, ++index) { + for (let curr: Node | null = node; curr && cclegacy.Node.isNode(curr); curr = curr.parent, ++index) { const comp = curr.getComponent(ctor); if (comp) { const next = { @@ -444,7 +446,7 @@ export class NodeEventProcessor { this.shouldHandleEventMouse = true; } if ((isTouchEvent || isMouseEvent) && !this._hasPointerListeners()) { - NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.ADD_POINTER_EVENT_PROCESSOR, this); + globalCallbacksInvoker.emit(DispatcherEventType.ADD_POINTER_EVENT_PROCESSOR, this); } } @@ -463,7 +465,7 @@ export class NodeEventProcessor { this.shouldHandleEventMouse = false; } if (!this._hasPointerListeners()) { - NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this); + globalCallbacksInvoker.emit(DispatcherEventType.REMOVE_POINTER_EVENT_PROCESSOR, this); } }); return callbacksInvoker; @@ -692,4 +694,4 @@ export class NodeEventProcessor { // #endregion handle touch event } -legacyCC.NodeEventProcessor = NodeEventProcessor; +cclegacy.NodeEventProcessor = NodeEventProcessor; diff --git a/cocos/scene-graph/node.jsb.ts b/cocos/scene-graph/node.jsb.ts index 4b99c37f42b..a6585a8ba4a 100644 --- a/cocos/scene-graph/node.jsb.ts +++ b/cocos/scene-graph/node.jsb.ts @@ -21,7 +21,7 @@ */ import { EDITOR, EDITOR_NOT_IN_PREVIEW } from 'internal:constants'; -import { legacyCC } from '../core/global-exports'; +import { cclegacy } from '../core/global-exports'; import { errorID, getError } from '../core/platform/debug'; import { Component } from './component'; import { NodeEventType } from './node-event'; @@ -38,14 +38,16 @@ import { nodePolyfill } from './node-dev'; import * as js from '../core/utils/js'; import { patch_cc_Node } from '../native-binding/decorators'; import type { Node as JsbNode } from './node'; +import { DispatcherEventType, NodeEventProcessor } from './node-event-processor'; const reserveContentsForAllSyncablePrefabTag = Symbol('ReserveContentsForAllSyncablePrefab'); declare const jsb: any; +declare const EditorExtends: any; export const Node: typeof JsbNode = jsb.Node; export type Node = JsbNode; -legacyCC.Node = Node; +cclegacy.Node = Node; const NodeCls: any = Node; @@ -81,6 +83,7 @@ const TRANSFORMBIT_TRS = TransformBit.TRS; const nodeProto: any = jsb.Node.prototype; export const TRANSFORM_ON = 1 << 0; +const ACTIVE_ON = 1 << 1; const Destroying = CCObject.Flags.Destroying; // TODO: `_setTempFloatArray` is only implemented on Native platforms. @dumganhar @@ -162,7 +165,7 @@ nodeProto.addComponent = function (typeOrClassName) { if (typeof typeOrClassName === 'string') { constructor = getClassByName(typeOrClassName); if (!constructor) { - if (legacyCC._RF.peek()) { + if (cclegacy._RF.peek()) { errorID(3808, typeOrClassName); } throw TypeError(getError(3807, typeOrClassName)); @@ -221,7 +224,7 @@ nodeProto.addComponent = function (typeOrClassName) { } this.emit(NodeEventType.COMPONENT_ADDED, component); if (this._activeInHierarchy) { - legacyCC.director._nodeActivator.activateComp(component); + cclegacy.director._nodeActivator.activateComp(component); } if (EDITOR_NOT_IN_PREVIEW) { component.resetInEditor?.(); @@ -419,7 +422,7 @@ nodeProto._onEditorAttached = function (attached: boolean) { }; nodeProto._onRemovePersistRootNode = function () { - legacyCC.game.removePersistRootNode(this); + cclegacy.game.removePersistRootNode(this); }; nodeProto._onDestroyComponents = function () { @@ -496,11 +499,17 @@ nodeProto._onSiblingOrderChanged = function () { }; nodeProto._onActivateNode = function (shouldActiveNow) { - legacyCC.director._nodeActivator.activateNode(this, shouldActiveNow); + cclegacy.director._nodeActivator.activateNode(this, shouldActiveNow); }; nodeProto._onPostActivated = function (active: boolean) { - this._eventProcessor.setEnabled(active); + const eventProcessor = this._eventProcessor; + if (eventProcessor.isEnabled === active) { + NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.MARK_LIST_DIRTY); + } + + eventProcessor.setEnabled(active); + if (active) { // in case transform updated during deactivated period this.invalidateChildren(TransformBit.TRS); @@ -598,7 +607,7 @@ NodeCls._findChildComponents = function (children, constructor, components) { // @ts-ignore NodeCls.isNode = function (obj: unknown): obj is jsb.Node { // @ts-ignore - return obj instanceof jsb.Node && (obj.constructor === jsb.Node || !(obj instanceof legacyCC.Scene)); + return obj instanceof jsb.Node && (obj.constructor === jsb.Node || !(obj instanceof cclegacy.Scene)); }; let _tempQuat = new Quat(); @@ -1259,7 +1268,7 @@ nodeProto[serializeTag] = function (serializationOutput: SerializationOutput, co }; nodeProto._onActiveNode = function (shouldActiveNow: boolean) { - legacyCC.director._nodeActivator.activateNode(this, shouldActiveNow); + cclegacy.director._nodeActivator.activateNode(this, shouldActiveNow); }; nodeProto._onBatchCreated = function (dontSyncChildPrefab: boolean) { @@ -1323,7 +1332,7 @@ nodeProto._onLocalPositionRotationScaleUpdated = function (px, py, pz, rx, ry, r nodeProto._instantiate = function (cloned: Node, isSyncedNode: boolean) { if (!cloned) { - cloned = legacyCC.instantiate._clone(this, this); + cloned = cclegacy.instantiate._clone(this, this); } // TODO(PP_Pro): after we support editorOnly tag, we could remove this any type assertion. @@ -1372,7 +1381,7 @@ nodeProto._ctor = function (name?: string) { this.__editorExtras__ = { editorOnly: true }; this._components = []; - this._eventProcessor = new legacyCC.NodeEventProcessor(this); + this._eventProcessor = new NodeEventProcessor(this); this._uiProps = new NodeUIProperties(this); const sharedArrayBuffer = this._initAndReturnSharedBuffer(); diff --git a/cocos/scene-graph/node.ts b/cocos/scene-graph/node.ts index c3710626f84..6865fba97dc 100644 --- a/cocos/scene-graph/node.ts +++ b/cocos/scene-graph/node.ts @@ -26,7 +26,7 @@ import { ccclass, editable, serializable, type } from 'cc.decorator'; import { DEV, DEBUG, EDITOR, EDITOR_NOT_IN_PREVIEW } from 'internal:constants'; import { Layers } from './layers'; import { NodeUIProperties } from './node-ui-properties'; -import { legacyCC } from '../core/global-exports'; +import { cclegacy } from '../core/global-exports'; import { nodePolyfill } from './node-dev'; import { ISchedulable } from '../core/scheduler'; import { approx, EPSILON, Mat3, mat4, Mat4, quat, Quat, v3, Vec3 } from '../core/math'; @@ -36,11 +36,14 @@ import { errorID, warnID, error, log, getError } from '../core/platform/debug'; import { Component } from './component'; import { property } from '../core/data/decorators/property'; import { CCObject, js } from '../core'; -import type { Scene } from './scene'; import { PrefabInfo, PrefabInstance } from './prefab/prefab-info'; import { NodeEventType } from './node-event'; import { Event } from '../input/types'; -import type { NodeEventProcessor } from './node-event-processor'; +import { DispatcherEventType, NodeEventProcessor } from './node-event-processor'; + +import type { Scene } from './scene'; +import type { Director } from '../game/director'; +import type { Game } from '../game/game'; const Destroying = CCObject.Flags.Destroying; const DontDestroy = CCObject.Flags.DontDestroy; @@ -191,7 +194,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { if (parent) { const couldActiveInScene = parent._activeInHierarchy; if (couldActiveInScene) { - legacyCC.director._nodeActivator.activateNode(this, isActive); + (cclegacy.director as Director)._nodeActivator.activateNode(this, isActive); } } } @@ -382,7 +385,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { public set id (v: string) { this._id = v; } protected _id: string = idGenerator.getNewId(); - protected _eventProcessor: NodeEventProcessor = new (legacyCC.NodeEventProcessor as typeof NodeEventProcessor)(this); + protected _eventProcessor: NodeEventProcessor = new NodeEventProcessor(this); protected _eventMask = 0; protected _siblingIndex = 0; @@ -974,7 +977,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { if (typeof typeOrClassName === 'string') { constructor = js.getClassByName(typeOrClassName) as Constructor | undefined; if (!constructor) { - if (legacyCC._RF.peek()) { + if (cclegacy._RF.peek()) { errorID(3808, typeOrClassName); } throw TypeError(getError(3807, typeOrClassName)); @@ -991,7 +994,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { if (typeof constructor !== 'function') { throw TypeError(getError(3809)); } - if (!js.isChildClassOf(constructor, legacyCC.Component)) { + if (!js.isChildClassOf(constructor, cclegacy.Component)) { throw TypeError(getError(3810)); } @@ -1038,7 +1041,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { } this.emit(NodeEventType.COMPONENT_ADDED, component); if (this._activeInHierarchy) { - legacyCC.director._nodeActivator.activateComp(component); + (cclegacy.director as Director)._nodeActivator.activateComp(component); } if (EDITOR_NOT_IN_PREVIEW) { component.resetInEditor?.(); @@ -1329,7 +1332,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { protected _instantiate (cloned?: Node | null, isSyncedNode: boolean = false): Node { if (!cloned) { - cloned = legacyCC.instantiate._clone(this, this) as Node; + cloned = cclegacy.instantiate._clone(this, this) as Node; } const newPrefabInfo = cloned._prefab; @@ -1353,15 +1356,15 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { protected _onHierarchyChangedBase (oldParent: this | null): void { const newParent = this._parent; - if (this._persistNode && !(newParent instanceof legacyCC.Scene)) { - legacyCC.game.removePersistRootNode(this); + if (this._persistNode && !(newParent instanceof cclegacy.Scene)) { + cclegacy.game.removePersistRootNode(this); if (EDITOR) { warnID(1623); } } if (EDITOR) { - const scene = legacyCC.director.getScene() as this | null; + const scene = (cclegacy.director as Director).getScene() as this | null; const inCurrentSceneBefore = oldParent && oldParent.isChildOf(scene); const inCurrentSceneNow = newParent && newParent.isChildOf(scene); if (!inCurrentSceneBefore && inCurrentSceneNow) { @@ -1382,7 +1385,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { const shouldActiveNow = this._active && !!(newParent && newParent._activeInHierarchy); if (this._activeInHierarchy !== shouldActiveNow) { - legacyCC.director._nodeActivator.activateNode(this, shouldActiveNow); + (cclegacy.director as Director)._nodeActivator.activateNode(this, shouldActiveNow); } } @@ -1401,7 +1404,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { // remove from persist if (this._persistNode) { - legacyCC.game.removePersistRootNode(this); + (cclegacy.game as Game).removePersistRootNode(this); } if (!destroyByParent) { @@ -1560,7 +1563,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { * @zh 指定对象是否是普通的节点?如果传入 [[Scene]] 会返回 false。 */ public static isNode (obj: unknown): obj is Node { - return obj instanceof Node && (obj.constructor === Node || !(obj instanceof legacyCC.Scene)); + return obj instanceof Node && (obj.constructor === Node || !(obj instanceof cclegacy.Scene)); } protected _onPreDestroy (): boolean { @@ -1934,7 +1937,23 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { this.emit(NodeEventType.ACTIVE_CHANGED, this, active); } - this._eventProcessor.setEnabled(active); + const eventProcessor = this._eventProcessor; + // If the 'enable' state of event processor is equal to the node's active state, we should mark the list dirty for the global callback invoker + // which will trigger re-sorting logic in PointerEventDispatcher._sortPointerEventProcessorList. + // Otherwise, pointerEventProcessorList will not be sorted correctly since the 'enable' state may not change and the following + // eventProcessor.setEnabled(active) may return directly. + // Think of the case: + // this.node.pauseSystemEvents(true); // child's eventProcessor will be disabled. + // child.active = false; // child's active state is false and its eventProcessor keeps disabled. + // this.node.resumeSystemEvents(true); // child's eventProcessor will be enabled, MARK_LIST_DIRTY will be emitted, + // but the node is not active, so the resorting logic will take the child to the end of the list, + // see PointerEventDispatcher._sortByPriority. + // child.active = true; // child's eventProcessor has already been enabled, eventProcessor.setEnabled(true) will do nothing. + if (eventProcessor.isEnabled === active) { + NodeEventProcessor.callbacksInvoker.emit(DispatcherEventType.MARK_LIST_DIRTY); + } + + eventProcessor.setEnabled(active); if (active) { // activated // in case transform updated during deactivated period @@ -2684,7 +2703,7 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { public getPathInHierarchy (): string { let result = this.name; let curNode: Node | null = this.parent; - while (curNode && !(curNode instanceof legacyCC.Scene)) { + while (curNode && !(curNode instanceof cclegacy.Scene)) { result = `${curNode.name}/${result}`; curNode = curNode.parent; } @@ -2695,4 +2714,4 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable { nodePolyfill(Node); -legacyCC.Node = Node; +cclegacy.Node = Node;