增量序列化支持二进制
This commit is contained in:
@@ -251,13 +251,25 @@ console.log('更新组件:', stats.updatedComponents);
|
|||||||
#### 4. 序列化增量数据
|
#### 4. 序列化增量数据
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 转换为JSON字符串
|
// JSON格式(默认)
|
||||||
const json = IncrementalSerializer.serializeIncremental(incremental);
|
const jsonData = IncrementalSerializer.serializeIncremental(incremental, {
|
||||||
|
format: 'json'
|
||||||
|
});
|
||||||
|
|
||||||
// 发送到服务器或保存
|
// 二进制格式(更小的体积,更高性能)
|
||||||
socket.send(json);
|
const binaryData = IncrementalSerializer.serializeIncremental(incremental, {
|
||||||
// 或
|
format: 'binary'
|
||||||
localStorage.setItem('changes', json);
|
});
|
||||||
|
|
||||||
|
// 美化JSON输出(便于调试)
|
||||||
|
const prettyJson = IncrementalSerializer.serializeIncremental(incremental, {
|
||||||
|
format: 'json',
|
||||||
|
pretty: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送或保存
|
||||||
|
socket.send(binaryData); // 网络传输使用二进制
|
||||||
|
localStorage.setItem('changes', jsonData); // 本地存储可用JSON
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. 应用增量变更
|
#### 5. 应用增量变更
|
||||||
@@ -266,11 +278,16 @@ localStorage.setItem('changes', json);
|
|||||||
// 在另一个场景应用变更
|
// 在另一个场景应用变更
|
||||||
const otherScene = new Scene();
|
const otherScene = new Scene();
|
||||||
|
|
||||||
// 从JSON字符串应用
|
// 直接应用增量对象
|
||||||
otherScene.applyIncremental(json);
|
|
||||||
|
|
||||||
// 或直接应用增量对象
|
|
||||||
otherScene.applyIncremental(incremental);
|
otherScene.applyIncremental(incremental);
|
||||||
|
|
||||||
|
// 从JSON字符串应用
|
||||||
|
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
|
||||||
|
otherScene.applyIncremental(jsonData);
|
||||||
|
|
||||||
|
// 从二进制Buffer应用
|
||||||
|
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
|
||||||
|
otherScene.applyIncremental(binaryData);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 增量快照管理
|
### 增量快照管理
|
||||||
@@ -322,6 +339,16 @@ interface IncrementalSerializationOptions {
|
|||||||
// 是否压缩快照(使用JSON序列化)
|
// 是否压缩快照(使用JSON序列化)
|
||||||
// 默认false
|
// 默认false
|
||||||
compressSnapshot?: boolean;
|
compressSnapshot?: boolean;
|
||||||
|
|
||||||
|
// 序列化格式
|
||||||
|
// 'json': JSON格式(可读性好,方便调试)
|
||||||
|
// 'binary': MessagePack二进制格式(体积小,性能高)
|
||||||
|
// 默认 'json'
|
||||||
|
format?: 'json' | 'binary';
|
||||||
|
|
||||||
|
// 是否美化JSON输出(仅在format='json'时有效)
|
||||||
|
// 默认false
|
||||||
|
pretty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用选项
|
// 使用选项
|
||||||
@@ -513,17 +540,21 @@ class NetworkSync {
|
|||||||
|
|
||||||
// 只在有变更时发送
|
// 只在有变更时发送
|
||||||
if (stats.totalChanges > 0) {
|
if (stats.totalChanges > 0) {
|
||||||
const json = IncrementalSerializer.serializeIncremental(incremental);
|
// 使用二进制格式减少网络传输量
|
||||||
this.socket.send(json);
|
const binaryData = IncrementalSerializer.serializeIncremental(incremental, {
|
||||||
|
format: 'binary'
|
||||||
|
});
|
||||||
|
this.socket.send(binaryData);
|
||||||
|
|
||||||
// 更新基准
|
// 更新基准
|
||||||
this.scene.updateIncrementalSnapshot();
|
this.scene.updateIncrementalSnapshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private receiveIncremental(data: string): void {
|
private receiveIncremental(data: ArrayBuffer): void {
|
||||||
const incremental = IncrementalSerializer.deserializeIncremental(data);
|
// 直接应用二进制数据
|
||||||
this.scene.applyIncremental(incremental);
|
const buffer = Buffer.from(data);
|
||||||
|
this.scene.applyIncremental(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -750,23 +781,27 @@ class LargeDataComponent extends Component {
|
|||||||
|
|
||||||
### 全量序列化API
|
### 全量序列化API
|
||||||
|
|
||||||
- `scene.serialize(options?): string | Buffer` - 序列化场景
|
- [`Scene.serialize()`](/api/classes/Scene#serialize) - 序列化场景
|
||||||
- `scene.deserialize(data, options?)` - 反序列化场景
|
- [`Scene.deserialize()`](/api/classes/Scene#deserialize) - 反序列化场景
|
||||||
- `SceneSerializer.validate(data)` - 验证序列化数据
|
- [`SceneSerializer`](/api/classes/SceneSerializer) - 场景序列化器
|
||||||
- `SceneSerializer.getInfo(data)` - 获取序列化数据信息
|
- [`ComponentSerializer`](/api/classes/ComponentSerializer) - 组件序列化器
|
||||||
|
|
||||||
### 增量序列化API
|
### 增量序列化API
|
||||||
|
|
||||||
- `scene.createIncrementalSnapshot(options?)` - 创建基础快照
|
- [`Scene.createIncrementalSnapshot()`](/api/classes/Scene#createincrementalsnapshot) - 创建基础快照
|
||||||
- `scene.serializeIncremental(options?)` - 获取增量变更
|
- [`Scene.serializeIncremental()`](/api/classes/Scene#serializeincremental) - 获取增量变更
|
||||||
- `scene.applyIncremental(incremental)` - 应用增量变更
|
- [`Scene.applyIncremental()`](/api/classes/Scene#applyincremental) - 应用增量变更(支持IncrementalSnapshot对象、JSON字符串或二进制Buffer)
|
||||||
- `scene.updateIncrementalSnapshot(options?)` - 更新快照基准
|
- [`Scene.updateIncrementalSnapshot()`](/api/classes/Scene#updateincrementalsnapshot) - 更新快照基准
|
||||||
- `scene.clearIncrementalSnapshot()` - 清除快照
|
- [`Scene.clearIncrementalSnapshot()`](/api/classes/Scene#clearincrementalsnapshot) - 清除快照
|
||||||
- `scene.hasIncrementalSnapshot()` - 检查是否有快照
|
- [`Scene.hasIncrementalSnapshot()`](/api/classes/Scene#hasincrementalsnapshot) - 检查是否有快照
|
||||||
- `IncrementalSerializer.getIncrementalStats(incremental)` - 获取统计信息
|
- [`IncrementalSerializer`](/api/classes/IncrementalSerializer) - 增量序列化器
|
||||||
|
- [`IncrementalSnapshot`](/api/interfaces/IncrementalSnapshot) - 增量快照接口
|
||||||
|
- [`IncrementalSerializationOptions`](/api/interfaces/IncrementalSerializationOptions) - 增量序列化选项
|
||||||
|
- [`IncrementalSerializationFormat`](/api/type-aliases/IncrementalSerializationFormat) - 序列化格式类型
|
||||||
|
|
||||||
### 版本迁移API
|
### 版本迁移API
|
||||||
|
|
||||||
|
- [`VersionMigrationManager`](/api/classes/VersionMigrationManager) - 版本迁移管理器
|
||||||
- `VersionMigrationManager.registerComponentMigration()` - 注册组件迁移
|
- `VersionMigrationManager.registerComponentMigration()` - 注册组件迁移
|
||||||
- `VersionMigrationManager.registerSceneMigration()` - 注册场景迁移
|
- `VersionMigrationManager.registerSceneMigration()` - 注册场景迁移
|
||||||
- `VersionMigrationManager.canMigrateComponent()` - 检查是否可以迁移
|
- `VersionMigrationManager.canMigrateComponent()` - 检查是否可以迁移
|
||||||
|
|||||||
@@ -166,11 +166,17 @@ export class IncrementalSerializationDemo extends DemoBase {
|
|||||||
const incremental = this.scene.serializeIncremental();
|
const incremental = this.scene.serializeIncremental();
|
||||||
const stats = IncrementalSerializer.getIncrementalStats(incremental);
|
const stats = IncrementalSerializer.getIncrementalStats(incremental);
|
||||||
|
|
||||||
|
// 计算JSON和二进制格式的大小
|
||||||
|
const jsonSize = IncrementalSerializer.getIncrementalSize(incremental, 'json');
|
||||||
|
const binarySize = IncrementalSerializer.getIncrementalSize(incremental, 'binary');
|
||||||
|
|
||||||
this.incrementalHistory.push({
|
this.incrementalHistory.push({
|
||||||
label,
|
label,
|
||||||
incremental,
|
incremental,
|
||||||
stats,
|
stats,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now(),
|
||||||
|
jsonSize,
|
||||||
|
binarySize
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scene.updateIncrementalSnapshot();
|
this.scene.updateIncrementalSnapshot();
|
||||||
@@ -226,8 +232,16 @@ export class IncrementalSerializationDemo extends DemoBase {
|
|||||||
<div class="stat-value" id="historyCount">0</div>
|
<div class="stat-value" id="historyCount">0</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-label">最后快照大小</div>
|
<div class="stat-label">JSON大小</div>
|
||||||
<div class="stat-value" id="snapshotSize">0B</div>
|
<div class="stat-value" id="jsonSize">0B</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">二进制大小</div>
|
||||||
|
<div class="stat-value" id="binarySize">0B</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">压缩率</div>
|
||||||
|
<div class="stat-value" id="compressionRatio">0%</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-label">总变更数</div>
|
<div class="stat-label">总变更数</div>
|
||||||
@@ -358,8 +372,12 @@ export class IncrementalSerializationDemo extends DemoBase {
|
|||||||
</div>
|
</div>
|
||||||
<div style="font-size: 11px; color: #8892b0; margin-top: 4px;">
|
<div style="font-size: 11px; color: #8892b0; margin-top: 4px;">
|
||||||
实体: +${item.stats.addedEntities} -${item.stats.removedEntities} ~${item.stats.updatedEntities} |
|
实体: +${item.stats.addedEntities} -${item.stats.removedEntities} ~${item.stats.updatedEntities} |
|
||||||
组件: +${item.stats.addedComponents} -${item.stats.removedComponents} ~${item.stats.updatedComponents} |
|
组件: +${item.stats.addedComponents} -${item.stats.removedComponents} ~${item.stats.updatedComponents}
|
||||||
总变更: ${item.stats.totalChanges}
|
</div>
|
||||||
|
<div style="font-size: 11px; color: #8892b0; margin-top: 2px;">
|
||||||
|
JSON: ${this.formatBytes(item.jsonSize)} |
|
||||||
|
Binary: ${this.formatBytes(item.binarySize)} |
|
||||||
|
<span style="color: #4ecdc4;">节省: ${((1 - item.binarySize / item.jsonSize) * 100).toFixed(1)}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -381,11 +399,19 @@ export class IncrementalSerializationDemo extends DemoBase {
|
|||||||
const item = this.incrementalHistory[index];
|
const item = this.incrementalHistory[index];
|
||||||
const detailsPanel = document.getElementById('snapshotDetails')!;
|
const detailsPanel = document.getElementById('snapshotDetails')!;
|
||||||
|
|
||||||
|
const compressionRatio = ((1 - item.binarySize / item.jsonSize) * 100).toFixed(1);
|
||||||
|
|
||||||
const details = {
|
const details = {
|
||||||
版本: item.incremental.version,
|
版本: item.incremental.version,
|
||||||
基础版本: item.incremental.baseVersion,
|
基础版本: item.incremental.baseVersion,
|
||||||
时间戳: new Date(item.incremental.timestamp).toLocaleString(),
|
时间戳: new Date(item.incremental.timestamp).toLocaleString(),
|
||||||
场景名称: item.incremental.sceneName,
|
场景名称: item.incremental.sceneName,
|
||||||
|
格式对比: {
|
||||||
|
JSON大小: this.formatBytes(item.jsonSize),
|
||||||
|
二进制大小: this.formatBytes(item.binarySize),
|
||||||
|
压缩率: `${compressionRatio}%`,
|
||||||
|
节省字节: this.formatBytes(item.jsonSize - item.binarySize)
|
||||||
|
},
|
||||||
统计: item.stats,
|
统计: item.stats,
|
||||||
实体变更: item.incremental.entityChanges.map((c: any) => ({
|
实体变更: item.incremental.entityChanges.map((c: any) => ({
|
||||||
操作: c.operation,
|
操作: c.operation,
|
||||||
@@ -413,8 +439,15 @@ export class IncrementalSerializationDemo extends DemoBase {
|
|||||||
|
|
||||||
if (this.incrementalHistory.length > 0) {
|
if (this.incrementalHistory.length > 0) {
|
||||||
const lastItem = this.incrementalHistory[this.incrementalHistory.length - 1];
|
const lastItem = this.incrementalHistory[this.incrementalHistory.length - 1];
|
||||||
const size = IncrementalSerializer.getIncrementalSize(lastItem.incremental);
|
|
||||||
document.getElementById('snapshotSize')!.textContent = this.formatBytes(size);
|
document.getElementById('jsonSize')!.textContent = this.formatBytes(lastItem.jsonSize);
|
||||||
|
document.getElementById('binarySize')!.textContent = this.formatBytes(lastItem.binarySize);
|
||||||
|
|
||||||
|
const compressionRatio = ((1 - lastItem.binarySize / lastItem.jsonSize) * 100).toFixed(1);
|
||||||
|
const ratioElement = document.getElementById('compressionRatio')!;
|
||||||
|
ratioElement.textContent = `${compressionRatio}%`;
|
||||||
|
ratioElement.style.color = parseFloat(compressionRatio) > 30 ? '#4ecdc4' : '#ffe66d';
|
||||||
|
|
||||||
document.getElementById('totalChanges')!.textContent = lastItem.stats.totalChanges.toString();
|
document.getElementById('totalChanges')!.textContent = lastItem.stats.totalChanges.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -618,24 +618,28 @@ export class Scene implements IScene {
|
|||||||
/**
|
/**
|
||||||
* 应用增量变更到场景
|
* 应用增量变更到场景
|
||||||
*
|
*
|
||||||
* @param incremental 增量快照数据(JSON字符串或对象)
|
* @param incremental 增量快照数据(IncrementalSnapshot对象、JSON字符串或二进制Buffer)
|
||||||
* @param componentRegistry 组件类型注册表(可选,默认使用全局注册表)
|
* @param componentRegistry 组件类型注册表(可选,默认使用全局注册表)
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* // 应用增量变更
|
* // 应用增量变更对象
|
||||||
* scene.applyIncremental(incrementalSnapshot);
|
* scene.applyIncremental(incrementalSnapshot);
|
||||||
*
|
*
|
||||||
* // 或从JSON字符串应用
|
* // 从JSON字符串应用
|
||||||
* const incremental = IncrementalSerializer.deserializeIncremental(jsonString);
|
* const jsonData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'json' });
|
||||||
* scene.applyIncremental(incremental);
|
* scene.applyIncremental(jsonData);
|
||||||
|
*
|
||||||
|
* // 从二进制Buffer应用
|
||||||
|
* const binaryData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'binary' });
|
||||||
|
* scene.applyIncremental(binaryData);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public applyIncremental(
|
public applyIncremental(
|
||||||
incremental: IncrementalSnapshot | string,
|
incremental: IncrementalSnapshot | string | Buffer,
|
||||||
componentRegistry?: Map<string, any>
|
componentRegistry?: Map<string, any>
|
||||||
): void {
|
): void {
|
||||||
const snapshot = typeof incremental === 'string'
|
const snapshot = (typeof incremental === 'string' || Buffer.isBuffer(incremental))
|
||||||
? IncrementalSerializer.deserializeIncremental(incremental)
|
? IncrementalSerializer.deserializeIncremental(incremental)
|
||||||
: incremental;
|
: incremental;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Component } from '../Component';
|
|||||||
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
|
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
|
||||||
import { SerializedEntity } from './EntitySerializer';
|
import { SerializedEntity } from './EntitySerializer';
|
||||||
import { ComponentType } from '../Core/ComponentStorage';
|
import { ComponentType } from '../Core/ComponentStorage';
|
||||||
|
import * as msgpack from 'msgpack-lite';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变更操作类型
|
* 变更操作类型
|
||||||
@@ -117,6 +118,11 @@ interface SceneSnapshot {
|
|||||||
sceneData: Map<string, string>; // 使用JSON字符串存储场景数据
|
sceneData: Map<string, string>; // 使用JSON字符串存储场景数据
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增量序列化格式
|
||||||
|
*/
|
||||||
|
export type IncrementalSerializationFormat = 'json' | 'binary';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增量序列化选项
|
* 增量序列化选项
|
||||||
*/
|
*/
|
||||||
@@ -138,6 +144,20 @@ export interface IncrementalSerializationOptions {
|
|||||||
* 默认false,设为true可减少内存占用但增加CPU开销
|
* 默认false,设为true可减少内存占用但增加CPU开销
|
||||||
*/
|
*/
|
||||||
compressSnapshot?: boolean;
|
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 incremental 增量快照
|
||||||
* @param pretty 是否美化输出
|
* @param options 序列化选项
|
||||||
* @returns JSON字符串
|
* @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(
|
public static serializeIncremental(
|
||||||
incremental: IncrementalSnapshot,
|
incremental: IncrementalSnapshot,
|
||||||
pretty: boolean = false
|
options?: { format?: IncrementalSerializationFormat; pretty?: boolean }
|
||||||
): string {
|
): string | Buffer {
|
||||||
return pretty
|
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, null, 2)
|
||||||
: JSON.stringify(incremental);
|
: JSON.stringify(incremental);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从JSON反序列化增量快照
|
* 反序列化增量快照
|
||||||
*
|
*
|
||||||
* @param json JSON字符串
|
* @param data 序列化的数据(JSON字符串或二进制Buffer)
|
||||||
* @returns 增量快照
|
* @returns 增量快照
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // 从JSON反序列化
|
||||||
|
* const snapshot = IncrementalSerializer.deserializeIncremental(jsonString);
|
||||||
|
*
|
||||||
|
* // 从二进制反序列化
|
||||||
|
* const snapshot = IncrementalSerializer.deserializeIncremental(buffer);
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public static deserializeIncremental(json: string): IncrementalSnapshot {
|
public static deserializeIncremental(data: string | Buffer): IncrementalSnapshot {
|
||||||
return JSON.parse(json);
|
if (typeof data === 'string') {
|
||||||
|
// JSON格式
|
||||||
|
return JSON.parse(data);
|
||||||
|
} else {
|
||||||
|
// 二进制格式(MessagePack)
|
||||||
|
return msgpack.decode(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算增量快照的大小(字节)
|
* 计算增量快照的大小(字节)
|
||||||
*
|
*
|
||||||
* @param incremental 增量快照
|
* @param incremental 增量快照
|
||||||
|
* @param format 序列化格式,默认为 'json'
|
||||||
* @returns 字节数
|
* @returns 字节数
|
||||||
*/
|
*/
|
||||||
public static getIncrementalSize(incremental: IncrementalSnapshot): number {
|
public static getIncrementalSize(
|
||||||
const json = this.serializeIncremental(incremental);
|
incremental: IncrementalSnapshot,
|
||||||
return new Blob([json]).size;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export { IncrementalSerializer, ChangeOperation } from './IncrementalSerializer'
|
|||||||
export type {
|
export type {
|
||||||
IncrementalSnapshot,
|
IncrementalSnapshot,
|
||||||
IncrementalSerializationOptions,
|
IncrementalSerializationOptions,
|
||||||
|
IncrementalSerializationFormat,
|
||||||
EntityChange,
|
EntityChange,
|
||||||
ComponentChange,
|
ComponentChange,
|
||||||
SceneDataChange
|
SceneDataChange
|
||||||
|
|||||||
@@ -436,20 +436,42 @@ describe('Incremental Serialization System', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Incremental Serialization', () => {
|
describe('Incremental Serialization', () => {
|
||||||
it('应该序列化和反序列化增量快照', () => {
|
it('应该序列化和反序列化增量快照(JSON格式)', () => {
|
||||||
scene.createIncrementalSnapshot();
|
scene.createIncrementalSnapshot();
|
||||||
|
|
||||||
const entity = scene.createEntity('Entity');
|
const entity = scene.createEntity('Entity');
|
||||||
entity.addComponent(new PositionComponent(50, 100));
|
entity.addComponent(new PositionComponent(50, 100));
|
||||||
|
|
||||||
const incremental = scene.serializeIncremental();
|
const incremental = scene.serializeIncremental();
|
||||||
const json = IncrementalSerializer.serializeIncremental(incremental);
|
const json = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
|
||||||
|
|
||||||
expect(typeof json).toBe('string');
|
expect(typeof json).toBe('string');
|
||||||
|
|
||||||
const deserialized = IncrementalSerializer.deserializeIncremental(json);
|
const deserialized = IncrementalSerializer.deserializeIncremental(json);
|
||||||
expect(deserialized.version).toBe(incremental.version);
|
expect(deserialized.version).toBe(incremental.version);
|
||||||
expect(deserialized.entityChanges.length).toBe(incremental.entityChanges.length);
|
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输出', () => {
|
it('应该美化JSON输出', () => {
|
||||||
@@ -458,11 +480,158 @@ describe('Incremental Serialization System', () => {
|
|||||||
entity.addComponent(new PositionComponent(10, 20));
|
entity.addComponent(new PositionComponent(10, 20));
|
||||||
|
|
||||||
const incremental = scene.serializeIncremental();
|
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('\n');
|
||||||
expect(prettyJson).toContain(' ');
|
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', () => {
|
describe('Snapshot Management', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user