Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级 * feat(core): 层级系统重构与UI变换矩阵修复 * refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题 * fix(physics): 修复跨包组件类引用问题 * feat: 统一运行时架构与浏览器运行支持 * feat(asset): 实现浏览器运行时资产加载系统 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误 * test: 补齐核心模块测试用例,修复CI构建配置 * fix: 修复测试用例中的类型错误和断言问题 * fix: 修复 turbo build:npm 任务的依赖顺序问题 * fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
@@ -1,691 +0,0 @@
|
||||
import {
|
||||
EntityBuilder,
|
||||
SceneBuilder,
|
||||
ComponentBuilder,
|
||||
ECSFluentAPI,
|
||||
EntityBatchOperator,
|
||||
createECSAPI,
|
||||
initializeECS,
|
||||
ECS
|
||||
} from '../../../src/ECS/Core/FluentAPI';
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { QuerySystem } from '../../../src/ECS/Core/QuerySystem';
|
||||
import { TypeSafeEventSystem } from '../../../src/ECS/Core/EventSystem';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class TestComponent extends Component {
|
||||
public value: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [value = 0] = args as [number?];
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
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 TestSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(TestComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 测试系统
|
||||
}
|
||||
}
|
||||
|
||||
describe('FluentAPI - 流式API测试', () => {
|
||||
let scene: Scene;
|
||||
let querySystem: QuerySystem;
|
||||
let eventSystem: TypeSafeEventSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
querySystem = new QuerySystem();
|
||||
eventSystem = new TypeSafeEventSystem();
|
||||
});
|
||||
|
||||
describe('EntityBuilder - 实体构建器', () => {
|
||||
let builder: EntityBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
builder = new EntityBuilder(scene, scene.componentStorageManager);
|
||||
});
|
||||
|
||||
test('应该能够创建实体构建器', () => {
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('应该能够设置实体名称', () => {
|
||||
const entity = builder.named('TestEntity').build();
|
||||
expect(entity.name).toBe('TestEntity');
|
||||
});
|
||||
|
||||
test('应该能够设置实体标签', () => {
|
||||
const entity = builder.tagged(42).build();
|
||||
expect(entity.tag).toBe(42);
|
||||
});
|
||||
|
||||
test('应该能够添加组件', () => {
|
||||
const component = new TestComponent(100);
|
||||
const entity = builder.with(component).build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)).toBe(component);
|
||||
});
|
||||
|
||||
test('应该能够添加多个组件', () => {
|
||||
const comp1 = new TestComponent(100);
|
||||
const comp2 = new PositionComponent(10, 20);
|
||||
const comp3 = new VelocityComponent(1, 2);
|
||||
|
||||
const entity = builder.withComponents(comp1, comp2, comp3).build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(VelocityComponent)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够条件性添加组件', () => {
|
||||
const comp1 = new TestComponent(100);
|
||||
const comp2 = new PositionComponent(10, 20);
|
||||
|
||||
const entity = builder
|
||||
.withIf(true, comp1)
|
||||
.withIf(false, comp2)
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够使用工厂函数创建组件', () => {
|
||||
const entity = builder
|
||||
.withFactory(() => new TestComponent(200))
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)!.value).toBe(200);
|
||||
});
|
||||
|
||||
test('应该能够配置组件属性', () => {
|
||||
const entity = builder
|
||||
.with(new TestComponent(100))
|
||||
.configure(TestComponent, (comp) => {
|
||||
comp.value = 300;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(entity.getComponent(TestComponent)!.value).toBe(300);
|
||||
});
|
||||
|
||||
test('配置不存在的组件应该安全处理', () => {
|
||||
expect(() => {
|
||||
builder.configure(TestComponent, (comp) => {
|
||||
comp.value = 300;
|
||||
}).build();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('应该能够设置实体启用状态', () => {
|
||||
const entity1 = builder.enabled(true).build();
|
||||
const entity2 = new EntityBuilder(scene, scene.componentStorageManager).enabled(false).build();
|
||||
|
||||
expect(entity1.enabled).toBe(true);
|
||||
expect(entity2.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够设置实体活跃状态', () => {
|
||||
const entity1 = builder.active(true).build();
|
||||
const entity2 = new EntityBuilder(scene, scene.componentStorageManager).active(false).build();
|
||||
|
||||
expect(entity1.active).toBe(true);
|
||||
expect(entity2.active).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够添加子实体', () => {
|
||||
const childBuilder = new EntityBuilder(scene, scene.componentStorageManager)
|
||||
.named('Child')
|
||||
.with(new TestComponent(50));
|
||||
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChild(childBuilder)
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(1);
|
||||
expect(parent.children[0].name).toBe('Child');
|
||||
});
|
||||
|
||||
test('应该能够添加多个子实体', () => {
|
||||
const child1 = new EntityBuilder(scene, scene.componentStorageManager).named('Child1');
|
||||
const child2 = new EntityBuilder(scene, scene.componentStorageManager).named('Child2');
|
||||
const child3 = new EntityBuilder(scene, scene.componentStorageManager).named('Child3');
|
||||
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChildren(child1, child2, child3)
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(3);
|
||||
expect(parent.children[0].name).toBe('Child1');
|
||||
expect(parent.children[1].name).toBe('Child2');
|
||||
expect(parent.children[2].name).toBe('Child3');
|
||||
});
|
||||
|
||||
test('应该能够使用工厂函数创建子实体', () => {
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChildFactory((parentEntity) => {
|
||||
return new EntityBuilder(scene, scene.componentStorageManager)
|
||||
.named(`Child_of_${parentEntity.name}`)
|
||||
.with(new TestComponent(100));
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(1);
|
||||
expect(parent.children[0].name).toBe('Child_of_Parent');
|
||||
});
|
||||
|
||||
test('应该能够条件性添加子实体', () => {
|
||||
const child1 = new EntityBuilder(scene, scene.componentStorageManager).named('Child1');
|
||||
const child2 = new EntityBuilder(scene, scene.componentStorageManager).named('Child2');
|
||||
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChildIf(true, child1)
|
||||
.withChildIf(false, child2)
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(1);
|
||||
expect(parent.children[0].name).toBe('Child1');
|
||||
});
|
||||
|
||||
test('应该能够构建实体并添加到场景', () => {
|
||||
const entity = builder
|
||||
.named('SpawnedEntity')
|
||||
.with(new TestComponent(100))
|
||||
.spawn();
|
||||
|
||||
expect(entity.name).toBe('SpawnedEntity');
|
||||
expect(entity.scene).toBe(scene);
|
||||
});
|
||||
|
||||
test('应该能够克隆构建器', () => {
|
||||
const originalBuilder = builder.named('Original').with(new TestComponent(100));
|
||||
const clonedBuilder = originalBuilder.clone();
|
||||
|
||||
expect(clonedBuilder).toBeInstanceOf(EntityBuilder);
|
||||
expect(clonedBuilder).not.toBe(originalBuilder);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const entity = builder
|
||||
.named('ComplexEntity')
|
||||
.tagged(42)
|
||||
.with(new TestComponent(100))
|
||||
.with(new PositionComponent(10, 20))
|
||||
.enabled(true)
|
||||
.active(true)
|
||||
.configure(TestComponent, (comp) => {
|
||||
comp.value = 200;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(entity.name).toBe('ComplexEntity');
|
||||
expect(entity.tag).toBe(42);
|
||||
expect(entity.enabled).toBe(true);
|
||||
expect(entity.active).toBe(true);
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)!.value).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SceneBuilder - 场景构建器', () => {
|
||||
let builder: SceneBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
builder = new SceneBuilder();
|
||||
});
|
||||
|
||||
test('应该能够创建场景构建器', () => {
|
||||
expect(builder).toBeInstanceOf(SceneBuilder);
|
||||
});
|
||||
|
||||
test('应该能够设置场景名称', () => {
|
||||
const scene = builder.named('TestScene').build();
|
||||
expect(scene.name).toBe('TestScene');
|
||||
});
|
||||
|
||||
test('应该能够添加实体', () => {
|
||||
const entity = new Entity('TestEntity', 1);
|
||||
const scene = builder.withEntity(entity).build();
|
||||
|
||||
expect(scene.entities.count).toBe(1);
|
||||
expect(scene.findEntity('TestEntity')).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够使用实体构建器添加实体', () => {
|
||||
const scene = builder
|
||||
.withEntityBuilder((builder) => {
|
||||
return builder
|
||||
.named('BuilderEntity')
|
||||
.with(new TestComponent(100));
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(scene.entities.count).toBe(1);
|
||||
expect(scene.findEntity('BuilderEntity')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够批量添加实体', () => {
|
||||
const entity1 = new Entity('Entity1', 1);
|
||||
const entity2 = new Entity('Entity2', 2);
|
||||
const entity3 = new Entity('Entity3', 3);
|
||||
|
||||
const scene = builder
|
||||
.withEntities(entity1, entity2, entity3)
|
||||
.build();
|
||||
|
||||
expect(scene.entities.count).toBe(3);
|
||||
});
|
||||
|
||||
test('应该能够添加系统', () => {
|
||||
const system = new TestSystem();
|
||||
const scene = builder.withSystem(system).build();
|
||||
|
||||
expect(scene.systems.length).toBe(1);
|
||||
expect(scene.systems[0]).toBe(system);
|
||||
});
|
||||
|
||||
test('应该能够批量添加系统', () => {
|
||||
const system1 = new TestSystem();
|
||||
const system2 = new TestSystem();
|
||||
|
||||
const scene = builder
|
||||
.withSystems(system1, system2)
|
||||
.build();
|
||||
|
||||
expect(scene.systems.length).toBe(1);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const entity = new Entity('TestEntity', 1);
|
||||
const system = new TestSystem();
|
||||
|
||||
const scene = builder
|
||||
.named('ComplexScene')
|
||||
.withEntity(entity)
|
||||
.withSystem(system)
|
||||
.withEntityBuilder((builder) => {
|
||||
return builder.named('BuilderEntity');
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(scene.name).toBe('ComplexScene');
|
||||
expect(scene.entities.count).toBe(2);
|
||||
expect(scene.systems.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentBuilder - 组件构建器', () => {
|
||||
test('应该能够创建组件构建器', () => {
|
||||
const builder = new ComponentBuilder(TestComponent, 100);
|
||||
expect(builder).toBeInstanceOf(ComponentBuilder);
|
||||
});
|
||||
|
||||
test('应该能够设置组件属性', () => {
|
||||
const component = new ComponentBuilder(TestComponent, 100)
|
||||
.set('value', 200)
|
||||
.build();
|
||||
|
||||
expect(component.value).toBe(200);
|
||||
});
|
||||
|
||||
test('应该能够使用配置函数', () => {
|
||||
const component = new ComponentBuilder(PositionComponent, 10, 20)
|
||||
.configure((comp) => {
|
||||
comp.x = 30;
|
||||
comp.y = 40;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(component.x).toBe(30);
|
||||
expect(component.y).toBe(40);
|
||||
});
|
||||
|
||||
test('应该能够条件性设置属性', () => {
|
||||
const component = new ComponentBuilder(TestComponent, 100)
|
||||
.setIf(true, 'value', 200)
|
||||
.setIf(false, 'value', 300)
|
||||
.build();
|
||||
|
||||
expect(component.value).toBe(200);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const component = new ComponentBuilder(PositionComponent, 0, 0)
|
||||
.set('x', 10)
|
||||
.set('y', 20)
|
||||
.setIf(true, 'x', 30)
|
||||
.configure((comp) => {
|
||||
comp.y = 40;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(component.x).toBe(30);
|
||||
expect(component.y).toBe(40);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ECSFluentAPI - 主API', () => {
|
||||
let api: ECSFluentAPI;
|
||||
|
||||
beforeEach(() => {
|
||||
api = new ECSFluentAPI(scene, querySystem, eventSystem);
|
||||
});
|
||||
|
||||
test('应该能够创建ECS API', () => {
|
||||
expect(api).toBeInstanceOf(ECSFluentAPI);
|
||||
});
|
||||
|
||||
test('应该能够创建实体构建器', () => {
|
||||
const builder = api.createEntity();
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('应该能够创建场景构建器', () => {
|
||||
const builder = api.createScene();
|
||||
expect(builder).toBeInstanceOf(SceneBuilder);
|
||||
});
|
||||
|
||||
test('应该能够创建组件构建器', () => {
|
||||
const builder = api.createComponent(TestComponent, 100);
|
||||
expect(builder).toBeInstanceOf(ComponentBuilder);
|
||||
});
|
||||
|
||||
test('应该能够创建查询构建器', () => {
|
||||
const builder = api.query();
|
||||
expect(builder).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够查找实体', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new TestComponent(100));
|
||||
querySystem.setEntities([entity]);
|
||||
|
||||
const results = api.find(TestComponent);
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0]).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够查找第一个匹配的实体', () => {
|
||||
const entity1 = scene.createEntity('Entity1');
|
||||
const entity2 = scene.createEntity('Entity2');
|
||||
entity1.addComponent(new TestComponent(100));
|
||||
entity2.addComponent(new TestComponent(200));
|
||||
querySystem.setEntities([entity1, entity2]);
|
||||
|
||||
const result = api.findFirst(TestComponent);
|
||||
expect(result).not.toBeNull();
|
||||
expect([entity1, entity2]).toContain(result);
|
||||
});
|
||||
|
||||
test('查找不存在的实体应该返回null', () => {
|
||||
const result = api.findFirst(TestComponent);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够按名称查找实体', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
|
||||
const result = api.findByName('TestEntity');
|
||||
expect(result).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够按标签查找实体', () => {
|
||||
const entity1 = scene.createEntity('Entity1');
|
||||
const entity2 = scene.createEntity('Entity2');
|
||||
entity1.tag = 42;
|
||||
entity2.tag = 42;
|
||||
|
||||
const results = api.findByTag(42);
|
||||
expect(results.length).toBe(2);
|
||||
expect(results).toContain(entity1);
|
||||
expect(results).toContain(entity2);
|
||||
});
|
||||
|
||||
test('应该能够触发同步事件', () => {
|
||||
let eventReceived = false;
|
||||
let eventData: any = null;
|
||||
|
||||
api.on('test:event', (data) => {
|
||||
eventReceived = true;
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
api.emit('test:event', { message: 'hello' });
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
expect(eventData.message).toBe('hello');
|
||||
});
|
||||
|
||||
test('应该能够触发异步事件', async () => {
|
||||
let eventReceived = false;
|
||||
let eventData: any = null;
|
||||
|
||||
api.on('test:event', (data) => {
|
||||
eventReceived = true;
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
await api.emitAsync('test:event', { message: 'hello' });
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
expect(eventData.message).toBe('hello');
|
||||
});
|
||||
|
||||
test('应该能够一次性监听事件', () => {
|
||||
let callCount = 0;
|
||||
|
||||
api.once('test:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
api.emit('test:event', {});
|
||||
api.emit('test:event', {});
|
||||
|
||||
expect(callCount).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够移除事件监听器', () => {
|
||||
let callCount = 0;
|
||||
|
||||
const listenerId = api.on('test:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
api.emit('test:event', {});
|
||||
api.off('test:event', listenerId);
|
||||
api.emit('test:event', {});
|
||||
|
||||
expect(callCount).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够创建批量操作器', () => {
|
||||
const entity1 = new Entity('Entity1', 1);
|
||||
const entity2 = new Entity('Entity2', 2);
|
||||
|
||||
const batch = api.batch([entity1, entity2]);
|
||||
expect(batch).toBeInstanceOf(EntityBatchOperator);
|
||||
});
|
||||
|
||||
test('应该能够获取统计信息', () => {
|
||||
const stats = api.getStats();
|
||||
|
||||
expect(stats).toBeDefined();
|
||||
expect(stats.entityCount).toBeDefined();
|
||||
expect(stats.systemCount).toBeDefined();
|
||||
expect(stats.componentStats).toBeDefined();
|
||||
expect(stats.queryStats).toBeDefined();
|
||||
expect(stats.eventStats).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('EntityBatchOperator - 批量操作器', () => {
|
||||
let entity1: Entity;
|
||||
let entity2: Entity;
|
||||
let entity3: Entity;
|
||||
let batchOp: EntityBatchOperator;
|
||||
|
||||
beforeEach(() => {
|
||||
entity1 = scene.createEntity('Entity1');
|
||||
entity2 = scene.createEntity('Entity2');
|
||||
entity3 = scene.createEntity('Entity3');
|
||||
batchOp = new EntityBatchOperator([entity1, entity2, entity3]);
|
||||
});
|
||||
|
||||
test('应该能够创建批量操作器', () => {
|
||||
expect(batchOp).toBeInstanceOf(EntityBatchOperator);
|
||||
});
|
||||
|
||||
test('应该能够批量添加组件', () => {
|
||||
const component = new TestComponent(100);
|
||||
batchOp.addComponent(component);
|
||||
|
||||
expect(entity1.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity2.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity3.hasComponent(TestComponent)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够批量移除组件', () => {
|
||||
entity1.addComponent(new TestComponent(100));
|
||||
entity2.addComponent(new TestComponent(200));
|
||||
entity3.addComponent(new TestComponent(300));
|
||||
|
||||
batchOp.removeComponent(TestComponent);
|
||||
|
||||
expect(entity1.hasComponent(TestComponent)).toBe(false);
|
||||
expect(entity2.hasComponent(TestComponent)).toBe(false);
|
||||
expect(entity3.hasComponent(TestComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够批量设置活跃状态', () => {
|
||||
batchOp.setActive(false);
|
||||
|
||||
expect(entity1.active).toBe(false);
|
||||
expect(entity2.active).toBe(false);
|
||||
expect(entity3.active).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够批量设置标签', () => {
|
||||
batchOp.setTag(42);
|
||||
|
||||
expect(entity1.tag).toBe(42);
|
||||
expect(entity2.tag).toBe(42);
|
||||
expect(entity3.tag).toBe(42);
|
||||
});
|
||||
|
||||
test('应该能够批量执行操作', () => {
|
||||
const names: string[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
batchOp.forEach((entity, index) => {
|
||||
names.push(entity.name);
|
||||
indices.push(index);
|
||||
});
|
||||
|
||||
expect(names).toEqual(['Entity1', 'Entity2', 'Entity3']);
|
||||
expect(indices).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
test('应该能够过滤实体', () => {
|
||||
entity1.tag = 1;
|
||||
entity2.tag = 2;
|
||||
entity3.tag = 1;
|
||||
|
||||
const filtered = batchOp.filter(entity => entity.tag === 1);
|
||||
|
||||
expect(filtered.count()).toBe(2);
|
||||
expect(filtered.toArray()).toContain(entity1);
|
||||
expect(filtered.toArray()).toContain(entity3);
|
||||
});
|
||||
|
||||
test('应该能够获取实体数组', () => {
|
||||
const entities = batchOp.toArray();
|
||||
|
||||
expect(entities.length).toBe(3);
|
||||
expect(entities).toContain(entity1);
|
||||
expect(entities).toContain(entity2);
|
||||
expect(entities).toContain(entity3);
|
||||
});
|
||||
|
||||
test('应该能够获取实体数量', () => {
|
||||
expect(batchOp.count()).toBe(3);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const result = batchOp
|
||||
.addComponent(new TestComponent(100))
|
||||
.setActive(false)
|
||||
.setTag(42)
|
||||
.forEach((entity) => {
|
||||
entity.name = entity.name + '_Modified';
|
||||
});
|
||||
|
||||
expect(result).toBe(batchOp);
|
||||
expect(entity1.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity1.active).toBe(false);
|
||||
expect(entity1.tag).toBe(42);
|
||||
expect(entity1.name).toBe('Entity1_Modified');
|
||||
});
|
||||
});
|
||||
|
||||
describe('工厂函数和全局API', () => {
|
||||
test('createECSAPI应该创建API实例', () => {
|
||||
const api = createECSAPI(scene, querySystem, eventSystem);
|
||||
expect(api).toBeInstanceOf(ECSFluentAPI);
|
||||
});
|
||||
|
||||
test('initializeECS应该初始化全局ECS', () => {
|
||||
initializeECS(scene, querySystem, eventSystem);
|
||||
expect(ECS).toBeInstanceOf(ECSFluentAPI);
|
||||
});
|
||||
|
||||
test('全局ECS应该可用', () => {
|
||||
initializeECS(scene, querySystem, eventSystem);
|
||||
|
||||
const builder = ECS.createEntity();
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
});
|
||||
});
|
||||
321
packages/core/tests/ECS/Core/FluentAPI/EntityBuilder.test.ts
Normal file
321
packages/core/tests/ECS/Core/FluentAPI/EntityBuilder.test.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import { EntityBuilder } from '../../../../src/ECS/Core/FluentAPI/EntityBuilder';
|
||||
import { Scene } from '../../../../src/ECS/Scene';
|
||||
import { Component } from '../../../../src/ECS/Component';
|
||||
import { HierarchySystem } from '../../../../src/ECS/Systems/HierarchySystem';
|
||||
import { ECSComponent } from '../../../../src/ECS/Decorators';
|
||||
|
||||
@ECSComponent('BuilderTestPosition')
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ECSComponent('BuilderTestVelocity')
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
}
|
||||
|
||||
@ECSComponent('BuilderTestHealth')
|
||||
class HealthComponent extends Component {
|
||||
public current: number = 100;
|
||||
public max: number = 100;
|
||||
}
|
||||
|
||||
// Helper function to create EntityBuilder
|
||||
function createBuilder(scene: Scene): EntityBuilder {
|
||||
return new EntityBuilder(scene, scene.componentStorageManager);
|
||||
}
|
||||
|
||||
describe('EntityBuilder', () => {
|
||||
let scene: Scene;
|
||||
let hierarchySystem: HierarchySystem;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene({ name: 'BuilderTestScene' });
|
||||
hierarchySystem = new HierarchySystem();
|
||||
scene.addSystem(hierarchySystem);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
scene.end();
|
||||
});
|
||||
|
||||
describe('basic building', () => {
|
||||
test('should create entity with name', () => {
|
||||
const builder = createBuilder(scene);
|
||||
const entity = builder.named('TestEntity').build();
|
||||
|
||||
expect(entity.name).toBe('TestEntity');
|
||||
});
|
||||
|
||||
test('should create entity with tag', () => {
|
||||
const builder = createBuilder(scene);
|
||||
const entity = builder.tagged(0x100).build();
|
||||
|
||||
expect(entity.tag).toBe(0x100);
|
||||
});
|
||||
|
||||
test('should support chaining name and tag', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.named('ChainedEntity')
|
||||
.tagged(0x200)
|
||||
.build();
|
||||
|
||||
expect(entity.name).toBe('ChainedEntity');
|
||||
expect(entity.tag).toBe(0x200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('component management', () => {
|
||||
test('should add single component with .with()', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.with(new PositionComponent(10, 20))
|
||||
.build();
|
||||
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
expect(pos).not.toBeNull();
|
||||
expect(pos!.x).toBe(10);
|
||||
expect(pos!.y).toBe(20);
|
||||
});
|
||||
|
||||
test('should add multiple components with .withComponents()', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.withComponents(
|
||||
new PositionComponent(5, 10),
|
||||
new VelocityComponent(),
|
||||
new HealthComponent()
|
||||
)
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(VelocityComponent)).toBe(true);
|
||||
expect(entity.hasComponent(HealthComponent)).toBe(true);
|
||||
});
|
||||
|
||||
test('should conditionally add component with .withIf()', () => {
|
||||
const shouldAdd = true;
|
||||
const shouldNotAdd = false;
|
||||
|
||||
const entity = createBuilder(scene)
|
||||
.withIf(shouldAdd, new PositionComponent())
|
||||
.withIf(shouldNotAdd, new VelocityComponent())
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(VelocityComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('should add component using factory with .withFactory()', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.withFactory(() => new PositionComponent(100, 200))
|
||||
.build();
|
||||
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
expect(pos).not.toBeNull();
|
||||
expect(pos!.x).toBe(100);
|
||||
expect(pos!.y).toBe(200);
|
||||
});
|
||||
|
||||
test('should configure existing component with .configure()', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.with(new PositionComponent(0, 0))
|
||||
.configure(PositionComponent, (pos: PositionComponent) => {
|
||||
pos.x = 999;
|
||||
pos.y = 888;
|
||||
})
|
||||
.build();
|
||||
|
||||
const pos = entity.getComponent(PositionComponent);
|
||||
expect(pos!.x).toBe(999);
|
||||
expect(pos!.y).toBe(888);
|
||||
});
|
||||
|
||||
test('.configure() should do nothing if component does not exist', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.configure(PositionComponent, (pos: PositionComponent) => {
|
||||
pos.x = 100;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('entity state', () => {
|
||||
test('should set enabled state', () => {
|
||||
const disabledEntity = createBuilder(scene)
|
||||
.enabled(false)
|
||||
.build();
|
||||
|
||||
const enabledEntity = createBuilder(scene)
|
||||
.enabled(true)
|
||||
.build();
|
||||
|
||||
expect(disabledEntity.enabled).toBe(false);
|
||||
expect(enabledEntity.enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('should set active state', () => {
|
||||
const inactiveEntity = createBuilder(scene)
|
||||
.active(false)
|
||||
.build();
|
||||
|
||||
const activeEntity = createBuilder(scene)
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
expect(inactiveEntity.active).toBe(false);
|
||||
expect(activeEntity.active).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hierarchy building', () => {
|
||||
test('should call withChild method', () => {
|
||||
const childBuilder = createBuilder(scene).named('Child');
|
||||
const builder = createBuilder(scene)
|
||||
.named('Parent')
|
||||
.withChild(childBuilder);
|
||||
|
||||
// withChild returns the builder for chaining
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('should call withChildren method', () => {
|
||||
const child1Builder = createBuilder(scene).named('Child1');
|
||||
const child2Builder = createBuilder(scene).named('Child2');
|
||||
|
||||
const builder = createBuilder(scene)
|
||||
.named('Parent')
|
||||
.withChildren(child1Builder, child2Builder);
|
||||
|
||||
// withChildren returns the builder for chaining
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('should call withChildFactory method', () => {
|
||||
const builder = createBuilder(scene)
|
||||
.named('Parent')
|
||||
.with(new PositionComponent(100, 100))
|
||||
.withChildFactory((parentEntity) => {
|
||||
return createBuilder(scene)
|
||||
.named('ChildFromFactory')
|
||||
.with(new PositionComponent(10, 20));
|
||||
});
|
||||
|
||||
// withChildFactory returns the builder for chaining
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('should call withChildIf method', () => {
|
||||
const shouldAdd = true;
|
||||
const shouldNotAdd = false;
|
||||
|
||||
const child1Builder = createBuilder(scene).named('Child1');
|
||||
const builder1 = createBuilder(scene)
|
||||
.named('Parent')
|
||||
.withChildIf(shouldAdd, child1Builder);
|
||||
|
||||
expect(builder1).toBeInstanceOf(EntityBuilder);
|
||||
|
||||
const child2Builder = createBuilder(scene).named('Child2');
|
||||
const builder2 = createBuilder(scene)
|
||||
.named('Parent2')
|
||||
.withChildIf(shouldNotAdd, child2Builder);
|
||||
|
||||
expect(builder2).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('spawning and cloning', () => {
|
||||
test('should spawn entity to scene with .spawn()', () => {
|
||||
const initialCount = scene.entities.count;
|
||||
|
||||
const entity = createBuilder(scene)
|
||||
.named('SpawnedEntity')
|
||||
.with(new PositionComponent())
|
||||
.spawn();
|
||||
|
||||
expect(scene.entities.count).toBe(initialCount + 1);
|
||||
expect(scene.findEntityById(entity.id)).toBe(entity);
|
||||
});
|
||||
|
||||
test('.build() should not add to scene automatically', () => {
|
||||
const initialCount = scene.entities.count;
|
||||
|
||||
createBuilder(scene)
|
||||
.named('BuiltEntity')
|
||||
.build();
|
||||
|
||||
expect(scene.entities.count).toBe(initialCount);
|
||||
});
|
||||
|
||||
test('should clone builder', () => {
|
||||
const builder = createBuilder(scene)
|
||||
.named('OriginalEntity')
|
||||
.tagged(0x50);
|
||||
|
||||
const clonedBuilder = builder.clone();
|
||||
|
||||
expect(clonedBuilder).not.toBe(builder);
|
||||
expect(clonedBuilder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('complex building scenarios', () => {
|
||||
test('should build complete entity with all options', () => {
|
||||
const entity = createBuilder(scene)
|
||||
.named('CompleteEntity')
|
||||
.tagged(0x100)
|
||||
.with(new PositionComponent(50, 75))
|
||||
.with(new VelocityComponent())
|
||||
.withFactory(() => new HealthComponent())
|
||||
.configure(HealthComponent, (h: HealthComponent) => {
|
||||
h.current = 80;
|
||||
h.max = 100;
|
||||
})
|
||||
.enabled(true)
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
expect(entity.name).toBe('CompleteEntity');
|
||||
expect(entity.tag).toBe(0x100);
|
||||
expect(entity.enabled).toBe(true);
|
||||
expect(entity.active).toBe(true);
|
||||
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(VelocityComponent)).toBe(true);
|
||||
expect(entity.hasComponent(HealthComponent)).toBe(true);
|
||||
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
expect(health!.current).toBe(80);
|
||||
expect(health!.max).toBe(100);
|
||||
});
|
||||
|
||||
test('should support complex chaining', () => {
|
||||
const builder = createBuilder(scene)
|
||||
.named('Root')
|
||||
.with(new PositionComponent(1, 1));
|
||||
|
||||
// Add child builder chain
|
||||
const childBuilder = createBuilder(scene)
|
||||
.named('Child')
|
||||
.with(new PositionComponent(2, 2));
|
||||
|
||||
// Chain withChild
|
||||
builder.withChild(childBuilder);
|
||||
|
||||
// Build and spawn
|
||||
const root = builder.spawn();
|
||||
|
||||
expect(root.name).toBe('Root');
|
||||
expect(root.hasComponent(PositionComponent)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user