/** * 增量序列化系统测试 */ import { Component } from '../../../src/ECS/Component'; import { Scene } from '../../../src/ECS/Scene'; import { Entity } from '../../../src/ECS/Entity'; import { Serializable, Serialize, IncrementalSerializer, ChangeOperation } from '../../../src/ECS/Serialization'; import { ECSComponent } from '../../../src/ECS/Decorators'; import { ComponentRegistry } from '../../../src/ECS/Core/ComponentStorage'; // 测试组件定义 @ECSComponent('IncTest_Position') @Serializable({ version: 1 }) class PositionComponent extends Component { @Serialize() public x: number = 0; @Serialize() public y: number = 0; constructor(x: number = 0, y: number = 0) { super(); this.x = x; this.y = y; } } @ECSComponent('IncTest_Velocity') @Serializable({ version: 1 }) class VelocityComponent extends Component { @Serialize() public dx: number = 0; @Serialize() public dy: number = 0; } @ECSComponent('IncTest_Health') @Serializable({ version: 1 }) class HealthComponent extends Component { @Serialize() public current: number = 100; @Serialize() public max: number = 100; } describe('Incremental Serialization System', () => { let scene: Scene; beforeEach(() => { IncrementalSerializer.resetVersion(); ComponentRegistry.reset(); // 重新注册测试组件 ComponentRegistry.register(PositionComponent); ComponentRegistry.register(VelocityComponent); ComponentRegistry.register(HealthComponent); scene = new Scene({ name: 'IncrementalTestScene' }); }); afterEach(() => { scene.end(); }); describe('Scene Snapshot', () => { it('应该创建场景快照', () => { const entity1 = scene.createEntity('Entity1'); entity1.addComponent(new PositionComponent(10, 20)); const entity2 = scene.createEntity('Entity2'); entity2.addComponent(new VelocityComponent()); scene.createIncrementalSnapshot(); expect(scene.hasIncrementalSnapshot()).toBe(true); }); it('应该在快照中包含所有实体', () => { const entity1 = scene.createEntity('Entity1'); entity1.addComponent(new PositionComponent(10, 20)); const entity2 = scene.createEntity('Entity2'); entity2.addComponent(new VelocityComponent()); const snapshot = IncrementalSerializer.createSnapshot(scene); expect(snapshot.entityIds.size).toBe(2); expect(snapshot.entityIds.has(entity1.id)).toBe(true); expect(snapshot.entityIds.has(entity2.id)).toBe(true); }); it('应该在快照中包含实体基本信息', () => { const entity = scene.createEntity('TestEntity'); entity.tag = 42; entity.active = false; entity.enabled = false; entity.updateOrder = 5; const snapshot = IncrementalSerializer.createSnapshot(scene); const entityData = snapshot.entities.get(entity.id); expect(entityData).toBeDefined(); expect(entityData!.name).toBe('TestEntity'); expect(entityData!.tag).toBe(42); expect(entityData!.active).toBe(false); expect(entityData!.enabled).toBe(false); expect(entityData!.updateOrder).toBe(5); }); it('应该在快照中包含组件数据', () => { const entity = scene.createEntity('Entity'); entity.addComponent(new PositionComponent(100, 200)); const snapshot = IncrementalSerializer.createSnapshot(scene, { deepComponentComparison: true }); const components = snapshot.components.get(entity.id); expect(components).toBeDefined(); expect(components!.has('IncTest_Position')).toBe(true); }); }); describe('Entity Changes Detection', () => { it('应该检测新增的实体', () => { scene.createIncrementalSnapshot(); const newEntity = scene.createEntity('NewEntity'); newEntity.addComponent(new PositionComponent(50, 100)); const incremental = scene.serializeIncremental(); expect(incremental.entityChanges.length).toBe(1); expect(incremental.entityChanges[0].operation).toBe(ChangeOperation.EntityAdded); expect(incremental.entityChanges[0].entityId).toBe(newEntity.id); expect(incremental.entityChanges[0].entityName).toBe('NewEntity'); }); it('应该检测删除的实体', () => { const entity = scene.createEntity('ToDelete'); scene.createIncrementalSnapshot(); entity.destroy(); const incremental = scene.serializeIncremental(); expect(incremental.entityChanges.length).toBe(1); expect(incremental.entityChanges[0].operation).toBe(ChangeOperation.EntityRemoved); expect(incremental.entityChanges[0].entityId).toBe(entity.id); }); it('应该检测实体属性变更', () => { const entity = scene.createEntity('Entity'); scene.createIncrementalSnapshot(); entity.name = 'UpdatedName'; entity.tag = 99; entity.active = false; const incremental = scene.serializeIncremental(); expect(incremental.entityChanges.length).toBe(1); expect(incremental.entityChanges[0].operation).toBe(ChangeOperation.EntityUpdated); expect(incremental.entityChanges[0].entityData!.name).toBe('UpdatedName'); expect(incremental.entityChanges[0].entityData!.tag).toBe(99); expect(incremental.entityChanges[0].entityData!.active).toBe(false); }); }); describe('Component Changes Detection', () => { it('应该检测新增的组件', () => { const entity = scene.createEntity('Entity'); scene.createIncrementalSnapshot(); entity.addComponent(new PositionComponent(10, 20)); const incremental = scene.serializeIncremental(); expect(incremental.componentChanges.length).toBe(1); expect(incremental.componentChanges[0].operation).toBe(ChangeOperation.ComponentAdded); expect(incremental.componentChanges[0].entityId).toBe(entity.id); expect(incremental.componentChanges[0].componentType).toBe('IncTest_Position'); }); it('应该检测删除的组件', () => { const entity = scene.createEntity('Entity'); entity.addComponent(new PositionComponent(10, 20)); scene.createIncrementalSnapshot(); entity.removeComponentByType(PositionComponent); const incremental = scene.serializeIncremental(); expect(incremental.componentChanges.length).toBe(1); expect(incremental.componentChanges[0].operation).toBe(ChangeOperation.ComponentRemoved); expect(incremental.componentChanges[0].componentType).toBe('IncTest_Position'); }); it('应该检测组件数据变更', () => { const entity = scene.createEntity('Entity'); const pos = new PositionComponent(10, 20); entity.addComponent(pos); scene.createIncrementalSnapshot(); pos.x = 100; pos.y = 200; const incremental = scene.serializeIncremental(); expect(incremental.componentChanges.length).toBe(1); expect(incremental.componentChanges[0].operation).toBe(ChangeOperation.ComponentUpdated); expect(incremental.componentChanges[0].componentData!.data.x).toBe(100); expect(incremental.componentChanges[0].componentData!.data.y).toBe(200); }); it('应该检测多个组件变更', () => { const entity = scene.createEntity('Entity'); entity.addComponent(new PositionComponent(10, 20)); scene.createIncrementalSnapshot(); entity.addComponent(new VelocityComponent()); entity.addComponent(new HealthComponent()); entity.removeComponentByType(PositionComponent); const incremental = scene.serializeIncremental(); expect(incremental.componentChanges.length).toBe(3); }); }); describe('Scene Data Changes Detection', () => { it('应该检测新增的场景数据', () => { scene.createIncrementalSnapshot(); scene.sceneData.set('weather', 'sunny'); scene.sceneData.set('time', 12.5); const incremental = scene.serializeIncremental(); expect(incremental.sceneDataChanges.length).toBe(2); }); it('应该检测更新的场景数据', () => { scene.sceneData.set('weather', 'sunny'); scene.createIncrementalSnapshot(); scene.sceneData.set('weather', 'rainy'); const incremental = scene.serializeIncremental(); expect(incremental.sceneDataChanges.length).toBe(1); expect(incremental.sceneDataChanges[0].key).toBe('weather'); expect(incremental.sceneDataChanges[0].value).toBe('rainy'); }); it('应该检测删除的场景数据', () => { scene.sceneData.set('temp', 'value'); scene.createIncrementalSnapshot(); scene.sceneData.delete('temp'); const incremental = scene.serializeIncremental(); expect(incremental.sceneDataChanges.length).toBe(1); expect(incremental.sceneDataChanges[0].deleted).toBe(true); }); }); describe('Apply Incremental Changes', () => { it('应该应用实体添加变更', () => { const scene1 = new Scene({ name: 'Scene1' }); scene1.createIncrementalSnapshot(); const newEntity = scene1.createEntity('NewEntity'); newEntity.addComponent(new PositionComponent(50, 100)); const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); scene2.applyIncremental(incremental); expect(scene2.entities.count).toBe(1); const entity = scene2.findEntity('NewEntity'); expect(entity).not.toBeNull(); expect(entity!.hasComponent(PositionComponent)).toBe(true); scene1.end(); scene2.end(); }); it('应该应用实体删除变更', () => { const scene1 = new Scene({ name: 'Scene1' }); const entity = scene1.createEntity('ToDelete'); scene1.createIncrementalSnapshot(); entity.destroy(); const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); const entity2 = scene2.createEntity('ToDelete'); Object.defineProperty(entity2, 'id', { value: entity.id, writable: true }); scene2.applyIncremental(incremental); expect(scene2.entities.count).toBe(0); scene1.end(); scene2.end(); }); it('应该应用实体属性更新', () => { const scene1 = new Scene({ name: 'Scene1' }); const entity1 = scene1.createEntity('Entity'); scene1.createIncrementalSnapshot(); entity1.name = 'UpdatedName'; entity1.tag = 42; entity1.active = false; const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); const entity2 = scene2.createEntity('Entity'); Object.defineProperty(entity2, 'id', { value: entity1.id, writable: true }); scene2.applyIncremental(incremental); expect(entity2.name).toBe('UpdatedName'); expect(entity2.tag).toBe(42); expect(entity2.active).toBe(false); scene1.end(); scene2.end(); }); it('应该应用组件添加变更', () => { const scene1 = new Scene({ name: 'Scene1' }); const entity1 = scene1.createEntity('Entity'); scene1.createIncrementalSnapshot(); entity1.addComponent(new PositionComponent(100, 200)); const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); const entity2 = scene2.createEntity('Entity'); Object.defineProperty(entity2, 'id', { value: entity1.id, writable: true }); scene2.applyIncremental(incremental); expect(entity2.hasComponent(PositionComponent)).toBe(true); const pos = entity2.getComponent(PositionComponent); expect(pos!.x).toBe(100); expect(pos!.y).toBe(200); scene1.end(); scene2.end(); }); it('应该应用组件删除变更', () => { const scene1 = new Scene({ name: 'Scene1' }); const entity1 = scene1.createEntity('Entity'); entity1.addComponent(new PositionComponent(10, 20)); scene1.createIncrementalSnapshot(); entity1.removeComponentByType(PositionComponent); const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); const entity2 = scene2.createEntity('Entity'); Object.defineProperty(entity2, 'id', { value: entity1.id, writable: true }); entity2.addComponent(new PositionComponent(10, 20)); scene2.applyIncremental(incremental); expect(entity2.hasComponent(PositionComponent)).toBe(false); scene1.end(); scene2.end(); }); it('应该应用组件数据更新', () => { const scene1 = new Scene({ name: 'Scene1' }); const entity1 = scene1.createEntity('Entity'); const pos1 = new PositionComponent(10, 20); entity1.addComponent(pos1); scene1.createIncrementalSnapshot(); pos1.x = 100; pos1.y = 200; const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); const entity2 = scene2.createEntity('Entity'); Object.defineProperty(entity2, 'id', { value: entity1.id, writable: true }); entity2.addComponent(new PositionComponent(10, 20)); scene2.applyIncremental(incremental); const pos2 = entity2.getComponent(PositionComponent); expect(pos2!.x).toBe(100); expect(pos2!.y).toBe(200); scene1.end(); scene2.end(); }); it('应该应用场景数据变更', () => { const scene1 = new Scene({ name: 'Scene1' }); scene1.createIncrementalSnapshot(); scene1.sceneData.set('weather', 'sunny'); scene1.sceneData.set('time', 12.5); const incremental = scene1.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); scene2.applyIncremental(incremental); expect(scene2.sceneData.get('weather')).toBe('sunny'); expect(scene2.sceneData.get('time')).toBe(12.5); scene1.end(); scene2.end(); }); }); describe('Incremental Serialization', () => { it('应该序列化和反序列化增量快照', () => { scene.createIncrementalSnapshot(); const entity = scene.createEntity('Entity'); entity.addComponent(new PositionComponent(50, 100)); const incremental = scene.serializeIncremental(); const json = IncrementalSerializer.serializeIncremental(incremental); expect(typeof json).toBe('string'); const deserialized = IncrementalSerializer.deserializeIncremental(json); expect(deserialized.version).toBe(incremental.version); expect(deserialized.entityChanges.length).toBe(incremental.entityChanges.length); }); it('应该美化JSON输出', () => { scene.createIncrementalSnapshot(); const entity = scene.createEntity('Entity'); entity.addComponent(new PositionComponent(10, 20)); const incremental = scene.serializeIncremental(); const prettyJson = IncrementalSerializer.serializeIncremental(incremental, true); expect(prettyJson).toContain('\n'); expect(prettyJson).toContain(' '); }); }); describe('Snapshot Management', () => { it('应该更新增量快照基准', () => { const entity = scene.createEntity('Entity'); scene.createIncrementalSnapshot(); entity.addComponent(new PositionComponent(10, 20)); const incremental1 = scene.serializeIncremental(); scene.updateIncrementalSnapshot(); const pos = entity.getComponent(PositionComponent)!; pos.x = 100; const incremental2 = scene.serializeIncremental(); // incremental2应该只包含Position的更新,不包含添加 expect(incremental1.componentChanges.length).toBe(1); expect(incremental2.componentChanges.length).toBe(1); expect(incremental2.componentChanges[0].operation).toBe(ChangeOperation.ComponentUpdated); }); it('应该清除增量快照', () => { scene.createIncrementalSnapshot(); expect(scene.hasIncrementalSnapshot()).toBe(true); scene.clearIncrementalSnapshot(); expect(scene.hasIncrementalSnapshot()).toBe(false); }); it('应该在没有快照时抛出错误', () => { expect(() => { scene.serializeIncremental(); }).toThrow('必须先调用 createIncrementalSnapshot() 创建基础快照'); }); }); describe('Statistics and Utilities', () => { it('应该计算增量快照大小', () => { scene.createIncrementalSnapshot(); const entity = scene.createEntity('Entity'); entity.addComponent(new PositionComponent(10, 20)); const incremental = scene.serializeIncremental(); const size = IncrementalSerializer.getIncrementalSize(incremental); expect(size).toBeGreaterThan(0); }); it('应该提供增量快照统计信息', () => { const entity1 = scene.createEntity('Entity1'); entity1.addComponent(new PositionComponent(10, 20)); scene.createIncrementalSnapshot(); const entity2 = scene.createEntity('Entity2'); entity2.addComponent(new VelocityComponent()); entity1.destroy(); const incremental = scene.serializeIncremental(); const stats = IncrementalSerializer.getIncrementalStats(incremental); expect(stats.addedEntities).toBe(1); expect(stats.removedEntities).toBe(1); expect(stats.addedComponents).toBe(1); expect(stats.totalChanges).toBeGreaterThan(0); }); }); describe('Performance and Edge Cases', () => { it('应该处理大量实体变更', () => { scene.createIncrementalSnapshot(); for (let i = 0; i < 100; i++) { const entity = scene.createEntity(`Entity_${i}`); entity.addComponent(new PositionComponent(i, i * 2)); } const incremental = scene.serializeIncremental(); expect(incremental.entityChanges.length).toBe(100); expect(incremental.componentChanges.length).toBe(100); }); it('应该处理空变更', () => { scene.createIncrementalSnapshot(); const incremental = scene.serializeIncremental(); expect(incremental.entityChanges.length).toBe(0); expect(incremental.componentChanges.length).toBe(0); expect(incremental.sceneDataChanges.length).toBe(0); }); it('应该处理复杂嵌套场景数据', () => { scene.createIncrementalSnapshot(); scene.sceneData.set('config', { nested: { deep: { value: 42 } }, array: [1, 2, 3] }); const incremental = scene.serializeIncremental(); const scene2 = new Scene({ name: 'Scene2' }); scene2.applyIncremental(incremental); const config = scene2.sceneData.get('config'); expect(config.nested.deep.value).toBe(42); expect(config.array).toEqual([1, 2, 3]); scene2.end(); }); it('应该正确处理快照版本号', () => { IncrementalSerializer.resetVersion(); const snapshot1 = IncrementalSerializer.createSnapshot(scene); expect(snapshot1.version).toBe(1); const snapshot2 = IncrementalSerializer.createSnapshot(scene); expect(snapshot2.version).toBe(2); }); }); });