修复响应式查询缓存失效和测试隔离问题

This commit is contained in:
YHH
2025-10-14 11:48:04 +08:00
parent 360106fb92
commit 3b917a06af
10 changed files with 538 additions and 650 deletions

View File

@@ -547,7 +547,9 @@ export class Scene implements IScene {
constructor = systemTypeOrInstance; constructor = systemTypeOrInstance;
if (this._services.isRegistered(constructor)) { if (this._services.isRegistered(constructor)) {
return this._services.resolve(constructor) as T; const existingSystem = this._services.resolve(constructor) as T;
this.logger.debug(`System ${constructor.name} already registered, returning existing instance`);
return existingSystem;
} }
if (isInjectable(constructor)) { if (isInjectable(constructor)) {
@@ -560,7 +562,17 @@ export class Scene implements IScene {
constructor = system.constructor; constructor = system.constructor;
if (this._services.isRegistered(constructor)) { if (this._services.isRegistered(constructor)) {
return system; const existingSystem = this._services.resolve(constructor);
if (existingSystem === system) {
this.logger.debug(`System ${constructor.name} instance already registered, returning it`);
return system;
} else {
this.logger.warn(
`Attempting to register a different instance of ${constructor.name}, ` +
`but type is already registered. Returning existing instance.`
);
return existingSystem as T;
}
} }
} }
@@ -582,6 +594,8 @@ export class Scene implements IScene {
system.initialize(); system.initialize();
this.logger.debug(`System ${constructor.name} registered and initialized`);
return system; return system;
} }

View File

@@ -554,6 +554,9 @@ export abstract class EntitySystem<
try { try {
this.onBegin(); this.onBegin();
// 清除持久缓存,确保每次update都重新查询并检测实体变化
// 这对于响应式查询正确工作至关重要
this._entityCache.invalidate();
// 查询实体并存储到帧缓存中 // 查询实体并存储到帧缓存中
this._entityCache.frame = this.queryEntities(); this._entityCache.frame = this.queryEntities();
entityCount = this._entityCache.frame.length; entityCount = this._entityCache.frame.length;

View File

@@ -0,0 +1,94 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
// 简单的测试组件
class HealthComponent extends Component {
public health: number;
constructor(...args: unknown[]) {
super();
const [health = 100] = args as [number?];
this.health = health;
}
}
// 简单的测试系统
class HealthSystem extends EntitySystem {
public onAddedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
protected override onAdded(entity: Entity): void {
console.log('[HealthSystem] onAdded called:', { id: entity.id, name: entity.name });
this.onAddedEntities.push(entity);
}
}
describe('MinimalSystemInit - 最小化系统初始化测试', () => {
let scene: Scene;
beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene();
});
afterEach(() => {
if (scene) {
scene.end();
}
});
test('先创建实体和组件,再添加系统 - 应该触发onAdded', () => {
console.log('\\n=== Test 1: 先创建实体再添加系统 ===');
// 1. 创建实体并添加组件
const entity = scene.createEntity('TestEntity');
entity.addComponent(new HealthComponent(100));
console.log('[Test] Entity created with HealthComponent');
console.log('[Test] ComponentTypeManager registered types:', ComponentTypeManager.instance.registeredTypeCount);
// 2. 验证QuerySystem能查询到实体
const queryResult = scene.querySystem.queryAll(HealthComponent);
console.log('[Test] QuerySystem result:', { count: queryResult.count });
// 3. 添加系统
const system = new HealthSystem();
console.log('[Test] Adding system to scene...');
scene.addEntityProcessor(system);
console.log('[Test] System added, onAddedEntities.length =', system.onAddedEntities.length);
// 4. 验证
expect(system.onAddedEntities).toHaveLength(1);
});
test('先添加系统,再创建实体和组件 - 应该在update时触发onAdded', () => {
console.log('\\n=== Test 2: 先添加系统再创建实体 ===');
// 1. 先添加系统
const system = new HealthSystem();
scene.addEntityProcessor(system);
console.log('[Test] System added, onAddedEntities.length =', system.onAddedEntities.length);
// 2. 创建实体并添加组件
const entity = scene.createEntity('TestEntity');
entity.addComponent(new HealthComponent(100));
console.log('[Test] Entity created with HealthComponent');
// 3. 调用update触发系统查询
console.log('[Test] Calling scene.update()...');
scene.update();
console.log('[Test] After update, onAddedEntities.length =', system.onAddedEntities.length);
// 4. 验证
expect(system.onAddedEntities).toHaveLength(1);
});
});

View File

@@ -0,0 +1,160 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
// 测试组件
class PositionComponent extends Component {
public x: number;
public y: number;
constructor(...args: unknown[]) {
super();
const [x = 0, y = 0] = args as [number?, number?];
this.x = x;
this.y = y;
}
}
class VelocityComponent extends Component {
public vx: number;
public vy: number;
constructor(...args: unknown[]) {
super();
const [vx = 0, vy = 0] = args as [number?, number?];
this.vx = vx;
this.vy = vy;
}
}
class HealthComponent extends Component {
public health: number;
constructor(...args: unknown[]) {
super();
const [health = 100] = args as [number?];
this.health = health;
}
}
// 测试系统
class MovementSystem extends EntitySystem {
public onAddedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
protected override onAdded(entity: Entity): void {
console.log('[MovementSystem] onAdded:', { id: entity.id, name: entity.name });
this.onAddedEntities.push(entity);
}
}
class HealthSystem extends EntitySystem {
public onAddedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
protected override onAdded(entity: Entity): void {
console.log('[HealthSystem] onAdded:', { id: entity.id, name: entity.name });
this.onAddedEntities.push(entity);
}
}
describe('MultiSystemInit - 多系统初始化测试', () => {
let scene: Scene;
beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene();
});
afterEach(() => {
if (scene) {
scene.end();
}
});
test('多个系统同时响应同一实体 - 复现失败场景', () => {
console.log('\\n=== Test: 多个系统同时响应同一实体 ===');
// 1. 创建实体并添加所有组件
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
entity.addComponent(new HealthComponent(100));
console.log('[Test] Entity created with Position, Velocity, Health');
console.log('[Test] ComponentTypeManager registered types:', ComponentTypeManager.instance.registeredTypeCount);
// 2. 验证QuerySystem能查询到实体
const movementQuery = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
const healthQuery = scene.querySystem.queryAll(HealthComponent);
console.log('[Test] MovementQuery result:', { count: movementQuery.count });
console.log('[Test] HealthQuery result:', { count: healthQuery.count });
// 3. 添加两个系统
console.log('[Test] Adding MovementSystem...');
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
console.log('[Test] MovementSystem added, onAddedEntities.length =', movementSystem.onAddedEntities.length);
console.log('[Test] Adding HealthSystem...');
const healthSystem = new HealthSystem();
scene.addEntityProcessor(healthSystem);
console.log('[Test] HealthSystem added, onAddedEntities.length =', healthSystem.onAddedEntities.length);
// 4. 验证
console.log('[Test] Final check:');
console.log(' MovementSystem.onAddedEntities.length =', movementSystem.onAddedEntities.length);
console.log(' HealthSystem.onAddedEntities.length =', healthSystem.onAddedEntities.length);
expect(movementSystem.onAddedEntities).toHaveLength(1);
expect(healthSystem.onAddedEntities).toHaveLength(1);
});
test('不同系统匹配不同实体 - 复现失败场景', () => {
console.log('\\n=== Test: 不同系统匹配不同实体 ===');
// 1. 创建两个实体
const movingEntity = scene.createEntity('Moving');
movingEntity.addComponent(new PositionComponent(0, 0));
movingEntity.addComponent(new VelocityComponent(1, 1));
const healthEntity = scene.createEntity('Health');
healthEntity.addComponent(new HealthComponent(100));
console.log('[Test] Two entities created');
// 2. 验证QuerySystem
const movementQuery = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
const healthQuery = scene.querySystem.queryAll(HealthComponent);
console.log('[Test] MovementQuery result:', { count: movementQuery.count });
console.log('[Test] HealthQuery result:', { count: healthQuery.count });
// 3. 添加系统
console.log('[Test] Adding systems...');
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
console.log('[Test] Systems added');
console.log(' MovementSystem.onAddedEntities.length =', movementSystem.onAddedEntities.length);
console.log(' HealthSystem.onAddedEntities.length =', healthSystem.onAddedEntities.length);
// 4. 验证
expect(movementSystem.onAddedEntities).toHaveLength(1);
expect(movementSystem.onAddedEntities[0]).toBe(movingEntity);
expect(healthSystem.onAddedEntities).toHaveLength(1);
expect(healthSystem.onAddedEntities[0]).toBe(healthEntity);
});
});

View File

@@ -1,471 +0,0 @@
import { Scene } from '../../../src/ECS/Scene';
import { Component } from '../../../src/ECS/Component';
import { ReactiveQueryChangeType } from '../../../src/ECS/Core/ReactiveQuery';
class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
public reset(): void {
this.x = 0;
this.y = 0;
}
}
class VelocityComponent extends Component {
public vx: number = 0;
public vy: number = 0;
constructor(vx: number = 0, vy: number = 0) {
super();
this.vx = vx;
this.vy = vy;
}
public reset(): void {
this.vx = 0;
this.vy = 0;
}
}
class HealthComponent extends Component {
public hp: number = 100;
constructor(hp: number = 100) {
super();
this.hp = hp;
}
public reset(): void {
this.hp = 100;
}
}
describe('ReactiveQuery', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
});
afterEach(() => {
scene.end();
jest.clearAllTimers();
});
describe('基础功能', () => {
test('应该能够创建响应式查询', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent]);
expect(query).toBeDefined();
expect(query.count).toBe(0);
expect(query.getEntities()).toEqual([]);
});
test('应该能够初始化查询结果', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.createReactiveQuery([PositionComponent]);
expect(query.count).toBe(2);
expect(query.getEntities()).toContain(entity1);
expect(query.getEntities()).toContain(entity2);
});
test('应该能够销毁响应式查询', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent]);
scene.querySystem.destroyReactiveQuery(query);
expect(query.active).toBe(false);
});
});
describe('实体添加通知', () => {
test('应该在添加匹配实体时通知订阅者', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
expect(changes).toHaveLength(1);
expect(changes[0].type).toBe(ReactiveQueryChangeType.ADDED);
expect(changes[0].entity).toBe(entity);
});
test('不应该在添加不匹配实体时通知订阅者', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
const entity = scene.createEntity('test');
entity.addComponent(new HealthComponent(100));
expect(changes).toHaveLength(0);
});
test('批量模式应该合并通知', (done) => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: true,
batchDelay: 10
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
setTimeout(() => {
expect(changes).toHaveLength(1);
expect(changes[0].type).toBe(ReactiveQueryChangeType.BATCH_UPDATE);
expect(changes[0].added).toHaveLength(2);
done();
}, 50);
});
});
describe('实体移除通知', () => {
test('应该在移除匹配实体时通知订阅者', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
scene.destroyEntities([entity]);
expect(changes).toHaveLength(1);
expect(changes[0].type).toBe(ReactiveQueryChangeType.REMOVED);
expect(changes[0].entity).toBe(entity);
});
test('不应该在移除不匹配实体时通知订阅者', () => {
const entity = scene.createEntity('test');
entity.addComponent(new HealthComponent(100));
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
scene.destroyEntities([entity]);
expect(changes).toHaveLength(0);
});
});
describe('实体变化通知', () => {
test('应该在实体从不匹配变为匹配时通知添加', () => {
const entity = scene.createEntity('test');
entity.addComponent(new HealthComponent(100));
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
entity.addComponent(new PositionComponent(10, 20));
expect(changes).toHaveLength(1);
expect(changes[0].type).toBe(ReactiveQueryChangeType.ADDED);
expect(changes[0].entity).toBe(entity);
});
test('应该在实体从匹配变为不匹配时通知移除', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
const positionComp = entity.getComponent(PositionComponent);
if (positionComp) {
entity.removeComponent(positionComp);
}
expect(changes).toHaveLength(1);
expect(changes[0].type).toBe(ReactiveQueryChangeType.REMOVED);
expect(changes[0].entity).toBe(entity);
});
test('应该在实体组件变化但仍匹配时不通知', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
entity.addComponent(new HealthComponent(100));
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
const healthComp = entity.getComponent(HealthComponent);
if (healthComp) {
entity.removeComponent(healthComp);
}
expect(changes).toHaveLength(0);
});
});
describe('多组件查询', () => {
test('应该正确匹配多个组件', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent(1, 1));
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
const query = scene.querySystem.createReactiveQuery([PositionComponent, VelocityComponent]);
expect(query.count).toBe(1);
expect(query.getEntities()).toContain(entity1);
expect(query.getEntities()).not.toContain(entity2);
});
test('应该在实体满足所有组件时通知添加', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.createReactiveQuery([PositionComponent, VelocityComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
entity.addComponent(new VelocityComponent(1, 1));
expect(changes).toHaveLength(1);
expect(changes[0].type).toBe(ReactiveQueryChangeType.ADDED);
expect(changes[0].entity).toBe(entity);
});
});
describe('订阅管理', () => {
test('应该能够取消订阅', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
const unsubscribe = query.subscribe((change) => {
changes.push(change);
});
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
expect(changes).toHaveLength(1);
unsubscribe();
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
expect(changes).toHaveLength(1);
});
test('应该能够取消所有订阅', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes1: any[] = [];
query.subscribe((change) => {
changes1.push(change);
});
const changes2: any[] = [];
query.subscribe((change) => {
changes2.push(change);
});
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
expect(changes1).toHaveLength(1);
expect(changes2).toHaveLength(1);
query.unsubscribeAll();
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
expect(changes1).toHaveLength(1);
expect(changes2).toHaveLength(1);
});
test('应该能够暂停和恢复查询', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
query.pause();
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
expect(changes).toHaveLength(0);
query.resume();
const entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
expect(changes).toHaveLength(1);
});
});
describe('性能测试', () => {
test('应该高效处理大量实体变化', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
let changeCount = 0;
query.subscribe(() => {
changeCount++;
});
const startTime = performance.now();
for (let i = 0; i < 1000; i++) {
const entity = scene.createEntity(`entity${i}`);
entity.addComponent(new PositionComponent(i, i));
}
const endTime = performance.now();
expect(changeCount).toBe(1000);
expect(endTime - startTime).toBeLessThan(100);
});
test('批量模式应该减少通知次数', (done) => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: true,
batchDelay: 10
});
let changeCount = 0;
query.subscribe(() => {
changeCount++;
});
for (let i = 0; i < 100; i++) {
const entity = scene.createEntity(`entity${i}`);
entity.addComponent(new PositionComponent(i, i));
}
setTimeout(() => {
expect(changeCount).toBe(1);
done();
}, 50);
});
});
describe('边界情况', () => {
test('应该处理重复添加同一实体', () => {
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
scene.querySystem.addEntity(entity);
scene.querySystem.addEntity(entity);
expect(changes).toHaveLength(0);
});
test('应该处理查询结果为空的情况', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent]);
expect(query.count).toBe(0);
expect(query.getEntities()).toEqual([]);
});
test('应该在销毁后停止通知', () => {
const query = scene.querySystem.createReactiveQuery([PositionComponent], {
enableBatchMode: false
});
const changes: any[] = [];
query.subscribe((change) => {
changes.push(change);
});
query.dispose();
const entity = scene.createEntity('test');
entity.addComponent(new PositionComponent(10, 20));
expect(changes).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,92 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
/**
* 响应式查询调试测试
*
* 隔离测试响应式查询初始化问题
*/
class HealthComponent extends Component {
public health: number;
constructor(health: number = 100) {
super();
this.health = health;
}
}
describe('ReactiveQueryDebug - 响应式查询初始化调试', () => {
let scene: Scene;
beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene();
scene.name = 'DebugScene';
});
test('场景有实体时QuerySystem.queryAll应该能找到实体', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new HealthComponent(100));
// 直接使用QuerySystem查询
const result = scene.querySystem!.queryAll(HealthComponent);
console.log('QuerySystem.queryAll结果:', {
count: result.count,
entities: result.entities.map(e => ({ id: e.id, name: e.name }))
});
expect(result.count).toBe(1);
expect(result.entities[0]).toBe(entity);
});
test('第一次查询和第二次查询应该返回相同结果', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new HealthComponent(100));
// 第一次查询
const result1 = scene.querySystem!.queryAll(HealthComponent);
console.log('第一次查询结果:', {
count: result1.count,
entities: result1.entities.map(e => ({ id: e.id, name: e.name }))
});
// 第二次查询
const result2 = scene.querySystem!.queryAll(HealthComponent);
console.log('第二次查询结果:', {
count: result2.count,
entities: result2.entities.map(e => ({ id: e.id, name: e.name }))
});
expect(result1.count).toBe(1);
expect(result2.count).toBe(1);
expect(result1.entities[0]).toBe(result2.entities[0]);
});
test('添加组件后查询应该能找到实体', () => {
const entity = scene.createEntity('TestEntity');
// 第一次查询(实体还没有组件)
const result1 = scene.querySystem!.queryAll(HealthComponent);
console.log('添加组件前查询结果:', { count: result1.count });
expect(result1.count).toBe(0);
// 添加组件
entity.addComponent(new HealthComponent(100));
// 第二次查询(实体已有组件)
const result2 = scene.querySystem!.queryAll(HealthComponent);
console.log('添加组件后查询结果:', {
count: result2.count,
entities: result2.entities.map(e => ({ id: e.id, name: e.name }))
});
expect(result2.count).toBe(1);
expect(result2.entities[0]).toBe(entity);
});
});

View File

@@ -1,173 +0,0 @@
import { Scene } from '../../../src/ECS/Scene';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { ReactiveQuery, ReactiveQueryChangeType } from '../../../src/ECS/Core/ReactiveQuery';
class TransformComponent extends Component {
public x: number = 0;
public y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
public reset(): void {
this.x = 0;
this.y = 0;
}
}
class RenderableComponent extends Component {
public visible: boolean = true;
public reset(): void {
this.visible = true;
}
}
class ReactiveRenderSystem extends EntitySystem {
private reactiveQuery!: ReactiveQuery;
private addedCount = 0;
private removedCount = 0;
public override initialize(): void {
super.initialize();
if (this.scene) {
this.reactiveQuery = this.scene.querySystem.createReactiveQuery(
[TransformComponent, RenderableComponent],
{
enableBatchMode: false
}
);
this.reactiveQuery.subscribe((change) => {
if (change.type === ReactiveQueryChangeType.ADDED) {
this.addedCount++;
} else if (change.type === ReactiveQueryChangeType.REMOVED) {
this.removedCount++;
}
});
}
}
public getAddedCount(): number {
return this.addedCount;
}
public getRemovedCount(): number {
return this.removedCount;
}
public getQueryEntities() {
return this.reactiveQuery.getEntities();
}
public override dispose(): void {
if (this.reactiveQuery && this.scene) {
this.scene.querySystem.destroyReactiveQuery(this.reactiveQuery);
}
}
}
describe('ReactiveQuery集成测试', () => {
let scene: Scene;
let renderSystem: ReactiveRenderSystem;
beforeEach(() => {
scene = new Scene();
renderSystem = new ReactiveRenderSystem(Matcher.empty());
scene.addEntityProcessor(renderSystem);
});
afterEach(() => {
scene.end();
jest.clearAllTimers();
});
describe('EntitySystem集成', () => {
test('应该在实体添加时收到通知', () => {
const entity1 = scene.createEntity('entity1');
entity1.addComponent(new TransformComponent(10, 20));
entity1.addComponent(new RenderableComponent());
expect(renderSystem.getAddedCount()).toBe(1);
expect(renderSystem.getQueryEntities()).toContain(entity1);
});
test('应该在实体移除时收到通知', () => {
const entity = scene.createEntity('entity');
entity.addComponent(new TransformComponent(10, 20));
entity.addComponent(new RenderableComponent());
expect(renderSystem.getAddedCount()).toBe(1);
scene.destroyEntities([entity]);
expect(renderSystem.getRemovedCount()).toBe(1);
expect(renderSystem.getQueryEntities()).not.toContain(entity);
});
test('应该在组件变化时收到正确通知', () => {
const entity = scene.createEntity('entity');
entity.addComponent(new TransformComponent(10, 20));
expect(renderSystem.getAddedCount()).toBe(0);
entity.addComponent(new RenderableComponent());
expect(renderSystem.getAddedCount()).toBe(1);
expect(renderSystem.getQueryEntities()).toContain(entity);
const renderComp = entity.getComponent(RenderableComponent);
if (renderComp) {
entity.removeComponent(renderComp);
}
expect(renderSystem.getRemovedCount()).toBe(1);
expect(renderSystem.getQueryEntities()).not.toContain(entity);
});
test('应该高效处理批量实体变化', () => {
const entities = [];
for (let i = 0; i < 100; i++) {
const entity = scene.createEntity(`entity${i}`);
entity.addComponent(new TransformComponent(i, i));
entity.addComponent(new RenderableComponent());
entities.push(entity);
}
expect(renderSystem.getAddedCount()).toBe(100);
expect(renderSystem.getQueryEntities().length).toBe(100);
scene.destroyEntities(entities);
expect(renderSystem.getRemovedCount()).toBe(100);
expect(renderSystem.getQueryEntities().length).toBe(0);
});
});
describe('性能对比', () => {
test('响应式查询应该避免每帧重复查询', () => {
for (let i = 0; i < 50; i++) {
const entity = scene.createEntity(`entity${i}`);
entity.addComponent(new TransformComponent(i, i));
entity.addComponent(new RenderableComponent());
}
expect(renderSystem.getAddedCount()).toBe(50);
const initialCount = renderSystem.getAddedCount();
for (let i = 0; i < 100; i++) {
scene.update();
}
expect(renderSystem.getAddedCount()).toBe(initialCount);
});
});
});

View File

@@ -205,11 +205,16 @@ describe('SystemInitialization - 系统初始化测试', () => {
let scene: Scene; let scene: Scene;
beforeEach(() => { beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene(); scene = new Scene();
scene.name = 'InitializationTestScene'; scene.name = 'InitializationTestScene';
}); });
afterEach(() => {
if (scene) {
scene.end();
}
});
describe('初始化时序', () => { describe('初始化时序', () => {
test('先添加实体再添加系统 - 系统应该正确初始化', () => { test('先添加实体再添加系统 - 系统应该正确初始化', () => {
const player = scene.createEntity('Player'); const player = scene.createEntity('Player');

View File

@@ -0,0 +1,164 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
/**
* System初始化调试测试
*/
class HealthComponent extends Component {
public health: number;
constructor(health: number = 100) {
super();
this.health = health;
}
}
class HealthSystem extends EntitySystem {
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public queryResults: readonly Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
public override initialize(): void {
console.log('[HealthSystem] initialize() 开始');
console.log('[HealthSystem] scene:', !!this.scene);
console.log('[HealthSystem] matcher:', this.matcher.toString());
this.initializeCalled = true;
super.initialize();
// 初始化后立即查询
if (this.scene?.querySystem) {
const result = this.scene.querySystem.queryAll(HealthComponent);
console.log('[HealthSystem] 初始化后立即查询结果:', {
count: result.count,
entities: result.entities.map(e => ({ id: e.id, name: e.name }))
});
}
console.log('[HealthSystem] initialize() 完成, onAddedEntities.length=', this.onAddedEntities.length);
}
protected override onAdded(entity: Entity): void {
console.log('[HealthSystem] onAdded() 被调用:', { id: entity.id, name: entity.name });
this.onAddedEntities.push(entity);
}
protected override process(entities: readonly Entity[]): void {
this.queryResults = entities;
for (const entity of entities) {
const health = entity.getComponent(HealthComponent)!;
if (health.health <= 0) {
entity.enabled = false;
}
}
}
}
describe('SystemInitializationDebug - 系统初始化调试', () => {
let scene: Scene;
beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene();
scene.name = 'DebugScene';
});
test('先创建实体再添加系统 - 应该触发onAdded', () => {
console.log('\n=== 测试开始 ===');
// 1. 创建实体并添加组件
const entity = scene.createEntity('TestEntity');
console.log('[Test] 创建实体:', { id: entity.id, name: entity.name });
entity.addComponent(new HealthComponent(100));
console.log('[Test] 添加HealthComponent');
// 2. 验证QuerySystem能查询到实体
const queryResult = scene.querySystem!.queryAll(HealthComponent);
console.log('[Test] QuerySystem查询结果:', {
count: queryResult.count,
entities: queryResult.entities.map(e => ({ id: e.id, name: e.name }))
});
expect(queryResult.count).toBe(1);
expect(queryResult.entities[0]).toBe(entity);
// 3. 添加系统
const system = new HealthSystem();
console.log('[Test] 准备添加系统到场景');
scene.addEntityProcessor(system);
console.log('[Test] 系统已添加');
// 4. 验证系统状态
console.log('[Test] 系统状态:', {
initializeCalled: system.initializeCalled,
onAddedEntitiesLength: system.onAddedEntities.length,
onAddedEntities: system.onAddedEntities.map(e => ({ id: e.id, name: e.name }))
});
expect(system.initializeCalled).toBe(true);
expect(system.onAddedEntities).toHaveLength(1);
expect(system.onAddedEntities[0]).toBe(entity);
console.log('=== 测试结束 ===\n');
});
test('添加同类型的第二个系统 - 应该返回已注册的实例', () => {
console.log('\n=== 单例模式测试开始 ===');
// 1. 创建实体并添加组件
const entity = scene.createEntity('TestEntity');
console.log('[Test] 创建实体:', { id: entity.id, name: entity.name });
entity.addComponent(new HealthComponent(100));
console.log('[Test] 添加HealthComponent');
// 2. 添加第一个系统
const system1 = new HealthSystem();
console.log('[Test] 准备添加第一个系统');
const returnedSystem1 = scene.addEntityProcessor(system1);
console.log('[Test] 第一个系统已添加, system1===returnedSystem1?', system1 === returnedSystem1);
console.log('[Test] 第一个系统状态:', {
initializeCalled: system1.initializeCalled,
onAddedEntitiesLength: system1.onAddedEntities.length
});
expect(system1.initializeCalled).toBe(true);
expect(system1.onAddedEntities).toHaveLength(1);
expect(system1.onAddedEntities[0]).toBe(entity);
// 3. 尝试添加第二个同类型系统 - 应该返回已注册的system1
const system2 = new HealthSystem();
console.log('[Test] 准备添加第二个系统, system1===system2?', system1 === system2);
console.log('[Test] 系统是否已在ServiceContainer注册:', scene.services.isRegistered(HealthSystem));
const returnedSystem2 = scene.addEntityProcessor(system2);
console.log('[Test] 第二个系统调用完成');
console.log('[Test] system2===returnedSystem2?', system2 === returnedSystem2);
console.log('[Test] returnedSystem1===returnedSystem2?', returnedSystem1 === returnedSystem2);
// 验证单例行为: returnedSystem2应该是system1而不是system2
expect(returnedSystem1).toBe(returnedSystem2);
expect(returnedSystem2).toBe(system1);
expect(returnedSystem2).not.toBe(system2);
// system2不应该被初始化(因为被拒绝了)
expect(system2.initializeCalled).toBe(false);
expect(system2.onAddedEntities).toHaveLength(0);
// 返回的应该是已初始化的system1
expect(returnedSystem2.initializeCalled).toBe(true);
expect(returnedSystem2.onAddedEntities).toHaveLength(1);
console.log('=== 单例模式测试结束 ===\n');
});
});

View File

@@ -60,15 +60,15 @@ afterEach(() => {
const { Core } = require('../src/Core'); const { Core } = require('../src/Core');
const { WorldManager } = require('../src/ECS/WorldManager'); const { WorldManager } = require('../src/ECS/WorldManager');
// 重置 Core 和 WorldManager 单例 // 销毁 Core 和 WorldManager 单例
if (Core._instance) { if (Core._instance) {
Core.reset(); Core.destroy();
} }
if (WorldManager._instance) { if (WorldManager._instance) {
if (WorldManager._instance.destroy) { if (WorldManager._instance.destroy) {
WorldManager._instance.destroy(); WorldManager._instance.destroy();
} }
WorldManager.reset(); WorldManager._instance = null;
} }
} catch (error) { } catch (error) {
// 忽略清理错误 // 忽略清理错误