2025-10-08 18:34:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 场景序列化器
|
|
|
|
|
|
*
|
|
|
|
|
|
* 负责整个场景的序列化和反序列化,包括实体、组件等
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import type { IScene } from '../IScene';
|
|
|
|
|
|
import { Entity } from '../Entity';
|
|
|
|
|
|
import { Component } from '../Component';
|
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';
|
2025-10-08 20:42:55 +08:00
|
|
|
|
import * as msgpack from 'msgpack-lite';
|
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 序列化选项
|
2025-10-08 20:42:55 +08:00
|
|
|
|
* @returns 序列化后的数据(JSON字符串或二进制Buffer)
|
2025-10-08 18:34:15 +08:00
|
|
|
|
*/
|
2025-10-08 20:42:55 +08:00
|
|
|
|
public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Buffer {
|
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 {
|
2025-10-08 20:42:55 +08:00
|
|
|
|
// 二进制格式(使用 MessagePack)
|
|
|
|
|
|
return msgpack.encode(serializedScene);
|
2025-10-08 18:34:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 反序列化场景
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param scene 目标场景
|
2025-10-08 20:42:55 +08:00
|
|
|
|
* @param saveData 序列化的数据(JSON字符串或二进制Buffer)
|
2025-10-08 18:34:15 +08:00
|
|
|
|
* @param options 反序列化选项
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static deserialize(
|
|
|
|
|
|
scene: IScene,
|
2025-10-08 20:42:55 +08:00
|
|
|
|
saveData: string | Buffer,
|
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') {
|
|
|
|
|
|
// JSON格式
|
|
|
|
|
|
serializedScene = JSON.parse(saveData);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 二进制格式(MessagePack)
|
|
|
|
|
|
serializedScene = msgpack.decode(saveData);
|
|
|
|
|
|
}
|
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
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 将实体添加到场景
|
|
|
|
|
|
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> {
|
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: 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|