支持二进制序列化
This commit is contained in:
43
package-lock.json
generated
43
package-lock.json
generated
@@ -5220,6 +5220,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/msgpack-lite": {
|
||||||
|
"version": "0.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/msgpack-lite/-/msgpack-lite-0.1.11.tgz",
|
||||||
|
"integrity": "sha512-cdCZS/gw+jIN22I4SUZUFf1ZZfVv5JM1//Br/MuZcI373sxiy3eSSoiyLu0oz+BPatTbGGGBO5jrcvd0siCdTQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/multer": {
|
"node_modules/@types/multer": {
|
||||||
"version": "1.4.13",
|
"version": "1.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
|
||||||
@@ -7705,6 +7715,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/event-lite": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
@@ -8656,7 +8672,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -8846,6 +8861,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/int64-buffer": {
|
||||||
|
"version": "0.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz",
|
||||||
|
"integrity": "sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||||
@@ -9117,7 +9138,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
@@ -11523,6 +11543,21 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/msgpack-lite": {
|
||||||
|
"version": "0.1.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz",
|
||||||
|
"integrity": "sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"event-lite": "^0.1.1",
|
||||||
|
"ieee754": "^1.1.8",
|
||||||
|
"int64-buffer": "^0.1.9",
|
||||||
|
"isarray": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"msgpack": "bin/msgpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/multimatch": {
|
"node_modules/multimatch": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
|
||||||
@@ -15524,6 +15559,9 @@
|
|||||||
"name": "@esengine/ecs-framework",
|
"name": "@esengine/ecs-framework",
|
||||||
"version": "2.1.51",
|
"version": "2.1.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"msgpack-lite": "^0.1.26"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.28.3",
|
"@babel/core": "^7.28.3",
|
||||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
||||||
@@ -15534,6 +15572,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/msgpack-lite": "^0.1.11",
|
||||||
"@types/node": "^20.19.17",
|
"@types/node": "^20.19.17",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/msgpack-lite": "^0.1.11",
|
||||||
"@types/node": "^20.19.17",
|
"@types/node": "^20.19.17",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
@@ -75,5 +76,8 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/esengine/ecs-framework.git",
|
"url": "https://github.com/esengine/ecs-framework.git",
|
||||||
"directory": "packages/core"
|
"directory": "packages/core"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"msgpack-lite": "^0.1.26"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -501,21 +501,26 @@ export class Scene implements IScene {
|
|||||||
/**
|
/**
|
||||||
* 序列化场景
|
* 序列化场景
|
||||||
*
|
*
|
||||||
* 将场景及其所有实体、组件序列化为JSON字符串
|
* 将场景及其所有实体、组件序列化为JSON字符串或二进制Buffer
|
||||||
*
|
*
|
||||||
* @param options 序列化选项
|
* @param options 序列化选项
|
||||||
* @returns 序列化后的JSON字符串
|
* @returns 序列化后的数据(JSON字符串或二进制Buffer)
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const saveData = scene.serialize({
|
* // JSON格式
|
||||||
* components: [PlayerComponent, PositionComponent],
|
* const jsonData = scene.serialize({
|
||||||
* format: 'json',
|
* format: 'json',
|
||||||
* pretty: true
|
* pretty: true
|
||||||
* });
|
* });
|
||||||
|
*
|
||||||
|
* // 二进制格式(更小、更快)
|
||||||
|
* const binaryData = scene.serialize({
|
||||||
|
* format: 'binary'
|
||||||
|
* });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public serialize(options?: SceneSerializationOptions): string {
|
public serialize(options?: SceneSerializationOptions): string | Buffer {
|
||||||
return SceneSerializer.serialize(this, options);
|
return SceneSerializer.serialize(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,19 +529,23 @@ export class Scene implements IScene {
|
|||||||
*
|
*
|
||||||
* 从序列化数据恢复场景状态
|
* 从序列化数据恢复场景状态
|
||||||
*
|
*
|
||||||
* @param saveData 序列化的数据
|
* @param saveData 序列化的数据(JSON字符串或二进制Buffer)
|
||||||
* @param options 反序列化选项
|
* @param options 反序列化选项
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* scene.deserialize(saveData, {
|
* // 从JSON恢复(自动从ComponentRegistry获取组件类型)
|
||||||
* strategy: 'replace',
|
* scene.deserialize(jsonData, {
|
||||||
* preserveIds: false,
|
* strategy: 'replace'
|
||||||
* componentRegistry: ComponentTypeRegistry.getRegistry()
|
* });
|
||||||
|
*
|
||||||
|
* // 从二进制恢复
|
||||||
|
* scene.deserialize(binaryData, {
|
||||||
|
* strategy: 'replace'
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public deserialize(saveData: string, options?: SceneDeserializationOptions): void {
|
public deserialize(saveData: string | Buffer, options?: SceneDeserializationOptions): void {
|
||||||
SceneSerializer.deserialize(this, saveData, options);
|
SceneSerializer.deserialize(this, saveData, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
/**
|
|
||||||
* 全局组件类型注册表
|
|
||||||
*
|
|
||||||
* 用于序列化系统的组件类型查找和管理
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component } from '../Component';
|
|
||||||
import { ComponentType } from '../Core/ComponentStorage';
|
|
||||||
import { getComponentTypeName } from '../Decorators';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局组件类型注册表
|
|
||||||
*
|
|
||||||
* 维护组件类型名称到构造函数的映射,用于序列化/反序列化
|
|
||||||
*/
|
|
||||||
export class ComponentTypeRegistry {
|
|
||||||
/**
|
|
||||||
* 组件类型映射表
|
|
||||||
* Map<类型名称, 构造函数>
|
|
||||||
*/
|
|
||||||
private static registry = new Map<string, ComponentType>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册组件类型
|
|
||||||
*
|
|
||||||
* @param componentClass 组件构造函数
|
|
||||||
* @param typeName 组件类型名称(可选,默认使用类名或@ECSComponent装饰器指定的名称)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* @ECSComponent('Player')
|
|
||||||
* @Serializable({ version: 1 })
|
|
||||||
* class PlayerComponent extends Component {
|
|
||||||
* @Serialize() name: string = '';
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // 注册组件
|
|
||||||
* ComponentTypeRegistry.register(PlayerComponent);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
public static register(componentClass: ComponentType, typeName?: string): void {
|
|
||||||
const name = typeName || getComponentTypeName(componentClass);
|
|
||||||
|
|
||||||
if (this.registry.has(name)) {
|
|
||||||
console.warn(`Component type "${name}" is already registered, overwriting...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registry.set(name, componentClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量注册组件类型
|
|
||||||
*
|
|
||||||
* @param componentClasses 组件构造函数数组
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* ComponentTypeRegistry.registerMany([
|
|
||||||
* PlayerComponent,
|
|
||||||
* PositionComponent,
|
|
||||||
* VelocityComponent
|
|
||||||
* ]);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
public static registerMany(componentClasses: ComponentType[]): void {
|
|
||||||
for (const componentClass of componentClasses) {
|
|
||||||
this.register(componentClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件类型
|
|
||||||
*
|
|
||||||
* @param typeName 组件类型名称
|
|
||||||
* @returns 组件构造函数,如果未找到则返回undefined
|
|
||||||
*/
|
|
||||||
public static get(typeName: string): ComponentType | undefined {
|
|
||||||
return this.registry.get(typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件类型是否已注册
|
|
||||||
*
|
|
||||||
* @param typeName 组件类型名称
|
|
||||||
* @returns 如果已注册返回true
|
|
||||||
*/
|
|
||||||
public static has(typeName: string): boolean {
|
|
||||||
return this.registry.has(typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消注册组件类型
|
|
||||||
*
|
|
||||||
* @param typeName 组件类型名称
|
|
||||||
* @returns 如果成功取消注册返回true
|
|
||||||
*/
|
|
||||||
public static unregister(typeName: string): boolean {
|
|
||||||
return this.registry.delete(typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空注册表
|
|
||||||
*/
|
|
||||||
public static clear(): void {
|
|
||||||
this.registry.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有已注册的组件类型名称
|
|
||||||
*
|
|
||||||
* @returns 组件类型名称数组
|
|
||||||
*/
|
|
||||||
public static getAllTypeNames(): string[] {
|
|
||||||
return Array.from(this.registry.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有已注册的组件类型
|
|
||||||
*
|
|
||||||
* @returns 组件构造函数数组
|
|
||||||
*/
|
|
||||||
public static getAllTypes(): ComponentType[] {
|
|
||||||
return Array.from(this.registry.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取注册表的Map副本
|
|
||||||
*
|
|
||||||
* @returns 组件类型注册表的副本
|
|
||||||
*/
|
|
||||||
public static getRegistry(): Map<string, ComponentType> {
|
|
||||||
return new Map(this.registry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取注册的组件数量
|
|
||||||
*
|
|
||||||
* @returns 已注册的组件类型数量
|
|
||||||
*/
|
|
||||||
public static get size(): number {
|
|
||||||
return this.registry.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从组件实例获取类型名称
|
|
||||||
*
|
|
||||||
* @param component 组件实例
|
|
||||||
* @returns 组件类型名称
|
|
||||||
*/
|
|
||||||
public static getTypeName(component: Component): string {
|
|
||||||
return getComponentTypeName(component.constructor as ComponentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据组件类查找已注册的类型名称
|
|
||||||
*
|
|
||||||
* @param componentClass 组件构造函数
|
|
||||||
* @returns 类型名称,如果未注册则返回undefined
|
|
||||||
*/
|
|
||||||
public static findTypeName(componentClass: ComponentType): string | undefined {
|
|
||||||
const typeName = getComponentTypeName(componentClass);
|
|
||||||
|
|
||||||
// 检查是否已注册
|
|
||||||
if (this.registry.get(typeName) === componentClass) {
|
|
||||||
return typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 遍历查找
|
|
||||||
for (const [name, cls] of this.registry) {
|
|
||||||
if (cls === componentClass) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动发现并注册所有装饰的组件
|
|
||||||
*
|
|
||||||
* 注意:此方法需要组件类已经被加载到内存中
|
|
||||||
*
|
|
||||||
* @param components 组件类数组
|
|
||||||
*/
|
|
||||||
public static autoRegister(components: ComponentType[]): void {
|
|
||||||
for (const component of components) {
|
|
||||||
try {
|
|
||||||
this.register(component);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to auto-register component ${component.name}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,11 +7,11 @@
|
|||||||
import type { IScene } from '../IScene';
|
import type { IScene } from '../IScene';
|
||||||
import { Entity } from '../Entity';
|
import { Entity } from '../Entity';
|
||||||
import { Component } from '../Component';
|
import { Component } from '../Component';
|
||||||
import { ComponentType } from '../Core/ComponentStorage';
|
import { ComponentType, ComponentRegistry } from '../Core/ComponentStorage';
|
||||||
import { EntitySerializer, SerializedEntity } from './EntitySerializer';
|
import { EntitySerializer, SerializedEntity } from './EntitySerializer';
|
||||||
import { getComponentTypeName } from '../Decorators';
|
import { getComponentTypeName } from '../Decorators';
|
||||||
import { getSerializationMetadata } from './SerializationDecorators';
|
import { getSerializationMetadata } from './SerializationDecorators';
|
||||||
import { ComponentTypeRegistry } from './ComponentTypeRegistry';
|
import * as msgpack from 'msgpack-lite';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 场景序列化格式
|
* 场景序列化格式
|
||||||
@@ -154,9 +154,9 @@ export class SceneSerializer {
|
|||||||
*
|
*
|
||||||
* @param scene 要序列化的场景
|
* @param scene 要序列化的场景
|
||||||
* @param options 序列化选项
|
* @param options 序列化选项
|
||||||
* @returns 序列化后的数据(JSON字符串或二进制数据)
|
* @returns 序列化后的数据(JSON字符串或二进制Buffer)
|
||||||
*/
|
*/
|
||||||
public static serialize(scene: IScene, options?: SceneSerializationOptions): string {
|
public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Buffer {
|
||||||
const opts: SceneSerializationOptions = {
|
const opts: SceneSerializationOptions = {
|
||||||
systems: false,
|
systems: false,
|
||||||
format: 'json',
|
format: 'json',
|
||||||
@@ -206,8 +206,8 @@ export class SceneSerializer {
|
|||||||
? JSON.stringify(serializedScene, null, 2)
|
? JSON.stringify(serializedScene, null, 2)
|
||||||
: JSON.stringify(serializedScene);
|
: JSON.stringify(serializedScene);
|
||||||
} else {
|
} else {
|
||||||
// 二进制格式(未来实现)
|
// 二进制格式(使用 MessagePack)
|
||||||
throw new Error('Binary serialization format is not yet implemented');
|
return msgpack.encode(serializedScene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,12 +215,12 @@ export class SceneSerializer {
|
|||||||
* 反序列化场景
|
* 反序列化场景
|
||||||
*
|
*
|
||||||
* @param scene 目标场景
|
* @param scene 目标场景
|
||||||
* @param saveData 序列化的数据
|
* @param saveData 序列化的数据(JSON字符串或二进制Buffer)
|
||||||
* @param options 反序列化选项
|
* @param options 反序列化选项
|
||||||
*/
|
*/
|
||||||
public static deserialize(
|
public static deserialize(
|
||||||
scene: IScene,
|
scene: IScene,
|
||||||
saveData: string,
|
saveData: string | Buffer,
|
||||||
options?: SceneDeserializationOptions
|
options?: SceneDeserializationOptions
|
||||||
): void {
|
): void {
|
||||||
const opts: SceneDeserializationOptions = {
|
const opts: SceneDeserializationOptions = {
|
||||||
@@ -232,7 +232,13 @@ export class SceneSerializer {
|
|||||||
// 解析数据
|
// 解析数据
|
||||||
let serializedScene: SerializedScene;
|
let serializedScene: SerializedScene;
|
||||||
try {
|
try {
|
||||||
|
if (typeof saveData === 'string') {
|
||||||
|
// JSON格式
|
||||||
serializedScene = JSON.parse(saveData);
|
serializedScene = JSON.parse(saveData);
|
||||||
|
} else {
|
||||||
|
// 二进制格式(MessagePack)
|
||||||
|
serializedScene = msgpack.decode(saveData);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to parse save data: ${error}`);
|
throw new Error(`Failed to parse save data: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -455,7 +461,7 @@ export class SceneSerializer {
|
|||||||
* 从所有已注册的组件类型构建注册表
|
* 从所有已注册的组件类型构建注册表
|
||||||
*/
|
*/
|
||||||
private static getGlobalComponentRegistry(): Map<string, ComponentType> {
|
private static getGlobalComponentRegistry(): Map<string, ComponentType> {
|
||||||
return ComponentTypeRegistry.getRegistry();
|
return ComponentRegistry.getAllComponentNames() as Map<string, ComponentType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,6 +49,3 @@ export type {
|
|||||||
ComponentMigrationFunction,
|
ComponentMigrationFunction,
|
||||||
SceneMigrationFunction
|
SceneMigrationFunction
|
||||||
} from './VersionMigration';
|
} from './VersionMigration';
|
||||||
|
|
||||||
// 组件类型注册表
|
|
||||||
export { ComponentTypeRegistry } from './ComponentTypeRegistry';
|
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
|
|||||||
protected sharedBuffer: SharedArrayBuffer | null = null;
|
protected sharedBuffer: SharedArrayBuffer | null = null;
|
||||||
protected sharedFloatArray: Float32Array | null = null;
|
protected sharedFloatArray: Float32Array | null = null;
|
||||||
private platformAdapter: IPlatformAdapter;
|
private platformAdapter: IPlatformAdapter;
|
||||||
|
private hasLoggedSyncMode = false;
|
||||||
|
|
||||||
constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) {
|
constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) {
|
||||||
super(matcher);
|
super(matcher);
|
||||||
@@ -449,7 +450,10 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 同步处理(最后的fallback)
|
// 同步处理(最后的fallback)
|
||||||
|
if (!this.hasLoggedSyncMode) {
|
||||||
this.logger.info(`${this.systemName}: Worker不可用,使用同步处理`);
|
this.logger.info(`${this.systemName}: Worker不可用,使用同步处理`);
|
||||||
|
this.hasLoggedSyncMode = true;
|
||||||
|
}
|
||||||
this.processSynchronously(entities);
|
this.processSynchronously(entities);
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export * from './ECS';
|
|||||||
|
|
||||||
// TypeScript类型增强API
|
// TypeScript类型增强API
|
||||||
export * from './ECS/TypedEntity';
|
export * from './ECS/TypedEntity';
|
||||||
export * from './ECS/Systems/TypedEntitySystem';
|
|
||||||
export * from './ECS/Core/Query/TypedQuery';
|
export * from './ECS/Core/Query/TypedQuery';
|
||||||
|
|
||||||
// 事件系统
|
// 事件系统
|
||||||
@@ -39,5 +38,8 @@ export { ECSEventType, EventPriority, EVENT_TYPES, EventTypeValidator } from './
|
|||||||
export * from './Utils';
|
export * from './Utils';
|
||||||
export * from './Types';
|
export * from './Types';
|
||||||
|
|
||||||
|
// 显式导出ComponentPool类(解决与Types中ComponentPool接口的命名冲突)
|
||||||
|
export { ComponentPool, ComponentPoolManager } from './ECS/Core/Storage';
|
||||||
|
|
||||||
// 平台适配
|
// 平台适配
|
||||||
export * from './Platform';
|
export * from './Platform';
|
||||||
@@ -14,11 +14,11 @@ import {
|
|||||||
ComponentSerializer,
|
ComponentSerializer,
|
||||||
EntitySerializer,
|
EntitySerializer,
|
||||||
SceneSerializer,
|
SceneSerializer,
|
||||||
ComponentTypeRegistry,
|
|
||||||
VersionMigrationManager,
|
VersionMigrationManager,
|
||||||
MigrationBuilder
|
MigrationBuilder
|
||||||
} from '../../../src/ECS/Serialization';
|
} from '../../../src/ECS/Serialization';
|
||||||
import { ECSComponent } from '../../../src/ECS/Decorators';
|
import { ECSComponent } from '../../../src/ECS/Decorators';
|
||||||
|
import { ComponentRegistry } from '../../../src/ECS/Core/ComponentStorage';
|
||||||
|
|
||||||
// 测试组件定义
|
// 测试组件定义
|
||||||
@ECSComponent('Position')
|
@ECSComponent('Position')
|
||||||
@@ -83,14 +83,8 @@ class NonSerializableComponent extends Component {
|
|||||||
|
|
||||||
describe('ECS Serialization System', () => {
|
describe('ECS Serialization System', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// 注册组件类型
|
// @ECSComponent装饰器会自动注册组件到ComponentRegistry,无需手动注册
|
||||||
ComponentTypeRegistry.clear();
|
ComponentRegistry.reset(); // 清空测试环境
|
||||||
ComponentTypeRegistry.registerMany([
|
|
||||||
PositionComponent,
|
|
||||||
VelocityComponent,
|
|
||||||
PlayerComponent,
|
|
||||||
HealthComponent
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Component Serialization', () => {
|
describe('Component Serialization', () => {
|
||||||
@@ -112,7 +106,7 @@ describe('ECS Serialization System', () => {
|
|||||||
data: { x: 150, y: 250 }
|
data: { x: 150, y: 250 }
|
||||||
};
|
};
|
||||||
|
|
||||||
const registry = ComponentTypeRegistry.getRegistry();
|
const registry = ComponentRegistry.getAllComponentNames() as Map<string, any>;
|
||||||
const component = ComponentSerializer.deserialize(serializedData, registry);
|
const component = ComponentSerializer.deserialize(serializedData, registry);
|
||||||
|
|
||||||
expect(component).not.toBeNull();
|
expect(component).not.toBeNull();
|
||||||
@@ -152,7 +146,7 @@ describe('ECS Serialization System', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const registry = ComponentTypeRegistry.getRegistry();
|
const registry = ComponentRegistry.getAllComponentNames() as Map<string, any>;
|
||||||
const component = ComponentSerializer.deserialize(
|
const component = ComponentSerializer.deserialize(
|
||||||
serializedData,
|
serializedData,
|
||||||
registry
|
registry
|
||||||
@@ -231,7 +225,7 @@ describe('ECS Serialization System', () => {
|
|||||||
children: []
|
children: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const registry = ComponentTypeRegistry.getRegistry();
|
const registry = ComponentRegistry.getAllComponentNames() as Map<string, any>;
|
||||||
let idCounter = 10;
|
let idCounter = 10;
|
||||||
const entity = EntitySerializer.deserialize(
|
const entity = EntitySerializer.deserialize(
|
||||||
serializedEntity,
|
serializedEntity,
|
||||||
@@ -267,8 +261,9 @@ describe('ECS Serialization System', () => {
|
|||||||
const saveData = scene.serialize({ format: 'json', pretty: true });
|
const saveData = scene.serialize({ format: 'json', pretty: true });
|
||||||
|
|
||||||
expect(saveData).toBeTruthy();
|
expect(saveData).toBeTruthy();
|
||||||
|
expect(typeof saveData).toBe('string');
|
||||||
|
|
||||||
const parsed = JSON.parse(saveData);
|
const parsed = JSON.parse(saveData as string);
|
||||||
expect(parsed.name).toBe('TestScene');
|
expect(parsed.name).toBe('TestScene');
|
||||||
expect(parsed.version).toBe(1);
|
expect(parsed.version).toBe(1);
|
||||||
expect(parsed.entities.length).toBe(2);
|
expect(parsed.entities.length).toBe(2);
|
||||||
@@ -287,7 +282,7 @@ describe('ECS Serialization System', () => {
|
|||||||
// 清空并重新加载
|
// 清空并重新加载
|
||||||
scene.deserialize(saveData, {
|
scene.deserialize(saveData, {
|
||||||
strategy: 'replace',
|
strategy: 'replace',
|
||||||
componentRegistry: ComponentTypeRegistry.getRegistry()
|
// componentRegistry会自动从ComponentRegistry获取
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(scene.entities.count).toBeGreaterThan(0);
|
expect(scene.entities.count).toBeGreaterThan(0);
|
||||||
@@ -300,10 +295,11 @@ describe('ECS Serialization System', () => {
|
|||||||
entity.addComponent(new HealthComponent());
|
entity.addComponent(new HealthComponent());
|
||||||
|
|
||||||
const saveData = scene.serialize({
|
const saveData = scene.serialize({
|
||||||
components: [PositionComponent, PlayerComponent]
|
components: [PositionComponent, PlayerComponent],
|
||||||
|
format: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = JSON.parse(saveData);
|
const parsed = JSON.parse(saveData as string);
|
||||||
expect(parsed.entities.length).toBeGreaterThan(0);
|
expect(parsed.entities.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -315,8 +311,8 @@ describe('ECS Serialization System', () => {
|
|||||||
parent.addComponent(new PositionComponent(0, 0));
|
parent.addComponent(new PositionComponent(0, 0));
|
||||||
child.addComponent(new PositionComponent(10, 10));
|
child.addComponent(new PositionComponent(10, 10));
|
||||||
|
|
||||||
const saveData = scene.serialize();
|
const saveData = scene.serialize({ format: 'json' });
|
||||||
const parsed = JSON.parse(saveData);
|
const parsed = JSON.parse(saveData as string);
|
||||||
|
|
||||||
// 只有父实体在顶层
|
// 只有父实体在顶层
|
||||||
expect(parsed.entities.length).toBe(1);
|
expect(parsed.entities.length).toBe(1);
|
||||||
@@ -327,8 +323,8 @@ describe('ECS Serialization System', () => {
|
|||||||
const entity = scene.createEntity('Test');
|
const entity = scene.createEntity('Test');
|
||||||
entity.addComponent(new PositionComponent(5, 5));
|
entity.addComponent(new PositionComponent(5, 5));
|
||||||
|
|
||||||
const saveData = scene.serialize();
|
const saveData = scene.serialize({ format: 'json' });
|
||||||
const validation = SceneSerializer.validate(saveData);
|
const validation = SceneSerializer.validate(saveData as string);
|
||||||
|
|
||||||
expect(validation.valid).toBe(true);
|
expect(validation.valid).toBe(true);
|
||||||
expect(validation.version).toBe(1);
|
expect(validation.version).toBe(1);
|
||||||
@@ -338,8 +334,8 @@ describe('ECS Serialization System', () => {
|
|||||||
const entity = scene.createEntity('InfoTest');
|
const entity = scene.createEntity('InfoTest');
|
||||||
entity.addComponent(new PositionComponent(1, 1));
|
entity.addComponent(new PositionComponent(1, 1));
|
||||||
|
|
||||||
const saveData = scene.serialize();
|
const saveData = scene.serialize({ format: 'json' });
|
||||||
const info = SceneSerializer.getInfo(saveData);
|
const info = SceneSerializer.getInfo(saveData as string);
|
||||||
|
|
||||||
expect(info).not.toBeNull();
|
expect(info).not.toBeNull();
|
||||||
expect(info!.name).toBe('TestScene');
|
expect(info!.name).toBe('TestScene');
|
||||||
@@ -434,48 +430,7 @@ describe('ECS Serialization System', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ComponentTypeRegistry', () => {
|
// ComponentTypeRegistry已被移除,现在使用ComponentRegistry自动管理组件类型
|
||||||
it('should register and retrieve component types', () => {
|
|
||||||
ComponentTypeRegistry.clear();
|
|
||||||
ComponentTypeRegistry.register(PositionComponent);
|
|
||||||
|
|
||||||
expect(ComponentTypeRegistry.has('Position')).toBe(true);
|
|
||||||
expect(ComponentTypeRegistry.get('Position')).toBe(PositionComponent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should register multiple component types', () => {
|
|
||||||
ComponentTypeRegistry.clear();
|
|
||||||
ComponentTypeRegistry.registerMany([
|
|
||||||
PositionComponent,
|
|
||||||
VelocityComponent,
|
|
||||||
PlayerComponent
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(ComponentTypeRegistry.size).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get all type names', () => {
|
|
||||||
ComponentTypeRegistry.clear();
|
|
||||||
ComponentTypeRegistry.register(PositionComponent);
|
|
||||||
ComponentTypeRegistry.register(VelocityComponent);
|
|
||||||
|
|
||||||
const typeNames = ComponentTypeRegistry.getAllTypeNames();
|
|
||||||
|
|
||||||
expect(typeNames).toContain('Position');
|
|
||||||
expect(typeNames).toContain('Velocity');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should unregister component types', () => {
|
|
||||||
ComponentTypeRegistry.clear();
|
|
||||||
ComponentTypeRegistry.register(PositionComponent);
|
|
||||||
|
|
||||||
expect(ComponentTypeRegistry.has('Position')).toBe(true);
|
|
||||||
|
|
||||||
ComponentTypeRegistry.unregister('Position');
|
|
||||||
|
|
||||||
expect(ComponentTypeRegistry.has('Position')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Integration Tests', () => {
|
describe('Integration Tests', () => {
|
||||||
it('should perform full save/load cycle', () => {
|
it('should perform full save/load cycle', () => {
|
||||||
@@ -508,7 +463,7 @@ describe('ECS Serialization System', () => {
|
|||||||
// 反序列化
|
// 反序列化
|
||||||
scene2.deserialize(saveData, {
|
scene2.deserialize(saveData, {
|
||||||
strategy: 'replace',
|
strategy: 'replace',
|
||||||
componentRegistry: ComponentTypeRegistry.getRegistry()
|
// componentRegistry会自动从ComponentRegistry获取
|
||||||
});
|
});
|
||||||
|
|
||||||
// 验证
|
// 验证
|
||||||
@@ -549,7 +504,7 @@ describe('ECS Serialization System', () => {
|
|||||||
// 反序列化
|
// 反序列化
|
||||||
scene2.deserialize(saveData, {
|
scene2.deserialize(saveData, {
|
||||||
strategy: 'replace',
|
strategy: 'replace',
|
||||||
componentRegistry: ComponentTypeRegistry.getRegistry()
|
// componentRegistry会自动从ComponentRegistry获取
|
||||||
});
|
});
|
||||||
|
|
||||||
// 验证场景数据
|
// 验证场景数据
|
||||||
@@ -571,5 +526,100 @@ describe('ECS Serialization System', () => {
|
|||||||
scene1.end();
|
scene1.end();
|
||||||
scene2.end();
|
scene2.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should serialize and deserialize using binary format', () => {
|
||||||
|
const scene1 = new Scene({ name: 'BinaryTest' });
|
||||||
|
|
||||||
|
// 创建测试数据
|
||||||
|
const player = scene1.createEntity('Player');
|
||||||
|
const playerComp = new PlayerComponent();
|
||||||
|
playerComp.name = 'BinaryHero';
|
||||||
|
playerComp.level = 5;
|
||||||
|
playerComp.inventory.set('sword', 1);
|
||||||
|
player.addComponent(playerComp);
|
||||||
|
player.addComponent(new PositionComponent(100, 200));
|
||||||
|
|
||||||
|
scene1.sceneData.set('weather', 'sunny');
|
||||||
|
scene1.sceneData.set('score', 9999);
|
||||||
|
|
||||||
|
// 二进制序列化
|
||||||
|
const binaryData = scene1.serialize({ format: 'binary' });
|
||||||
|
|
||||||
|
// 验证是Buffer类型
|
||||||
|
expect(Buffer.isBuffer(binaryData)).toBe(true);
|
||||||
|
|
||||||
|
// JSON序列化对比
|
||||||
|
const jsonData = scene1.serialize({ format: 'json', pretty: false });
|
||||||
|
|
||||||
|
// 二进制应该更小
|
||||||
|
const binarySize = (binaryData as Buffer).length;
|
||||||
|
const jsonSize = (jsonData as string).length;
|
||||||
|
console.log(`Binary size: ${binarySize} bytes, JSON size: ${jsonSize} bytes`);
|
||||||
|
expect(binarySize).toBeLessThan(jsonSize);
|
||||||
|
|
||||||
|
// 新场景反序列化二进制数据
|
||||||
|
const scene2 = new Scene({ name: 'LoadTest' });
|
||||||
|
scene2.deserialize(binaryData, {
|
||||||
|
strategy: 'replace',
|
||||||
|
// componentRegistry会自动从ComponentRegistry获取
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证数据完整性
|
||||||
|
const loadedPlayer = scene2.findEntity('Player');
|
||||||
|
expect(loadedPlayer).not.toBeNull();
|
||||||
|
|
||||||
|
const loadedPlayerComp = loadedPlayer!.getComponent(PlayerComponent as any) as PlayerComponent;
|
||||||
|
expect(loadedPlayerComp.name).toBe('BinaryHero');
|
||||||
|
expect(loadedPlayerComp.level).toBe(5);
|
||||||
|
expect(loadedPlayerComp.inventory.get('sword')).toBe(1);
|
||||||
|
|
||||||
|
const loadedPos = loadedPlayer!.getComponent(PositionComponent as any) as PositionComponent;
|
||||||
|
expect(loadedPos.x).toBe(100);
|
||||||
|
expect(loadedPos.y).toBe(200);
|
||||||
|
|
||||||
|
expect(scene2.sceneData.get('weather')).toBe('sunny');
|
||||||
|
expect(scene2.sceneData.get('score')).toBe(9999);
|
||||||
|
|
||||||
|
scene1.end();
|
||||||
|
scene2.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex nested data in binary format', () => {
|
||||||
|
const scene1 = new Scene({ name: 'NestedBinaryTest' });
|
||||||
|
|
||||||
|
// 复杂嵌套数据
|
||||||
|
scene1.sceneData.set('config', {
|
||||||
|
graphics: {
|
||||||
|
quality: 'high',
|
||||||
|
resolution: { width: 1920, height: 1080 }
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
masterVolume: 0.8,
|
||||||
|
effects: new Map([['music', 0.7], ['sfx', 0.9]])
|
||||||
|
},
|
||||||
|
tags: new Set(['multiplayer', 'ranked']),
|
||||||
|
timestamp: new Date('2024-01-01')
|
||||||
|
});
|
||||||
|
|
||||||
|
// 二进制序列化
|
||||||
|
const binaryData = scene1.serialize({ format: 'binary' });
|
||||||
|
|
||||||
|
// 反序列化
|
||||||
|
const scene2 = new Scene({ name: 'LoadTest' });
|
||||||
|
scene2.deserialize(binaryData, {
|
||||||
|
// componentRegistry会自动从ComponentRegistry获取
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = scene2.sceneData.get('config');
|
||||||
|
expect(config.graphics.quality).toBe('high');
|
||||||
|
expect(config.graphics.resolution.width).toBe(1920);
|
||||||
|
expect(config.audio.masterVolume).toBe(0.8);
|
||||||
|
expect(config.audio.effects.get('music')).toBe(0.7);
|
||||||
|
expect(config.tags.has('multiplayer')).toBe(true);
|
||||||
|
expect(config.timestamp).toBeInstanceOf(Date);
|
||||||
|
|
||||||
|
scene1.end();
|
||||||
|
scene2.end();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user