使用ComponentRegistry来确保与Entity.componentMask使用相同的bitIndex

This commit is contained in:
YHH
2025-10-14 17:34:15 +08:00
parent b97f3a8431
commit d979c38615
3 changed files with 218 additions and 8 deletions

View File

@@ -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<ReactiveQuery>();
@@ -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);
}
}
}

View File

@@ -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');
});
});

View File

@@ -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');
});
});