增量序列化支持二进制

This commit is contained in:
YHH
2025-10-09 17:14:18 +08:00
parent 959879440d
commit 97a69fed09
6 changed files with 373 additions and 58 deletions

View File

@@ -618,24 +618,28 @@ export class Scene implements IScene {
/**
* 应用增量变更到场景
*
* @param incremental 增量快照数据(JSON字符串或对象
* @param incremental 增量快照数据(IncrementalSnapshot对象、JSON字符串或二进制Buffer
* @param componentRegistry 组件类型注册表(可选,默认使用全局注册表)
*
* @example
* ```typescript
* // 应用增量变更
* // 应用增量变更对象
* scene.applyIncremental(incrementalSnapshot);
*
* // 从JSON字符串应用
* const incremental = IncrementalSerializer.deserializeIncremental(jsonString);
* scene.applyIncremental(incremental);
* // 从JSON字符串应用
* const jsonData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'json' });
* scene.applyIncremental(jsonData);
*
* // 从二进制Buffer应用
* const binaryData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'binary' });
* scene.applyIncremental(binaryData);
* ```
*/
public applyIncremental(
incremental: IncrementalSnapshot | string,
incremental: IncrementalSnapshot | string | Buffer,
componentRegistry?: Map<string, any>
): void {
const snapshot = typeof incremental === 'string'
const snapshot = (typeof incremental === 'string' || Buffer.isBuffer(incremental))
? IncrementalSerializer.deserializeIncremental(incremental)
: incremental;

View File

@@ -11,6 +11,7 @@ import { Component } from '../Component';
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
import { SerializedEntity } from './EntitySerializer';
import { ComponentType } from '../Core/ComponentStorage';
import * as msgpack from 'msgpack-lite';
/**
* 变更操作类型
@@ -117,6 +118,11 @@ interface SceneSnapshot {
sceneData: Map<string, string>; // 使用JSON字符串存储场景数据
}
/**
* 增量序列化格式
*/
export type IncrementalSerializationFormat = 'json' | 'binary';
/**
* 增量序列化选项
*/
@@ -138,6 +144,20 @@ export interface IncrementalSerializationOptions {
* 默认false设为true可减少内存占用但增加CPU开销
*/
compressSnapshot?: boolean;
/**
* 序列化格式
* - 'json': JSON格式可读性好方便调试
* - 'binary': MessagePack二进制格式体积小性能高
* 默认 'json'
*/
format?: IncrementalSerializationFormat;
/**
* 是否美化JSON输出仅在format='json'时有效)
* 默认false
*/
pretty?: boolean;
}
/**
@@ -585,40 +605,93 @@ export class IncrementalSerializer {
}
/**
* 序列化增量快照为JSON
* 序列化增量快照
*
* @param incremental 增量快照
* @param pretty 是否美化输出
* @returns JSON字符串
* @param options 序列化选项
* @returns 序列化后的数据JSON字符串或二进制Buffer
*
* @example
* ```typescript
* // JSON格式默认
* const jsonData = IncrementalSerializer.serializeIncremental(snapshot);
*
* // 二进制格式
* const binaryData = IncrementalSerializer.serializeIncremental(snapshot, {
* format: 'binary'
* });
*
* // 美化JSON
* const prettyJson = IncrementalSerializer.serializeIncremental(snapshot, {
* format: 'json',
* pretty: true
* });
* ```
*/
public static serializeIncremental(
incremental: IncrementalSnapshot,
pretty: boolean = false
): string {
return pretty
? JSON.stringify(incremental, null, 2)
: JSON.stringify(incremental);
options?: { format?: IncrementalSerializationFormat; pretty?: boolean }
): string | Buffer {
const opts = {
format: 'json' as IncrementalSerializationFormat,
pretty: false,
...options
};
if (opts.format === 'binary') {
return msgpack.encode(incremental);
} else {
return opts.pretty
? JSON.stringify(incremental, null, 2)
: JSON.stringify(incremental);
}
}
/**
* 从JSON反序列化增量快照
* 反序列化增量快照
*
* @param json JSON字符串
* @param data 序列化的数据JSON字符串或二进制Buffer
* @returns 增量快照
*
* @example
* ```typescript
* // 从JSON反序列化
* const snapshot = IncrementalSerializer.deserializeIncremental(jsonString);
*
* // 从二进制反序列化
* const snapshot = IncrementalSerializer.deserializeIncremental(buffer);
* ```
*/
public static deserializeIncremental(json: string): IncrementalSnapshot {
return JSON.parse(json);
public static deserializeIncremental(data: string | Buffer): IncrementalSnapshot {
if (typeof data === 'string') {
// JSON格式
return JSON.parse(data);
} else {
// 二进制格式MessagePack
return msgpack.decode(data);
}
}
/**
* 计算增量快照的大小(字节)
*
* @param incremental 增量快照
* @param format 序列化格式,默认为 'json'
* @returns 字节数
*/
public static getIncrementalSize(incremental: IncrementalSnapshot): number {
const json = this.serializeIncremental(incremental);
return new Blob([json]).size;
public static getIncrementalSize(
incremental: IncrementalSnapshot,
format: IncrementalSerializationFormat = 'json'
): number {
const data = this.serializeIncremental(incremental, { format });
if (typeof data === 'string') {
// JSON格式计算UTF-8编码后的字节数
return Buffer.byteLength(data, 'utf8');
} else {
// 二进制格式直接返回Buffer长度
return data.length;
}
}
/**

View File

@@ -55,6 +55,7 @@ export { IncrementalSerializer, ChangeOperation } from './IncrementalSerializer'
export type {
IncrementalSnapshot,
IncrementalSerializationOptions,
IncrementalSerializationFormat,
EntityChange,
ComponentChange,
SceneDataChange

View File

@@ -436,20 +436,42 @@ describe('Incremental Serialization System', () => {
});
describe('Incremental Serialization', () => {
it('应该序列化和反序列化增量快照', () => {
it('应该序列化和反序列化增量快照JSON格式', () => {
scene.createIncrementalSnapshot();
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(50, 100));
const incremental = scene.serializeIncremental();
const json = IncrementalSerializer.serializeIncremental(incremental);
const json = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
expect(typeof json).toBe('string');
const deserialized = IncrementalSerializer.deserializeIncremental(json);
expect(deserialized.version).toBe(incremental.version);
expect(deserialized.entityChanges.length).toBe(incremental.entityChanges.length);
expect(deserialized.componentChanges.length).toBe(incremental.componentChanges.length);
});
it('应该序列化和反序列化增量快照(二进制格式)', () => {
scene.createIncrementalSnapshot();
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(50, 100));
entity.tag = 42;
scene.sceneData.set('weather', 'sunny');
const incremental = scene.serializeIncremental();
const binary = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
expect(Buffer.isBuffer(binary)).toBe(true);
const deserialized = IncrementalSerializer.deserializeIncremental(binary);
expect(deserialized.version).toBe(incremental.version);
expect(deserialized.sceneName).toBe(incremental.sceneName);
expect(deserialized.entityChanges.length).toBe(incremental.entityChanges.length);
expect(deserialized.componentChanges.length).toBe(incremental.componentChanges.length);
expect(deserialized.sceneDataChanges.length).toBe(incremental.sceneDataChanges.length);
});
it('应该美化JSON输出', () => {
@@ -458,11 +480,158 @@ describe('Incremental Serialization System', () => {
entity.addComponent(new PositionComponent(10, 20));
const incremental = scene.serializeIncremental();
const prettyJson = IncrementalSerializer.serializeIncremental(incremental, true);
const prettyJson = IncrementalSerializer.serializeIncremental(incremental, { format: 'json', pretty: true });
expect(typeof prettyJson).toBe('string');
expect(prettyJson).toContain('\n');
expect(prettyJson).toContain(' ');
});
it('二进制格式应该比JSON格式更小', () => {
const entities = [];
for (let i = 0; i < 50; i++) {
const entity = scene.createEntity(`Entity_${i}`);
entity.addComponent(new PositionComponent(i * 10, i * 20));
entity.addComponent(new VelocityComponent());
entities.push(entity);
}
scene.createIncrementalSnapshot();
for (const entity of entities) {
const pos = entity.getComponent(PositionComponent)!;
pos.x += 100;
pos.y += 200;
}
const incremental = scene.serializeIncremental();
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
const jsonSize = Buffer.byteLength(jsonData as string);
const binarySize = (binaryData as Buffer).length;
expect(binarySize).toBeLessThan(jsonSize);
});
it('二进制和JSON格式应该包含相同的数据', () => {
scene.createIncrementalSnapshot();
const entity1 = scene.createEntity('Entity1');
entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent());
entity1.tag = 99;
const entity2 = scene.createEntity('Entity2');
entity2.addComponent(new HealthComponent());
scene.sceneData.set('level', 5);
scene.sceneData.set('score', 1000);
const incremental = scene.serializeIncremental();
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
const fromJson = IncrementalSerializer.deserializeIncremental(jsonData);
const fromBinary = IncrementalSerializer.deserializeIncremental(binaryData);
expect(fromJson.version).toBe(fromBinary.version);
expect(fromJson.timestamp).toBe(fromBinary.timestamp);
expect(fromJson.sceneName).toBe(fromBinary.sceneName);
expect(fromJson.entityChanges.length).toBe(fromBinary.entityChanges.length);
expect(fromJson.componentChanges.length).toBe(fromBinary.componentChanges.length);
expect(fromJson.sceneDataChanges.length).toBe(fromBinary.sceneDataChanges.length);
expect(fromJson.entityChanges[0].entityName).toBe(fromBinary.entityChanges[0].entityName);
expect(fromJson.entityChanges[0].entityData?.tag).toBe(fromBinary.entityChanges[0].entityData?.tag);
});
it('应该正确应用二进制格式反序列化的增量快照', () => {
const scene1 = new Scene({ name: 'Scene1' });
scene1.createIncrementalSnapshot();
const entity = scene1.createEntity('TestEntity');
entity.addComponent(new PositionComponent(100, 200));
entity.tag = 77;
const incremental = scene1.serializeIncremental();
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
const deserializedIncremental = IncrementalSerializer.deserializeIncremental(binaryData);
const scene2 = new Scene({ name: 'Scene2' });
scene2.applyIncremental(deserializedIncremental);
expect(scene2.entities.count).toBe(1);
const restoredEntity = scene2.findEntity('TestEntity');
expect(restoredEntity).not.toBeNull();
expect(restoredEntity!.tag).toBe(77);
expect(restoredEntity!.hasComponent(PositionComponent)).toBe(true);
const pos = restoredEntity!.getComponent(PositionComponent)!;
expect(pos.x).toBe(100);
expect(pos.y).toBe(200);
scene1.end();
scene2.end();
});
it('Scene.applyIncremental应该直接支持二进制Buffer', () => {
const scene1 = new Scene({ name: 'Scene1' });
scene1.createIncrementalSnapshot();
const entity1 = scene1.createEntity('Entity1');
entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent());
const entity2 = scene1.createEntity('Entity2');
entity2.addComponent(new HealthComponent());
const incremental = scene1.serializeIncremental();
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
const scene2 = new Scene({ name: 'Scene2' });
scene2.applyIncremental(binaryData);
expect(scene2.entities.count).toBe(2);
const e1 = scene2.findEntity('Entity1');
expect(e1).not.toBeNull();
expect(e1!.hasComponent(PositionComponent)).toBe(true);
expect(e1!.hasComponent(VelocityComponent)).toBe(true);
const e2 = scene2.findEntity('Entity2');
expect(e2).not.toBeNull();
expect(e2!.hasComponent(HealthComponent)).toBe(true);
scene1.end();
scene2.end();
});
it('Scene.applyIncremental应该直接支持JSON字符串', () => {
const scene1 = new Scene({ name: 'Scene1' });
scene1.createIncrementalSnapshot();
const entity = scene1.createEntity('TestEntity');
entity.addComponent(new PositionComponent(50, 100));
entity.tag = 99;
const incremental = scene1.serializeIncremental();
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
const scene2 = new Scene({ name: 'Scene2' });
scene2.applyIncremental(jsonData);
expect(scene2.entities.count).toBe(1);
const restoredEntity = scene2.findEntity('TestEntity');
expect(restoredEntity).not.toBeNull();
expect(restoredEntity!.tag).toBe(99);
scene1.end();
scene2.end();
});
});
describe('Snapshot Management', () => {