修复响应式查询缓存失效和测试隔离问题
This commit is contained in:
94
packages/core/tests/ECS/Core/MinimalSystemInit.test.ts
Normal file
94
packages/core/tests/ECS/Core/MinimalSystemInit.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
160
packages/core/tests/ECS/Core/MultiSystemInit.test.ts
Normal file
160
packages/core/tests/ECS/Core/MultiSystemInit.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
92
packages/core/tests/ECS/Core/ReactiveQueryDebug.test.ts
Normal file
92
packages/core/tests/ECS/Core/ReactiveQueryDebug.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -205,11 +205,16 @@ describe('SystemInitialization - 系统初始化测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
scene = new Scene();
|
||||
scene.name = 'InitializationTestScene';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (scene) {
|
||||
scene.end();
|
||||
}
|
||||
});
|
||||
|
||||
describe('初始化时序', () => {
|
||||
test('先添加实体再添加系统 - 系统应该正确初始化', () => {
|
||||
const player = scene.createEntity('Player');
|
||||
|
||||
164
packages/core/tests/ECS/Core/SystemInitializationDebug.test.ts
Normal file
164
packages/core/tests/ECS/Core/SystemInitializationDebug.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -60,15 +60,15 @@ afterEach(() => {
|
||||
const { Core } = require('../src/Core');
|
||||
const { WorldManager } = require('../src/ECS/WorldManager');
|
||||
|
||||
// 重置 Core 和 WorldManager 单例
|
||||
// 销毁 Core 和 WorldManager 单例
|
||||
if (Core._instance) {
|
||||
Core.reset();
|
||||
Core.destroy();
|
||||
}
|
||||
if (WorldManager._instance) {
|
||||
if (WorldManager._instance.destroy) {
|
||||
WorldManager._instance.destroy();
|
||||
}
|
||||
WorldManager.reset();
|
||||
WorldManager._instance = null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略清理错误
|
||||
|
||||
Reference in New Issue
Block a user