From 241ee577feb98620c4965d7ebfd195ef9321979b Mon Sep 17 00:00:00 2001 From: yhh <359807859@qq.com> Date: Thu, 4 Dec 2025 12:44:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=B3=BB=E7=BB=9F=20o?= =?UTF-8?q?nAdded=20=E5=9B=9E=E8=B0=83=E5=8F=97=E6=B3=A8=E5=86=8C=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F=E5=BD=B1=E5=93=8D=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/ECS/Entity.ts | 14 +- packages/core/src/ECS/IScene.ts | 19 +- packages/core/src/ECS/Scene.ts | 174 +++++++++++++++ packages/core/src/ECS/Systems/EntitySystem.ts | 144 +++++++++++- .../tests/ECS/Systems/EntitySystem.test.ts | 209 ++++++++++++++++++ 5 files changed, 547 insertions(+), 13 deletions(-) diff --git a/packages/core/src/ECS/Entity.ts b/packages/core/src/ECS/Entity.ts index 6f15f4f6..a4a944d6 100644 --- a/packages/core/src/ECS/Entity.ts +++ b/packages/core/src/ECS/Entity.ts @@ -321,11 +321,19 @@ export class Entity { /** * 通知Scene中的QuerySystem实体组件发生变动 + * + * Notify the QuerySystem in Scene that entity components have changed + * + * @param changedComponentType 变化的组件类型(可选,用于优化通知) | Changed component type (optional, for optimized notification) */ - private notifyQuerySystems(): void { + private notifyQuerySystems(changedComponentType?: ComponentType): void { if (this.scene && this.scene.querySystem) { this.scene.querySystem.updateEntity(this); this.scene.clearSystemEntityCaches(); + // 事件驱动:立即通知关心该组件的系统 | Event-driven: notify systems that care about this component + if (this.scene.notifyEntityComponentChanged) { + this.scene.notifyEntityComponentChanged(this, changedComponentType); + } } } @@ -381,7 +389,7 @@ export class Entity { }); } - this.notifyQuerySystems(); + this.notifyQuerySystems(componentType); return component; } @@ -514,7 +522,7 @@ export class Entity { }); } - this.notifyQuerySystems(); + this.notifyQuerySystems(componentType); } /** diff --git a/packages/core/src/ECS/IScene.ts b/packages/core/src/ECS/IScene.ts index 16bb91a0..df9ad67e 100644 --- a/packages/core/src/ECS/IScene.ts +++ b/packages/core/src/ECS/IScene.ts @@ -2,7 +2,7 @@ import { Entity } from './Entity'; import { EntityList } from './Utils/EntityList'; import { IdentifierPool } from './Utils/IdentifierPool'; import { EntitySystem } from './Systems/EntitySystem'; -import { ComponentStorageManager } from './Core/ComponentStorage'; +import { ComponentStorageManager, ComponentType } from './Core/ComponentStorage'; import { QuerySystem } from './Core/QuerySystem'; import { TypeSafeEventSystem } from './Core/EventSystem'; import type { ReferenceTracker } from './Core/ReferenceTracker'; @@ -120,9 +120,26 @@ export interface IScene { /** * 清除所有EntitySystem的实体缓存 + * Clear all EntitySystem entity caches */ clearSystemEntityCaches(): void; + /** + * 通知相关系统实体的组件发生了变化 + * + * 当组件被添加或移除时调用,立即通知相关系统检查该实体是否匹配, + * 并触发 onAdded/onRemoved 回调。通过组件ID索引优化,只通知关心该组件的系统。 + * + * Notify relevant systems that an entity's components have changed. + * Called when a component is added or removed, immediately notifying + * relevant systems to check if the entity matches and trigger onAdded/onRemoved callbacks. + * Optimized via component ID indexing to only notify systems that care about the changed component. + * + * @param entity 组件发生变化的实体 | The entity whose components changed + * @param changedComponentType 变化的组件类型(可选) | The changed component type (optional) + */ + notifyEntityComponentChanged(entity: Entity, changedComponentType?: ComponentType): void; + /** * 添加实体 */ diff --git a/packages/core/src/ECS/Scene.ts b/packages/core/src/ECS/Scene.ts index 317496e1..f135a30c 100644 --- a/packages/core/src/ECS/Scene.ts +++ b/packages/core/src/ECS/Scene.ts @@ -151,6 +151,30 @@ export class Scene implements IScene { */ private _systemAddCounter: number = 0; + /** + * 组件ID到系统的索引映射 + * + * 用于快速查找关心特定组件的系统,避免遍历所有系统。 + * 使用组件ID(数字)而非ComponentType作为key,避免类引用问题。 + * + * Component ID to systems index map. + * Used for fast lookup of systems that care about specific components. + * Uses component ID (number) instead of ComponentType as key to avoid class reference issues. + */ + private _componentIdToSystems: Map> = new Map(); + + /** + * 需要接收所有组件变化通知的系统集合 + * + * 包括使用 none 条件、tag/name 查询、或空匹配器的系统。 + * 这些系统无法通过组件ID索引优化,需要在每次组件变化时都检查。 + * + * Systems that need to receive all component change notifications. + * Includes systems using none conditions, tag/name queries, or empty matchers. + * These systems cannot be optimized via component ID indexing. + */ + private _globalNotifySystems: Set = new Set(); + /** * 获取场景中所有已注册的EntitySystem * @@ -344,6 +368,10 @@ export class Scene implements IScene { // 清空系统缓存 this._cachedSystems = null; this._systemsOrderDirty = true; + + // 清空组件索引 | Clear component indices + this._componentIdToSystems.clear(); + this._globalNotifySystems.clear(); } /** @@ -453,6 +481,146 @@ export class Scene implements IScene { } } + /** + * 通知相关系统实体的组件发生了变化 + * + * 这是事件驱动设计的核心:当组件被添加或移除时,立即通知相关系统检查该实体是否匹配, + * 并触发 onAdded/onRemoved 回调。通过组件ID索引优化,只通知关心该组件的系统。 + * + * This is the core of event-driven design: when a component is added or removed, + * immediately notify relevant systems to check if the entity matches and trigger + * onAdded/onRemoved callbacks. Optimized via component ID indexing to only notify + * systems that care about the changed component. + * + * @param entity 组件发生变化的实体 | The entity whose components changed + * @param changedComponentType 变化的组件类型(可选) | The changed component type (optional) + */ + public notifyEntityComponentChanged(entity: Entity, changedComponentType?: ComponentType): void { + // 已通知的系统集合,避免重复通知 | Set of notified systems to avoid duplicates + const notifiedSystems = new Set(); + + // 如果提供了组件类型,使用索引优化 | If component type provided, use index optimization + if (changedComponentType && ComponentRegistry.isRegistered(changedComponentType)) { + const componentId = ComponentRegistry.getBitIndex(changedComponentType); + const interestedSystems = this._componentIdToSystems.get(componentId); + + if (interestedSystems) { + for (const system of interestedSystems) { + system.handleEntityComponentChanged(entity); + notifiedSystems.add(system); + } + } + } + + // 通知全局监听系统(none条件、tag/name查询等) | Notify global listener systems + for (const system of this._globalNotifySystems) { + if (!notifiedSystems.has(system)) { + system.handleEntityComponentChanged(entity); + notifiedSystems.add(system); + } + } + + // 如果没有提供组件类型,回退到遍历所有系统 | Fallback to all systems if no component type + if (!changedComponentType) { + for (const system of this.systems) { + if (!notifiedSystems.has(system)) { + system.handleEntityComponentChanged(entity); + } + } + } + } + + /** + * 将系统添加到组件索引 + * + * 根据系统的 Matcher 条件,将系统注册到相应的组件ID索引中。 + * + * Index a system by its interested component types. + * Registers the system to component ID indices based on its Matcher conditions. + * + * @param system 要索引的系统 | The system to index + */ + private indexSystemByComponents(system: EntitySystem): void { + const matcher = system.matcher; + if (!matcher) { + return; + } + + // nothing 匹配器不需要索引 | Nothing matcher doesn't need indexing + if (matcher.isNothing()) { + return; + } + + const condition = matcher.getCondition(); + + // 有 none/tag/name 条件的系统加入全局通知 | Systems with none/tag/name go to global + if (condition.none.length > 0 || condition.tag !== undefined || condition.name !== undefined) { + this._globalNotifySystems.add(system); + } + + // 空匹配器(匹配所有实体)加入全局通知 | Empty matcher (matches all) goes to global + if (matcher.isEmpty()) { + this._globalNotifySystems.add(system); + return; + } + + // 索引 all 条件中的组件 | Index components in all condition + for (const componentType of condition.all) { + this.addSystemToComponentIndex(componentType, system); + } + + // 索引 any 条件中的组件 | Index components in any condition + for (const componentType of condition.any) { + this.addSystemToComponentIndex(componentType, system); + } + + // 索引单组件查询 | Index single component query + if (condition.component) { + this.addSystemToComponentIndex(condition.component, system); + } + } + + /** + * 将系统添加到指定组件的索引 + * + * Add system to the index for a specific component type. + * + * @param componentType 组件类型 | Component type + * @param system 系统 | System + */ + private addSystemToComponentIndex(componentType: ComponentType, system: EntitySystem): void { + if (!ComponentRegistry.isRegistered(componentType)) { + ComponentRegistry.register(componentType); + } + + const componentId = ComponentRegistry.getBitIndex(componentType); + let systems = this._componentIdToSystems.get(componentId); + + if (!systems) { + systems = new Set(); + this._componentIdToSystems.set(componentId, systems); + } + + systems.add(system); + } + + /** + * 从组件索引中移除系统 + * + * Remove a system from all component indices. + * + * @param system 要移除的系统 | The system to remove + */ + private removeSystemFromIndex(system: EntitySystem): void { + // 从全局通知列表移除 | Remove from global notify list + this._globalNotifySystems.delete(system); + + // 从所有组件索引中移除 | Remove from all component indices + for (const systems of this._componentIdToSystems.values()) { + systems.delete(system); + } + } + /** * 在场景的实体列表中添加一个实体 * @param entity 要添加的实体 @@ -738,6 +906,9 @@ export class Scene implements IScene { // 标记系统列表已变化 this.markSystemsOrderDirty(); + // 建立组件类型到系统的索引 | Build component type to system index + this.indexSystemByComponents(system); + injectProperties(system, this._services); // 调试模式下自动包装系统方法以收集性能数据(ProfilerSDK 启用时表示调试模式) @@ -822,6 +993,9 @@ export class Scene implements IScene { // 标记系统列表已变化 this.markSystemsOrderDirty(); + // 从组件类型索引中移除 | Remove from component type index + this.removeSystemFromIndex(processor); + // 重置System状态 processor.reset(); } diff --git a/packages/core/src/ECS/Systems/EntitySystem.ts b/packages/core/src/ECS/Systems/EntitySystem.ts index e7128604..cb6d9bb6 100644 --- a/packages/core/src/ECS/Systems/EntitySystem.ts +++ b/packages/core/src/ECS/Systems/EntitySystem.ts @@ -235,7 +235,7 @@ export abstract class EntitySystem implements ISystemBase, IService { * 在系统创建时调用。框架内部使用,用户不应直接调用。 */ public initialize(): void { - // 防止重复初始化 + // 防止重复初始化 | Prevent re-initialization if (this._initialized) { return; } @@ -243,13 +243,20 @@ export abstract class EntitySystem implements ISystemBase, IService { this._initialized = true; // 框架内部初始化:触发一次实体查询,以便正确跟踪现有实体 + // Framework initialization: query entities once to track existing entities if (this.scene) { - // 清理缓存确保初始化时重新查询 + // 清理缓存确保初始化时重新查询 | Clear cache to ensure fresh query this._entityCache.invalidate(); - this.queryEntities(); + const entities = this.queryEntities(); + + // 初始化时对已存在的匹配实体触发 onAdded + // Trigger onAdded for existing matching entities during initialization + for (const entity of entities) { + this.onAdded(entity); + } } - // 调用用户可重写的初始化方法 + // 调用用户可重写的初始化方法 | Call user-overridable initialization method this.onInitialize(); } @@ -718,32 +725,151 @@ export abstract class EntitySystem implements ISystemBase, IService { return `${this._systemName}[${entityCount} entities]${perfInfo}`; } + /** + * 检查实体是否匹配当前系统的查询条件 + * Check if an entity matches this system's query condition + * + * @param entity 要检查的实体 / The entity to check + * @returns 是否匹配 / Whether the entity matches + */ + public matchesEntity(entity: Entity): boolean { + if (!this._matcher) { + return false; + } + + // nothing 匹配器不匹配任何实体 + if (this._matcher.isNothing()) { + return false; + } + + // 空匹配器匹配所有实体 + if (this._matcher.isEmpty()) { + return true; + } + + const condition = this._matcher.getCondition(); + + // 检查 all 条件 + for (const componentType of condition.all) { + if (!entity.hasComponent(componentType)) { + return false; + } + } + + // 检查 any 条件 + if (condition.any.length > 0) { + let hasAny = false; + for (const componentType of condition.any) { + if (entity.hasComponent(componentType)) { + hasAny = true; + break; + } + } + if (!hasAny) { + return false; + } + } + + // 检查 none 条件 + for (const componentType of condition.none) { + if (entity.hasComponent(componentType)) { + return false; + } + } + + // 检查 tag 条件 + if (condition.tag !== undefined && entity.tag !== condition.tag) { + return false; + } + + // 检查 name 条件 + if (condition.name !== undefined && entity.name !== condition.name) { + return false; + } + + // 检查单组件条件 + if (condition.component !== undefined && !entity.hasComponent(condition.component)) { + return false; + } + + return true; + } + + /** + * 检查实体是否正在被此系统跟踪 + * Check if an entity is being tracked by this system + * + * @param entity 要检查的实体 / The entity to check + * @returns 是否正在跟踪 / Whether the entity is being tracked + */ + public isTracking(entity: Entity): boolean { + return this._entityCache.isTracked(entity); + } + + /** + * 当实体的组件发生变化时由 Scene 调用 + * + * 立即检查实体是否匹配并触发 onAdded/onRemoved 回调。 + * 这是事件驱动设计的核心:组件变化时立即通知相关系统。 + * + * Called by Scene when an entity's components change. + * Immediately checks if the entity matches and triggers onAdded/onRemoved callbacks. + * This is the core of event-driven design: notify relevant systems immediately when components change. + * + * @param entity 组件发生变化的实体 / The entity whose components changed + * @internal 由 Scene.notifyEntityComponentChanged 调用 / Called by Scene.notifyEntityComponentChanged + */ + public handleEntityComponentChanged(entity: Entity): void { + if (!this._matcher || !this._enabled) { + return; + } + + const wasTracked = this._entityCache.isTracked(entity); + const nowMatches = this.matchesEntity(entity); + + if (!wasTracked && nowMatches) { + // 新匹配:添加跟踪并触发 onAdded | New match: add tracking and trigger onAdded + this._entityCache.addTracked(entity); + this._entityCache.invalidate(); + this.onAdded(entity); + } else if (wasTracked && !nowMatches) { + // 不再匹配:移除跟踪并触发 onRemoved | No longer matches: remove tracking and trigger onRemoved + this._entityCache.removeTracked(entity); + this._entityCache.invalidate(); + this.onRemoved(entity); + } + } + /** * 更新实体跟踪,检查新增和移除的实体 + * + * 由于采用了事件驱动设计,运行时的 onAdded/onRemoved 已在 handleEntityComponentChanged 中 + * 立即触发。此方法不再触发回调,只同步跟踪状态。 + * + * With event-driven design, runtime onAdded/onRemoved are triggered immediately in + * handleEntityComponentChanged. This method no longer triggers callbacks, only syncs tracking state. */ private updateEntityTracking(currentEntities: readonly Entity[]): void { const currentSet = new Set(currentEntities); let hasChanged = false; - // 检查新增的实体 + // 检查新增的实体 | Check for newly added entities for (const entity of currentEntities) { if (!this._entityCache.isTracked(entity)) { this._entityCache.addTracked(entity); - this.onAdded(entity); hasChanged = true; } } - // 检查移除的实体 + // 检查移除的实体 | Check for removed entities for (const entity of this._entityCache.getTracked()) { if (!currentSet.has(entity)) { this._entityCache.removeTracked(entity); - this.onRemoved(entity); hasChanged = true; } } - // 如果实体发生了变化,使缓存失效 + // 如果实体发生了变化,使缓存失效 | If entities changed, invalidate cache if (hasChanged) { this._entityCache.invalidate(); } diff --git a/packages/core/tests/ECS/Systems/EntitySystem.test.ts b/packages/core/tests/ECS/Systems/EntitySystem.test.ts index 91daed01..04846493 100644 --- a/packages/core/tests/ECS/Systems/EntitySystem.test.ts +++ b/packages/core/tests/ECS/Systems/EntitySystem.test.ts @@ -439,6 +439,215 @@ describe('EntitySystem', () => { scene.removeSystem(trackingSystem); }); + + it('在系统 process 中添加组件时应立即触发其他系统的 onAdded', () => { + // 使用独立的场景,避免 beforeEach 创建的实体干扰 + // Use independent scene to avoid interference from beforeEach entities + const testScene = new Scene(); + + // 组件定义 + class TagComponent extends TestComponent {} + + // SystemA: 匹配 TestComponent + TagComponent + class SystemA extends EntitySystem { + public onAddedEntities: Entity[] = []; + + constructor() { + super(Matcher.all(TestComponent, TagComponent)); + } + + protected override onAdded(entity: Entity): void { + this.onAddedEntities.push(entity); + } + } + + // TriggerSystem: 在 process 中添加 TagComponent + class TriggerSystem extends EntitySystem { + constructor() { + super(Matcher.all(TestComponent)); + } + + protected override process(entities: readonly Entity[]): void { + for (const entity of entities) { + if (!entity.hasComponent(TagComponent)) { + entity.addComponent(new TagComponent()); + } + } + } + } + + const systemA = new SystemA(); + const triggerSystem = new TriggerSystem(); + + // 注意:SystemA 先注册,TriggerSystem 后注册 + // 事件驱动设计确保即使 SystemA 已执行完毕,也能收到 onAdded 通知 + testScene.addSystem(systemA); + testScene.addSystem(triggerSystem); + + // 创建实体(已有 TestComponent) + const testEntity = testScene.createEntity('test'); + testEntity.addComponent(new TestComponent()); + + // 执行一帧:TriggerSystem 会添加 TagComponent,SystemA 应立即收到 onAdded + testScene.update(); + + expect(systemA.onAddedEntities.length).toBe(1); + expect(systemA.onAddedEntities[0]).toBe(testEntity); + + testScene.removeSystem(systemA); + testScene.removeSystem(triggerSystem); + }); + + it('同一帧内添加后移除组件,onAdded 和 onRemoved 都应触发', () => { + // 使用独立的场景,避免 beforeEach 创建的实体干扰 + // Use independent scene to avoid interference from beforeEach entities + const testScene = new Scene(); + + class TagComponent extends TestComponent {} + + class TrackingSystemWithTag extends EntitySystem { + public onAddedEntities: Entity[] = []; + public onRemovedEntities: Entity[] = []; + + constructor() { + super(Matcher.all(TestComponent, TagComponent)); + } + + protected override onAdded(entity: Entity): void { + this.onAddedEntities.push(entity); + } + + protected override onRemoved(entity: Entity): void { + this.onRemovedEntities.push(entity); + } + } + + // AddSystem: 在 process 中添加 TagComponent + class AddSystem extends EntitySystem { + constructor() { + super(Matcher.all(TestComponent)); + } + + protected override process(entities: readonly Entity[]): void { + for (const entity of entities) { + if (!entity.hasComponent(TagComponent)) { + entity.addComponent(new TagComponent()); + } + } + } + } + + // RemoveSystem: 在 lateProcess 中移除 TagComponent + class RemoveSystem extends EntitySystem { + constructor() { + super(Matcher.all(TagComponent)); + } + + protected override lateProcess(entities: readonly Entity[]): void { + for (const entity of entities) { + const tag = entity.getComponent(TagComponent); + if (tag) { + entity.removeComponent(tag); + } + } + } + } + + const trackingSystem = new TrackingSystemWithTag(); + const addSystem = new AddSystem(); + const removeSystem = new RemoveSystem(); + + testScene.addSystem(trackingSystem); + testScene.addSystem(addSystem); + testScene.addSystem(removeSystem); + + const testEntity = testScene.createEntity('test'); + testEntity.addComponent(new TestComponent()); + + // 执行一帧 + testScene.update(); + + // AddSystem 添加了 TagComponent,RemoveSystem 在 lateProcess 中移除 + expect(testEntity.hasComponent(TagComponent)).toBe(false); + + // 事件驱动:onAdded 应该在组件添加时立即触发 + expect(trackingSystem.onAddedEntities.length).toBe(1); + // onRemoved 应该在组件移除时立即触发 + expect(trackingSystem.onRemovedEntities.length).toBe(1); + + testScene.removeSystem(trackingSystem); + testScene.removeSystem(addSystem); + testScene.removeSystem(removeSystem); + }); + + it('多个系统监听同一组件变化时都应收到 onAdded 通知', () => { + // 使用独立的场景,避免 beforeEach 创建的实体干扰 + // Use independent scene to avoid interference from beforeEach entities + const testScene = new Scene(); + + // 使用独立的组件类,避免继承带来的问题 + // Use independent component class to avoid inheritance issues + class TagComponent2 extends Component {} + + class SystemA extends EntitySystem { + public onAddedEntities: Entity[] = []; + + constructor() { + super(Matcher.all(TestComponent, TagComponent2)); + } + + protected override onAdded(entity: Entity): void { + this.onAddedEntities.push(entity); + } + } + + class SystemB extends EntitySystem { + public onAddedEntities: Entity[] = []; + + constructor() { + super(Matcher.all(TestComponent, TagComponent2)); + } + + protected override onAdded(entity: Entity): void { + this.onAddedEntities.push(entity); + } + } + + class TriggerSystem extends EntitySystem { + constructor() { + super(Matcher.all(TestComponent)); + } + + protected override process(entities: readonly Entity[]): void { + for (const entity of entities) { + if (!entity.hasComponent(TagComponent2)) { + entity.addComponent(new TagComponent2()); + } + } + } + } + + const systemA = new SystemA(); + const systemB = new SystemB(); + const triggerSystem = new TriggerSystem(); + + testScene.addSystem(systemA); + testScene.addSystem(systemB); + testScene.addSystem(triggerSystem); + + const testEntity = testScene.createEntity('test'); + testEntity.addComponent(new TestComponent()); + + testScene.update(); + + // 两个系统都应收到 onAdded 通知 + expect(systemA.onAddedEntities.length).toBe(1); + expect(systemB.onAddedEntities.length).toBe(1); + + testScene.removeSystem(systemA); + testScene.removeSystem(systemB); + testScene.removeSystem(triggerSystem); + }); }); describe('reset 方法', () => {