Files
esengine/tests/ECS/Scene.test.ts

552 lines
19 KiB
TypeScript
Raw Permalink 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 RenderComponent extends Component {
constructor(public visible: boolean = true) {
super();
}
}
// 测试系统
class MovementSystem extends EntitySystem {
public processCallCount = 0;
public lastProcessedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
protected override process(entities: Entity[]): void {
this.processCallCount++;
this.lastProcessedEntities = [...entities];
for (const entity of entities) {
const position = entity.getComponent(PositionComponent);
const velocity = entity.getComponent(VelocityComponent);
if (position && velocity) {
position.x += velocity.vx;
position.y += velocity.vy;
}
}
}
}
class RenderSystem extends EntitySystem {
public processCallCount = 0;
public lastProcessedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, RenderComponent));
}
protected override process(entities: Entity[]): void {
this.processCallCount++;
this.lastProcessedEntities = [...entities];
}
}
// 测试场景
class TestScene extends Scene {
public initializeCalled = false;
public beginCalled = false;
public endCalled = false;
public updateCallCount = 0;
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
public override begin(): void {
this.beginCalled = true;
super.begin();
}
public override end(): void {
this.endCalled = true;
super.end();
}
public override update(): void {
this.updateCallCount++;
super.update();
}
}
describe('Scene - 场景管理系统测试', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
});
describe('基本功能测试', () => {
test('应该能够创建场景', () => {
expect(scene).toBeDefined();
expect(scene).toBeInstanceOf(Scene);
expect(scene.name).toBe("");
expect(scene.entities).toBeDefined();
expect(scene.entityProcessors).toBeDefined();
expect(scene.identifierPool).toBeDefined();
});
test('应该能够设置场景名称', () => {
scene.name = "TestScene";
expect(scene.name).toBe("TestScene");
});
test('场景应该有正确的初始状态', () => {
expect(scene.entities.count).toBe(0);
expect(scene.entityProcessors.count).toBe(0);
});
});
describe('实体管理', () => {
test('应该能够创建实体', () => {
const entity = scene.createEntity("TestEntity");
expect(entity).toBeDefined();
expect(entity.name).toBe("TestEntity");
expect(entity.id).toBeGreaterThan(0);
expect(scene.entities.count).toBe(1);
});
test('应该能够批量创建实体', () => {
const entities = scene.createEntities(5, "Entity");
expect(entities.length).toBe(5);
expect(scene.entities.count).toBe(5);
for (let i = 0; i < entities.length; i++) {
expect(entities[i].name).toBe(`Entity_${i}`);
expect(entities[i].id).toBeGreaterThan(0);
}
});
test('应该能够通过ID查找实体', () => {
const entity = scene.createEntity("TestEntity");
const found = scene.findEntityById(entity.id);
expect(found).toBe(entity);
});
test('查找不存在的实体应该返回null', () => {
const found = scene.findEntityById(999999);
expect(found).toBeNull();
});
test('应该能够销毁实体', () => {
const entity = scene.createEntity("TestEntity");
const entityId = entity.id;
scene.entities.remove(entity);
expect(scene.entities.count).toBe(0);
expect(scene.findEntityById(entityId)).toBeNull();
});
test('应该能够通过ID销毁实体', () => {
const entity = scene.createEntity("TestEntity");
const entityId = entity.id;
const entityToRemove = scene.findEntityById(entityId)!;
scene.entities.remove(entityToRemove);
expect(scene.entities.count).toBe(0);
expect(scene.findEntityById(entityId)).toBeNull();
});
test('销毁不存在的实体应该安全处理', () => {
expect(() => {
// Scene doesn't have destroyEntity method
expect(scene.findEntityById(999999)).toBeNull();
}).not.toThrow();
});
test('应该能够销毁所有实体', () => {
scene.createEntities(10, "Entity");
expect(scene.entities.count).toBe(10);
scene.destroyAllEntities();
expect(scene.entities.count).toBe(0);
});
});
describe('实体系统管理', () => {
let movementSystem: MovementSystem;
let renderSystem: RenderSystem;
beforeEach(() => {
movementSystem = new MovementSystem();
renderSystem = new RenderSystem();
});
test('应该能够添加实体系统', () => {
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1);
expect(movementSystem.scene).toBe(scene);
});
test('应该能够移除实体系统', () => {
scene.addEntityProcessor(movementSystem);
scene.removeEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(0);
expect(movementSystem.scene).toBeNull();
});
test('移除不存在的系统应该安全处理', () => {
expect(() => {
scene.removeEntityProcessor(movementSystem);
}).not.toThrow();
});
test('应该能够管理多个实体系统', () => {
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(renderSystem);
expect(scene.entityProcessors.count).toBe(2);
});
test('系统应该按更新顺序执行', () => {
movementSystem.updateOrder = 1;
renderSystem.updateOrder = 0;
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(renderSystem);
// 创建测试实体
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
entity.addComponent(new RenderComponent(true));
scene.update();
// RenderSystem应该先执行updateOrder = 0
// MovementSystem应该后执行updateOrder = 1
expect(renderSystem.processCallCount).toBe(1);
expect(movementSystem.processCallCount).toBe(1);
});
});
describe('组件查询系统', () => {
beforeEach(() => {
// 创建测试实体
const entity1 = scene.createEntity("Entity1");
entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent(1, 0));
const entity2 = scene.createEntity("Entity2");
entity2.addComponent(new PositionComponent(30, 40));
entity2.addComponent(new HealthComponent(80));
const entity3 = scene.createEntity("Entity3");
entity3.addComponent(new VelocityComponent(0, 1));
entity3.addComponent(new HealthComponent(120));
});
test('应该能够查询具有特定组件的实体', () => {
const result = scene.querySystem.queryAll(PositionComponent);
expect(result.entities.length).toBe(2);
expect(result.entities[0].name).toBe("Entity1");
expect(result.entities[1].name).toBe("Entity2");
});
test('应该能够查询具有多个组件的实体', () => {
const result = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
expect(result.entities.length).toBe(1);
expect(result.entities[0].name).toBe("Entity1");
});
test('查询不存在的组件应该返回空结果', () => {
const result = scene.querySystem.queryAll(RenderComponent);
expect(result.entities.length).toBe(0);
});
test('查询系统应该支持缓存', () => {
// 第一次查询
const result1 = scene.querySystem.queryAll(PositionComponent);
// 第二次查询(应该使用缓存)
const result2 = scene.querySystem.queryAll(PositionComponent);
// 实体数组应该相同,并且第二次查询应该来自缓存
expect(result1.entities).toEqual(result2.entities);
expect(result2.fromCache).toBe(true);
});
test('组件变化应该更新查询缓存', () => {
const result1 = scene.querySystem.queryAll(PositionComponent);
expect(result1.entities.length).toBe(2);
// 添加新实体
const entity4 = scene.createEntity("Entity4");
entity4.addComponent(new PositionComponent(50, 60));
const result2 = scene.querySystem.queryAll(PositionComponent);
expect(result2.entities.length).toBe(3);
});
});
describe('事件系统', () => {
test('场景应该有事件系统', () => {
expect(scene.eventSystem).toBeDefined();
});
test('应该能够监听实体事件', () => {
let entityCreatedEvent: any = null;
scene.eventSystem.on('entity:created', (data: any) => {
entityCreatedEvent = data;
});
const entity = scene.createEntity("TestEntity");
expect(entityCreatedEvent).toBeDefined();
expect(entityCreatedEvent.entityName).toBe("TestEntity");
});
test('应该能够监听组件事件', () => {
let componentAddedEvent: any = null;
scene.eventSystem.on('component:added', (data: any) => {
componentAddedEvent = data;
});
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(10, 20));
expect(componentAddedEvent).toBeDefined();
expect(componentAddedEvent.componentType).toBe('PositionComponent');
});
});
describe('场景生命周期管理', () => {
let testScene: TestScene;
beforeEach(() => {
testScene = new TestScene();
});
test('应该能够初始化场景', () => {
testScene.initialize();
expect(testScene.initializeCalled).toBe(true);
});
test('应该能够开始场景', () => {
testScene.begin();
expect(testScene.beginCalled).toBe(true);
});
test('应该能够结束场景', () => {
testScene.end();
expect(testScene.endCalled).toBe(true);
});
test('应该能够更新场景', () => {
const movementSystem = new MovementSystem();
testScene.addEntityProcessor(movementSystem);
// 创建测试实体
const entity = testScene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
testScene.update();
expect(testScene.updateCallCount).toBe(1);
expect(movementSystem.processCallCount).toBe(1);
// 验证移动系统是否正确处理了实体
const position = entity.getComponent(PositionComponent);
expect(position?.x).toBe(1);
expect(position?.y).toBe(1);
});
});
describe('统计和调试信息', () => {
test('应该能够获取场景统计信息', () => {
// 创建一些实体和系统
scene.createEntities(5, "Entity");
scene.addEntityProcessor(new MovementSystem());
scene.addEntityProcessor(new RenderSystem());
const stats = scene.getStats();
expect(stats.entityCount).toBe(5);
expect(stats.processorCount).toBe(2);
});
test('空场景的统计信息应该正确', () => {
const stats = scene.getStats();
expect(stats.entityCount).toBe(0);
expect(stats.processorCount).toBe(0);
});
test('查询系统应该有统计信息', () => {
// 执行一些查询以产生统计数据
scene.querySystem.queryAll(PositionComponent);
scene.querySystem.queryAll(VelocityComponent);
const stats = scene.querySystem.getStats();
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThanOrEqual(0);
});
});
describe('内存管理和性能', () => {
test('销毁大量实体应该正确清理内存', () => {
const entityCount = 1000;
const entities = scene.createEntities(entityCount, "Entity");
// 为每个实体添加组件
entities.forEach(entity => {
entity.addComponent(new PositionComponent(Math.random() * 100, Math.random() * 100));
entity.addComponent(new VelocityComponent(Math.random() * 5, Math.random() * 5));
});
expect(scene.entities.count).toBe(entityCount);
// 销毁所有实体
scene.destroyAllEntities();
expect(scene.entities.count).toBe(0);
// 查询应该返回空结果
const positionResult = scene.querySystem.queryAll(PositionComponent);
const velocityResult = scene.querySystem.queryAll(VelocityComponent);
expect(positionResult.entities.length).toBe(0);
expect(velocityResult.entities.length).toBe(0);
});
test('大量实体的创建和查询性能应该可接受', () => {
const entityCount = 5000;
const startTime = performance.now();
// 创建大量实体
const entities = scene.createEntities(entityCount, "Entity");
// 为每个实体添加组件
entities.forEach((entity, index) => {
entity.addComponent(new PositionComponent(index, index));
if (index % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1));
}
if (index % 3 === 0) {
entity.addComponent(new HealthComponent(100));
}
});
const creationTime = performance.now() - startTime;
// 测试查询性能
const queryStartTime = performance.now();
const positionResult = scene.querySystem.queryAll(PositionComponent);
const velocityResult = scene.querySystem.queryAll(VelocityComponent);
const healthResult = scene.querySystem.queryAll(HealthComponent);
const queryTime = performance.now() - queryStartTime;
expect(positionResult.entities.length).toBe(entityCount);
expect(velocityResult.entities.length).toBe(entityCount / 2);
expect(healthResult.entities.length).toBe(Math.floor(entityCount / 3) + 1);
// 性能断言(这些值可能需要根据实际环境调整)
expect(creationTime).toBeLessThan(2000); // 创建应该在2秒内完成
expect(queryTime).toBeLessThan(100); // 查询应该在100ms内完成
console.log(`创建${entityCount}个实体耗时: ${creationTime.toFixed(2)}ms`);
console.log(`查询操作耗时: ${queryTime.toFixed(2)}ms`);
});
});
describe('错误处理和边界情况', () => {
test('重复添加同一个系统应该安全处理', () => {
const system = new MovementSystem();
scene.addEntityProcessor(system);
scene.addEntityProcessor(system); // 重复添加
expect(scene.entityProcessors.count).toBe(1);
});
test('系统处理过程中的异常应该被正确处理', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
class ErrorSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PositionComponent));
}
protected override process(entities: Entity[]): void {
throw new Error("Test system error");
}
}
const errorSystem = new ErrorSystem();
scene.addEntityProcessor(errorSystem);
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
// 更新不应该抛出异常
expect(() => {
scene.update();
}).not.toThrow();
consoleSpy.mockRestore();
});
test('空场景的更新应该安全', () => {
expect(() => {
scene.update();
}).not.toThrow();
});
test('对已销毁实体的操作应该安全处理', () => {
const entity = scene.createEntity("TestEntity");
scene.entities.remove(entity);
// 对已销毁实体的操作应该安全
expect(() => {
entity.addComponent(new PositionComponent(0, 0));
}).not.toThrow();
});
});
});