支持二进制序列化
This commit is contained in:
43
package-lock.json
generated
43
package-lock.json
generated
@@ -5220,6 +5220,16 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
|
||||
@@ -7705,6 +7715,12 @@
|
||||
"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": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
@@ -8656,7 +8672,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -8846,6 +8861,12 @@
|
||||
"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": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||
@@ -9117,7 +9138,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
@@ -11523,6 +11543,21 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
|
||||
@@ -15524,6 +15559,9 @@
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.1.51",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"msgpack-lite": "^0.1.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
||||
@@ -15534,6 +15572,7 @@
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/msgpack-lite": "^0.1.11",
|
||||
"@types/node": "^20.19.17",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/msgpack-lite": "^0.1.11",
|
||||
"@types/node": "^20.19.17",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
@@ -75,5 +76,8 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/esengine/ecs-framework.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"dependencies": {
|
||||
"msgpack-lite": "^0.1.26"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,21 +501,26 @@ export class Scene implements IScene {
|
||||
/**
|
||||
* 序列化场景
|
||||
*
|
||||
* 将场景及其所有实体、组件序列化为JSON字符串
|
||||
* 将场景及其所有实体、组件序列化为JSON字符串或二进制Buffer
|
||||
*
|
||||
* @param options 序列化选项
|
||||
* @returns 序列化后的JSON字符串
|
||||
* @returns 序列化后的数据(JSON字符串或二进制Buffer)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const saveData = scene.serialize({
|
||||
* components: [PlayerComponent, PositionComponent],
|
||||
* // JSON格式
|
||||
* const jsonData = scene.serialize({
|
||||
* format: 'json',
|
||||
* pretty: true
|
||||
* });
|
||||
*
|
||||
* // 二进制格式(更小、更快)
|
||||
* const binaryData = scene.serialize({
|
||||
* format: 'binary'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public serialize(options?: SceneSerializationOptions): string {
|
||||
public serialize(options?: SceneSerializationOptions): string | Buffer {
|
||||
return SceneSerializer.serialize(this, options);
|
||||
}
|
||||
|
||||
@@ -524,19 +529,23 @@ export class Scene implements IScene {
|
||||
*
|
||||
* 从序列化数据恢复场景状态
|
||||
*
|
||||
* @param saveData 序列化的数据
|
||||
* @param saveData 序列化的数据(JSON字符串或二进制Buffer)
|
||||
* @param options 反序列化选项
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* scene.deserialize(saveData, {
|
||||
* strategy: 'replace',
|
||||
* preserveIds: false,
|
||||
* componentRegistry: ComponentTypeRegistry.getRegistry()
|
||||
* // 从JSON恢复(自动从ComponentRegistry获取组件类型)
|
||||
* scene.deserialize(jsonData, {
|
||||
* strategy: 'replace'
|
||||
* });
|
||||
*
|
||||
* // 从二进制恢复
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -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 { Entity } from '../Entity';
|
||||
import { Component } from '../Component';
|
||||
import { ComponentType } from '../Core/ComponentStorage';
|
||||
import { ComponentType, ComponentRegistry } from '../Core/ComponentStorage';
|
||||
import { EntitySerializer, SerializedEntity } from './EntitySerializer';
|
||||
import { getComponentTypeName } from '../Decorators';
|
||||
import { getSerializationMetadata } from './SerializationDecorators';
|
||||
import { ComponentTypeRegistry } from './ComponentTypeRegistry';
|
||||
import * as msgpack from 'msgpack-lite';
|
||||
|
||||
/**
|
||||
* 场景序列化格式
|
||||
@@ -154,9 +154,9 @@ export class SceneSerializer {
|
||||
*
|
||||
* @param scene 要序列化的场景
|
||||
* @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 = {
|
||||
systems: false,
|
||||
format: 'json',
|
||||
@@ -206,8 +206,8 @@ export class SceneSerializer {
|
||||
? JSON.stringify(serializedScene, null, 2)
|
||||
: JSON.stringify(serializedScene);
|
||||
} else {
|
||||
// 二进制格式(未来实现)
|
||||
throw new Error('Binary serialization format is not yet implemented');
|
||||
// 二进制格式(使用 MessagePack)
|
||||
return msgpack.encode(serializedScene);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,12 +215,12 @@ export class SceneSerializer {
|
||||
* 反序列化场景
|
||||
*
|
||||
* @param scene 目标场景
|
||||
* @param saveData 序列化的数据
|
||||
* @param saveData 序列化的数据(JSON字符串或二进制Buffer)
|
||||
* @param options 反序列化选项
|
||||
*/
|
||||
public static deserialize(
|
||||
scene: IScene,
|
||||
saveData: string,
|
||||
saveData: string | Buffer,
|
||||
options?: SceneDeserializationOptions
|
||||
): void {
|
||||
const opts: SceneDeserializationOptions = {
|
||||
@@ -232,7 +232,13 @@ export class SceneSerializer {
|
||||
// 解析数据
|
||||
let serializedScene: SerializedScene;
|
||||
try {
|
||||
serializedScene = JSON.parse(saveData);
|
||||
if (typeof saveData === 'string') {
|
||||
// JSON格式
|
||||
serializedScene = JSON.parse(saveData);
|
||||
} else {
|
||||
// 二进制格式(MessagePack)
|
||||
serializedScene = msgpack.decode(saveData);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse save data: ${error}`);
|
||||
}
|
||||
@@ -455,7 +461,7 @@ export class SceneSerializer {
|
||||
* 从所有已注册的组件类型构建注册表
|
||||
*/
|
||||
private static getGlobalComponentRegistry(): Map<string, ComponentType> {
|
||||
return ComponentTypeRegistry.getRegistry();
|
||||
return ComponentRegistry.getAllComponentNames() as Map<string, ComponentType>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,3 @@ export type {
|
||||
ComponentMigrationFunction,
|
||||
SceneMigrationFunction
|
||||
} from './VersionMigration';
|
||||
|
||||
// 组件类型注册表
|
||||
export { ComponentTypeRegistry } from './ComponentTypeRegistry';
|
||||
|
||||
@@ -198,6 +198,7 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
|
||||
protected sharedBuffer: SharedArrayBuffer | null = null;
|
||||
protected sharedFloatArray: Float32Array | null = null;
|
||||
private platformAdapter: IPlatformAdapter;
|
||||
private hasLoggedSyncMode = false;
|
||||
|
||||
constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) {
|
||||
super(matcher);
|
||||
@@ -449,7 +450,10 @@ export abstract class WorkerEntitySystem<TEntityData = any> extends EntitySystem
|
||||
}
|
||||
} else {
|
||||
// 同步处理(最后的fallback)
|
||||
this.logger.info(`${this.systemName}: Worker不可用,使用同步处理`);
|
||||
if (!this.hasLoggedSyncMode) {
|
||||
this.logger.info(`${this.systemName}: Worker不可用,使用同步处理`);
|
||||
this.hasLoggedSyncMode = true;
|
||||
}
|
||||
this.processSynchronously(entities);
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ export * from './ECS';
|
||||
|
||||
// TypeScript类型增强API
|
||||
export * from './ECS/TypedEntity';
|
||||
export * from './ECS/Systems/TypedEntitySystem';
|
||||
export * from './ECS/Core/Query/TypedQuery';
|
||||
|
||||
// 事件系统
|
||||
@@ -39,5 +38,8 @@ export { ECSEventType, EventPriority, EVENT_TYPES, EventTypeValidator } from './
|
||||
export * from './Utils';
|
||||
export * from './Types';
|
||||
|
||||
// 显式导出ComponentPool类(解决与Types中ComponentPool接口的命名冲突)
|
||||
export { ComponentPool, ComponentPoolManager } from './ECS/Core/Storage';
|
||||
|
||||
// 平台适配
|
||||
export * from './Platform';
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
ComponentSerializer,
|
||||
EntitySerializer,
|
||||
SceneSerializer,
|
||||
ComponentTypeRegistry,
|
||||
VersionMigrationManager,
|
||||
MigrationBuilder
|
||||
} from '../../../src/ECS/Serialization';
|
||||
import { ECSComponent } from '../../../src/ECS/Decorators';
|
||||
import { ComponentRegistry } from '../../../src/ECS/Core/ComponentStorage';
|
||||
|
||||
// 测试组件定义
|
||||
@ECSComponent('Position')
|
||||
@@ -83,14 +83,8 @@ class NonSerializableComponent extends Component {
|
||||
|
||||
describe('ECS Serialization System', () => {
|
||||
beforeEach(() => {
|
||||
// 注册组件类型
|
||||
ComponentTypeRegistry.clear();
|
||||
ComponentTypeRegistry.registerMany([
|
||||
PositionComponent,
|
||||
VelocityComponent,
|
||||
PlayerComponent,
|
||||
HealthComponent
|
||||
]);
|
||||
// @ECSComponent装饰器会自动注册组件到ComponentRegistry,无需手动注册
|
||||
ComponentRegistry.reset(); // 清空测试环境
|
||||
});
|
||||
|
||||
describe('Component Serialization', () => {
|
||||
@@ -112,7 +106,7 @@ describe('ECS Serialization System', () => {
|
||||
data: { x: 150, y: 250 }
|
||||
};
|
||||
|
||||
const registry = ComponentTypeRegistry.getRegistry();
|
||||
const registry = ComponentRegistry.getAllComponentNames() as Map<string, any>;
|
||||
const component = ComponentSerializer.deserialize(serializedData, registry);
|
||||
|
||||
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(
|
||||
serializedData,
|
||||
registry
|
||||
@@ -231,7 +225,7 @@ describe('ECS Serialization System', () => {
|
||||
children: []
|
||||
};
|
||||
|
||||
const registry = ComponentTypeRegistry.getRegistry();
|
||||
const registry = ComponentRegistry.getAllComponentNames() as Map<string, any>;
|
||||
let idCounter = 10;
|
||||
const entity = EntitySerializer.deserialize(
|
||||
serializedEntity,
|
||||
@@ -267,8 +261,9 @@ describe('ECS Serialization System', () => {
|
||||
const saveData = scene.serialize({ format: 'json', pretty: true });
|
||||
|
||||
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.version).toBe(1);
|
||||
expect(parsed.entities.length).toBe(2);
|
||||
@@ -287,7 +282,7 @@ describe('ECS Serialization System', () => {
|
||||
// 清空并重新加载
|
||||
scene.deserialize(saveData, {
|
||||
strategy: 'replace',
|
||||
componentRegistry: ComponentTypeRegistry.getRegistry()
|
||||
// componentRegistry会自动从ComponentRegistry获取
|
||||
});
|
||||
|
||||
expect(scene.entities.count).toBeGreaterThan(0);
|
||||
@@ -300,10 +295,11 @@ describe('ECS Serialization System', () => {
|
||||
entity.addComponent(new HealthComponent());
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -315,8 +311,8 @@ describe('ECS Serialization System', () => {
|
||||
parent.addComponent(new PositionComponent(0, 0));
|
||||
child.addComponent(new PositionComponent(10, 10));
|
||||
|
||||
const saveData = scene.serialize();
|
||||
const parsed = JSON.parse(saveData);
|
||||
const saveData = scene.serialize({ format: 'json' });
|
||||
const parsed = JSON.parse(saveData as string);
|
||||
|
||||
// 只有父实体在顶层
|
||||
expect(parsed.entities.length).toBe(1);
|
||||
@@ -327,8 +323,8 @@ describe('ECS Serialization System', () => {
|
||||
const entity = scene.createEntity('Test');
|
||||
entity.addComponent(new PositionComponent(5, 5));
|
||||
|
||||
const saveData = scene.serialize();
|
||||
const validation = SceneSerializer.validate(saveData);
|
||||
const saveData = scene.serialize({ format: 'json' });
|
||||
const validation = SceneSerializer.validate(saveData as string);
|
||||
|
||||
expect(validation.valid).toBe(true);
|
||||
expect(validation.version).toBe(1);
|
||||
@@ -338,8 +334,8 @@ describe('ECS Serialization System', () => {
|
||||
const entity = scene.createEntity('InfoTest');
|
||||
entity.addComponent(new PositionComponent(1, 1));
|
||||
|
||||
const saveData = scene.serialize();
|
||||
const info = SceneSerializer.getInfo(saveData);
|
||||
const saveData = scene.serialize({ format: 'json' });
|
||||
const info = SceneSerializer.getInfo(saveData as string);
|
||||
|
||||
expect(info).not.toBeNull();
|
||||
expect(info!.name).toBe('TestScene');
|
||||
@@ -434,48 +430,7 @@ describe('ECS Serialization System', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentTypeRegistry', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
// ComponentTypeRegistry已被移除,现在使用ComponentRegistry自动管理组件类型
|
||||
|
||||
describe('Integration Tests', () => {
|
||||
it('should perform full save/load cycle', () => {
|
||||
@@ -508,7 +463,7 @@ describe('ECS Serialization System', () => {
|
||||
// 反序列化
|
||||
scene2.deserialize(saveData, {
|
||||
strategy: 'replace',
|
||||
componentRegistry: ComponentTypeRegistry.getRegistry()
|
||||
// componentRegistry会自动从ComponentRegistry获取
|
||||
});
|
||||
|
||||
// 验证
|
||||
@@ -549,7 +504,7 @@ describe('ECS Serialization System', () => {
|
||||
// 反序列化
|
||||
scene2.deserialize(saveData, {
|
||||
strategy: 'replace',
|
||||
componentRegistry: ComponentTypeRegistry.getRegistry()
|
||||
// componentRegistry会自动从ComponentRegistry获取
|
||||
});
|
||||
|
||||
// 验证场景数据
|
||||
@@ -571,5 +526,100 @@ describe('ECS Serialization System', () => {
|
||||
scene1.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