Files
esengine/src/Utils/Serialization/StaticProtobufSerializer.ts
2025-08-06 17:04:02 +08:00

371 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 静态Protobuf序列化器
*
* 使用预生成的protobuf静态模块进行序列化
*/
import { Component } from '../../ECS/Component';
import {
ProtobufRegistry,
ProtoComponentDefinition,
isProtoSerializable,
getProtoName
} from './ProtobufDecorators';
/**
* 序列化数据接口
*/
export interface SerializedData {
/** 序列化类型 */
type: 'protobuf' | 'json';
/** 组件类型名称 */
componentType: string;
/** 序列化后的数据 */
data: Uint8Array | any;
/** 数据大小(字节) */
size: number;
}
/**
* 静态Protobuf序列化器
*
* 使用CLI预生成的protobuf静态模块
*/
export class StaticProtobufSerializer {
private registry: ProtobufRegistry;
private static instance: StaticProtobufSerializer;
/** 预生成的protobuf根对象 */
private protobufRoot: any = null;
private isInitialized: boolean = false;
private constructor() {
this.registry = ProtobufRegistry.getInstance();
this.initializeStaticProtobuf();
}
public static getInstance(): StaticProtobufSerializer {
if (!StaticProtobufSerializer.instance) {
StaticProtobufSerializer.instance = new StaticProtobufSerializer();
}
return StaticProtobufSerializer.instance;
}
/**
* 初始化静态protobuf模块
*/
private async initializeStaticProtobuf(): Promise<void> {
try {
// 尝试加载预生成的protobuf模块
const ecsProto = await this.loadGeneratedProtobuf();
if (ecsProto && ecsProto.ecs) {
this.protobufRoot = ecsProto.ecs;
this.isInitialized = true;
console.log('[StaticProtobufSerializer] 预生成的Protobuf模块已加载');
} else {
console.warn('[StaticProtobufSerializer] 未找到预生成的protobuf模块将使用JSON序列化');
console.log('💡 请运行: npm run proto:build');
}
} catch (error) {
console.warn('[StaticProtobufSerializer] 初始化失败将使用JSON序列化:', error.message);
}
}
/**
* 加载预生成的protobuf模块
*/
private async loadGeneratedProtobuf(): Promise<any> {
const possiblePaths = [
// 项目中的生成路径
'./generated/ecs-components',
'../generated/ecs-components',
'../../generated/ecs-components',
// 相对于当前文件的路径
'../../../generated/ecs-components'
];
for (const path of possiblePaths) {
try {
const module = await import(path);
return module;
} catch (error) {
// 继续尝试下一个路径
continue;
}
}
// 如果所有路径都失败尝试require方式
for (const path of possiblePaths) {
try {
const module = require(path);
return module;
} catch (error) {
continue;
}
}
return null;
}
/**
* 序列化组件
*/
public serialize(component: Component): SerializedData {
const componentType = component.constructor.name;
// 检查是否支持protobuf序列化
if (!isProtoSerializable(component) || !this.isInitialized) {
return this.fallbackToJSON(component);
}
try {
const protoName = getProtoName(component);
if (!protoName) {
return this.fallbackToJSON(component);
}
const definition = this.registry.getComponentDefinition(protoName);
if (!definition) {
console.warn(`[StaticProtobufSerializer] 未找到组件定义: ${protoName}`);
return this.fallbackToJSON(component);
}
// 获取对应的protobuf消息类型
const MessageType = this.protobufRoot[protoName];
if (!MessageType) {
console.warn(`[StaticProtobufSerializer] 未找到protobuf消息类型: ${protoName}`);
return this.fallbackToJSON(component);
}
// 构建protobuf数据对象
const protoData = this.buildProtoData(component, definition);
// 验证数据
const error = MessageType.verify(protoData);
if (error) {
console.warn(`[StaticProtobufSerializer] 数据验证失败: ${error}`);
return this.fallbackToJSON(component);
}
// 创建消息并编码
const message = MessageType.create(protoData);
const buffer = MessageType.encode(message).finish();
return {
type: 'protobuf',
componentType: componentType,
data: buffer,
size: buffer.length
};
} catch (error) {
console.warn(`[StaticProtobufSerializer] 序列化失败回退到JSON: ${componentType}`, error);
return this.fallbackToJSON(component);
}
}
/**
* 反序列化组件
*/
public deserialize(component: Component, serializedData: SerializedData): void {
if (serializedData.type === 'json') {
this.deserializeFromJSON(component, serializedData.data);
return;
}
if (!this.isInitialized) {
console.warn('[StaticProtobufSerializer] Protobuf未初始化无法反序列化');
return;
}
try {
const protoName = getProtoName(component);
if (!protoName) {
this.deserializeFromJSON(component, serializedData.data);
return;
}
const MessageType = this.protobufRoot[protoName];
if (!MessageType) {
console.warn(`[StaticProtobufSerializer] 反序列化时未找到消息类型: ${protoName}`);
return;
}
// 解码消息
const message = MessageType.decode(serializedData.data as Uint8Array);
const data = MessageType.toObject(message);
// 应用数据到组件
this.applyDataToComponent(component, data);
} catch (error) {
console.warn(`[StaticProtobufSerializer] 反序列化失败: ${component.constructor.name}`, error);
}
}
/**
* 检查组件是否支持protobuf序列化
*/
public canSerialize(component: Component): boolean {
return this.isInitialized && isProtoSerializable(component);
}
/**
* 获取序列化统计信息
*/
public getStats(): {
registeredComponents: number;
protobufAvailable: boolean;
initialized: boolean;
} {
return {
registeredComponents: this.registry.getAllComponents().size,
protobufAvailable: !!this.protobufRoot,
initialized: this.isInitialized
};
}
/**
* 手动设置protobuf根对象用于测试
*/
public setProtobufRoot(root: any): void {
this.protobufRoot = root;
this.isInitialized = !!root;
}
/**
* 构建protobuf数据对象
*/
private buildProtoData(component: Component, definition: ProtoComponentDefinition): any {
const data: any = {};
for (const [propertyName, fieldDef] of definition.fields) {
const value = (component as any)[propertyName];
if (value !== undefined && value !== null) {
data[fieldDef.name] = this.convertValueToProtoType(value, fieldDef.type);
}
}
return data;
}
/**
* 转换值到protobuf类型
*/
private convertValueToProtoType(value: any, type: string): any {
switch (type) {
case 'int32':
case 'uint32':
case 'sint32':
case 'fixed32':
case 'sfixed32':
return parseInt(value) || 0;
case 'float':
case 'double':
return parseFloat(value) || 0;
case 'bool':
return Boolean(value);
case 'string':
return String(value);
default:
return value;
}
}
/**
* 应用数据到组件
*/
private applyDataToComponent(component: Component, data: any): void {
const protoName = getProtoName(component);
if (!protoName) return;
const definition = this.registry.getComponentDefinition(protoName);
if (!definition) return;
for (const [propertyName, fieldDef] of definition.fields) {
const value = data[fieldDef.name];
if (value !== undefined) {
(component as any)[propertyName] = value;
}
}
}
/**
* 回退到JSON序列化
*/
private fallbackToJSON(component: Component): SerializedData {
const data = this.defaultJSONSerialize(component);
const jsonString = JSON.stringify(data);
return {
type: 'json',
componentType: component.constructor.name,
data: data,
size: new Blob([jsonString]).size
};
}
/**
* 默认JSON序列化
*/
private defaultJSONSerialize(component: Component): any {
const data: any = {};
for (const key in component) {
if (component.hasOwnProperty(key) &&
typeof (component as any)[key] !== 'function' &&
key !== 'id' &&
key !== 'entity' &&
key !== '_enabled' &&
key !== '_updateOrder') {
const value = (component as any)[key];
if (this.isSerializableValue(value)) {
data[key] = value;
}
}
}
return data;
}
/**
* JSON反序列化
*/
private deserializeFromJSON(component: Component, data: any): void {
for (const key in data) {
if (component.hasOwnProperty(key) &&
typeof (component as any)[key] !== 'function' &&
key !== 'id' &&
key !== 'entity' &&
key !== '_enabled' &&
key !== '_updateOrder') {
(component as any)[key] = data[key];
}
}
}
/**
* 检查值是否可序列化
*/
private isSerializableValue(value: any): boolean {
if (value === null || value === undefined) return true;
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;
if (Array.isArray(value)) return value.every(v => this.isSerializableValue(v));
if (typeof value === 'object') {
try {
JSON.stringify(value);
return true;
} catch {
return false;
}
}
return false;
}
}