Files
esengine/packages/core/src/ECS/Serialization/SceneSerializer.ts

554 lines
15 KiB
TypeScript
Raw Normal View History

2025-10-08 18:34:15 +08:00
/**
*
*
*
*/
import type { IScene } from '../IScene';
import { Entity } from '../Entity';
2025-10-08 20:42:55 +08:00
import { ComponentType, ComponentRegistry } from '../Core/ComponentStorage';
2025-10-08 18:34:15 +08:00
import { EntitySerializer, SerializedEntity } from './EntitySerializer';
import { getComponentTypeName } from '../Decorators';
import { getSerializationMetadata } from './SerializationDecorators';
import { BinarySerializer } from '../../Utils/BinarySerializer';
2025-10-08 18:34:15 +08:00
/**
*
*/
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字符串或二进制Uint8Array
2025-10-08 18:34:15 +08:00
*/
public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Uint8Array {
2025-10-08 18:34:15 +08:00
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 {
return BinarySerializer.encode(serializedScene);
2025-10-08 18:34:15 +08:00
}
}
/**
*
*
* @param scene
* @param saveData JSON字符串或二进制Uint8Array
2025-10-08 18:34:15 +08:00
* @param options
*/
public static deserialize(
scene: IScene,
saveData: string | Uint8Array,
2025-10-08 18:34:15 +08:00
options?: SceneDeserializationOptions
): void {
const opts: SceneDeserializationOptions = {
strategy: 'replace',
preserveIds: false,
...options
};
let serializedScene: SerializedScene;
try {
2025-10-08 20:42:55 +08:00
if (typeof saveData === 'string') {
serializedScene = JSON.parse(saveData);
} else {
serializedScene = BinarySerializer.decode(saveData) as SerializedScene;
2025-10-08 20:42:55 +08:00
}
2025-10-08 18:34:15 +08:00
} 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,
scene
2025-10-08 18:34:15 +08:00
);
// 将实体添加到场景
for (const entity of entities) {
scene.addEntity(entity, true);
this.addChildrenRecursively(entity, scene);
2025-10-08 18:34:15 +08:00
}
// 统一清理缓存(批量操作完成后)
scene.querySystem.clearCache();
scene.clearSystemEntityCaches();
2025-10-08 18:34:15 +08:00
// 反序列化场景自定义数据
if (serializedScene.sceneData) {
this.deserializeSceneData(serializedScene.sceneData, scene.sceneData);
}
}
/**
*
*
*
* EntitySerializer.deserialize会提前设置子实体的scene引用
* Entity.addChild的条件判断(!child.scene)scene.addEntity调用
* SceneSerializer中统一递归添加所有子实体
*
* @param entity
* @param scene
*/
private static addChildrenRecursively(entity: Entity, scene: IScene): void {
for (const child of entity.children) {
scene.addEntity(child, true); // 延迟缓存清理
this.addChildrenRecursively(child, scene); // 递归处理子实体的子实体
}
2025-10-08 18:34:15 +08:00
}
/**
*
*
* 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> {
2025-10-08 20:42:55 +08:00
return ComponentRegistry.getAllComponentNames() as Map<string, ComponentType>;
2025-10-08 18:34:15 +08:00
}
/**
*
*
* @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.length > 0 && { errors })
2025-10-08 18:34:15 +08:00
};
} 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,
...(data.timestamp !== undefined && { timestamp: data.timestamp }),
2025-10-08 18:34:15 +08:00
entityCount: data.metadata?.entityCount || data.entities.length,
componentTypeCount: data.componentTypeRegistry.length
};
} catch (error) {
return null;
}
}
}