Files
esengine/tests/ECS/Core/SystemInitializeIssue.test.ts
YHH 65386ff731 优化EntitySystem初始化逻辑/防止多次初始化
增加matcher和entitysystem的测试
2025-07-30 15:42:19 +08:00

484 lines
19 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 {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class VelocityComponent extends Component {
constructor(public vx: number = 0, public vy: number = 0) {
super();
}
}
class HealthComponent extends Component {
constructor(public health: number = 100) {
super();
}
}
class TagComponent extends Component {
constructor(public tag: string = '') {
super();
}
}
// 测试系统
class MovementSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public onRemovedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override onRemoved(entity: Entity): void {
this.onRemovedEntities.push(entity);
}
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[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public onRemovedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override onRemoved(entity: Entity): void {
this.onRemovedEntities.push(entity);
}
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;
}
}
}
}
class MultiComponentSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
constructor() {
super(Matcher.empty().all(PositionComponent, HealthComponent, TagComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
}
}
describe('ECS系统初始化时序问题深度测试', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
scene.name = "InitializeTestScene";
});
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);
// 验证initialize方法被调用
expect(movementSystem.initializeCalled).toBe(true);
expect(healthSystem.initializeCalled).toBe(true);
// 验证系统正确识别已存在的实体
expect(movementSystem.entities.length).toBe(2);
expect(healthSystem.entities.length).toBe(2);
// 验证onAdded回调被正确调用
expect(movementSystem.onAddedEntities.length).toBe(2);
expect(movementSystem.onAddedEntities).toContain(player);
expect(movementSystem.onAddedEntities).toContain(enemy);
// 运行更新确认处理
scene.update();
expect(movementSystem.processedEntities.length).toBe(2);
expect(healthSystem.processedEntities.length).toBe(2);
// 检查移动逻辑是否生效
const playerPos = player.getComponent(PositionComponent)!;
expect(playerPos.x).toBe(11);
expect(playerPos.y).toBe(21);
});
test('先添加系统再添加实体 - 正常工作', () => {
// 先添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证initialize被调用但没有发现实体
expect(movementSystem.initializeCalled).toBe(true);
expect(healthSystem.initializeCalled).toBe(true);
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));
// 系统应该自动识别新实体
expect(movementSystem.entities.length).toBe(1);
expect(healthSystem.entities.length).toBe(1);
expect(movementSystem.onAddedEntities.length).toBe(1);
});
});
describe('复杂场景的时序测试', () => {
test('部分匹配实体的初始化', () => {
// 创建不同类型的实体
const fullEntity = scene.createEntity("FullEntity");
fullEntity.addComponent(new PositionComponent(0, 0));
fullEntity.addComponent(new VelocityComponent(1, 1));
fullEntity.addComponent(new HealthComponent(100));
const partialEntity1 = scene.createEntity("PartialEntity1");
partialEntity1.addComponent(new PositionComponent(10, 10));
partialEntity1.addComponent(new HealthComponent(50));
// 缺少VelocityComponent
const partialEntity2 = scene.createEntity("PartialEntity2");
partialEntity2.addComponent(new PositionComponent(20, 20));
partialEntity2.addComponent(new VelocityComponent(2, 2));
// 缺少HealthComponent
// 添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证选择性匹配
expect(movementSystem.entities).toContain(fullEntity);
expect(movementSystem.entities).not.toContain(partialEntity1);
expect(movementSystem.entities).toContain(partialEntity2);
expect(movementSystem.entities.length).toBe(2);
expect(healthSystem.entities).toContain(fullEntity);
expect(healthSystem.entities).toContain(partialEntity1);
expect(healthSystem.entities).not.toContain(partialEntity2);
expect(healthSystem.entities.length).toBe(2);
});
test('多组件要求系统的初始化', () => {
// 创建具有不同组件组合的实体
const entity1 = scene.createEntity("Entity1");
entity1.addComponent(new PositionComponent(0, 0));
entity1.addComponent(new HealthComponent(100));
entity1.addComponent(new TagComponent("player"));
const entity2 = scene.createEntity("Entity2");
entity2.addComponent(new PositionComponent(10, 10));
entity2.addComponent(new HealthComponent(80));
// 缺少TagComponent
const entity3 = scene.createEntity("Entity3");
entity3.addComponent(new PositionComponent(20, 20));
entity3.addComponent(new TagComponent("enemy"));
// 缺少HealthComponent
// 添加要求三个组件的系统
const multiSystem = new MultiComponentSystem();
scene.addEntityProcessor(multiSystem);
// 只有entity1应该匹配
expect(multiSystem.entities.length).toBe(1);
expect(multiSystem.entities).toContain(entity1);
expect(multiSystem.entities).not.toContain(entity2);
expect(multiSystem.entities).not.toContain(entity3);
});
test('批量实体创建后的系统初始化', () => {
// 批量创建实体
const entities = scene.createEntities(10, "BatchEntity");
// 为所有实体添加组件
entities.forEach((entity, index) => {
entity.addComponent(new PositionComponent(index * 10, index * 10));
entity.addComponent(new VelocityComponent(index, index));
if (index % 2 === 0) {
entity.addComponent(new HealthComponent(100 - index * 10));
}
});
// 添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证系统正确处理批量实体
expect(movementSystem.entities.length).toBe(10); // 所有实体都有Position+Velocity
expect(healthSystem.entities.length).toBe(5); // 只有偶数索引的实体有Health
// 验证onAdded回调被正确调用
expect(movementSystem.onAddedEntities.length).toBe(10);
expect(healthSystem.onAddedEntities.length).toBe(5);
});
});
describe('动态组件修改后的系统响应', () => {
test('运行时添加组件 - 系统自动响应', () => {
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
// 创建只有位置组件的实体
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
// 系统不应该匹配
expect(movementSystem.entities.length).toBe(0);
// 添加速度组件
entity.addComponent(new VelocityComponent(5, 5));
// 系统应该立即识别
expect(movementSystem.entities.length).toBe(1);
expect(movementSystem.entities).toContain(entity);
expect(movementSystem.onAddedEntities).toContain(entity);
});
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);
}
// 系统应该移除实体
expect(movementSystem.entities.length).toBe(0);
expect(movementSystem.onRemovedEntities).toContain(entity);
});
test('复杂的组件添加移除序列', () => {
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
const entity = scene.createEntity("ComplexEntity");
// 初始状态:无组件
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 添加位置组件
entity.addComponent(new PositionComponent(0, 0));
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 添加健康组件
entity.addComponent(new HealthComponent(100));
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(1);
// 添加速度组件
entity.addComponent(new VelocityComponent(1, 1));
expect(movementSystem.entities.length).toBe(1);
expect(healthSystem.entities.length).toBe(1);
// 移除健康组件
const healthComponent = entity.getComponent(HealthComponent);
if (healthComponent) {
entity.removeComponent(healthComponent);
}
expect(movementSystem.entities.length).toBe(1);
expect(healthSystem.entities.length).toBe(0);
// 移除位置组件
const positionComponent = entity.getComponent(PositionComponent);
if (positionComponent) {
entity.removeComponent(positionComponent);
}
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
});
});
describe('系统重复添加和移除测试', () => {
test('重复添加同一个系统 - 应该忽略', () => {
const movementSystem = new MovementSystem();
// 第一次添加
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1);
expect(movementSystem.initializeCalled).toBe(true);
// 重置标志
movementSystem.initializeCalled = false;
// 第二次添加同一个系统
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1); // 没有增加
expect(movementSystem.initializeCalled).toBe(false); // initialize不应该再次调用
});
test('添加后移除再添加 - 应该重新初始化', () => {
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
const movementSystem = new MovementSystem();
// 第一次添加
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(1);
expect(movementSystem.initializeCalled).toBe(true);
// 移除系统
scene.removeEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(0);
// 重置状态
movementSystem.initializeCalled = false;
movementSystem.onAddedEntities = [];
// 重新添加
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(1);
expect(movementSystem.initializeCalled).toBe(true);
expect(movementSystem.onAddedEntities.length).toBe(1);
});
});
describe('空场景和空系统的边界情况', () => {
test('空场景添加系统 - 不应该出错', () => {
const movementSystem = new MovementSystem();
expect(() => {
scene.addEntityProcessor(movementSystem);
}).not.toThrow();
expect(movementSystem.initializeCalled).toBe(true);
expect(movementSystem.entities.length).toBe(0);
});
test('有实体但没有匹配组件 - 系统应该为空', () => {
// 创建只有健康组件的实体
const entity = scene.createEntity("HealthOnlyEntity");
entity.addComponent(new HealthComponent(100));
// 添加移动系统需要Position+Velocity
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(0);
expect(movementSystem.onAddedEntities.length).toBe(0);
});
test('实体被禁用 - 系统仍应包含但不处理', () => {
const entity = scene.createEntity("DisabledEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(1);
// 禁用实体
entity.enabled = false;
// 系统仍然包含实体,但处理时应该跳过
expect(movementSystem.entities.length).toBe(1);
scene.update();
// 处理逻辑中应该检查enabled状态
// 由于实体被禁用,位置不应该改变(这取决于系统实现)
});
});
afterEach(() => {
scene.destroyAllEntities();
const processors = [...scene.entityProcessors.processors];
processors.forEach(processor => scene.removeEntityProcessor(processor));
});
});