From 9ad015c631cba941538251e83f80cd4b68bba75d Mon Sep 17 00:00:00 2001 From: qgh Date: Tue, 23 Jun 2026 18:30:18 +0800 Subject: [PATCH] refactor: rename change-node to node-change, unify event name to node:change --- src/core/scene/common/message.ts | 1 - .../service/gizmo/node/transform-base.ts | 2 +- .../scene/scene-process/service/message.ts | 6 +- .../scene/scene-process/service/prefab.ts | 6 +- .../scene-process/service/service-manager.ts | 3 +- .../service-core/message-callsite.test.ts | 20 ++-- .../scene/test/service-core/message.test.ts | 62 ++++++------ .../service-manager-forwarding.test.ts | 95 +++++++++++++++++++ 8 files changed, 144 insertions(+), 51 deletions(-) create mode 100644 src/core/scene/test/service-core/service-manager-forwarding.test.ts diff --git a/src/core/scene/common/message.ts b/src/core/scene/common/message.ts index 285da9e0e..591c5e30a 100644 --- a/src/core/scene/common/message.ts +++ b/src/core/scene/common/message.ts @@ -14,7 +14,6 @@ import type { IUndoEvents } from './undo'; */ export interface ISceneEvents { 'scene:dimension-changed': [is2D: boolean]; - 'scene:change-node': [...args: any[]]; } /** diff --git a/src/core/scene/scene-process/service/gizmo/node/transform-base.ts b/src/core/scene/scene-process/service/gizmo/node/transform-base.ts index 9ca000f65..b34c4dc48 100644 --- a/src/core/scene/scene-process/service/gizmo/node/transform-base.ts +++ b/src/core/scene/scene-process/service/gizmo/node/transform-base.ts @@ -92,7 +92,7 @@ class TransformBaseGizmo extends GizmoBase { // 发送节点修改消息 protected broadcastNodeChangeMessage(node: Node) { const EditorExtends = (cc as any).EditorExtends || (globalThis as any).EditorExtends; - ServiceEvents.broadcast('scene:change-node', EditorExtends.Node.getNodePath(node)); + ServiceEvents.broadcast('node:change', EditorExtends.Node.getNodePath(node)); } getSnappedValue(inNumber: number, snapStep: number): number { diff --git a/src/core/scene/scene-process/service/message.ts b/src/core/scene/scene-process/service/message.ts index fc653e83b..e8b9e0339 100644 --- a/src/core/scene/scene-process/service/message.ts +++ b/src/core/scene/scene-process/service/message.ts @@ -40,9 +40,9 @@ class MessageManager { } } - // 因为ChangeNode消息有可能每帧都发送(特别是骨骼动画),太频繁了造成卡顿,所以限制了发送频率 - public broadcastChangeNodeMsg(...args: any[]) { - this._timerUtil.callFunctionLimit(args[0], this.broadcast.bind(this), 'scene:change-node', ...args); + // node:change 消息可能每帧都发送(特别是骨骼动画),太频繁造成卡顿,限制发送频率 + public broadcastNodeChangeMsg(...args: any[]) { + this._timerUtil.callFunctionLimit(args[0], this.broadcast.bind(this), 'node:change', ...args); } } diff --git a/src/core/scene/scene-process/service/prefab.ts b/src/core/scene/scene-process/service/prefab.ts index f2a7d4388..70990fb1f 100644 --- a/src/core/scene/scene-process/service/prefab.ts +++ b/src/core/scene/scene-process/service/prefab.ts @@ -479,7 +479,7 @@ export class PrefabService extends BaseService implements IPrefab if (!prefabUtils.isPrefabInstanceRoot(node) && prefabUtils.isPartOfAssetInPrefabInstance(node)) { console.warn(`Node [${node.name}] is a prefab child of prefabInstance [${node['_prefab']?.root?.name}], ${operationTips}`); // 消除其它面板的等待操作,例如hierarchy操作节点时会先进入等待状态,如果没有node的change消息,就会一直处于等待状态。 - ServiceEvents.broadcast('scene:change-node', EditorExtends.Node.getNodePath(node)); + ServiceEvents.broadcast('node:change', EditorExtends.Node.getNodePath(node)); continue; } @@ -506,7 +506,7 @@ export class PrefabService extends BaseService implements IPrefab if (prefabUtils.isPartOfAssetInPrefabInstance(node)) { console.warn(`Node [${node.name}] is part of prefabInstance [${node['_prefab']?.root?.name}], ${operationTips}`); // 消除其它面板的等待操作,例如hierarchy操作节点时会先进入等待状态,如果没有node的change消息,就会一直处于等待状态。 - ServiceEvents.broadcast('scene:change-node', EditorExtends.Node.getNodePath(node)); + ServiceEvents.broadcast('node:change', EditorExtends.Node.getNodePath(node)); continue; } @@ -559,7 +559,7 @@ export class PrefabService extends BaseService implements IPrefab console.warn(`Node [${child.name}] is a prefab child of prefabInstance [${child['_prefab'].root?.name}], \ it's not allowed to modify hierarchy in current context, you can modify it in it's prefabAsset or do it after unlink prefab from root node`); // 消除其它面板的等待操作,例如hierarchy操作节点时会先进入等待状态,如果没有node的change消息,就会一直处于等待状态。 - ServiceEvents.broadcast('scene:change-node', EditorExtends.Node.getNodePath(child)); + ServiceEvents.broadcast('node:change', EditorExtends.Node.getNodePath(child)); return false; } } diff --git a/src/core/scene/scene-process/service/service-manager.ts b/src/core/scene/scene-process/service/service-manager.ts index c2a542b11..20cd122d6 100644 --- a/src/core/scene/scene-process/service/service-manager.ts +++ b/src/core/scene/scene-process/service/service-manager.ts @@ -20,7 +20,6 @@ const MESSAGE_ONLY_EVENTS = [ 'gizmo:view-mode-changed', 'gizmo:tool-changed', 'scene:dimension-changed', - 'scene:change-node', 'camera:mode-change', 'camera:projection-changed', 'camera:fov-changed', @@ -175,7 +174,7 @@ export class ServiceManager { } if (!broadcastToMessage) return; if (isNodeChange) { - messageManager.broadcastChangeNodeMsg(...args); + messageManager.broadcastNodeChangeMsg(...args); } else { messageManager.broadcast(eventType, ...args); } diff --git a/src/core/scene/test/service-core/message-callsite.test.ts b/src/core/scene/test/service-core/message-callsite.test.ts index 023bd1e90..9903fa3ef 100644 --- a/src/core/scene/test/service-core/message-callsite.test.ts +++ b/src/core/scene/test/service-core/message-callsite.test.ts @@ -465,9 +465,9 @@ describe('ServiceEvents 事件发射集成测试', () => { NodeMock.getNodePath = jest.fn((node: any) => `/${node.name}`); }); - it('filterChildOfAssetOfPrefabInstance 中 prefab 子节点应 emit scene:change-node', () => { + it('filterChildOfAssetOfPrefabInstance 中 prefab 子节点应 emit node:change', () => { const listener = jest.fn(); - globalEventEmitter.on('scene:change-node', listener); + globalEventEmitter.on('node:change', listener); prefabUtilsMock.isOutmostPrefabInstanceMountedChildren.mockReturnValue(false); prefabUtilsMock.isPrefabInstanceRoot.mockReturnValue(false); @@ -478,9 +478,9 @@ describe('ServiceEvents 事件发射集成测试', () => { expect(listener).toHaveBeenCalledWith('/Node-child-uuid-1'); }); - it('filterChildOfAssetOfPrefabInstance 中非 prefab 子节点不应 emit scene:change-node', () => { + it('filterChildOfAssetOfPrefabInstance 中非 prefab 子节点不应 emit node:change', () => { const listener = jest.fn(); - globalEventEmitter.on('scene:change-node', listener); + globalEventEmitter.on('node:change', listener); prefabUtilsMock.isOutmostPrefabInstanceMountedChildren.mockReturnValue(false); prefabUtilsMock.isPrefabInstanceRoot.mockReturnValue(false); @@ -492,9 +492,9 @@ describe('ServiceEvents 事件发射集成测试', () => { expect(result).toContain('normal-uuid'); }); - it('filterPartOfPrefabAsset 中 prefab 部件应 emit scene:change-node', () => { + it('filterPartOfPrefabAsset 中 prefab 部件应 emit node:change', () => { const listener = jest.fn(); - globalEventEmitter.on('scene:change-node', listener); + globalEventEmitter.on('node:change', listener); prefabUtilsMock.isPartOfAssetInPrefabInstance.mockReturnValue(true); @@ -503,9 +503,9 @@ describe('ServiceEvents 事件发射集成测试', () => { expect(listener).toHaveBeenCalledWith('/Node-part-uuid'); }); - it('filterPartOfPrefabAsset 中非 prefab 部件不应 emit scene:change-node', () => { + it('filterPartOfPrefabAsset 中非 prefab 部件不应 emit node:change', () => { const listener = jest.fn(); - globalEventEmitter.on('scene:change-node', listener); + globalEventEmitter.on('node:change', listener); prefabUtilsMock.isPartOfAssetInPrefabInstance.mockReturnValue(false); @@ -515,9 +515,9 @@ describe('ServiceEvents 事件发射集成测试', () => { expect(result).toContain('normal-uuid'); }); - it('canModifySibling 中不可移动的 prefab 子节点应 emit scene:change-node', () => { + it('canModifySibling 中不可移动的 prefab 子节点应 emit node:change', () => { const listener = jest.fn(); - globalEventEmitter.on('scene:change-node', listener); + globalEventEmitter.on('node:change', listener); const child = { uuid: 'prefab-child', diff --git a/src/core/scene/test/service-core/message.test.ts b/src/core/scene/test/service-core/message.test.ts index c2c07c966..50a8dc0dd 100644 --- a/src/core/scene/test/service-core/message.test.ts +++ b/src/core/scene/test/service-core/message.test.ts @@ -106,14 +106,14 @@ describe('MessageManager', () => { }); }); - // ── broadcastChangeNodeMsg 节流功能 ── + // ── broadcastNodeChangeMsg 节流功能 ── - describe('broadcastChangeNodeMsg', () => { - it('首次调用应立即广播 scene:change-node', () => { + describe('broadcastNodeChangeMsg', () => { + it('首次调用应立即广播 node:change', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); - messageManager.broadcastChangeNodeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith('uuid-1'); @@ -121,21 +121,21 @@ describe('MessageManager', () => { it('200ms 内同 uuid 重复调用应被节流', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); - messageManager.broadcastChangeNodeMsg('uuid-1'); - messageManager.broadcastChangeNodeMsg('uuid-1'); - messageManager.broadcastChangeNodeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); expect(listener).toHaveBeenCalledTimes(1); }); it('200ms 后应执行最后一次挂起的调用', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); - messageManager.broadcastChangeNodeMsg('uuid-1'); - messageManager.broadcastChangeNodeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); jest.advanceTimersByTime(200); @@ -146,10 +146,10 @@ describe('MessageManager', () => { it('不同 uuid 的节点应各自独立广播', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); - messageManager.broadcastChangeNodeMsg('uuid-A'); - messageManager.broadcastChangeNodeMsg('uuid-B'); + messageManager.broadcastNodeChangeMsg('uuid-A'); + messageManager.broadcastNodeChangeMsg('uuid-B'); expect(listener).toHaveBeenCalledTimes(2); expect(listener).toHaveBeenNthCalledWith(1, 'uuid-A'); @@ -157,8 +157,8 @@ describe('MessageManager', () => { }); it('节流期间 process.send 不应被调用', () => { - messageManager.broadcastChangeNodeMsg('uuid-1'); - messageManager.broadcastChangeNodeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); + messageManager.broadcastNodeChangeMsg('uuid-1'); expect(processSend).not.toHaveBeenCalled(); @@ -201,12 +201,12 @@ describe('MessageManager', () => { expect(listener).toHaveBeenCalledWith(mockNode); }); - it('scene:change-node — 通过 broadcastChangeNodeMsg 节流广播', () => { + it('node:change — 通过 broadcastNodeChangeMsg 节流广播', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); - messageManager.broadcastChangeNodeMsg('node-uuid-1'); - messageManager.broadcastChangeNodeMsg('node-uuid-1'); + messageManager.broadcastNodeChangeMsg('node-uuid-1'); + messageManager.broadcastNodeChangeMsg('node-uuid-1'); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith('node-uuid-1'); @@ -385,13 +385,13 @@ describe('MessageManager', () => { // ── 骨骼动画高频场景 ── - describe('高频 change-node 场景(骨骼动画模拟)', () => { - it('连续 10 次 broadcastChangeNodeMsg 同一节点,只应执行 2 次', () => { + describe('高频 node-change 场景(骨骼动画模拟)', () => { + it('连续 10 次 broadcastNodeChangeMsg 同一节点,只应执行 2 次', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); for (let i = 0; i < 10; i++) { - messageManager.broadcastChangeNodeMsg('bone-uuid'); + messageManager.broadcastNodeChangeMsg('bone-uuid'); } expect(listener).toHaveBeenCalledTimes(1); @@ -403,13 +403,13 @@ describe('MessageManager', () => { it('多个骨骼节点并发变更应各自独立节流', () => { const listener = jest.fn(); - messageManager.on('scene:change-node', listener); + messageManager.on('node:change', listener); - messageManager.broadcastChangeNodeMsg('bone-1'); - messageManager.broadcastChangeNodeMsg('bone-2'); - messageManager.broadcastChangeNodeMsg('bone-3'); - messageManager.broadcastChangeNodeMsg('bone-1'); - messageManager.broadcastChangeNodeMsg('bone-2'); + messageManager.broadcastNodeChangeMsg('bone-1'); + messageManager.broadcastNodeChangeMsg('bone-2'); + messageManager.broadcastNodeChangeMsg('bone-3'); + messageManager.broadcastNodeChangeMsg('bone-1'); + messageManager.broadcastNodeChangeMsg('bone-2'); // bone-1, bone-2, bone-3 各首次立即执行 = 3 expect(listener).toHaveBeenCalledTimes(3); diff --git a/src/core/scene/test/service-core/service-manager-forwarding.test.ts b/src/core/scene/test/service-core/service-manager-forwarding.test.ts new file mode 100644 index 000000000..4c3d138fb --- /dev/null +++ b/src/core/scene/test/service-core/service-manager-forwarding.test.ts @@ -0,0 +1,95 @@ +/** + * ServiceEvents → MessageManager 事件名一致性测试 + * + * 确保 ServiceManager 转发事件时,ServiceEvents 上的事件名 + * 与 MessageManager 广播的事件名完全一致,不发生名称翻译。 + */ + +jest.mock('cc', () => ({})); + +jest.mock('../../scene-process/service/core', () => { + const actual = jest.requireActual('../../scene-process/service/core/global-events'); + return { + getServiceAll: () => [], + ServiceEvents: actual.ServiceEvents, + }; +}); + +jest.mock('../../scene-process/service/core/internal-events', () => ({ + InternalServiceEvents: { + EditorReloadClose: 'editor:reload-close', + EditorReloadOpen: 'editor:reload-open', + EditorDisposed: 'editor:disposed', + }, +})); + +import { globalEventEmitter } from '../../scene-process/service/core/global-events'; +import { messageManager } from '../../scene-process/service/message'; +import { serviceManager } from '../../scene-process/service/service-manager'; + +// ── ServiceManager 转发的所有事件 ── + +const SERVICE_MAP_EVENTS = [ + 'editor:open', 'editor:close', 'editor:reload', 'editor:save', + 'node:add', 'node:remove', 'node:before-remove', 'node:before-add', + 'node:before-change', 'node:change', 'node:added', 'node:removed', + 'asset:change', 'asset:deleted', + 'component:add', 'component:remove', 'component:added', 'component:removed', + 'component:set-property', 'component:before-add-component', 'component:before-remove-component', + 'script:execution-finished', + 'selection:select', 'selection:unselect', 'selection:clear', +]; + +const MESSAGE_ONLY_EVENTS = [ + 'dirty:changed', + 'gizmo:coordinate-changed', 'gizmo:pivot-changed', 'gizmo:view-mode-changed', 'gizmo:tool-changed', + 'scene:dimension-changed', + 'camera:mode-change', 'camera:projection-changed', 'camera:fov-changed', + 'scene-view:visibility-changed', 'scene-view:light-changed', +]; + +const ALL_FORWARDED_EVENTS = [...SERVICE_MAP_EVENTS, ...MESSAGE_ONLY_EVENTS]; + +// ── 测试 ── + +describe('ServiceEvents → MessageManager 事件名一致性', () => { + beforeAll(() => { + serviceManager.initialize('http://test'); + }); + + beforeEach(() => { + jest.useFakeTimers(); + messageManager.clear(); + (messageManager as any)._timerUtil.clear(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it.each(ALL_FORWARDED_EVENTS)( + '"%s": ServiceEvents 与 MessageManager 广播名一致', + (eventName) => { + const listener = jest.fn(); + messageManager.on(eventName, listener); + + globalEventEmitter.emit(eventName, 'test-arg'); + + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toHaveBeenCalledWith('test-arg'); + } + ); + + it('内部事件不应转发到 MessageManager', () => { + const listener = jest.fn(); + messageManager.on('editor:reload-close', listener); + messageManager.on('editor:reload-open', listener); + messageManager.on('editor:disposed', listener); + + globalEventEmitter.emit('editor:reload-close'); + globalEventEmitter.emit('editor:reload-open'); + globalEventEmitter.emit('editor:disposed'); + + expect(listener).not.toHaveBeenCalled(); + }); +});