Files
esengine/tests/ECS/Core/SystemTimingIssue.test.ts
2025-07-31 15:37:40 +08:00

375 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
// 测试组件
class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
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 = 0;
public vy: number = 0;
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 = 100;
constructor(...args: unknown[]) {
super();
const [health = 100] = args as [number?];
this.health = health;
}
}
// 测试系统
class MovementSystem extends EntitySystem {
public processedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
// 简单的移动逻辑
for (const entity of entities) {
const position = entity.getComponent(PositionComponent)!;
const velocity = entity.getComponent(VelocityComponent)!;
position.x += velocity.vx;
position.y += velocity.vy;
}
}
}
class HealthSystem extends EntitySystem {
public processedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
// 简单的健康检查逻辑
for (const entity of entities) {
const health = entity.getComponent(HealthComponent)!;
if (health.health <= 0) {
// 标记为死亡,但不在这里销毁实体
entity.enabled = false;
}
}
}
}
describe('ECS系统时序问题测试', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
scene.name = "TimingTestScene";
});
describe('实体添加时序问题', () => {
test(' 先添加实体再添加系统 - 暴露时序问题', () => {
// 第一步:先创建并添加实体
const player = scene.createEntity("Player");
player.addComponent(new PositionComponent(10, 20));
player.addComponent(new VelocityComponent(1, 1));
player.addComponent(new HealthComponent(100));
const enemy = scene.createEntity("Enemy");
enemy.addComponent(new PositionComponent(50, 60));
enemy.addComponent(new VelocityComponent(-1, 0));
enemy.addComponent(new HealthComponent(80));
// 验证实体已创建
expect(scene.entities.count).toBe(2);
// 第二步:然后添加系统(这里可能出现时序问题)
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证系统已添加
expect(scene.entityProcessors.count).toBe(2);
// 关键测试:检查系统是否正确识别了已存在的实体
console.log("MovementSystem匹配的实体数量:", movementSystem.entities.length);
console.log("HealthSystem匹配的实体数量:", healthSystem.entities.length);
console.log("Player组件:", player.components.map(c => c.constructor.name));
console.log("Enemy组件:", enemy.components.map(c => c.constructor.name));
// 预期结果移动系统应该匹配到2个实体都有Position+Velocity
expect(movementSystem.entities.length).toBe(2);
// 预期结果健康系统应该匹配到2个实体都有Health
expect(healthSystem.entities.length).toBe(2);
// 运行一次更新看看
scene.update();
// 检查系统是否处理了实体
expect(movementSystem.processedEntities.length).toBe(2);
expect(healthSystem.processedEntities.length).toBe(2);
// 检查移动逻辑是否生效
const playerPos = player.getComponent(PositionComponent)!;
expect(playerPos.x).toBe(11); // 10 + 1
expect(playerPos.y).toBe(21); // 20 + 1
});
test(' 先添加系统再添加实体 - 正常工作的情况', () => {
// 第一步:先添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证系统已添加但没有实体
expect(scene.entityProcessors.count).toBe(2);
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 第二步:然后创建并添加实体
const player = scene.createEntity("Player");
player.addComponent(new PositionComponent(10, 20));
player.addComponent(new VelocityComponent(1, 1));
player.addComponent(new HealthComponent(100));
const enemy = scene.createEntity("Enemy");
enemy.addComponent(new PositionComponent(50, 60));
enemy.addComponent(new VelocityComponent(-1, 0));
enemy.addComponent(new HealthComponent(80));
// 验证实体已创建
expect(scene.entities.count).toBe(2);
// 关键测试:检查系统是否正确识别了新添加的实体
console.log("MovementSystem匹配的实体数量:", movementSystem.entities.length);
console.log("HealthSystem匹配的实体数量:", healthSystem.entities.length);
// 预期结果:系统应该自动匹配到新实体
expect(movementSystem.entities.length).toBe(2);
expect(healthSystem.entities.length).toBe(2);
// 运行一次更新
scene.update();
// 检查系统是否处理了实体
expect(movementSystem.processedEntities.length).toBe(2);
expect(healthSystem.processedEntities.length).toBe(2);
});
});
describe('组件动态修改时序问题', () => {
test(' 运行时动态添加组件 - 检查系统响应', () => {
// 先设置好系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 创建只有位置组件的实体
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
// 初始状态:只有健康系统不匹配,移动系统不匹配
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 动态添加速度组件
entity.addComponent(new VelocityComponent(5, 5));
// 关键测试:移动系统是否立即识别到这个实体
console.log("添加VelocityComponent后MovementSystem实体数:", movementSystem.entities.length);
expect(movementSystem.entities.length).toBe(1);
// 动态添加健康组件
entity.addComponent(new HealthComponent(50));
// 关键测试:健康系统是否立即识别到这个实体
console.log("添加HealthComponent后HealthSystem实体数:", healthSystem.entities.length);
expect(healthSystem.entities.length).toBe(1);
// 运行更新确认处理
scene.update();
expect(movementSystem.processedEntities.length).toBe(1);
expect(healthSystem.processedEntities.length).toBe(1);
});
test(' 运行时动态移除组件 - 检查系统响应', () => {
// 先设置好系统
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
// 创建完整的可移动实体
const entity = scene.createEntity("MovableEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(5, 5));
// 确认系统识别了实体
expect(movementSystem.entities.length).toBe(1);
// 动态移除速度组件
const velocityComponent = entity.getComponent(VelocityComponent);
if (velocityComponent) {
entity.removeComponent(velocityComponent);
}
// 关键测试:移动系统是否立即移除了这个实体
console.log("移除VelocityComponent后MovementSystem实体数:", movementSystem.entities.length);
expect(movementSystem.entities.length).toBe(0);
// 重新添加速度组件
entity.addComponent(new VelocityComponent(3, 3));
// 关键测试:移动系统是否重新识别到这个实体
console.log("重新添加VelocityComponent后MovementSystem实体数:", movementSystem.entities.length);
expect(movementSystem.entities.length).toBe(1);
});
});
describe('系统初始化时序问题', () => {
test(' 系统initialize方法调用时机', () => {
// 创建实体
const entity1 = scene.createEntity("Entity1");
entity1.addComponent(new PositionComponent(10, 10));
entity1.addComponent(new VelocityComponent(1, 1));
const entity2 = scene.createEntity("Entity2");
entity2.addComponent(new PositionComponent(20, 20));
entity2.addComponent(new VelocityComponent(2, 2));
// 创建系统但先不添加到场景
const movementSystem = new MovementSystem();
// 检查系统初始状态
expect(movementSystem.entities.length).toBe(0);
// 添加系统到场景
scene.addEntityProcessor(movementSystem);
// 关键测试:系统是否在添加时正确初始化并发现已存在的实体
console.log("系统添加后发现的实体数:", movementSystem.entities.length);
// 这个测试会暴露initialize方法是否被正确调用
expect(movementSystem.entities.length).toBe(2);
});
test(' 批量实体操作的时序问题', () => {
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
// 批量创建实体
const entities = scene.createEntities(5, "BatchEntity");
// 为所有实体添加组件
entities.forEach((entity, index) => {
entity.addComponent(new PositionComponent(index * 10, index * 10));
entity.addComponent(new VelocityComponent(index, index));
});
// 关键测试:系统是否识别了所有批量创建的实体
console.log("批量操作后系统实体数:", movementSystem.entities.length);
expect(movementSystem.entities.length).toBe(5);
// 运行更新确认处理
scene.update();
expect(movementSystem.processedEntities.length).toBe(5);
});
});
describe('事件装饰器时序问题', () => {
// 模拟使用事件装饰器的系统
class EventDecoratedSystem extends EntitySystem {
public receivedEvents: any[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent));
// 模拟装饰器初始化
this.initEventListeners();
}
// 模拟 @EventListener('entity:moved') 装饰器
private initEventListeners() {
// 这里应该通过装饰器自动完成
scene.eventSystem?.on('entity:moved', (data) => {
this.onEntityMoved(data);
});
}
public onEntityMoved(data: any) {
this.receivedEvents.push(data);
}
protected override process(entities: Entity[]): void {
// 处理实体移动并发射事件
for (const entity of entities) {
const pos = entity.getComponent(PositionComponent)!;
// 模拟移动
pos.x += 1;
// 发射移动事件
scene.eventSystem?.emit('entity:moved', {
entityId: entity.id,
position: { x: pos.x, y: pos.y }
});
}
}
}
test(' 事件装饰器系统的初始化时序', () => {
// 先创建实体
const entity = scene.createEntity("EventTestEntity");
entity.addComponent(new PositionComponent(0, 0));
// 然后添加使用事件装饰器的系统
const eventSystem = new EventDecoratedSystem();
scene.addEntityProcessor(eventSystem);
// 验证系统正确识别了实体
expect(eventSystem.entities.length).toBe(1);
// 运行更新,应该触发事件
scene.update();
// 关键测试:事件装饰器是否正确工作
console.log("接收到的事件数量:", eventSystem.receivedEvents.length);
expect(eventSystem.receivedEvents.length).toBe(1);
// 验证事件数据
const event = eventSystem.receivedEvents[0];
expect(event.entityId).toBe(entity.id);
expect(event.position.x).toBe(1); // 移动后的位置
});
});
afterEach(() => {
// 清理场景
scene.destroyAllEntities();
// 手动清理系统
const processors = [...scene.entityProcessors.processors];
processors.forEach(processor => scene.removeEntityProcessor(processor));
});
});