修复QuerySystem/ArchetypeSystem未响应实体增删Component的问题
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询包含指定组件组合的原型
|
||||
*/
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体添加到各种索引中
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getComponentTypeName } from '../Decorators';
|
||||
/**
|
||||
* 查询条件类型
|
||||
*/
|
||||
interface QueryCondition {
|
||||
export interface QueryCondition {
|
||||
all: ComponentType[];
|
||||
any: ComponentType[];
|
||||
none: ComponentType[];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user