diff --git a/packages/core/src/ECS/Core/ArchetypeSystem.ts b/packages/core/src/ECS/Core/ArchetypeSystem.ts index 62af1d54..e7745156 100644 --- a/packages/core/src/ECS/Core/ArchetypeSystem.ts +++ b/packages/core/src/ECS/Core/ArchetypeSystem.ts @@ -87,16 +87,64 @@ export class ArchetypeSystem { public removeEntity(entity: Entity): void { const archetype = this._entityToArchetype.get(entity); if (!archetype) return; - + const index = archetype.entities.indexOf(entity); if (index !== -1) { archetype.entities.splice(index, 1); archetype.updatedAt = Date.now(); } - + this._entityToArchetype.delete(entity); this.invalidateQueryCache(); } + + /** + * 更新实体的原型归属 + * + * 当实体的组件组合发生变化时调用此方法,将实体从旧原型移动到新原型。 + * 如果新的组件组合对应的原型不存在,将自动创建新原型。 + * + * @param entity 要更新的实体 + */ + public updateEntity(entity: Entity): void { + const currentArchetype = this._entityToArchetype.get(entity); + const newComponentTypes = this.getEntityComponentTypes(entity); + const newArchetypeId = this.generateArchetypeId(newComponentTypes); + + // 如果实体已在正确的原型中,无需更新 + if (currentArchetype && currentArchetype.id === newArchetypeId) { + return; + } + + // 从旧原型中移除实体 + if (currentArchetype) { + const index = currentArchetype.entities.indexOf(entity); + if (index !== -1) { + currentArchetype.entities.splice(index, 1); + currentArchetype.updatedAt = Date.now(); + } + } + + // 获取或创建新原型 + let newArchetype = this._archetypes.get(newArchetypeId); + if (!newArchetype) { + newArchetype = this.createArchetype(newComponentTypes); + } + + // 将实体添加到新原型 + newArchetype.entities.push(entity); + newArchetype.updatedAt = Date.now(); + this._entityToArchetype.set(entity, newArchetype); + + // 更新组件索引 + if (currentArchetype) { + this.updateComponentIndexes(currentArchetype, currentArchetype.componentTypes, false); + } + this.updateComponentIndexes(newArchetype, newComponentTypes, true); + + // 使查询缓存失效 + this.invalidateQueryCache(); + } /** * 查询包含指定组件组合的原型 diff --git a/packages/core/src/ECS/Core/QuerySystem.ts b/packages/core/src/ECS/Core/QuerySystem.ts index 6f4b52da..d458e05f 100644 --- a/packages/core/src/ECS/Core/QuerySystem.ts +++ b/packages/core/src/ECS/Core/QuerySystem.ts @@ -229,9 +229,9 @@ export class QuerySystem { /** * 从查询系统移除实体 - * + * * 从查询系统中移除指定实体,并清理相关索引。 - * + * * @param entity 要移除的实体 */ public removeEntity(entity: Entity): void { @@ -244,12 +244,47 @@ export class QuerySystem { this.archetypeSystem.removeEntity(entity); this.clearQueryCache(); - + // 更新版本号 this._version++; } } + /** + * 更新实体在查询系统中的索引 + * + * 当实体的组件组合发生变化时调用此方法,高效地更新实体在查询系统中的索引。 + * + * @param entity 要更新的实体 + */ + public updateEntity(entity: Entity): void { + // 检查实体是否在查询系统中 + if (!this.entities.includes(entity)) { + // 如果实体不在系统中,直接添加 + this.addEntity(entity); + return; + } + + // 先从索引中移除实体的旧状态 + this.removeEntityFromIndexes(entity); + + // 更新ArchetypeSystem中的实体状态 + this.archetypeSystem.updateEntity(entity); + + // 更新ComponentIndexManager中的实体状态 + this.componentIndexManager.removeEntity(entity); + this.componentIndexManager.addEntity(entity); + + // 重新添加实体到索引(基于新的组件状态) + this.addEntityToIndexes(entity); + + // 清理查询缓存,因为实体组件状态已改变 + this.clearQueryCache(); + + // 更新版本号以使缓存失效 + this._version++; + } + /** * 将实体添加到各种索引中 */ diff --git a/packages/core/src/ECS/Entity.ts b/packages/core/src/ECS/Entity.ts index 9dcfbbe0..af136260 100644 --- a/packages/core/src/ECS/Entity.ts +++ b/packages/core/src/ECS/Entity.ts @@ -71,6 +71,19 @@ export class Entity { * 用于发射组件相关事件 */ public static eventBus: EventBus | null = null; + + /** + * 通知Scene中的QuerySystem实体组件发生变动 + * + * @param entity 发生组件变动的实体 + */ + private static notifyQuerySystems(entity: Entity): void { + // 只通知Scene中的QuerySystem + if (entity.scene && entity.scene.querySystem) { + entity.scene.querySystem.updateEntity(entity); + entity.scene.clearSystemEntityCaches(); + } + } /** * 实体名称 @@ -369,12 +382,9 @@ export class Entity { }); } - - if (this.scene && this.scene.querySystem) { - this.scene.querySystem.removeEntity(this); - this.scene.querySystem.addEntity(this); - this.scene.clearSystemEntityCaches(); - } + + // 通知所有相关的QuerySystem组件已变动 + Entity.notifyQuerySystems(this); return component; } @@ -521,11 +531,8 @@ export class Entity { component.entity = null as any; - if (this.scene && this.scene.querySystem) { - this.scene.querySystem.removeEntity(this); - this.scene.querySystem.addEntity(this); - this.scene.clearSystemEntityCaches(); - } + // 通知所有相关的QuerySystem组件已变动 + Entity.notifyQuerySystems(this); } /** @@ -564,8 +571,11 @@ export class Entity { component.entity = null as any; } - + this.components.length = 0; + + // 通知所有相关的QuerySystem组件已全部移除 + Entity.notifyQuerySystems(this); } /** diff --git a/packages/core/src/ECS/Systems/EntitySystem.ts b/packages/core/src/ECS/Systems/EntitySystem.ts index ac90dde9..cbf1a5cd 100644 --- a/packages/core/src/ECS/Systems/EntitySystem.ts +++ b/packages/core/src/ECS/Systems/EntitySystem.ts @@ -1,6 +1,6 @@ import { Entity } from '../Entity'; import { PerformanceMonitor } from '../../Utils/PerformanceMonitor'; -import { Matcher } from '../Utils/Matcher'; +import { Matcher, type QueryCondition } from '../Utils/Matcher'; import type { Scene } from '../Scene'; import type { ISystemBase } from '../../Types'; import type { QuerySystem } from '../Core/QuerySystem'; @@ -274,7 +274,7 @@ export abstract class EntitySystem implements ISystemBase { /** * 检查是否为单一条件查询 */ - private isSingleCondition(condition: any): boolean { + private isSingleCondition(condition: QueryCondition): boolean { const flags = ((condition.all.length > 0) ? 1 : 0) | ((condition.any.length > 0) ? 2 : 0) | @@ -289,7 +289,7 @@ export abstract class EntitySystem implements ISystemBase { /** * 执行单一条件查询 */ - private executeSingleConditionQuery(condition: any, querySystem: any): readonly Entity[] { + private executeSingleConditionQuery(condition: QueryCondition, querySystem: QuerySystem): readonly Entity[] { // 按标签查询 if (condition.tag !== undefined) { return querySystem.queryByTag(condition.tag).entities; @@ -324,7 +324,7 @@ export abstract class EntitySystem implements ISystemBase { /** * 执行复合查询 */ - private executeComplexQueryWithIdSets(condition: any, querySystem: QuerySystem): readonly Entity[] { + private executeComplexQueryWithIdSets(condition: QueryCondition, querySystem: QuerySystem): readonly Entity[] { let resultIds: Set | null = null; // 1. 应用标签条件作为基础集合 @@ -492,7 +492,7 @@ export abstract class EntitySystem implements ISystemBase { * * 使用基于ID集合的单次扫描算法进行复杂查询 */ - private executeComplexQuery(condition: any, querySystem: QuerySystem): readonly Entity[] { + private executeComplexQuery(condition: QueryCondition, querySystem: QuerySystem): readonly Entity[] { return this.executeComplexQueryWithIdSets(condition, querySystem); } diff --git a/packages/core/src/ECS/Utils/Matcher.ts b/packages/core/src/ECS/Utils/Matcher.ts index c856d4d2..e8dcc096 100644 --- a/packages/core/src/ECS/Utils/Matcher.ts +++ b/packages/core/src/ECS/Utils/Matcher.ts @@ -4,7 +4,7 @@ import { getComponentTypeName } from '../Decorators'; /** * 查询条件类型 */ -interface QueryCondition { +export interface QueryCondition { all: ComponentType[]; any: ComponentType[]; none: ComponentType[]; diff --git a/packages/core/tests/ECS/Core/QuerySystem.test.ts b/packages/core/tests/ECS/Core/QuerySystem.test.ts index d9ababa9..35beb55c 100644 --- a/packages/core/tests/ECS/Core/QuerySystem.test.ts +++ b/packages/core/tests/ECS/Core/QuerySystem.test.ts @@ -844,4 +844,158 @@ describe('QuerySystem - 查询系统测试', () => { expect(archetype === undefined || typeof archetype === 'object').toBe(true); }); }); + + describe('组件变动同步问题测试', () => { + test('没有Scene时组件变动不会自动同步(符合ECS架构)', () => { + // 创建一个独立的QuerySystem和实体 + const independentQuerySystem = new QuerySystem(); + const testEntity = new Entity('TestEntity', 9999); + + // 确保实体没有scene + expect(testEntity.scene).toBe(null); + + // 添加实体到查询系统 + independentQuerySystem.addEntity(testEntity); + + // 初始查询:应该没有PositionComponent的实体 + const result1 = independentQuerySystem.queryAll(PositionComponent); + expect(result1.entities.length).toBe(0); + + // 添加组件,但没有Scene,不会自动同步 + testEntity.addComponent(new PositionComponent(100, 200)); + + // 查询系统不知道组件变化(这是预期行为) + const result2 = independentQuerySystem.queryAll(PositionComponent); + expect(result2.entities.length).toBe(0); // 查询系统没有自动更新 + expect(testEntity.hasComponent(PositionComponent)).toBe(true); // 但实体确实有这个组件 + + // 手动同步后应该能找到 + independentQuerySystem.updateEntity(testEntity); + const result3 = independentQuerySystem.queryAll(PositionComponent); + expect(result3.entities.length).toBe(1); + expect(result3.entities[0]).toBe(testEntity); + }); + + test('有Scene但没有querySystem时组件变动应该安全', () => { + const testEntity = new Entity('TestEntity2', 9998); + + // 模拟一个没有querySystem的scene + const mockScene = { + querySystem: null, + componentStorageManager: null, + clearSystemEntityCaches: jest.fn() + }; + testEntity.scene = mockScene as any; + + // 添加组件应该不会抛出错误 + expect(() => { + testEntity.addComponent(new PositionComponent(100, 200)); + }).not.toThrow(); + + expect(testEntity.hasComponent(PositionComponent)).toBe(true); + }); + + test('有Scene时ArchetypeSystem组件变动能正确同步', () => { + const independentQuerySystem = new QuerySystem(); + const testEntity = new Entity('ArchetypeTestEntity', 9997); + + // 模拟Scene环境 + const mockScene = { + querySystem: independentQuerySystem, + componentStorageManager: null, + clearSystemEntityCaches: jest.fn() + }; + testEntity.scene = mockScene as any; + + // 添加初始组件组合 + testEntity.addComponent(new PositionComponent(0, 0)); + independentQuerySystem.addEntity(testEntity); + + // 获取初始archetype + const initialArchetype = independentQuerySystem.getEntityArchetype(testEntity); + expect(initialArchetype).toBeDefined(); + + // 添加另一个组件,通过Scene自动同步 + testEntity.addComponent(new VelocityComponent(1, 1)); + + // 检查是否已移动到新的archetype + const currentArchetype = independentQuerySystem.getEntityArchetype(testEntity); + + // 实体组件组合已改变,archetype系统应该已更新 + const posVelQuery = independentQuerySystem.queryAll(PositionComponent, VelocityComponent); + + console.log('有Scene时组件变动查询结果:', posVelQuery.entities.length); + console.log('实体是否有Position组件:', testEntity.hasComponent(PositionComponent)); + console.log('实体是否有Velocity组件:', testEntity.hasComponent(VelocityComponent)); + + expect(posVelQuery.entities.length).toBe(1); + expect(posVelQuery.entities[0]).toBe(testEntity); + expect(testEntity.hasComponent(PositionComponent)).toBe(true); + expect(testEntity.hasComponent(VelocityComponent)).toBe(true); + + // 验证archetype确实已更新 + if (initialArchetype && currentArchetype) { + expect(currentArchetype.id).not.toBe(initialArchetype.id); + } + }); + + test('有Scene时removeAllComponents应该正确同步QuerySystem', () => { + const independentQuerySystem = new QuerySystem(); + const testEntity = new Entity('RemoveAllTestEntity', 9996); + + // 模拟Scene环境 + const mockScene = { + querySystem: independentQuerySystem, + componentStorageManager: null, + clearSystemEntityCaches: jest.fn() + }; + testEntity.scene = mockScene as any; + + // 添加多个组件 + testEntity.addComponent(new PositionComponent(10, 20)); + testEntity.addComponent(new VelocityComponent(1, 1)); + testEntity.addComponent(new HealthComponent(100)); + independentQuerySystem.addEntity(testEntity); + + // 验证实体有组件且能被查询到 + const result1 = independentQuerySystem.queryAll(PositionComponent); + expect(result1.entities.length).toBe(1); + expect(result1.entities[0]).toBe(testEntity); + + // 移除所有组件 + testEntity.removeAllComponents(); + + // 查询系统应该知道组件已全部移除 + const result2 = independentQuerySystem.queryAll(PositionComponent); + const result3 = independentQuerySystem.queryAll(VelocityComponent); + const result4 = independentQuerySystem.queryAll(HealthComponent); + + expect(result2.entities.length).toBe(0); + expect(result3.entities.length).toBe(0); + expect(result4.entities.length).toBe(0); + expect(testEntity.components.length).toBe(0); + }); + + test('手动同步updateEntity应该工作正常', () => { + const independentQuerySystem = new QuerySystem(); + const testEntity = new Entity('ManualSyncTestEntity', 9995); + + independentQuerySystem.addEntity(testEntity); + + // 添加组件但没有Scene,不会自动同步 + testEntity.addComponent(new PositionComponent(10, 20)); + + // 查询系统还不知道 + let result = independentQuerySystem.queryAll(PositionComponent); + expect(result.entities.length).toBe(0); + + // 手动同步 + independentQuerySystem.updateEntity(testEntity); + + // 现在应该能找到 + result = independentQuerySystem.queryAll(PositionComponent); + expect(result.entities.length).toBe(1); + expect(result.entities[0]).toBe(testEntity); + }); + }); }); \ No newline at end of file