修复QuerySystem/ArchetypeSystem未响应实体增删Component的问题

This commit is contained in:
YHH
2025-09-28 15:23:59 +08:00
parent 945f772c30
commit 6178851def
6 changed files with 270 additions and 23 deletions

View File

@@ -98,6 +98,54 @@ export class ArchetypeSystem {
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();
}
/**
* 查询包含指定组件组合的原型
*/

View File

@@ -250,6 +250,41 @@ export class QuerySystem {
}
}
/**
* 更新实体在查询系统中的索引
*
* 当实体的组件组合发生变化时调用此方法,高效地更新实体在查询系统中的索引。
*
* @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++;
}
/**
* 将实体添加到各种索引中
*/

View File

@@ -72,6 +72,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();
}
}
/**
* 实体名称
*/
@@ -370,11 +383,8 @@ 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);
}
/**
@@ -566,6 +573,9 @@ export class Entity {
}
this.components.length = 0;
// 通知所有相关的QuerySystem组件已全部移除
Entity.notifyQuerySystems(this);
}
/**

View File

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

View File

@@ -4,7 +4,7 @@ import { getComponentTypeName } from '../Decorators';
/**
* 查询条件类型
*/
interface QueryCondition {
export interface QueryCondition {
all: ComponentType[];
any: ComponentType[];
none: ComponentType[];

View File

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