移除 Entity._localComponents/强制Entity必须属于Scene/简化组件操作逻辑

This commit is contained in:
YHH
2025-10-10 16:31:43 +08:00
parent bf14b59a28
commit 62d7521384
12 changed files with 158 additions and 158 deletions

View File

@@ -14,7 +14,9 @@ export class EntityBuilder {
constructor(scene: IScene, storageManager: ComponentStorageManager) { constructor(scene: IScene, storageManager: ComponentStorageManager) {
this.scene = scene; this.scene = scene;
this.storageManager = storageManager; this.storageManager = storageManager;
this.entity = new Entity("", scene.identifierPool.checkOut()); const id = scene.identifierPool.checkOut();
this.entity = new Entity("", id);
this.entity.scene = this.scene as any;
} }
/** /**

View File

@@ -145,12 +145,6 @@ export class Entity {
*/ */
private _componentCache: Component[] | null = null; private _componentCache: Component[] | null = null;
/**
* 本地组件存储(用于没有 Scene 的 Entity
* 当 Entity 添加到 Scene 时,组件会迁移到 Scene 的 componentStorageManager
*/
private _localComponents: Map<ComponentType, Component> = new Map();
/** /**
* 构造函数 * 构造函数
* *
@@ -186,28 +180,23 @@ export class Entity {
*/ */
private _rebuildComponentCache(): void { private _rebuildComponentCache(): void {
const components: Component[] = []; const components: Component[] = [];
const mask = this._componentMask;
if (!this.scene?.componentStorageManager) {
this._componentCache = components;
return;
}
const mask = this._componentMask;
const maxBitIndex = ComponentRegistry.getRegisteredCount(); const maxBitIndex = ComponentRegistry.getRegisteredCount();
for (let bitIndex = 0; bitIndex < maxBitIndex; bitIndex++) { for (let bitIndex = 0; bitIndex < maxBitIndex; bitIndex++) {
if (BitMask64Utils.getBit(mask, bitIndex)) { if (BitMask64Utils.getBit(mask, bitIndex)) {
const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex); const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex);
if (componentType) { if (componentType) {
let component: Component | null = null; const component = this.scene.componentStorageManager.getComponent(
this.id,
// 优先从 Scene 存储获取 componentType
if (this.scene?.componentStorageManager) { );
component = this.scene.componentStorageManager.getComponent(
this.id,
componentType
);
}
// Fallback 到本地存储
if (!component) {
component = this._localComponents.get(componentType) || null;
}
if (component) { if (component) {
components.push(component); components.push(component);
@@ -378,9 +367,6 @@ export class Entity {
ComponentRegistry.register(componentType); ComponentRegistry.register(componentType);
} }
// 存储到本地 Map
this._localComponents.set(componentType, component);
// 更新位掩码 // 更新位掩码
const componentMask = ComponentRegistry.getBitMask(componentType); const componentMask = ComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(this._componentMask, componentMask); BitMask64Utils.orInPlace(this._componentMask, componentMask);
@@ -406,19 +392,25 @@ export class Entity {
*/ */
public addComponent<T extends Component>(component: T): T { public addComponent<T extends Component>(component: T): T {
const componentType = component.constructor as ComponentType<T>; const componentType = component.constructor as ComponentType<T>;
if (!this.scene) {
throw new Error(`Entity must be added to Scene before adding components. Use scene.createEntity() instead of new Entity()`);
}
if (!this.scene.componentStorageManager) {
throw new Error(`Scene does not have componentStorageManager`);
}
if (this.hasComponent(componentType)) { if (this.hasComponent(componentType)) {
throw new Error(`Entity ${this.name} already has component ${getComponentTypeName(componentType)}`); throw new Error(`Entity ${this.name} already has component ${getComponentTypeName(componentType)}`);
} }
this.addComponentInternal(component); this.addComponentInternal(component);
if (this.scene && this.scene.componentStorageManager) { this.scene.componentStorageManager.addComponent(this.id, component);
this.scene.componentStorageManager.addComponent(this.id, component);
}
component.onAddedToEntity(); component.onAddedToEntity();
if (Entity.eventBus) { if (Entity.eventBus) {
Entity.eventBus.emitComponentAdded({ Entity.eventBus.emitComponentAdded({
timestamp: Date.now(), timestamp: Date.now(),
@@ -430,7 +422,7 @@ export class Entity {
component: component component: component
}); });
} }
// 通知所有相关的QuerySystem组件已变动 // 通知所有相关的QuerySystem组件已变动
Entity.notifyQuerySystems(this); Entity.notifyQuerySystems(this);
@@ -459,16 +451,13 @@ export class Entity {
return null; return null;
} }
// 优先从 Scene 存储获取 // Scene存储获取
if (this.scene?.componentStorageManager) { if (!this.scene?.componentStorageManager) {
const component = this.scene.componentStorageManager.getComponent(this.id, type); return null;
if (component) {
return component as T;
}
} }
// Fallback 到本地存储 const component = this.scene.componentStorageManager.getComponent(this.id, type);
return (this._localComponents.get(type) as T) || null; return component as T | null;
} }
@@ -538,17 +527,14 @@ export class Entity {
const bitIndex = ComponentRegistry.getBitIndex(componentType); const bitIndex = ComponentRegistry.getBitIndex(componentType);
// 从本地存储移除
this._localComponents.delete(componentType);
// 更新位掩码 // 更新位掩码
BitMask64Utils.clearBit(this._componentMask, bitIndex); BitMask64Utils.clearBit(this._componentMask, bitIndex);
// 使缓存失效 // 使缓存失效
this._componentCache = null; this._componentCache = null;
// 从 Scene 存储移除 // 从Scene存储移除
if (this.scene && this.scene.componentStorageManager) { if (this.scene?.componentStorageManager) {
this.scene.componentStorageManager.removeComponent(this.id, componentType); this.scene.componentStorageManager.removeComponent(this.id, componentType);
} }
@@ -593,9 +579,6 @@ export class Entity {
public removeAllComponents(): void { public removeAllComponents(): void {
const componentsToRemove = [...this.components]; const componentsToRemove = [...this.components];
// 清除本地存储
this._localComponents.clear();
// 清除位掩码 // 清除位掩码
BitMask64Utils.clear(this._componentMask); BitMask64Utils.clear(this._componentMask);
@@ -605,7 +588,7 @@ export class Entity {
for (const component of componentsToRemove) { for (const component of componentsToRemove) {
const componentType = component.constructor as ComponentType; const componentType = component.constructor as ComponentType;
if (this.scene && this.scene.componentStorageManager) { if (this.scene?.componentStorageManager) {
this.scene.componentStorageManager.removeComponent(this.id, componentType); this.scene.componentStorageManager.removeComponent(this.id, componentType);
} }

View File

@@ -8,6 +8,7 @@ import { Entity } from '../Entity';
import { Component } from '../Component'; import { Component } from '../Component';
import { ComponentType } from '../Core/ComponentStorage'; import { ComponentType } from '../Core/ComponentStorage';
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer'; import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
import { IScene } from '../IScene';
/** /**
* 序列化后的实体数据 * 序列化后的实体数据
@@ -108,18 +109,25 @@ export class EntitySerializer {
* @param componentRegistry 组件类型注册表 * @param componentRegistry 组件类型注册表
* @param idGenerator 实体ID生成器用于生成新ID或保持原ID * @param idGenerator 实体ID生成器用于生成新ID或保持原ID
* @param preserveIds 是否保持原始ID默认false * @param preserveIds 是否保持原始ID默认false
* @param scene 目标场景可选用于设置entity.scene以支持添加组件
* @returns 反序列化后的实体 * @returns 反序列化后的实体
*/ */
public static deserialize( public static deserialize(
serializedEntity: SerializedEntity, serializedEntity: SerializedEntity,
componentRegistry: Map<string, ComponentType>, componentRegistry: Map<string, ComponentType>,
idGenerator: () => number, idGenerator: () => number,
preserveIds: boolean = false preserveIds: boolean = false,
scene?: IScene
): Entity { ): Entity {
// 创建实体使用原始ID或新生成的ID // 创建实体使用原始ID或新生成的ID
const entityId = preserveIds ? serializedEntity.id : idGenerator(); const entityId = preserveIds ? serializedEntity.id : idGenerator();
const entity = new Entity(serializedEntity.name, entityId); const entity = new Entity(serializedEntity.name, entityId);
// 如果提供了scene先设置entity.scene以支持添加组件
if (scene) {
entity.scene = scene;
}
// 恢复实体属性 // 恢复实体属性
entity.tag = serializedEntity.tag; entity.tag = serializedEntity.tag;
entity.active = serializedEntity.active; entity.active = serializedEntity.active;
@@ -142,7 +150,8 @@ export class EntitySerializer {
childData, childData,
componentRegistry, componentRegistry,
idGenerator, idGenerator,
preserveIds preserveIds,
scene
); );
entity.addChild(childEntity); entity.addChild(childEntity);
} }
@@ -181,13 +190,15 @@ export class EntitySerializer {
* @param componentRegistry 组件类型注册表 * @param componentRegistry 组件类型注册表
* @param idGenerator 实体ID生成器 * @param idGenerator 实体ID生成器
* @param preserveIds 是否保持原始ID * @param preserveIds 是否保持原始ID
* @param scene 目标场景可选用于设置entity.scene以支持添加组件
* @returns 反序列化后的实体数组 * @returns 反序列化后的实体数组
*/ */
public static deserializeEntities( public static deserializeEntities(
serializedEntities: SerializedEntity[], serializedEntities: SerializedEntity[],
componentRegistry: Map<string, ComponentType>, componentRegistry: Map<string, ComponentType>,
idGenerator: () => number, idGenerator: () => number,
preserveIds: boolean = false preserveIds: boolean = false,
scene?: IScene
): Entity[] { ): Entity[] {
const result: Entity[] = []; const result: Entity[] = [];
@@ -196,7 +207,8 @@ export class EntitySerializer {
serialized, serialized,
componentRegistry, componentRegistry,
idGenerator, idGenerator,
preserveIds preserveIds,
scene
); );
result.push(entity); result.push(entity);
} }

View File

@@ -269,7 +269,8 @@ export class SceneSerializer {
serializedScene.entities, serializedScene.entities,
componentRegistry, componentRegistry,
idGenerator, idGenerator,
opts.preserveIds || false opts.preserveIds || false,
scene
); );
// 将实体添加到场景 // 将实体添加到场景

View File

@@ -1,5 +1,6 @@
import { Component } from '../../src/ECS/Component'; import { Component } from '../../src/ECS/Component';
import { Entity } from '../../src/ECS/Entity'; import { Entity } from '../../src/ECS/Entity';
import { Scene } from '../../src/ECS/Scene';
// 测试组件 // 测试组件
class TestComponent extends Component { class TestComponent extends Component {
@@ -23,12 +24,13 @@ class AnotherTestComponent extends Component {
describe('Component - 组件基类测试', () => { describe('Component - 组件基类测试', () => {
let component: TestComponent; let component: TestComponent;
let entity: Entity; let entity: Entity;
let scene: Scene;
beforeEach(() => { beforeEach(() => {
// Reset component ID generator to avoid BigInt issues
Component._idGenerator = 0; Component._idGenerator = 0;
component = new TestComponent(); component = new TestComponent();
entity = new Entity('TestEntity', 1); scene = new Scene();
entity = scene.createEntity('TestEntity');
}); });
describe('基本功能', () => { describe('基本功能', () => {

View File

@@ -568,9 +568,9 @@ describe('FluentAPI - 流式API测试', () => {
let batchOp: EntityBatchOperator; let batchOp: EntityBatchOperator;
beforeEach(() => { beforeEach(() => {
entity1 = new Entity('Entity1', 1); entity1 = scene.createEntity('Entity1');
entity2 = new Entity('Entity2', 2); entity2 = scene.createEntity('Entity2');
entity3 = new Entity('Entity3', 3); entity3 = scene.createEntity('Entity3');
batchOp = new EntityBatchOperator([entity1, entity2, entity3]); batchOp = new EntityBatchOperator([entity1, entity2, entity3]);
}); });

View File

@@ -2,6 +2,7 @@ import { QuerySystem, QueryBuilder, QueryConditionType } from '../../../src/ECS/
import { Entity } from '../../../src/ECS/Entity'; import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component'; import { Component } from '../../../src/ECS/Component';
import { ComponentRegistry, ComponentType } from '../../../src/ECS/Core/ComponentStorage'; import { ComponentRegistry, ComponentType } from '../../../src/ECS/Core/ComponentStorage';
import { Scene } from '../../../src/ECS/Scene';
// 测试组件 // 测试组件
class PositionComponent extends Component { class PositionComponent extends Component {
@@ -75,6 +76,7 @@ class PhysicsComponent extends Component {
describe('QuerySystem - 查询系统测试', () => { describe('QuerySystem - 查询系统测试', () => {
let querySystem: QuerySystem; let querySystem: QuerySystem;
let entities: Entity[]; let entities: Entity[];
let scene: Scene;
let originalAddComponent: any; let originalAddComponent: any;
let originalRemoveComponent: any; let originalRemoveComponent: any;
let originalRemoveAllComponents: any; let originalRemoveAllComponents: any;
@@ -82,13 +84,14 @@ describe('QuerySystem - 查询系统测试', () => {
beforeEach(() => { beforeEach(() => {
querySystem = new QuerySystem(); querySystem = new QuerySystem();
entities = []; entities = [];
scene = new Scene();
// 创建测试实体 // 创建测试实体
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const entity = new Entity(`Entity_${i}`, i + 1); const entity = scene.createEntity(`Entity_${i}`);
entities.push(entity); entities.push(entity);
} }
// 将实体添加到查询系统 // 将实体添加到查询系统
querySystem.setEntities(entities); querySystem.setEntities(entities);
@@ -288,16 +291,12 @@ describe('QuerySystem - 查询系统测试', () => {
// 创建大量具有相同组件组合的实体 // 创建大量具有相同组件组合的实体
for (let i = 0; i < entityCount; i++) { for (let i = 0; i < entityCount; i++) {
const entity = new Entity(`PerfEntity_${i}`, i + 100); const entity = scene.createEntity(`PerfEntity_${i}`);
testEntities.push(entity);
}
// 先添加组件
for (const entity of testEntities) {
entity.addComponent(new PositionComponent(0, 0)); entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1)); entity.addComponent(new VelocityComponent(1, 1));
testEntities.push(entity);
} }
// 将实体添加到查询系统 // 将实体添加到查询系统
querySystem.setEntities([...entities, ...testEntities]); querySystem.setEntities([...entities, ...testEntities]);
@@ -343,31 +342,27 @@ describe('QuerySystem - 查询系统测试', () => {
const entityCount = 5000; const entityCount = 5000;
const testEntities: Entity[] = []; const testEntities: Entity[] = [];
// 创建大量实体 // 创建大量实体并分配组件
for (let i = 0; i < entityCount; i++) { for (let i = 0; i < entityCount; i++) {
const entity = new Entity(`MaskEntity_${i}`, i + 200); const entity = scene.createEntity(`MaskEntity_${i}`);
testEntities.push(entity);
}
// 先随机分配组件
for (let i = 0; i < entityCount; i++) {
const entity = testEntities[i];
entity.addComponent(new PositionComponent(i, i)); entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) { if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1)); entity.addComponent(new VelocityComponent(1, 1));
} }
if (i % 3 === 0) { if (i % 3 === 0) {
entity.addComponent(new HealthComponent(100)); entity.addComponent(new HealthComponent(100));
} }
if (i % 5 === 0) { if (i % 5 === 0) {
entity.addComponent(new RenderComponent(true)); entity.addComponent(new RenderComponent(true));
} }
testEntities.push(entity);
} }
// 将实体添加到查询系统 // 将实体添加到查询系统
querySystem.setEntities([...entities, ...testEntities]); querySystem.setEntities([...entities, ...testEntities]);
@@ -499,13 +494,13 @@ describe('QuerySystem - 查询系统测试', () => {
// 创建大量实体 // 创建大量实体
for (let i = 0; i < entityCount; i++) { for (let i = 0; i < entityCount; i++) {
const entity = new Entity(`MemEntity_${i}`, i + 300); const entity = scene.createEntity(`MemEntity_${i}`);
entity.addComponent(new PositionComponent(i, i)); entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) { if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1)); entity.addComponent(new VelocityComponent(1, 1));
} }
testEntities.push(entity); testEntities.push(entity);
} }
@@ -785,7 +780,7 @@ describe('QuerySystem - 查询系统测试', () => {
describe('实体管理功能', () => { describe('实体管理功能', () => {
test('应该能够添加和移除单个实体', () => { test('应该能够添加和移除单个实体', () => {
const newEntity = new Entity('NewEntity', 999); const newEntity = scene.createEntity('NewEntity');
querySystem.addEntity(newEntity); querySystem.addEntity(newEntity);
let stats = querySystem.getStats(); let stats = querySystem.getStats();
@@ -798,9 +793,9 @@ describe('QuerySystem - 查询系统测试', () => {
test('应该能够批量添加实体', () => { test('应该能够批量添加实体', () => {
const newEntities = [ const newEntities = [
new Entity('Batch1', 997), scene.createEntity('Batch1'),
new Entity('Batch2', 998), scene.createEntity('Batch2'),
new Entity('Batch3', 999) scene.createEntity('Batch3')
]; ];
querySystem.addEntities(newEntities); querySystem.addEntities(newEntities);
@@ -810,8 +805,8 @@ describe('QuerySystem - 查询系统测试', () => {
test('应该能够批量添加实体(无重复检查)', () => { test('应该能够批量添加实体(无重复检查)', () => {
const newEntities = [ const newEntities = [
new Entity('Unchecked1', 995), scene.createEntity('Unchecked1'),
new Entity('Unchecked2', 996) scene.createEntity('Unchecked2')
]; ];
querySystem.addEntitiesUnchecked(newEntities); querySystem.addEntitiesUnchecked(newEntities);
@@ -846,43 +841,34 @@ describe('QuerySystem - 查询系统测试', () => {
}); });
describe('组件变动同步问题测试', () => { describe('组件变动同步问题测试', () => {
test('没有Scene时组件变动不会自动同步符合ECS架构', () => { test('Entity必须有Scene才能添加组件', () => {
// 创建一个独立的QuerySystem和实体 // 创建一个独立的QuerySystem和实体
const independentQuerySystem = new QuerySystem(); const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('TestEntity', 9999); const testEntity = scene.createEntity('TestEntity');
// 确保实体没有scene // Entity现在必须有scene
expect(testEntity.scene).toBe(null); expect(testEntity.scene).toBeTruthy();
// 添加实体到查询系统 // 添加实体到查询系统
independentQuerySystem.addEntity(testEntity); independentQuerySystem.addEntity(testEntity);
// 初始查询应该没有PositionComponent的实体 // 添加组件
const result1 = independentQuerySystem.queryAll(PositionComponent);
expect(result1.entities.length).toBe(0);
// 添加组件但没有Scene不会自动同步
testEntity.addComponent(new PositionComponent(100, 200)); testEntity.addComponent(new PositionComponent(100, 200));
// 查询系统不知道组件变化(这是预期行为)
const result2 = independentQuerySystem.queryAll(PositionComponent);
expect(result2.entities.length).toBe(0); // 查询系统没有自动更新
expect(testEntity.hasComponent(PositionComponent)).toBe(true); // 但实体确实有这个组件
// 手动同步后应该能找到 // 手动同步后应该能找到
independentQuerySystem.updateEntity(testEntity); independentQuerySystem.updateEntity(testEntity);
const result3 = independentQuerySystem.queryAll(PositionComponent); const result = independentQuerySystem.queryAll(PositionComponent);
expect(result3.entities.length).toBe(1); expect(result.entities.length).toBe(1);
expect(result3.entities[0]).toBe(testEntity); expect(result.entities[0]).toBe(testEntity);
}); });
test('有Scene但没有querySystem时组件变动应该安全', () => { test('有Scene但没有querySystem时组件变动应该安全', () => {
const testEntity = new Entity('TestEntity2', 9998); const testEntity = scene.createEntity('TestEntity2');
// 模拟一个没有querySystem的scene // 模拟一个没有querySystem的scene但保留componentStorageManager
const mockScene = { const mockScene = {
querySystem: null, querySystem: null,
componentStorageManager: null, componentStorageManager: scene.componentStorageManager,
clearSystemEntityCaches: jest.fn() clearSystemEntityCaches: jest.fn()
}; };
testEntity.scene = mockScene as any; testEntity.scene = mockScene as any;
@@ -897,12 +883,12 @@ describe('QuerySystem - 查询系统测试', () => {
test('有Scene时ArchetypeSystem组件变动能正确同步', () => { test('有Scene时ArchetypeSystem组件变动能正确同步', () => {
const independentQuerySystem = new QuerySystem(); const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('ArchetypeTestEntity', 9997); const testEntity = scene.createEntity('ArchetypeTestEntity');
// 模拟Scene环境 // 模拟Scene环境保留componentStorageManager
const mockScene = { const mockScene = {
querySystem: independentQuerySystem, querySystem: independentQuerySystem,
componentStorageManager: null, componentStorageManager: scene.componentStorageManager,
clearSystemEntityCaches: jest.fn() clearSystemEntityCaches: jest.fn()
}; };
testEntity.scene = mockScene as any; testEntity.scene = mockScene as any;
@@ -941,12 +927,12 @@ describe('QuerySystem - 查询系统测试', () => {
test('有Scene时removeAllComponents应该正确同步QuerySystem', () => { test('有Scene时removeAllComponents应该正确同步QuerySystem', () => {
const independentQuerySystem = new QuerySystem(); const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('RemoveAllTestEntity', 9996); const testEntity = scene.createEntity('RemoveAllTestEntity');
// 模拟Scene环境 // 模拟Scene环境保留componentStorageManager
const mockScene = { const mockScene = {
querySystem: independentQuerySystem, querySystem: independentQuerySystem,
componentStorageManager: null, componentStorageManager: scene.componentStorageManager,
clearSystemEntityCaches: jest.fn() clearSystemEntityCaches: jest.fn()
}; };
testEntity.scene = mockScene as any; testEntity.scene = mockScene as any;
@@ -978,7 +964,7 @@ describe('QuerySystem - 查询系统测试', () => {
test('手动同步updateEntity应该工作正常', () => { test('手动同步updateEntity应该工作正常', () => {
const independentQuerySystem = new QuerySystem(); const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('ManualSyncTestEntity', 9995); const testEntity = scene.createEntity('ManualSyncTestEntity');
independentQuerySystem.addEntity(testEntity); independentQuerySystem.addEntity(testEntity);
@@ -1002,9 +988,9 @@ describe('QuerySystem - 查询系统测试', () => {
const querySystem = new QuerySystem(); const querySystem = new QuerySystem();
// 创建带组件的实体 // 创建带组件的实体
const entity1 = new Entity('BatchEntity1', 8001); const entity1 = scene.createEntity('BatchEntity1');
const entity2 = new Entity('BatchEntity2', 8002); const entity2 = scene.createEntity('BatchEntity2');
const entity3 = new Entity('BatchEntity3', 8003); const entity3 = scene.createEntity('BatchEntity3');
entity1.addComponent(new PositionComponent(10, 20)); entity1.addComponent(new PositionComponent(10, 20));
entity2.addComponent(new PositionComponent(30, 40)); entity2.addComponent(new PositionComponent(30, 40));
@@ -1053,9 +1039,9 @@ describe('QuerySystem - 查询系统测试', () => {
describe('组件掩码字符串索引', () => { describe('组件掩码字符串索引', () => {
test('应该为不同的组件组合生成不同的掩码字符串', () => { test('应该为不同的组件组合生成不同的掩码字符串', () => {
// 创建具有不同组件组合的实体 // 创建具有不同组件组合的实体
const entity1 = new Entity('Entity1', 101); const entity1 = scene.createEntity('Entity1');
const entity2 = new Entity('Entity2', 102); const entity2 = scene.createEntity('Entity2');
const entity3 = new Entity('Entity3', 103); const entity3 = scene.createEntity('Entity3');
entity1.addComponent(new PositionComponent(1, 1)); entity1.addComponent(new PositionComponent(1, 1));
entity2.addComponent(new VelocityComponent(2, 2)); entity2.addComponent(new VelocityComponent(2, 2));
@@ -1071,22 +1057,22 @@ describe('QuerySystem - 查询系统测试', () => {
const withBoth = querySystem.queryAll(PositionComponent, VelocityComponent); const withBoth = querySystem.queryAll(PositionComponent, VelocityComponent);
// entity1 应该在 withPosition 中 // entity1 应该在 withPosition 中
expect(withPosition.entities.some(e => e.id === 101)).toBe(true); expect(withPosition.entities).toContain(entity1);
// entity2 应该在 withVelocity 中 // entity2 应该在 withVelocity 中
expect(withVelocity.entities.some(e => e.id === 102)).toBe(true); expect(withVelocity.entities).toContain(entity2);
// entity3 应该在所有查询中(因为它包含 Position 和 Velocity // entity3 应该在所有查询中(因为它包含 Position 和 Velocity
expect(withPosition.entities.some(e => e.id === 103)).toBe(true); expect(withPosition.entities).toContain(entity3);
expect(withVelocity.entities.some(e => e.id === 103)).toBe(true); expect(withVelocity.entities).toContain(entity3);
expect(withBoth.entities.some(e => e.id === 103)).toBe(true); expect(withBoth.entities).toContain(entity3);
// withBoth 只应该包含同时有两个组件的实体 // withBoth 只应该包含同时有两个组件的实体
expect(withBoth.entities.some(e => e.id === 101)).toBe(false); // 只有 Position expect(withBoth.entities).not.toContain(entity1); // 只有 Position
expect(withBoth.entities.some(e => e.id === 102)).toBe(false); // 只有 Velocity expect(withBoth.entities).not.toContain(entity2); // 只有 Velocity
}); });
test('相同组件组合的实体应该使用相同的掩码索引', () => { test('相同组件组合的实体应该使用相同的掩码索引', () => {
const entity1 = new Entity('Entity1', 201); const entity1 = scene.createEntity('Entity1');
const entity2 = new Entity('Entity2', 202); const entity2 = scene.createEntity('Entity2');
// 两个实体都有相同的组件组合 // 两个实体都有相同的组件组合
entity1.addComponent(new PositionComponent(1, 1)); entity1.addComponent(new PositionComponent(1, 1));
@@ -1101,8 +1087,8 @@ describe('QuerySystem - 查询系统测试', () => {
// 查询应该同时返回这两个实体 // 查询应该同时返回这两个实体
const result = querySystem.queryAll(PositionComponent, VelocityComponent); const result = querySystem.queryAll(PositionComponent, VelocityComponent);
expect(result.entities.some(e => e.id === 201)).toBe(true); expect(result.entities).toContain(entity1);
expect(result.entities.some(e => e.id === 202)).toBe(true); expect(result.entities).toContain(entity2);
}); });
}); });
}); });

View File

@@ -1,5 +1,6 @@
import { Entity } from '../../src/ECS/Entity'; import { Entity } from '../../src/ECS/Entity';
import { Component } from '../../src/ECS/Component'; import { Component } from '../../src/ECS/Component';
import { Scene } from '../../src/ECS/Scene';
// 测试组件类 // 测试组件类
class TestPositionComponent extends Component { class TestPositionComponent extends Component {
@@ -48,17 +49,19 @@ class TestRenderComponent extends Component {
describe('Entity - 组件缓存优化测试', () => { describe('Entity - 组件缓存优化测试', () => {
let entity: Entity; let entity: Entity;
let scene: Scene;
beforeEach(() => { beforeEach(() => {
// 创建新的实体 scene = new Scene();
entity = new Entity('TestEntity', 1); entity = scene.createEntity('TestEntity');
}); });
describe('基本功能测试', () => { describe('基本功能测试', () => {
test('应该能够创建实体', () => { test('应该能够创建实体', () => {
expect(entity.name).toBe('TestEntity'); expect(entity.name).toBe('TestEntity');
expect(entity.id).toBe(1); expect(entity.id).toBeGreaterThanOrEqual(0);
expect(entity.components.length).toBe(0); expect(entity.components.length).toBe(0);
expect(entity.scene).toBe(scene);
}); });
test('应该能够添加组件', () => { test('应该能够添加组件', () => {
@@ -262,7 +265,7 @@ describe('Entity - 组件缓存优化测试', () => {
const debugInfo = entity.getDebugInfo(); const debugInfo = entity.getDebugInfo();
expect(debugInfo.name).toBe('TestEntity'); expect(debugInfo.name).toBe('TestEntity');
expect(debugInfo.id).toBe(1); expect(debugInfo.id).toBeGreaterThanOrEqual(0);
expect(debugInfo.componentCount).toBe(2); expect(debugInfo.componentCount).toBe(2);
expect(debugInfo.componentTypes).toContain('TestPositionComponent'); expect(debugInfo.componentTypes).toContain('TestPositionComponent');
expect(debugInfo.componentTypes).toContain('TestHealthComponent'); expect(debugInfo.componentTypes).toContain('TestHealthComponent');

View File

@@ -82,6 +82,8 @@ class NonSerializableComponent extends Component {
} }
describe('ECS Serialization System', () => { describe('ECS Serialization System', () => {
let scene: Scene;
beforeEach(() => { beforeEach(() => {
// 清空测试环境 // 清空测试环境
ComponentRegistry.reset(); ComponentRegistry.reset();
@@ -91,6 +93,9 @@ describe('ECS Serialization System', () => {
ComponentRegistry.register(VelocityComponent); ComponentRegistry.register(VelocityComponent);
ComponentRegistry.register(PlayerComponent); ComponentRegistry.register(PlayerComponent);
ComponentRegistry.register(HealthComponent); ComponentRegistry.register(HealthComponent);
// 创建测试场景
scene = new Scene();
}); });
describe('Component Serialization', () => { describe('Component Serialization', () => {
@@ -185,22 +190,22 @@ describe('ECS Serialization System', () => {
describe('Entity Serialization', () => { describe('Entity Serialization', () => {
it('should serialize an entity with components', () => { it('should serialize an entity with components', () => {
const entity = new Entity('Player', 1); const entity = scene.createEntity('Player');
entity.addComponent(new PositionComponent(50, 100)); entity.addComponent(new PositionComponent(50, 100));
entity.addComponent(new VelocityComponent()); entity.addComponent(new VelocityComponent());
entity.tag = 10; entity.tag = 10;
const serialized = EntitySerializer.serialize(entity); const serialized = EntitySerializer.serialize(entity);
expect(serialized.id).toBe(1); expect(serialized.id).toBe(entity.id);
expect(serialized.name).toBe('Player'); expect(serialized.name).toBe('Player');
expect(serialized.tag).toBe(10); expect(serialized.tag).toBe(10);
expect(serialized.components.length).toBe(2); expect(serialized.components.length).toBe(2);
}); });
it('should serialize entity hierarchy', () => { it('should serialize entity hierarchy', () => {
const parent = new Entity('Parent', 1); const parent = scene.createEntity('Parent');
const child = new Entity('Child', 2); const child = scene.createEntity('Child');
parent.addComponent(new PositionComponent(0, 0)); parent.addComponent(new PositionComponent(0, 0));
child.addComponent(new PositionComponent(10, 10)); child.addComponent(new PositionComponent(10, 10));
@@ -209,7 +214,7 @@ describe('ECS Serialization System', () => {
const serialized = EntitySerializer.serialize(parent); const serialized = EntitySerializer.serialize(parent);
expect(serialized.children.length).toBe(1); expect(serialized.children.length).toBe(1);
expect(serialized.children[0].id).toBe(2); expect(serialized.children[0].id).toBe(child.id);
expect(serialized.children[0].name).toBe('Child'); expect(serialized.children[0].name).toBe('Child');
}); });
@@ -237,7 +242,8 @@ describe('ECS Serialization System', () => {
serializedEntity, serializedEntity,
registry, registry,
() => idCounter++, () => idCounter++,
false false,
scene
); );
expect(entity.name).toBe('TestEntity'); expect(entity.name).toBe('TestEntity');

View File

@@ -90,7 +90,7 @@ describe('EntitySystem', () => {
beforeEach(() => { beforeEach(() => {
scene = new Scene(); scene = new Scene();
system = new ConcreteEntitySystem(); system = new ConcreteEntitySystem();
entity = new Entity('test_entity', 1); entity = scene.createEntity('test_entity');
entity.addComponent(new TestComponent(10)); entity.addComponent(new TestComponent(10));
scene.addEntity(entity); scene.addEntity(entity);

View File

@@ -1,3 +1,4 @@
import { Scene } from '../../../src/ECS/Scene';
import { PassiveSystem } from '../../../src/ECS/Systems/PassiveSystem'; import { PassiveSystem } from '../../../src/ECS/Systems/PassiveSystem';
import { IntervalSystem } from '../../../src/ECS/Systems/IntervalSystem'; import { IntervalSystem } from '../../../src/ECS/Systems/IntervalSystem';
import { ProcessingSystem } from '../../../src/ECS/Systems/ProcessingSystem'; import { ProcessingSystem } from '../../../src/ECS/Systems/ProcessingSystem';
@@ -78,10 +79,12 @@ class ConcreteProcessingSystem extends ProcessingSystem {
} }
describe('System Types - 系统类型测试', () => { describe('System Types - 系统类型测试', () => {
let scene: Scene;
let entity: Entity; let entity: Entity;
beforeEach(() => { beforeEach(() => {
entity = new Entity('TestEntity', 1); scene = new Scene();
entity = scene.createEntity('TestEntity');
// 重置时间系统 // 重置时间系统
Time.update(0.016); Time.update(0.016);
// 注册测试组件类型 // 注册测试组件类型
@@ -282,10 +285,10 @@ describe('System Types - 系统类型测试', () => {
const interval = new ConcreteIntervalSystem(0.1); const interval = new ConcreteIntervalSystem(0.1);
const processing = new ConcreteProcessingSystem(); const processing = new ConcreteProcessingSystem();
const matchingEntity = new Entity('Matching', 1); const matchingEntity = scene.createEntity('Matching');
matchingEntity.addComponent(new TestComponent(100)); matchingEntity.addComponent(new TestComponent(100));
const nonMatchingEntity = new Entity('NonMatching', 2); const nonMatchingEntity = scene.createEntity('NonMatching');
nonMatchingEntity.addComponent(new AnotherComponent('test')); nonMatchingEntity.addComponent(new AnotherComponent('test'));
// 所有系统都应该匹配TestComponent // 所有系统都应该匹配TestComponent

View File

@@ -1,6 +1,7 @@
import { ComponentSparseSet } from '../../../src/ECS/Utils/ComponentSparseSet'; import { ComponentSparseSet } from '../../../src/ECS/Utils/ComponentSparseSet';
import { Entity } from '../../../src/ECS/Entity'; import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component'; import { Component } from '../../../src/ECS/Component';
import { Scene } from '../../../src/ECS/Scene';
// 测试组件类 // 测试组件类
class PositionComponent extends Component { class PositionComponent extends Component {
@@ -32,20 +33,21 @@ describe('ComponentSparseSet', () => {
let entity1: Entity; let entity1: Entity;
let entity2: Entity; let entity2: Entity;
let entity3: Entity; let entity3: Entity;
let scene: Scene;
beforeEach(() => { beforeEach(() => {
componentSparseSet = new ComponentSparseSet(); componentSparseSet = new ComponentSparseSet();
scene = new Scene();
// 创建测试实体
entity1 = new Entity('entity1', 1); entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20)); entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent(1, 2)); entity1.addComponent(new VelocityComponent(1, 2));
entity2 = new Entity('entity2', 2); entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40)); entity2.addComponent(new PositionComponent(30, 40));
entity2.addComponent(new HealthComponent(80, 100)); entity2.addComponent(new HealthComponent(80, 100));
entity3 = new Entity('entity3', 3); entity3 = scene.createEntity('entity3');
entity3.addComponent(new VelocityComponent(3, 4)); entity3.addComponent(new VelocityComponent(3, 4));
entity3.addComponent(new HealthComponent(50, 100)); entity3.addComponent(new HealthComponent(50, 100));
entity3.addComponent(new RenderComponent(true)); entity3.addComponent(new RenderComponent(true));
@@ -358,16 +360,16 @@ describe('ComponentSparseSet', () => {
// 创建大量实体 // 创建大量实体
for (let i = 0; i < 1000; i++) { for (let i = 0; i < 1000; i++) {
const entity = new Entity(`entity${i}`, i); const entity = scene.createEntity(`entity${i}`);
entity.addComponent(new PositionComponent(i, i)); entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) { if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1)); entity.addComponent(new VelocityComponent(1, 1));
} }
if (i % 3 === 0) { if (i % 3 === 0) {
entity.addComponent(new HealthComponent(100, 100)); entity.addComponent(new HealthComponent(100, 100));
} }
entities.push(entity); entities.push(entity);
componentSparseSet.addEntity(entity); componentSparseSet.addEntity(entity);
} }