场景自定义序列化支持

This commit is contained in:
YHH
2025-10-08 18:34:15 +08:00
parent c631290049
commit 06b3f92007
11 changed files with 2610 additions and 4 deletions

View File

@@ -9,7 +9,7 @@ import { TypeSafeEventSystem } from './Core/EventSystem';
/** /**
* 场景接口定义 * 场景接口定义
* *
* 定义场景应该实现的核心功能和属性,使用接口而非继承提供更灵活的实现方式。 * 定义场景应该实现的核心功能和属性,使用接口而非继承提供更灵活的实现方式。
*/ */
export interface IScene { export interface IScene {
@@ -18,6 +18,25 @@ export interface IScene {
*/ */
name: string; name: string;
/**
* 场景自定义数据
*
* 用于存储场景级别的配置和状态数据,例如:
* - 天气状态
* - 时间设置
* - 游戏难度
* - 音频配置
* - 关卡检查点
*
* @example
* ```typescript
* scene.sceneData.set('weather', 'rainy');
* scene.sceneData.set('timeOfDay', 14.5);
* scene.sceneData.set('checkpoint', { x: 100, y: 200 });
* ```
*/
readonly sceneData: Map<string, any>;
/** /**
* 场景中的实体集合 * 场景中的实体集合
*/ */

View File

@@ -10,6 +10,7 @@ import { EventBus } from './Core/EventBus';
import { IScene, ISceneConfig } from './IScene'; import { IScene, ISceneConfig } from './IScene';
import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators'; import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators';
import { TypedQueryBuilder } from './Core/Query/TypedQuery'; import { TypedQueryBuilder } from './Core/Query/TypedQuery';
import { SceneSerializer, SceneSerializationOptions, SceneDeserializationOptions } from './Serialization/SceneSerializer';
/** /**
* 游戏场景默认实现类 * 游戏场景默认实现类
@@ -20,14 +21,21 @@ import { TypedQueryBuilder } from './Core/Query/TypedQuery';
export class Scene implements IScene { export class Scene implements IScene {
/** /**
* 场景名称 * 场景名称
* *
* 用于标识和调试的友好名称。 * 用于标识和调试的友好名称。
*/ */
public name: string = ""; public name: string = "";
/**
* 场景自定义数据
*
* 用于存储场景级别的配置和状态数据。
*/
public readonly sceneData: Map<string, any> = new Map();
/** /**
* 场景中的实体集合 * 场景中的实体集合
* *
* 管理场景内所有实体的生命周期。 * 管理场景内所有实体的生命周期。
*/ */
public readonly entities: EntityList; public readonly entities: EntityList;
@@ -489,4 +497,46 @@ export class Scene implements IScene {
componentStats: this.componentStorageManager.getAllStats() componentStats: this.componentStorageManager.getAllStats()
}; };
} }
/**
* 序列化场景
*
* 将场景及其所有实体、组件序列化为JSON字符串
*
* @param options 序列化选项
* @returns 序列化后的JSON字符串
*
* @example
* ```typescript
* const saveData = scene.serialize({
* components: [PlayerComponent, PositionComponent],
* format: 'json',
* pretty: true
* });
* ```
*/
public serialize(options?: SceneSerializationOptions): string {
return SceneSerializer.serialize(this, options);
}
/**
* 反序列化场景
*
* 从序列化数据恢复场景状态
*
* @param saveData 序列化的数据
* @param options 反序列化选项
*
* @example
* ```typescript
* scene.deserialize(saveData, {
* strategy: 'replace',
* preserveIds: false,
* componentRegistry: ComponentTypeRegistry.getRegistry()
* });
* ```
*/
public deserialize(saveData: string, options?: SceneDeserializationOptions): void {
SceneSerializer.deserialize(this, saveData, options);
}
} }

View File

@@ -0,0 +1,336 @@
/**
* 组件序列化器
*
* 负责组件的序列化和反序列化操作
*/
import { Component } from '../Component';
import { ComponentType } from '../Core/ComponentStorage';
import { getComponentTypeName } from '../Decorators';
import {
getSerializationMetadata,
isSerializable,
SerializationMetadata
} from './SerializationDecorators';
/**
* 序列化后的组件数据
*/
export interface SerializedComponent {
/**
* 组件类型名称
*/
type: string;
/**
* 序列化版本
*/
version: number;
/**
* 组件数据
*/
data: Record<string, any>;
}
/**
* 组件序列化器类
*/
export class ComponentSerializer {
/**
* 序列化单个组件
*
* @param component 要序列化的组件实例
* @returns 序列化后的组件数据如果组件不可序列化则返回null
*/
public static serialize(component: Component): SerializedComponent | null {
const metadata = getSerializationMetadata(component);
if (!metadata) {
// 组件没有使用@Serializable装饰器不可序列化
return null;
}
const componentType = component.constructor as ComponentType;
const typeName = metadata.options.typeId || getComponentTypeName(componentType);
const data: Record<string, any> = {};
// 序列化标记的字段
for (const [fieldName, options] of metadata.fields) {
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
const value = (component as any)[fieldName];
// 跳过忽略的字段
if (metadata.ignoredFields.has(fieldName)) {
continue;
}
// 使用自定义序列化器或默认序列化
const serializedValue = options.serializer
? options.serializer(value)
: this.serializeValue(value);
// 使用别名或原始字段名
const key = options.alias || fieldKey;
data[key] = serializedValue;
}
return {
type: typeName,
version: metadata.options.version,
data
};
}
/**
* 反序列化组件
*
* @param serializedData 序列化的组件数据
* @param componentRegistry 组件类型注册表 (类型名 -> 构造函数)
* @returns 反序列化后的组件实例如果失败则返回null
*/
public static deserialize(
serializedData: SerializedComponent,
componentRegistry: Map<string, ComponentType>
): Component | null {
const componentClass = componentRegistry.get(serializedData.type);
if (!componentClass) {
console.warn(`未找到组件类型: ${serializedData.type}`);
return null;
}
const metadata = getSerializationMetadata(componentClass);
if (!metadata) {
console.warn(`组件 ${serializedData.type} 不可序列化`);
return null;
}
// 创建组件实例
const component = new componentClass();
// 反序列化字段
for (const [fieldName, options] of metadata.fields) {
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
const key = options.alias || fieldKey;
const serializedValue = serializedData.data[key];
if (serializedValue === undefined) {
continue; // 字段不存在于序列化数据中
}
// 使用自定义反序列化器或默认反序列化
const value = options.deserializer
? options.deserializer(serializedValue)
: this.deserializeValue(serializedValue);
(component as any)[fieldName] = value;
}
return component;
}
/**
* 批量序列化组件
*
* @param components 组件数组
* @returns 序列化后的组件数据数组
*/
public static serializeComponents(components: Component[]): SerializedComponent[] {
const result: SerializedComponent[] = [];
for (const component of components) {
const serialized = this.serialize(component);
if (serialized) {
result.push(serialized);
}
}
return result;
}
/**
* 批量反序列化组件
*
* @param serializedComponents 序列化的组件数据数组
* @param componentRegistry 组件类型注册表
* @returns 反序列化后的组件数组
*/
public static deserializeComponents(
serializedComponents: SerializedComponent[],
componentRegistry: Map<string, ComponentType>
): Component[] {
const result: Component[] = [];
for (const serialized of serializedComponents) {
const component = this.deserialize(serialized, componentRegistry);
if (component) {
result.push(component);
}
}
return result;
}
/**
* 默认值序列化
*
* 处理基本类型、数组、对象等的序列化
*/
private static serializeValue(value: any): any {
if (value === null || value === undefined) {
return value;
}
// 基本类型
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return value;
}
// 日期
if (value instanceof Date) {
return {
__type: 'Date',
value: value.toISOString()
};
}
// 数组
if (Array.isArray(value)) {
return value.map(item => this.serializeValue(item));
}
// Map (如果没有使用@SerializeMap装饰器)
if (value instanceof Map) {
return {
__type: 'Map',
value: Array.from(value.entries())
};
}
// Set
if (value instanceof Set) {
return {
__type: 'Set',
value: Array.from(value)
};
}
// 普通对象
if (type === 'object') {
const result: Record<string, any> = {};
for (const key in value) {
if (value.hasOwnProperty(key)) {
result[key] = this.serializeValue(value[key]);
}
}
return result;
}
// 其他类型(函数等)不序列化
return undefined;
}
/**
* 默认值反序列化
*/
private static deserializeValue(value: any): any {
if (value === null || value === undefined) {
return value;
}
// 基本类型直接返回
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return value;
}
// 处理特殊类型标记
if (type === 'object' && value.__type) {
switch (value.__type) {
case 'Date':
return new Date(value.value);
case 'Map':
return new Map(value.value);
case 'Set':
return new Set(value.value);
}
}
// 数组
if (Array.isArray(value)) {
return value.map(item => this.deserializeValue(item));
}
// 普通对象
if (type === 'object') {
const result: Record<string, any> = {};
for (const key in value) {
if (value.hasOwnProperty(key)) {
result[key] = this.deserializeValue(value[key]);
}
}
return result;
}
return value;
}
/**
* 验证序列化数据的版本
*
* @param serializedData 序列化数据
* @param expectedVersion 期望的版本号
* @returns 版本是否匹配
*/
public static validateVersion(
serializedData: SerializedComponent,
expectedVersion: number
): boolean {
return serializedData.version === expectedVersion;
}
/**
* 获取组件的序列化信息
*
* @param component 组件实例或组件类
* @returns 序列化信息对象,包含类型名、版本、可序列化字段列表
*/
public static getSerializationInfo(component: Component | ComponentType): {
type: string;
version: number;
fields: string[];
ignoredFields: string[];
isSerializable: boolean;
} | null {
const metadata = getSerializationMetadata(component);
if (!metadata) {
return {
type: 'unknown',
version: 0,
fields: [],
ignoredFields: [],
isSerializable: false
};
}
const componentType = typeof component === 'function'
? component
: (component.constructor as ComponentType);
return {
type: metadata.options.typeId || getComponentTypeName(componentType),
version: metadata.options.version,
fields: Array.from(metadata.fields.keys()).map(k =>
typeof k === 'symbol' ? k.toString() : k
),
ignoredFields: Array.from(metadata.ignoredFields).map(k =>
typeof k === 'symbol' ? k.toString() : k
),
isSerializable: true
};
}
}

View File

@@ -0,0 +1,194 @@
/**
* 全局组件类型注册表
*
* 用于序列化系统的组件类型查找和管理
*/
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);
}
}
}
}

View File

@@ -0,0 +1,223 @@
/**
* 实体序列化器
*
* 负责实体的序列化和反序列化操作
*/
import { Entity } from '../Entity';
import { Component } from '../Component';
import { ComponentType } from '../Core/ComponentStorage';
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
/**
* 序列化后的实体数据
*/
export interface SerializedEntity {
/**
* 实体ID
*/
id: number;
/**
* 实体名称
*/
name: string;
/**
* 实体标签
*/
tag: number;
/**
* 激活状态
*/
active: boolean;
/**
* 启用状态
*/
enabled: boolean;
/**
* 更新顺序
*/
updateOrder: number;
/**
* 组件列表
*/
components: SerializedComponent[];
/**
* 子实体列表
*/
children: SerializedEntity[];
/**
* 父实体ID如果有
*/
parentId?: number;
}
/**
* 实体序列化器类
*/
export class EntitySerializer {
/**
* 序列化单个实体
*
* @param entity 要序列化的实体
* @param includeChildren 是否包含子实体默认true
* @returns 序列化后的实体数据
*/
public static serialize(entity: Entity, includeChildren: boolean = true): SerializedEntity {
const serializedComponents = ComponentSerializer.serializeComponents(
Array.from(entity.components)
);
const serializedEntity: SerializedEntity = {
id: entity.id,
name: entity.name,
tag: entity.tag,
active: entity.active,
enabled: entity.enabled,
updateOrder: entity.updateOrder,
components: serializedComponents,
children: []
};
// 序列化父实体引用
if (entity.parent) {
serializedEntity.parentId = entity.parent.id;
}
// 序列化子实体
if (includeChildren) {
for (const child of entity.children) {
serializedEntity.children.push(this.serialize(child, true));
}
}
return serializedEntity;
}
/**
* 反序列化实体
*
* @param serializedEntity 序列化的实体数据
* @param componentRegistry 组件类型注册表
* @param idGenerator 实体ID生成器用于生成新ID或保持原ID
* @param preserveIds 是否保持原始ID默认false
* @returns 反序列化后的实体
*/
public static deserialize(
serializedEntity: SerializedEntity,
componentRegistry: Map<string, ComponentType>,
idGenerator: () => number,
preserveIds: boolean = false
): Entity {
// 创建实体使用原始ID或新生成的ID
const entityId = preserveIds ? serializedEntity.id : idGenerator();
const entity = new Entity(serializedEntity.name, entityId);
// 恢复实体属性
entity.tag = serializedEntity.tag;
entity.active = serializedEntity.active;
entity.enabled = serializedEntity.enabled;
entity.updateOrder = serializedEntity.updateOrder;
// 反序列化组件
const components = ComponentSerializer.deserializeComponents(
serializedEntity.components,
componentRegistry
);
for (const component of components) {
entity.addComponent(component);
}
// 反序列化子实体
for (const childData of serializedEntity.children) {
const childEntity = this.deserialize(
childData,
componentRegistry,
idGenerator,
preserveIds
);
entity.addChild(childEntity);
}
return entity;
}
/**
* 批量序列化实体
*
* @param entities 实体数组
* @param includeChildren 是否包含子实体
* @returns 序列化后的实体数据数组
*/
public static serializeEntities(
entities: Entity[],
includeChildren: boolean = true
): SerializedEntity[] {
const result: SerializedEntity[] = [];
for (const entity of entities) {
// 只序列化顶层实体(没有父实体的实体)
// 子实体会在父实体序列化时一并处理
if (!entity.parent || !includeChildren) {
result.push(this.serialize(entity, includeChildren));
}
}
return result;
}
/**
* 批量反序列化实体
*
* @param serializedEntities 序列化的实体数据数组
* @param componentRegistry 组件类型注册表
* @param idGenerator 实体ID生成器
* @param preserveIds 是否保持原始ID
* @returns 反序列化后的实体数组
*/
public static deserializeEntities(
serializedEntities: SerializedEntity[],
componentRegistry: Map<string, ComponentType>,
idGenerator: () => number,
preserveIds: boolean = false
): Entity[] {
const result: Entity[] = [];
for (const serialized of serializedEntities) {
const entity = this.deserialize(
serialized,
componentRegistry,
idGenerator,
preserveIds
);
result.push(entity);
}
return result;
}
/**
* 创建实体的深拷贝
*
* @param entity 要拷贝的实体
* @param componentRegistry 组件类型注册表
* @param idGenerator ID生成器
* @returns 拷贝后的新实体
*/
public static clone(
entity: Entity,
componentRegistry: Map<string, ComponentType>,
idGenerator: () => number
): Entity {
const serialized = this.serialize(entity, true);
return this.deserialize(serialized, componentRegistry, idGenerator, false);
}
}

View File

@@ -0,0 +1,529 @@
/**
* 场景序列化器
*
* 负责整个场景的序列化和反序列化,包括实体、组件等
*/
import type { IScene } from '../IScene';
import { Entity } from '../Entity';
import { Component } from '../Component';
import { ComponentType } from '../Core/ComponentStorage';
import { EntitySerializer, SerializedEntity } from './EntitySerializer';
import { getComponentTypeName } from '../Decorators';
import { getSerializationMetadata } from './SerializationDecorators';
import { ComponentTypeRegistry } from './ComponentTypeRegistry';
/**
* 场景序列化格式
*/
export type SerializationFormat = 'json' | 'binary';
/**
* 场景序列化策略
*/
export type DeserializationStrategy = 'merge' | 'replace';
/**
* 版本迁移函数
*/
export type MigrationFunction = (
oldVersion: number,
newVersion: number,
data: any
) => any;
/**
* 场景序列化选项
*/
export interface SceneSerializationOptions {
/**
* 要序列化的组件类型列表
* 如果未指定,则序列化所有可序列化的组件
*/
components?: ComponentType[];
/**
* 是否序列化系统状态(当前不支持)
*/
systems?: boolean;
/**
* 序列化格式
*/
format?: SerializationFormat;
/**
* 是否美化JSON输出仅在format='json'时有效)
*/
pretty?: boolean;
/**
* 是否包含元数据(如序列化时间、版本等)
*/
includeMetadata?: boolean;
}
/**
* 场景反序列化选项
*/
export interface SceneDeserializationOptions {
/**
* 反序列化策略
* - 'merge': 合并到现有场景
* - 'replace': 替换现有场景内容
*/
strategy?: DeserializationStrategy;
/**
* 版本迁移函数
*/
migration?: MigrationFunction;
/**
* 是否保持原始实体ID
*/
preserveIds?: boolean;
/**
* 组件类型注册表
* 如果未提供,将尝试从全局注册表获取
*/
componentRegistry?: Map<string, ComponentType>;
}
/**
* 序列化后的场景数据
*/
export interface SerializedScene {
/**
* 场景名称
*/
name: string;
/**
* 序列化版本
*/
version: number;
/**
* 序列化时间戳
*/
timestamp?: number;
/**
* 场景自定义数据
*
* 存储场景级别的配置和状态
*/
sceneData?: Record<string, any>;
/**
* 实体列表
*/
entities: SerializedEntity[];
/**
* 元数据
*/
metadata?: {
entityCount: number;
componentTypeCount: number;
serializationOptions?: SceneSerializationOptions;
};
/**
* 组件类型注册信息
*/
componentTypeRegistry: Array<{
typeName: string;
version: number;
}>;
}
/**
* 场景序列化器类
*/
export class SceneSerializer {
/**
* 当前序列化版本
*/
private static readonly SERIALIZATION_VERSION = 1;
/**
* 序列化场景
*
* @param scene 要序列化的场景
* @param options 序列化选项
* @returns 序列化后的数据JSON字符串或二进制数据
*/
public static serialize(scene: IScene, options?: SceneSerializationOptions): string {
const opts: SceneSerializationOptions = {
systems: false,
format: 'json',
pretty: true,
includeMetadata: true,
...options
};
// 过滤实体和组件
const entities = this.filterEntities(scene, opts);
// 序列化实体
const serializedEntities = EntitySerializer.serializeEntities(entities, true);
// 收集组件类型信息
const componentTypeRegistry = this.buildComponentTypeRegistry(entities);
// 序列化场景自定义数据
const sceneData = this.serializeSceneData(scene.sceneData);
// 构建序列化数据
const serializedScene: SerializedScene = {
name: scene.name,
version: this.SERIALIZATION_VERSION,
entities: serializedEntities,
componentTypeRegistry
};
// 添加场景数据(如果有)
if (sceneData && Object.keys(sceneData).length > 0) {
serializedScene.sceneData = sceneData;
}
// 添加元数据
if (opts.includeMetadata) {
serializedScene.timestamp = Date.now();
serializedScene.metadata = {
entityCount: serializedEntities.length,
componentTypeCount: componentTypeRegistry.length,
serializationOptions: opts
};
}
// 根据格式返回数据
if (opts.format === 'json') {
return opts.pretty
? JSON.stringify(serializedScene, null, 2)
: JSON.stringify(serializedScene);
} else {
// 二进制格式(未来实现)
throw new Error('Binary serialization format is not yet implemented');
}
}
/**
* 反序列化场景
*
* @param scene 目标场景
* @param saveData 序列化的数据
* @param options 反序列化选项
*/
public static deserialize(
scene: IScene,
saveData: string,
options?: SceneDeserializationOptions
): void {
const opts: SceneDeserializationOptions = {
strategy: 'replace',
preserveIds: false,
...options
};
// 解析数据
let serializedScene: SerializedScene;
try {
serializedScene = JSON.parse(saveData);
} catch (error) {
throw new Error(`Failed to parse save data: ${error}`);
}
// 版本迁移
if (opts.migration && serializedScene.version !== this.SERIALIZATION_VERSION) {
serializedScene = opts.migration(
serializedScene.version,
this.SERIALIZATION_VERSION,
serializedScene
);
}
// 构建组件注册表
const componentRegistry = opts.componentRegistry || this.getGlobalComponentRegistry();
// 根据策略处理场景
if (opts.strategy === 'replace') {
// 清空场景
scene.destroyAllEntities();
}
// ID生成器
const idGenerator = () => scene.identifierPool.checkOut();
// 反序列化实体
const entities = EntitySerializer.deserializeEntities(
serializedScene.entities,
componentRegistry,
idGenerator,
opts.preserveIds || false
);
// 将实体添加到场景
for (const entity of entities) {
scene.addEntity(entity);
}
// 反序列化场景自定义数据
if (serializedScene.sceneData) {
this.deserializeSceneData(serializedScene.sceneData, scene.sceneData);
}
}
/**
* 序列化场景自定义数据
*
* 将 Map<string, any> 转换为普通对象
*/
private static serializeSceneData(sceneData: Map<string, any>): Record<string, any> {
const result: Record<string, any> = {};
for (const [key, value] of sceneData) {
result[key] = this.serializeValue(value);
}
return result;
}
/**
* 反序列化场景自定义数据
*
* 将普通对象还原为 Map<string, any>
*/
private static deserializeSceneData(
data: Record<string, any>,
targetMap: Map<string, any>
): void {
targetMap.clear();
for (const [key, value] of Object.entries(data)) {
targetMap.set(key, this.deserializeValue(value));
}
}
/**
* 序列化单个值
*/
private static serializeValue(value: any): any {
if (value === null || value === undefined) {
return value;
}
// 基本类型
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return value;
}
// Date
if (value instanceof Date) {
return { __type: 'Date', value: value.toISOString() };
}
// Map
if (value instanceof Map) {
return { __type: 'Map', value: Array.from(value.entries()) };
}
// Set
if (value instanceof Set) {
return { __type: 'Set', value: Array.from(value) };
}
// 数组
if (Array.isArray(value)) {
return value.map(item => this.serializeValue(item));
}
// 普通对象
if (type === 'object') {
const result: Record<string, any> = {};
for (const key in value) {
if (value.hasOwnProperty(key)) {
result[key] = this.serializeValue(value[key]);
}
}
return result;
}
// 其他类型不序列化
return undefined;
}
/**
* 反序列化单个值
*/
private static deserializeValue(value: any): any {
if (value === null || value === undefined) {
return value;
}
// 基本类型
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return value;
}
// 处理特殊类型标记
if (type === 'object' && value.__type) {
switch (value.__type) {
case 'Date':
return new Date(value.value);
case 'Map':
return new Map(value.value);
case 'Set':
return new Set(value.value);
}
}
// 数组
if (Array.isArray(value)) {
return value.map(item => this.deserializeValue(item));
}
// 普通对象
if (type === 'object') {
const result: Record<string, any> = {};
for (const key in value) {
if (value.hasOwnProperty(key)) {
result[key] = this.deserializeValue(value[key]);
}
}
return result;
}
return value;
}
/**
* 过滤要序列化的实体和组件
*/
private static filterEntities(scene: IScene, options: SceneSerializationOptions): Entity[] {
const entities = Array.from(scene.entities.buffer);
// 如果指定了组件类型过滤
if (options.components && options.components.length > 0) {
const componentTypeSet = new Set(options.components);
// 只返回拥有指定组件的实体
return entities.filter(entity => {
return Array.from(entity.components).some(component =>
componentTypeSet.has(component.constructor as ComponentType)
);
});
}
return entities;
}
/**
* 构建组件类型注册表
*/
private static buildComponentTypeRegistry(
entities: Entity[]
): Array<{ typeName: string; version: number }> {
const registry = new Map<string, number>();
for (const entity of entities) {
for (const component of entity.components) {
const componentType = component.constructor as ComponentType;
const typeName = getComponentTypeName(componentType);
const metadata = getSerializationMetadata(component);
if (metadata && !registry.has(typeName)) {
registry.set(typeName, metadata.options.version);
}
}
}
return Array.from(registry.entries()).map(([typeName, version]) => ({
typeName,
version
}));
}
/**
* 获取全局组件注册表
*
* 从所有已注册的组件类型构建注册表
*/
private static getGlobalComponentRegistry(): Map<string, ComponentType> {
return ComponentTypeRegistry.getRegistry();
}
/**
* 验证保存数据的有效性
*
* @param saveData 序列化的数据
* @returns 验证结果
*/
public static validate(saveData: string): {
valid: boolean;
version?: number;
errors?: string[];
} {
const errors: string[] = [];
try {
const data = JSON.parse(saveData);
if (!data.version) {
errors.push('Missing version field');
}
if (!data.entities || !Array.isArray(data.entities)) {
errors.push('Missing or invalid entities field');
}
if (!data.componentTypeRegistry || !Array.isArray(data.componentTypeRegistry)) {
errors.push('Missing or invalid componentTypeRegistry field');
}
return {
valid: errors.length === 0,
version: data.version,
errors: errors.length > 0 ? errors : undefined
};
} catch (error) {
return {
valid: false,
errors: [`JSON parse error: ${error}`]
};
}
}
/**
* 获取保存数据的信息(不完全反序列化)
*
* @param saveData 序列化的数据
* @returns 保存数据的元信息
*/
public static getInfo(saveData: string): {
name: string;
version: number;
timestamp?: number;
entityCount: number;
componentTypeCount: number;
} | null {
try {
const data: SerializedScene = JSON.parse(saveData);
return {
name: data.name,
version: data.version,
timestamp: data.timestamp,
entityCount: data.metadata?.entityCount || data.entities.length,
componentTypeCount: data.componentTypeRegistry.length
};
} catch (error) {
return null;
}
}
}

View File

@@ -0,0 +1,254 @@
/**
* 序列化装饰器
*
* 提供组件级别的序列化支持,包括字段级装饰器和类级装饰器
*/
import { Component } from '../Component';
/**
* 序列化元数据的Symbol键
*/
export const SERIALIZABLE_METADATA = Symbol('SerializableMetadata');
export const SERIALIZE_FIELD = Symbol('SerializeField');
export const SERIALIZE_OPTIONS = Symbol('SerializeOptions');
/**
* 可序列化配置选项
*/
export interface SerializableOptions {
/**
* 序列化版本号,用于数据迁移
*/
version: number;
/**
* 组件类型标识符(可选,默认使用类名)
*/
typeId?: string;
}
/**
* 字段序列化配置
*/
export interface FieldSerializeOptions {
/**
* 自定义序列化器
*/
serializer?: (value: any) => any;
/**
* 自定义反序列化器
*/
deserializer?: (value: any) => any;
/**
* 字段别名(用于序列化后的键名)
*/
alias?: string;
}
/**
* 序列化元数据
*/
export interface SerializationMetadata {
options: SerializableOptions;
fields: Map<string | symbol, FieldSerializeOptions>;
ignoredFields: Set<string | symbol>;
}
/**
* 组件可序列化装饰器
*
* 标记组件类为可序列化,必须与字段装饰器配合使用
*
* @param options 序列化配置选项
*
* @example
* ```typescript
* @ECSComponent('Player')
* @Serializable({ version: 1 })
* class PlayerComponent extends Component {
* @Serialize() name: string = 'Player';
* @Serialize() level: number = 1;
* }
* ```
*/
export function Serializable(options: SerializableOptions) {
return function <T extends new (...args: any[]) => Component>(target: T): T {
if (!options || typeof options.version !== 'number') {
throw new Error('Serializable装饰器必须提供有效的版本号');
}
// 初始化或获取现有元数据
let metadata: SerializationMetadata = (target as any)[SERIALIZABLE_METADATA];
if (!metadata) {
metadata = {
options,
fields: new Map(),
ignoredFields: new Set()
};
(target as any)[SERIALIZABLE_METADATA] = metadata;
} else {
metadata.options = options;
}
return target;
};
}
/**
* 字段序列化装饰器
*
* 标记字段为可序列化
*
* @param options 字段序列化选项(可选)
*
* @example
* ```typescript
* @Serialize()
* name: string = 'Player';
*
* @Serialize({ alias: 'hp' })
* health: number = 100;
* ```
*/
export function Serialize(options?: FieldSerializeOptions) {
return function (target: any, propertyKey: string | symbol) {
const constructor = target.constructor;
// 获取或创建元数据
let metadata: SerializationMetadata = constructor[SERIALIZABLE_METADATA];
if (!metadata) {
metadata = {
options: { version: 1 }, // 默认版本
fields: new Map(),
ignoredFields: new Set()
};
constructor[SERIALIZABLE_METADATA] = metadata;
}
// 记录字段
metadata.fields.set(propertyKey, options || {});
};
}
/**
* Map序列化装饰器
*
* 专门用于序列化Map类型字段
*
* @example
* ```typescript
* @SerializeAsMap()
* inventory: Map<string, number> = new Map();
* ```
*/
export function SerializeAsMap() {
return function (target: any, propertyKey: string | symbol) {
Serialize({
serializer: (value: Map<any, any>) => {
if (!(value instanceof Map)) {
return null;
}
return Array.from(value.entries());
},
deserializer: (value: any) => {
if (!Array.isArray(value)) {
return new Map();
}
return new Map(value);
}
})(target, propertyKey);
};
}
/**
* Set序列化装饰器
*
* 专门用于序列化Set类型字段
*
* @example
* ```typescript
* @SerializeAsSet()
* tags: Set<string> = new Set();
* ```
*/
export function SerializeAsSet() {
return function (target: any, propertyKey: string | symbol) {
Serialize({
serializer: (value: Set<any>) => {
if (!(value instanceof Set)) {
return null;
}
return Array.from(value);
},
deserializer: (value: any) => {
if (!Array.isArray(value)) {
return new Set();
}
return new Set(value);
}
})(target, propertyKey);
};
}
/**
* 忽略序列化装饰器
*
* 标记字段不参与序列化
*
* @example
* ```typescript
* @IgnoreSerialization()
* tempCache: any = null;
* ```
*/
export function IgnoreSerialization() {
return function (target: any, propertyKey: string | symbol) {
const constructor = target.constructor;
// 获取或创建元数据
let metadata: SerializationMetadata = constructor[SERIALIZABLE_METADATA];
if (!metadata) {
metadata = {
options: { version: 1 },
fields: new Map(),
ignoredFields: new Set()
};
constructor[SERIALIZABLE_METADATA] = metadata;
}
// 记录忽略字段
metadata.ignoredFields.add(propertyKey);
};
}
/**
* 获取组件的序列化元数据
*
* @param componentClass 组件类或组件实例
* @returns 序列化元数据如果组件不可序列化则返回null
*/
export function getSerializationMetadata(componentClass: any): SerializationMetadata | null {
if (!componentClass) {
return null;
}
// 如果是实例,获取其构造函数
const constructor = typeof componentClass === 'function'
? componentClass
: componentClass.constructor;
return constructor[SERIALIZABLE_METADATA] || null;
}
/**
* 检查组件是否可序列化
*
* @param component 组件类或组件实例
* @returns 如果组件可序列化返回true
*/
export function isSerializable(component: any): boolean {
return getSerializationMetadata(component) !== null;
}

View File

@@ -0,0 +1,371 @@
/**
* 版本迁移系统
*
* 提供组件和场景数据的版本迁移支持
*/
import { SerializedComponent } from './ComponentSerializer';
import { SerializedScene } from './SceneSerializer';
/**
* 组件迁移函数
*/
export type ComponentMigrationFunction = (data: any, fromVersion: number, toVersion: number) => any;
/**
* 场景迁移函数
*/
export type SceneMigrationFunction = (
scene: SerializedScene,
fromVersion: number,
toVersion: number
) => SerializedScene;
/**
* 版本迁移管理器
*/
export class VersionMigrationManager {
/**
* 组件迁移函数注册表
* Map<组件类型名, Map<版本号, 迁移函数>>
*/
private static componentMigrations = new Map<string, Map<number, ComponentMigrationFunction>>();
/**
* 场景迁移函数注册表
* Map<版本号, 迁移函数>
*/
private static sceneMigrations = new Map<number, SceneMigrationFunction>();
/**
* 注册组件迁移函数
*
* @param componentType 组件类型名称
* @param fromVersion 源版本号
* @param toVersion 目标版本号
* @param migration 迁移函数
*
* @example
* ```typescript
* // 从版本1迁移到版本2
* VersionMigrationManager.registerComponentMigration(
* 'PlayerComponent',
* 1,
* 2,
* (data) => {
* // 添加新字段
* data.experience = 0;
* return data;
* }
* );
* ```
*/
public static registerComponentMigration(
componentType: string,
fromVersion: number,
toVersion: number,
migration: ComponentMigrationFunction
): void {
if (!this.componentMigrations.has(componentType)) {
this.componentMigrations.set(componentType, new Map());
}
const versionMap = this.componentMigrations.get(componentType)!;
// 使用fromVersion作为key表示"从这个版本迁移"
versionMap.set(fromVersion, migration);
}
/**
* 注册场景迁移函数
*
* @param fromVersion 源版本号
* @param toVersion 目标版本号
* @param migration 迁移函数
*
* @example
* ```typescript
* VersionMigrationManager.registerSceneMigration(
* 1,
* 2,
* (scene) => {
* // 迁移场景结构
* scene.metadata = { ...scene.metadata, migratedFrom: 1 };
* return scene;
* }
* );
* ```
*/
public static registerSceneMigration(
fromVersion: number,
toVersion: number,
migration: SceneMigrationFunction
): void {
this.sceneMigrations.set(fromVersion, migration);
}
/**
* 迁移组件数据
*
* @param component 序列化的组件数据
* @param targetVersion 目标版本号
* @returns 迁移后的组件数据
*/
public static migrateComponent(
component: SerializedComponent,
targetVersion: number
): SerializedComponent {
const currentVersion = component.version;
if (currentVersion === targetVersion) {
return component; // 版本相同,无需迁移
}
const migrations = this.componentMigrations.get(component.type);
if (!migrations) {
console.warn(`No migration path found for component ${component.type}`);
return component;
}
let migratedData = { ...component };
let version = currentVersion;
// 执行迁移链
while (version < targetVersion) {
const migration = migrations.get(version);
if (!migration) {
console.warn(
`Missing migration from version ${version} to ${version + 1} for ${component.type}`
);
break;
}
migratedData.data = migration(migratedData.data, version, version + 1);
version++;
}
migratedData.version = version;
return migratedData;
}
/**
* 迁移场景数据
*
* @param scene 序列化的场景数据
* @param targetVersion 目标版本号
* @returns 迁移后的场景数据
*/
public static migrateScene(scene: SerializedScene, targetVersion: number): SerializedScene {
const currentVersion = scene.version;
if (currentVersion === targetVersion) {
return scene; // 版本相同,无需迁移
}
let migratedScene = { ...scene };
let version = currentVersion;
// 执行场景级迁移
while (version < targetVersion) {
const migration = this.sceneMigrations.get(version);
if (!migration) {
console.warn(`Missing scene migration from version ${version} to ${version + 1}`);
break;
}
migratedScene = migration(migratedScene, version, version + 1);
version++;
}
migratedScene.version = version;
// 迁移所有组件
migratedScene = this.migrateSceneComponents(migratedScene);
return migratedScene;
}
/**
* 迁移场景中所有组件的版本
*/
private static migrateSceneComponents(scene: SerializedScene): SerializedScene {
const migratedScene = { ...scene };
migratedScene.entities = scene.entities.map(entity => ({
...entity,
components: entity.components.map(component => {
// 查找组件的目标版本
const typeInfo = scene.componentTypeRegistry.find(
t => t.typeName === component.type
);
if (typeInfo && typeInfo.version !== component.version) {
return this.migrateComponent(component, typeInfo.version);
}
return component;
}),
children: this.migrateEntitiesComponents(entity.children, scene.componentTypeRegistry)
}));
return migratedScene;
}
/**
* 递归迁移实体的组件
*/
private static migrateEntitiesComponents(
entities: any[],
typeRegistry: Array<{ typeName: string; version: number }>
): any[] {
return entities.map(entity => ({
...entity,
components: entity.components.map((component: SerializedComponent) => {
const typeInfo = typeRegistry.find(t => t.typeName === component.type);
if (typeInfo && typeInfo.version !== component.version) {
return this.migrateComponent(component, typeInfo.version);
}
return component;
}),
children: this.migrateEntitiesComponents(entity.children, typeRegistry)
}));
}
/**
* 清除所有迁移函数
*/
public static clearMigrations(): void {
this.componentMigrations.clear();
this.sceneMigrations.clear();
}
/**
* 获取组件的迁移路径
*
* @param componentType 组件类型名称
* @returns 可用的迁移版本列表
*/
public static getComponentMigrationPath(componentType: string): number[] {
const migrations = this.componentMigrations.get(componentType);
if (!migrations) {
return [];
}
return Array.from(migrations.keys()).sort((a, b) => a - b);
}
/**
* 获取场景的迁移路径
*
* @returns 可用的场景迁移版本列表
*/
public static getSceneMigrationPath(): number[] {
return Array.from(this.sceneMigrations.keys()).sort((a, b) => a - b);
}
/**
* 检查是否可以迁移组件
*
* @param componentType 组件类型名称
* @param fromVersion 源版本
* @param toVersion 目标版本
* @returns 是否存在完整的迁移路径
*/
public static canMigrateComponent(
componentType: string,
fromVersion: number,
toVersion: number
): boolean {
if (fromVersion === toVersion) {
return true;
}
const migrations = this.componentMigrations.get(componentType);
if (!migrations) {
return false;
}
// 检查是否存在完整的迁移路径
for (let v = fromVersion; v < toVersion; v++) {
if (!migrations.has(v)) {
return false;
}
}
return true;
}
/**
* 检查是否可以迁移场景
*
* @param fromVersion 源版本
* @param toVersion 目标版本
* @returns 是否存在完整的迁移路径
*/
public static canMigrateScene(fromVersion: number, toVersion: number): boolean {
if (fromVersion === toVersion) {
return true;
}
// 检查是否存在完整的场景迁移路径
for (let v = fromVersion; v < toVersion; v++) {
if (!this.sceneMigrations.has(v)) {
return false;
}
}
return true;
}
}
/**
* 便捷的迁移构建器
*
* 提供链式API来定义迁移
*/
export class MigrationBuilder {
private componentType?: string;
private fromVersion: number = 1;
private toVersion: number = 2;
/**
* 设置组件类型
*/
public forComponent(componentType: string): this {
this.componentType = componentType;
return this;
}
/**
* 设置版本范围
*/
public fromVersionToVersion(from: number, to: number): this {
this.fromVersion = from;
this.toVersion = to;
return this;
}
/**
* 注册迁移函数
*/
public migrate(migration: ComponentMigrationFunction | SceneMigrationFunction): void {
if (this.componentType) {
VersionMigrationManager.registerComponentMigration(
this.componentType,
this.fromVersion,
this.toVersion,
migration as ComponentMigrationFunction
);
} else {
VersionMigrationManager.registerSceneMigration(
this.fromVersion,
this.toVersion,
migration as SceneMigrationFunction
);
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* ECS序列化系统
*
* 提供完整的场景、实体和组件序列化支持
*/
// 装饰器
export {
Serializable,
Serialize,
SerializeAsMap,
SerializeAsSet,
IgnoreSerialization,
getSerializationMetadata,
isSerializable,
SERIALIZABLE_METADATA,
SERIALIZE_FIELD,
SERIALIZE_OPTIONS
} from './SerializationDecorators';
export type {
SerializableOptions,
FieldSerializeOptions,
SerializationMetadata
} from './SerializationDecorators';
// 组件序列化器
export { ComponentSerializer } from './ComponentSerializer';
export type { SerializedComponent } from './ComponentSerializer';
// 实体序列化器
export { EntitySerializer } from './EntitySerializer';
export type { SerializedEntity } from './EntitySerializer';
// 场景序列化器
export { SceneSerializer } from './SceneSerializer';
export type {
SerializedScene,
SerializationFormat,
DeserializationStrategy,
MigrationFunction,
SceneSerializationOptions,
SceneDeserializationOptions
} from './SceneSerializer';
// 版本迁移
export { VersionMigrationManager, MigrationBuilder } from './VersionMigration';
export type {
ComponentMigrationFunction,
SceneMigrationFunction
} from './VersionMigration';
// 组件类型注册表
export { ComponentTypeRegistry } from './ComponentTypeRegistry';

View File

@@ -11,4 +11,5 @@ export { WorldManager, IWorldManagerConfig } from './WorldManager';
export * from './Core/Events'; export * from './Core/Events';
export * from './Core/Query'; export * from './Core/Query';
export * from './Core/Storage'; export * from './Core/Storage';
export * from './Core/StorageDecorators'; export * from './Core/StorageDecorators';
export * from './Serialization';

View File

@@ -0,0 +1,575 @@
/**
* 序列化系统测试
*/
import { Component } from '../../../src/ECS/Component';
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import {
Serializable,
Serialize,
SerializeAsMap,
SerializeAsSet,
IgnoreSerialization,
ComponentSerializer,
EntitySerializer,
SceneSerializer,
ComponentTypeRegistry,
VersionMigrationManager,
MigrationBuilder
} from '../../../src/ECS/Serialization';
import { ECSComponent } from '../../../src/ECS/Decorators';
// 测试组件定义
@ECSComponent('Position')
@Serializable({ version: 1 })
class PositionComponent extends Component {
@Serialize()
public x: number = 0;
@Serialize()
public y: number = 0;
constructor(x: number = 0, y: number = 0) {
super();
this.x = x;
this.y = y;
}
}
@ECSComponent('Velocity')
@Serializable({ version: 1 })
class VelocityComponent extends Component {
@Serialize()
public dx: number = 0;
@Serialize()
public dy: number = 0;
}
@ECSComponent('Player')
@Serializable({ version: 1 })
class PlayerComponent extends Component {
@Serialize()
public name: string = '';
@Serialize()
public level: number = 1;
@SerializeAsMap()
public inventory: Map<string, number> = new Map();
@SerializeAsSet()
public tags: Set<string> = new Set();
@IgnoreSerialization()
public tempCache: any = null;
}
@ECSComponent('Health')
@Serializable({ version: 1 })
class HealthComponent extends Component {
@Serialize()
public current: number = 100;
@Serialize()
public max: number = 100;
}
// 非可序列化组件
class NonSerializableComponent extends Component {
public data: any = null;
}
describe('ECS Serialization System', () => {
beforeEach(() => {
// 注册组件类型
ComponentTypeRegistry.clear();
ComponentTypeRegistry.registerMany([
PositionComponent,
VelocityComponent,
PlayerComponent,
HealthComponent
]);
});
describe('Component Serialization', () => {
it('should serialize a simple component', () => {
const position = new PositionComponent(100, 200);
const serialized = ComponentSerializer.serialize(position);
expect(serialized).not.toBeNull();
expect(serialized!.type).toBe('Position');
expect(serialized!.version).toBe(1);
expect(serialized!.data.x).toBe(100);
expect(serialized!.data.y).toBe(200);
});
it('should deserialize a simple component', () => {
const serializedData = {
type: 'Position',
version: 1,
data: { x: 150, y: 250 }
};
const registry = ComponentTypeRegistry.getRegistry();
const component = ComponentSerializer.deserialize(serializedData, registry);
expect(component).not.toBeNull();
expect(component).toBeInstanceOf(PositionComponent);
expect((component as PositionComponent).x).toBe(150);
expect((component as PositionComponent).y).toBe(250);
});
it('should serialize Map fields', () => {
const player = new PlayerComponent();
player.name = 'Hero';
player.level = 5;
player.inventory.set('sword', 1);
player.inventory.set('potion', 10);
const serialized = ComponentSerializer.serialize(player);
expect(serialized).not.toBeNull();
expect(serialized!.data.inventory).toEqual([
['sword', 1],
['potion', 10]
]);
});
it('should deserialize Map fields', () => {
const serializedData = {
type: 'Player',
version: 1,
data: {
name: 'Hero',
level: 5,
inventory: [
['sword', 1],
['potion', 10]
],
tags: ['warrior', 'hero']
}
};
const registry = ComponentTypeRegistry.getRegistry();
const component = ComponentSerializer.deserialize(
serializedData,
registry
) as PlayerComponent;
expect(component).not.toBeNull();
expect(component.inventory.get('sword')).toBe(1);
expect(component.inventory.get('potion')).toBe(10);
expect(component.tags.has('warrior')).toBe(true);
expect(component.tags.has('hero')).toBe(true);
});
it('should ignore fields marked with @IgnoreSerialization', () => {
const player = new PlayerComponent();
player.tempCache = { foo: 'bar' };
const serialized = ComponentSerializer.serialize(player);
expect(serialized).not.toBeNull();
expect(serialized!.data.tempCache).toBeUndefined();
});
it('should return null for non-serializable components', () => {
const nonSerializable = new NonSerializableComponent();
const serialized = ComponentSerializer.serialize(nonSerializable);
expect(serialized).toBeNull();
});
});
describe('Entity Serialization', () => {
it('should serialize an entity with components', () => {
const entity = new Entity('Player', 1);
entity.addComponent(new PositionComponent(50, 100));
entity.addComponent(new VelocityComponent());
entity.tag = 10;
const serialized = EntitySerializer.serialize(entity);
expect(serialized.id).toBe(1);
expect(serialized.name).toBe('Player');
expect(serialized.tag).toBe(10);
expect(serialized.components.length).toBe(2);
});
it('should serialize entity hierarchy', () => {
const parent = new Entity('Parent', 1);
const child = new Entity('Child', 2);
parent.addComponent(new PositionComponent(0, 0));
child.addComponent(new PositionComponent(10, 10));
parent.addChild(child);
const serialized = EntitySerializer.serialize(parent);
expect(serialized.children.length).toBe(1);
expect(serialized.children[0].id).toBe(2);
expect(serialized.children[0].name).toBe('Child');
});
it('should deserialize an entity', () => {
const serializedEntity = {
id: 1,
name: 'TestEntity',
tag: 5,
active: true,
enabled: true,
updateOrder: 0,
components: [
{
type: 'Position',
version: 1,
data: { x: 100, y: 200 }
}
],
children: []
};
const registry = ComponentTypeRegistry.getRegistry();
let idCounter = 10;
const entity = EntitySerializer.deserialize(
serializedEntity,
registry,
() => idCounter++,
false
);
expect(entity.name).toBe('TestEntity');
expect(entity.tag).toBe(5);
expect(entity.components.length).toBe(1);
});
});
describe('Scene Serialization', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene({ name: 'TestScene' });
});
afterEach(() => {
scene.end();
});
it('should serialize a scene', () => {
const entity1 = scene.createEntity('Entity1');
entity1.addComponent(new PositionComponent(10, 20));
const entity2 = scene.createEntity('Entity2');
entity2.addComponent(new PlayerComponent());
const saveData = scene.serialize({ format: 'json', pretty: true });
expect(saveData).toBeTruthy();
const parsed = JSON.parse(saveData);
expect(parsed.name).toBe('TestScene');
expect(parsed.version).toBe(1);
expect(parsed.entities.length).toBe(2);
});
it('should deserialize a scene with replace strategy', () => {
// 创建初始实体
const entity1 = scene.createEntity('Initial');
entity1.addComponent(new PositionComponent(0, 0));
// 序列化
const entity2 = scene.createEntity('ToSave');
entity2.addComponent(new PositionComponent(100, 100));
const saveData = scene.serialize();
// 清空并重新加载
scene.deserialize(saveData, {
strategy: 'replace',
componentRegistry: ComponentTypeRegistry.getRegistry()
});
expect(scene.entities.count).toBeGreaterThan(0);
});
it('should filter components during serialization', () => {
const entity = scene.createEntity('Mixed');
entity.addComponent(new PositionComponent(1, 2));
entity.addComponent(new PlayerComponent());
entity.addComponent(new HealthComponent());
const saveData = scene.serialize({
components: [PositionComponent, PlayerComponent]
});
const parsed = JSON.parse(saveData);
expect(parsed.entities.length).toBeGreaterThan(0);
});
it('should preserve entity hierarchy', () => {
const parent = scene.createEntity('Parent');
const child = scene.createEntity('Child');
parent.addChild(child);
parent.addComponent(new PositionComponent(0, 0));
child.addComponent(new PositionComponent(10, 10));
const saveData = scene.serialize();
const parsed = JSON.parse(saveData);
// 只有父实体在顶层
expect(parsed.entities.length).toBe(1);
expect(parsed.entities[0].children.length).toBe(1);
});
it('should validate save data', () => {
const entity = scene.createEntity('Test');
entity.addComponent(new PositionComponent(5, 5));
const saveData = scene.serialize();
const validation = SceneSerializer.validate(saveData);
expect(validation.valid).toBe(true);
expect(validation.version).toBe(1);
});
it('should get save data info', () => {
const entity = scene.createEntity('InfoTest');
entity.addComponent(new PositionComponent(1, 1));
const saveData = scene.serialize();
const info = SceneSerializer.getInfo(saveData);
expect(info).not.toBeNull();
expect(info!.name).toBe('TestScene');
expect(info!.version).toBe(1);
});
});
describe('Version Migration', () => {
@ECSComponent('OldPlayer')
@Serializable({ version: 1 })
class OldPlayerV1 extends Component {
@Serialize()
public name: string = '';
@Serialize()
public hp: number = 100;
}
@ECSComponent('OldPlayer')
@Serializable({ version: 2 })
class OldPlayerV2 extends Component {
@Serialize()
public name: string = '';
@Serialize()
public health: number = 100; // 重命名了字段
@Serialize()
public maxHealth: number = 100; // 新增字段
}
beforeEach(() => {
VersionMigrationManager.clearMigrations();
});
it('should migrate component from v1 to v2', () => {
// 注册迁移
VersionMigrationManager.registerComponentMigration(
'OldPlayer',
1,
2,
(data) => {
return {
name: data.name,
health: data.hp,
maxHealth: data.hp
};
}
);
const v1Data = {
type: 'OldPlayer',
version: 1,
data: { name: 'Hero', hp: 80 }
};
const migrated = VersionMigrationManager.migrateComponent(v1Data, 2);
expect(migrated.version).toBe(2);
expect(migrated.data.health).toBe(80);
expect(migrated.data.maxHealth).toBe(80);
expect(migrated.data.hp).toBeUndefined();
});
it('should use MigrationBuilder for component migration', () => {
new MigrationBuilder()
.forComponent('Player')
.fromVersionToVersion(1, 2)
.migrate((data: any) => {
data.experience = 0;
return data;
});
expect(VersionMigrationManager.canMigrateComponent('Player', 1, 2)).toBe(true);
});
it('should check migration path availability', () => {
VersionMigrationManager.registerComponentMigration('Test', 1, 2, (d) => d);
VersionMigrationManager.registerComponentMigration('Test', 2, 3, (d) => d);
expect(VersionMigrationManager.canMigrateComponent('Test', 1, 3)).toBe(true);
expect(VersionMigrationManager.canMigrateComponent('Test', 1, 4)).toBe(false);
});
it('should get migration path', () => {
VersionMigrationManager.registerComponentMigration('PathTest', 1, 2, (d) => d);
VersionMigrationManager.registerComponentMigration('PathTest', 2, 3, (d) => d);
const path = VersionMigrationManager.getComponentMigrationPath('PathTest');
expect(path).toEqual([1, 2]);
});
});
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);
});
});
describe('Integration Tests', () => {
it('should perform full save/load cycle', () => {
const scene1 = new Scene({ name: 'SaveTest' });
// 创建复杂实体
const player = scene1.createEntity('Player');
const playerComp = new PlayerComponent();
playerComp.name = 'TestHero';
playerComp.level = 10;
playerComp.inventory.set('sword', 1);
playerComp.inventory.set('shield', 1);
playerComp.tags.add('warrior');
player.addComponent(playerComp);
player.addComponent(new PositionComponent(100, 200));
player.addComponent(new HealthComponent());
// 创建子实体
const weapon = scene1.createEntity('Weapon');
weapon.addComponent(new PositionComponent(5, 0));
player.addChild(weapon);
// 序列化
const saveData = scene1.serialize();
// 新场景
const scene2 = new Scene({ name: 'LoadTest' });
// 反序列化
scene2.deserialize(saveData, {
strategy: 'replace',
componentRegistry: ComponentTypeRegistry.getRegistry()
});
// 验证
const loadedPlayer = scene2.findEntity('Player');
expect(loadedPlayer).not.toBeNull();
const loadedPlayerComp = loadedPlayer!.getComponent(PlayerComponent as any) as PlayerComponent;
expect(loadedPlayerComp).not.toBeNull();
expect(loadedPlayerComp.name).toBe('TestHero');
expect(loadedPlayerComp.level).toBe(10);
expect(loadedPlayerComp.inventory.get('sword')).toBe(1);
expect(loadedPlayerComp.tags.has('warrior')).toBe(true);
// 验证层级结构
expect(loadedPlayer!.childCount).toBe(1);
scene1.end();
scene2.end();
});
it('should serialize and deserialize scene custom data', () => {
const scene1 = new Scene({ name: 'SceneDataTest' });
// 设置场景自定义数据
scene1.sceneData.set('weather', 'rainy');
scene1.sceneData.set('timeOfDay', 14.5);
scene1.sceneData.set('difficulty', 'hard');
scene1.sceneData.set('checkpoint', { x: 100, y: 200 });
scene1.sceneData.set('tags', new Set(['action', 'adventure']));
scene1.sceneData.set('metadata', new Map([['author', 'test'], ['version', '1.0']]));
// 序列化
const saveData = scene1.serialize();
// 新场景
const scene2 = new Scene({ name: 'LoadTest' });
// 反序列化
scene2.deserialize(saveData, {
strategy: 'replace',
componentRegistry: ComponentTypeRegistry.getRegistry()
});
// 验证场景数据
expect(scene2.sceneData.get('weather')).toBe('rainy');
expect(scene2.sceneData.get('timeOfDay')).toBe(14.5);
expect(scene2.sceneData.get('difficulty')).toBe('hard');
expect(scene2.sceneData.get('checkpoint')).toEqual({ x: 100, y: 200 });
const tags = scene2.sceneData.get('tags');
expect(tags).toBeInstanceOf(Set);
expect(tags.has('action')).toBe(true);
expect(tags.has('adventure')).toBe(true);
const metadata = scene2.sceneData.get('metadata');
expect(metadata).toBeInstanceOf(Map);
expect(metadata.get('author')).toBe('test');
expect(metadata.get('version')).toBe('1.0');
scene1.end();
scene2.end();
});
});
});