diff --git a/packages/core/src/ECS/Core/QuerySystem.ts b/packages/core/src/ECS/Core/QuerySystem.ts index a852fad3..946d5a82 100644 --- a/packages/core/src/ECS/Core/QuerySystem.ts +++ b/packages/core/src/ECS/Core/QuerySystem.ts @@ -875,6 +875,7 @@ export class QuerySystem { * * 根据组件类型列表生成对应的位掩码。 * 使用缓存避免重复计算。 + * 注意:必须使用ComponentRegistry来确保与Entity.componentMask使用相同的bitIndex * * @param componentTypes 组件类型列表 * @returns 生成的位掩码 @@ -891,10 +892,20 @@ export class QuerySystem { return cached; } - let mask = ComponentTypeManager.instance.getEntityBits(componentTypes); + // 使用ComponentRegistry而不是ComponentTypeManager,确保bitIndex一致 + let mask = BitMask64Utils.clone(BitMask64Utils.ZERO); + for (const type of componentTypes) { + // 确保组件已注册 + if (!ComponentRegistry.isRegistered(type)) { + ComponentRegistry.register(type); + } + const bitMask = ComponentRegistry.getBitMask(type); + BitMask64Utils.orInPlace(mask, bitMask); + } + // 缓存结果 - this.componentMaskCache.set(cacheKey, mask.getValue()); - return mask.getValue(); + this.componentMaskCache.set(cacheKey, mask); + return mask; } /** @@ -1165,13 +1176,15 @@ export class QuerySystem { * 通知响应式查询实体已变化 * * 使用混合策略: - * 1. 通知关心当前组件的查询 - * 2. 通知当前包含该实体的查询(处理组件移除情况) + * 1. 首先通知关心实体当前组件的查询 + * 2. 然后通知所有其他查询(包括那些可能因为组件移除而不再匹配的查询) * * @param entity 变化的实体 */ private notifyReactiveQueriesEntityChanged(entity: Entity): void { - if (this._reactiveQueries.size === 0) return; + if (this._reactiveQueries.size === 0) { + return; + } const notified = new Set(); @@ -1189,9 +1202,8 @@ export class QuerySystem { } for (const query of this._reactiveQueries.values()) { - if (!notified.has(query) && query.getEntities().includes(entity)) { + if (!notified.has(query)) { query.notifyEntityChanged(entity); - notified.add(query); } } } diff --git a/packages/core/tests/reactive-query-debug.test.ts b/packages/core/tests/reactive-query-debug.test.ts new file mode 100644 index 00000000..f114e3f9 --- /dev/null +++ b/packages/core/tests/reactive-query-debug.test.ts @@ -0,0 +1,109 @@ +import { describe, it, expect, beforeEach } from '@jest/globals'; +import { Scene, Entity, Component, EntitySystem, Matcher, ECSComponent } from '../src'; + +@ECSComponent('TestTransform') +class TestTransform extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +@ECSComponent('TestRenderable') +class TestRenderable extends Component { + constructor(public sprite: string = 'default') { + super(); + } +} + +class TestRenderSystem extends EntitySystem { + public entitiesFound: Entity[] = []; + + constructor() { + super(Matcher.all(TestTransform, TestRenderable)); + } + + protected override process(entities: readonly Entity[]): void { + this.entitiesFound = Array.from(entities); + console.log(`TestRenderSystem.process: 找到 ${entities.length} 个实体`); + } + + protected override onAdded(entity: Entity): void { + console.log(`TestRenderSystem.onAdded: 实体 ${entity.name}(${entity.id}) 被添加`); + } +} + +describe('响应式查询调试', () => { + let scene: Scene; + let system: TestRenderSystem; + + beforeEach(() => { + scene = new Scene(); + system = new TestRenderSystem(); + scene.addEntityProcessor(system); + scene.begin(); + }); + + it('应该在实体添加组件后能被System发现', () => { + console.log('\n=== 测试开始 ==='); + + // 1. 创建实体(此时没有组件) + console.log('\n步骤1: 创建实体'); + const entity = scene.createEntity('TestEntity'); + console.log(`实体已创建: ${entity.name}(${entity.id})`); + console.log(`QuerySystem中的实体数量: ${scene.querySystem.getAllEntities().length}`); + + // 2. 添加组件 + console.log('\n步骤2: 添加 TestTransform 组件'); + entity.addComponent(new TestTransform(100, 200)); + console.log(`实体组件数量: ${entity.components.length}`); + + console.log('\n步骤3: 添加 TestRenderable 组件'); + entity.addComponent(new TestRenderable('test-sprite')); + console.log(`实体组件数量: ${entity.components.length}`); + + // 3. 触发系统更新 + console.log('\n步骤4: 更新Scene'); + scene.update(); + + // 4. 检查System是否找到了实体 + console.log(`\nSystem找到的实体数量: ${system.entitiesFound.length}`); + if (system.entitiesFound.length > 0) { + console.log(`找到的实体: ${system.entitiesFound.map(e => `${e.name}(${e.id})`).join(', ')}`); + } + + // 5. 直接查询QuerySystem + console.log('\n步骤5: 直接查询QuerySystem'); + const queryResult = scene.querySystem.queryAll(TestTransform, TestRenderable); + console.log(`QuerySystem.queryAll 返回: ${queryResult.entities.length} 个实体`); + + console.log('\n=== 测试结束 ===\n'); + + expect(system.entitiesFound.length).toBe(1); + expect(system.entitiesFound[0]).toBe(entity); + expect(queryResult.entities.length).toBe(1); + expect(queryResult.entities[0]).toBe(entity); + }); + + it('应该测试响应式查询的内部状态', () => { + console.log('\n=== 响应式查询内部状态测试 ==='); + + // 创建实体并添加组件 + const entity = scene.createEntity('TestEntity'); + entity.addComponent(new TestTransform(100, 200)); + entity.addComponent(new TestRenderable('test-sprite')); + + // 获取QuerySystem的内部状态 + const querySystem = scene.querySystem as any; + console.log(`\n响应式查询数量: ${querySystem._reactiveQueries.size}`); + console.log(`组件索引数量: ${querySystem._reactiveQueriesByComponent.size}`); + + // 检查响应式查询 + for (const [key, query] of querySystem._reactiveQueries) { + console.log(`\n查询: ${key}`); + console.log(` 实体数量: ${(query as any)._entities.length}`); + console.log(` 实体ID集合: ${Array.from((query as any)._entityIdSet).join(', ')}`); + } + + console.log('\n=== 测试结束 ===\n'); + }); +}); diff --git a/packages/core/tests/reactive-query-timing.test.ts b/packages/core/tests/reactive-query-timing.test.ts new file mode 100644 index 00000000..c6aa760c --- /dev/null +++ b/packages/core/tests/reactive-query-timing.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect } from '@jest/globals'; +import { Scene, Entity, Component, EntitySystem, Matcher, ECSComponent } from '../src'; + +@ECSComponent('TestTransform') +class TestTransform extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +@ECSComponent('TestRenderable') +class TestRenderable extends Component { + constructor(public sprite: string = 'default') { + super(); + } +} + +class TestRenderSystem extends EntitySystem { + public entitiesFound: Entity[] = []; + + constructor() { + super(Matcher.all(TestTransform, TestRenderable)); + } + + protected override process(entities: readonly Entity[]): void { + this.entitiesFound = Array.from(entities); + console.log(`TestRenderSystem.process: 找到 ${entities.length} 个实体`); + } + + protected override onAdded(entity: Entity): void { + console.log(`TestRenderSystem.onAdded: 实体 ${entity.name}(${entity.id}) 被添加`); + } +} + +class TestGameScene extends Scene { + private renderSystem: TestRenderSystem | null = null; + + public override initialize(): void { + super.initialize(); + + console.log('\n=== Scene.initialize() 开始 ==='); + + // 1. 先添加System(这是GameScene的做法) + console.log('步骤1: 添加RenderSystem'); + this.renderSystem = new TestRenderSystem(); + this.addEntityProcessor(this.renderSystem); + + // 2. 然后创建实体(这是GameScene的做法) + console.log('\n步骤2: 创建实体'); + const entity = this.createEntity('Player'); + console.log(`实体已创建: ${entity.name}(${entity.id})`); + + console.log('\n步骤3: 添加组件'); + entity.addComponent(new TestTransform(100, 200)); + entity.addComponent(new TestRenderable('player-sprite')); + console.log(`实体组件数量: ${entity.components.length}`); + + console.log('=== Scene.initialize() 结束 ===\n'); + } + + public getRenderSystem(): TestRenderSystem | null { + return this.renderSystem; + } +} + +describe('响应式查询时序测试(模拟GameScene)', () => { + it('应该在Scene.initialize()中先添加System再创建实体时正常工作', () => { + console.log('\n\n========== 测试开始 =========='); + + const scene = new TestGameScene(); + + console.log('\n调用scene.initialize()'); + scene.initialize(); + + console.log('\n调用Scene.begin()'); + scene.begin(); + + console.log('\n第一次Scene.update()'); + scene.update(); + + const renderSystem = scene.getRenderSystem(); + expect(renderSystem).not.toBeNull(); + + console.log(`\nRenderSystem找到的实体数量: ${renderSystem!.entitiesFound.length}`); + expect(renderSystem!.entitiesFound.length).toBe(1); + + console.log('========== 测试结束 ==========\n\n'); + }); +});