新增protobuf依赖(为网络和序列化做准备)
更新readme
This commit is contained in:
284
src/Utils/Serialization/ProtobufDecorators.ts
Normal file
284
src/Utils/Serialization/ProtobufDecorators.ts
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Protobuf序列化装饰器
|
||||
*
|
||||
* 提供装饰器语法来标记组件和字段进行protobuf序列化
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { Component } from '../../ECS/Component';
|
||||
|
||||
/**
|
||||
* Protobuf字段类型枚举
|
||||
*/
|
||||
export enum ProtoFieldType {
|
||||
DOUBLE = 'double',
|
||||
FLOAT = 'float',
|
||||
INT32 = 'int32',
|
||||
INT64 = 'int64',
|
||||
UINT32 = 'uint32',
|
||||
UINT64 = 'uint64',
|
||||
SINT32 = 'sint32',
|
||||
SINT64 = 'sint64',
|
||||
FIXED32 = 'fixed32',
|
||||
FIXED64 = 'fixed64',
|
||||
SFIXED32 = 'sfixed32',
|
||||
SFIXED64 = 'sfixed64',
|
||||
BOOL = 'bool',
|
||||
STRING = 'string',
|
||||
BYTES = 'bytes'
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf字段定义接口
|
||||
*/
|
||||
export interface ProtoFieldDefinition {
|
||||
/** 字段编号 */
|
||||
fieldNumber: number;
|
||||
/** 字段类型 */
|
||||
type: ProtoFieldType;
|
||||
/** 是否为数组 */
|
||||
repeated?: boolean;
|
||||
/** 是否可选 */
|
||||
optional?: boolean;
|
||||
/** 字段名称 */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf组件定义接口
|
||||
*/
|
||||
export interface ProtoComponentDefinition {
|
||||
/** 组件名称 */
|
||||
name: string;
|
||||
/** 字段定义列表 */
|
||||
fields: Map<string, ProtoFieldDefinition>;
|
||||
/** 构造函数 */
|
||||
constructor: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf注册表
|
||||
*/
|
||||
export class ProtobufRegistry {
|
||||
private static instance: ProtobufRegistry;
|
||||
private components = new Map<string, ProtoComponentDefinition>();
|
||||
|
||||
public static getInstance(): ProtobufRegistry {
|
||||
if (!ProtobufRegistry.instance) {
|
||||
ProtobufRegistry.instance = new ProtobufRegistry();
|
||||
}
|
||||
return ProtobufRegistry.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件定义
|
||||
*/
|
||||
public registerComponent(componentName: string, definition: ProtoComponentDefinition): void {
|
||||
this.components.set(componentName, definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件定义
|
||||
*/
|
||||
public getComponentDefinition(componentName: string): ProtoComponentDefinition | undefined {
|
||||
return this.components.get(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否支持protobuf
|
||||
*/
|
||||
public hasProtoDefinition(componentName: string): boolean {
|
||||
return this.components.has(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的组件
|
||||
*/
|
||||
public getAllComponents(): Map<string, ProtoComponentDefinition> {
|
||||
return new Map(this.components);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成proto文件定义
|
||||
*/
|
||||
public generateProtoDefinition(): string {
|
||||
let protoContent = 'syntax = "proto3";\n\n';
|
||||
protoContent += 'package ecs;\n\n';
|
||||
|
||||
// 生成消息定义
|
||||
for (const [name, definition] of this.components) {
|
||||
protoContent += `message ${name} {\n`;
|
||||
|
||||
// 按字段编号排序
|
||||
const sortedFields = Array.from(definition.fields.values())
|
||||
.sort((a, b) => a.fieldNumber - b.fieldNumber);
|
||||
|
||||
for (const field of sortedFields) {
|
||||
let fieldDef = ' ';
|
||||
|
||||
if (field.repeated) {
|
||||
fieldDef += 'repeated ';
|
||||
} else if (field.optional) {
|
||||
fieldDef += 'optional ';
|
||||
}
|
||||
|
||||
fieldDef += `${field.type} ${field.name} = ${field.fieldNumber};\n`;
|
||||
protoContent += fieldDef;
|
||||
}
|
||||
|
||||
protoContent += '}\n\n';
|
||||
}
|
||||
|
||||
return protoContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ProtoSerializable 组件装饰器
|
||||
*
|
||||
* 标记组件支持protobuf序列化
|
||||
*
|
||||
* @param protoName - protobuf消息名称,默认使用类名
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @ProtoSerializable('Position')
|
||||
* class PositionComponent extends Component {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function ProtoSerializable(protoName?: string) {
|
||||
return function <T extends { new(...args: any[]): Component }>(constructor: T) {
|
||||
const componentName = protoName || constructor.name;
|
||||
const registry = ProtobufRegistry.getInstance();
|
||||
|
||||
// 获取字段定义(由ProtoField装饰器设置)
|
||||
const fields = (constructor.prototype._protoFields as Map<string, ProtoFieldDefinition>)
|
||||
|| new Map<string, ProtoFieldDefinition>();
|
||||
|
||||
// 注册组件定义
|
||||
registry.registerComponent(componentName, {
|
||||
name: componentName,
|
||||
fields: fields,
|
||||
constructor: constructor
|
||||
});
|
||||
|
||||
// 标记组件支持protobuf
|
||||
(constructor.prototype._isProtoSerializable = true);
|
||||
(constructor.prototype._protoName = componentName);
|
||||
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ProtoField 字段装饰器
|
||||
*
|
||||
* 标记字段参与protobuf序列化
|
||||
*
|
||||
* @param fieldNumber - protobuf字段编号(必须唯一且大于0)
|
||||
* @param type - 字段类型,默认自动推断
|
||||
* @param options - 额外选项
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class PositionComponent extends Component {
|
||||
* @ProtoField(1, ProtoFieldType.FLOAT)
|
||||
* public x: number = 0;
|
||||
*
|
||||
* @ProtoField(2, ProtoFieldType.FLOAT)
|
||||
* public y: number = 0;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function ProtoField(
|
||||
fieldNumber: number,
|
||||
type?: ProtoFieldType,
|
||||
options?: {
|
||||
repeated?: boolean;
|
||||
optional?: boolean;
|
||||
}
|
||||
) {
|
||||
return function (target: any, propertyKey: string) {
|
||||
// 验证字段编号
|
||||
if (fieldNumber <= 0) {
|
||||
throw new Error(`ProtoField: 字段编号必须大于0,当前值: ${fieldNumber}`);
|
||||
}
|
||||
|
||||
// 初始化字段集合
|
||||
if (!target._protoFields) {
|
||||
target._protoFields = new Map<string, ProtoFieldDefinition>();
|
||||
}
|
||||
|
||||
// 自动推断类型
|
||||
let inferredType = type;
|
||||
if (!inferredType) {
|
||||
const designType = Reflect.getMetadata?.('design:type', target, propertyKey);
|
||||
inferredType = inferProtoType(designType);
|
||||
}
|
||||
|
||||
// 检查字段编号冲突
|
||||
for (const [key, field] of target._protoFields) {
|
||||
if (field.fieldNumber === fieldNumber && key !== propertyKey) {
|
||||
throw new Error(`ProtoField: 字段编号 ${fieldNumber} 已被字段 ${key} 使用`);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加字段定义
|
||||
target._protoFields.set(propertyKey, {
|
||||
fieldNumber,
|
||||
type: inferredType || ProtoFieldType.STRING,
|
||||
repeated: options?.repeated || false,
|
||||
optional: options?.optional || false,
|
||||
name: propertyKey
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动推断protobuf类型
|
||||
*/
|
||||
function inferProtoType(jsType: any): ProtoFieldType {
|
||||
if (!jsType) return ProtoFieldType.STRING;
|
||||
|
||||
switch (jsType) {
|
||||
case Number:
|
||||
return ProtoFieldType.FLOAT;
|
||||
case Boolean:
|
||||
return ProtoFieldType.BOOL;
|
||||
case String:
|
||||
return ProtoFieldType.STRING;
|
||||
default:
|
||||
return ProtoFieldType.STRING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷装饰器 - 常用类型
|
||||
*/
|
||||
export const ProtoInt32 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoFieldType.INT32, options);
|
||||
|
||||
export const ProtoFloat = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoFieldType.FLOAT, options);
|
||||
|
||||
export const ProtoString = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoFieldType.STRING, options);
|
||||
|
||||
export const ProtoBool = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoFieldType.BOOL, options);
|
||||
|
||||
/**
|
||||
* 检查组件是否支持protobuf序列化
|
||||
*/
|
||||
export function isProtoSerializable(component: Component): boolean {
|
||||
return !!(component as any)._isProtoSerializable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件的protobuf名称
|
||||
*/
|
||||
export function getProtoName(component: Component): string | undefined {
|
||||
return (component as any)._protoName;
|
||||
}
|
||||
371
src/Utils/Serialization/ProtobufSerializer.ts
Normal file
371
src/Utils/Serialization/ProtobufSerializer.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* Protobuf序列化器
|
||||
*
|
||||
* 处理组件的protobuf序列化和反序列化
|
||||
*/
|
||||
|
||||
import { Component } from '../../ECS/Component';
|
||||
import {
|
||||
ProtobufRegistry,
|
||||
ProtoComponentDefinition,
|
||||
ProtoFieldDefinition,
|
||||
ProtoFieldType,
|
||||
isProtoSerializable,
|
||||
getProtoName
|
||||
} from './ProtobufDecorators';
|
||||
|
||||
/**
|
||||
* 序列化数据接口
|
||||
*/
|
||||
export interface SerializedData {
|
||||
/** 序列化类型 */
|
||||
type: 'protobuf' | 'json';
|
||||
/** 组件类型名称 */
|
||||
componentType: string;
|
||||
/** 序列化后的数据 */
|
||||
data: Uint8Array | any;
|
||||
/** 数据大小(字节) */
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf序列化器
|
||||
*/
|
||||
export class ProtobufSerializer {
|
||||
private registry: ProtobufRegistry;
|
||||
private static instance: ProtobufSerializer;
|
||||
|
||||
/** protobuf.js实例 */
|
||||
private protobuf: any = null;
|
||||
private root: any = null;
|
||||
|
||||
private constructor() {
|
||||
this.registry = ProtobufRegistry.getInstance();
|
||||
this.initializeProtobuf();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动初始化protobuf支持
|
||||
*/
|
||||
private async initializeProtobuf(): Promise<void> {
|
||||
try {
|
||||
// 动态导入protobufjs
|
||||
this.protobuf = await import('protobufjs');
|
||||
this.buildProtoDefinitions();
|
||||
console.log('[ProtobufSerializer] Protobuf支持已自动启用');
|
||||
} catch (error) {
|
||||
console.warn('[ProtobufSerializer] 无法加载protobufjs,将使用JSON序列化:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): ProtobufSerializer {
|
||||
if (!ProtobufSerializer.instance) {
|
||||
ProtobufSerializer.instance = new ProtobufSerializer();
|
||||
}
|
||||
return ProtobufSerializer.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动初始化protobuf.js(可选,通常会自动初始化)
|
||||
*
|
||||
* @param protobufJs - protobuf.js库实例
|
||||
*/
|
||||
public initialize(protobufJs: any): void {
|
||||
this.protobuf = protobufJs;
|
||||
this.buildProtoDefinitions();
|
||||
console.log('[ProtobufSerializer] Protobuf支持已手动启用');
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化组件
|
||||
*
|
||||
* @param component - 要序列化的组件
|
||||
* @returns 序列化数据
|
||||
*/
|
||||
public serialize(component: Component): SerializedData {
|
||||
const componentType = component.constructor.name;
|
||||
|
||||
// 检查是否支持protobuf序列化
|
||||
if (!isProtoSerializable(component)) {
|
||||
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(`[ProtobufSerializer] 未找到组件定义: ${protoName}`);
|
||||
return this.fallbackToJSON(component);
|
||||
}
|
||||
|
||||
// 构建protobuf数据对象
|
||||
const protoData = this.buildProtoData(component, definition);
|
||||
|
||||
// 获取protobuf消息类型
|
||||
const MessageType = this.getMessageType(protoName);
|
||||
if (!MessageType) {
|
||||
console.warn(`[ProtobufSerializer] 未找到消息类型: ${protoName}`);
|
||||
return this.fallbackToJSON(component);
|
||||
}
|
||||
|
||||
// 验证数据
|
||||
const error = MessageType.verify(protoData);
|
||||
if (error) {
|
||||
console.warn(`[ProtobufSerializer] 数据验证失败: ${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(`[ProtobufSerializer] 序列化失败,回退到JSON: ${componentType}`, error);
|
||||
return this.fallbackToJSON(component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化组件
|
||||
*
|
||||
* @param component - 目标组件实例
|
||||
* @param serializedData - 序列化数据
|
||||
*/
|
||||
public deserialize(component: Component, serializedData: SerializedData): void {
|
||||
if (serializedData.type === 'json') {
|
||||
this.deserializeFromJSON(component, serializedData.data);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const protoName = getProtoName(component);
|
||||
if (!protoName) {
|
||||
this.deserializeFromJSON(component, serializedData.data);
|
||||
return;
|
||||
}
|
||||
|
||||
const MessageType = this.getMessageType(protoName);
|
||||
if (!MessageType) {
|
||||
console.warn(`[ProtobufSerializer] 反序列化时未找到消息类型: ${protoName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解码消息
|
||||
const message = MessageType.decode(serializedData.data as Uint8Array);
|
||||
const data = MessageType.toObject(message);
|
||||
|
||||
// 应用数据到组件
|
||||
this.applyDataToComponent(component, data);
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[ProtobufSerializer] 反序列化失败: ${component.constructor.name}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否支持protobuf序列化
|
||||
*/
|
||||
public canSerialize(component: Component): boolean {
|
||||
if (!this.protobuf) return false;
|
||||
return isProtoSerializable(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取序列化统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
registeredComponents: number;
|
||||
protobufAvailable: boolean;
|
||||
} {
|
||||
return {
|
||||
registeredComponents: this.registry.getAllComponents().size,
|
||||
protobufAvailable: !!this.protobuf
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建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);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换值到protobuf类型
|
||||
*/
|
||||
private convertValueToProtoType(value: any, fieldDef: ProtoFieldDefinition): any {
|
||||
if (fieldDef.repeated && Array.isArray(value)) {
|
||||
return value.map(v => this.convertSingleValue(v, fieldDef.type));
|
||||
}
|
||||
|
||||
return this.convertSingleValue(value, fieldDef.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换单个值
|
||||
*/
|
||||
private convertSingleValue(value: any, type: ProtoFieldType): any {
|
||||
switch (type) {
|
||||
case ProtoFieldType.INT32:
|
||||
case ProtoFieldType.UINT32:
|
||||
case ProtoFieldType.SINT32:
|
||||
case ProtoFieldType.FIXED32:
|
||||
case ProtoFieldType.SFIXED32:
|
||||
return parseInt(value) || 0;
|
||||
|
||||
case ProtoFieldType.FLOAT:
|
||||
case ProtoFieldType.DOUBLE:
|
||||
return parseFloat(value) || 0;
|
||||
|
||||
case ProtoFieldType.BOOL:
|
||||
return Boolean(value);
|
||||
|
||||
case ProtoFieldType.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建protobuf定义
|
||||
*/
|
||||
private buildProtoDefinitions(): void {
|
||||
if (!this.protobuf) return;
|
||||
|
||||
try {
|
||||
const protoDefinition = this.registry.generateProtoDefinition();
|
||||
this.root = this.protobuf.parse(protoDefinition).root;
|
||||
} catch (error) {
|
||||
console.error('[ProtobufSerializer] 构建protobuf定义失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息类型
|
||||
*/
|
||||
private getMessageType(typeName: string): any {
|
||||
if (!this.root) return null;
|
||||
|
||||
try {
|
||||
return this.root.lookupType(`ecs.${typeName}`);
|
||||
} catch (error) {
|
||||
console.warn(`[ProtobufSerializer] 未找到消息类型: ecs.${typeName}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
371
src/Utils/Serialization/StaticProtobufSerializer.ts
Normal file
371
src/Utils/Serialization/StaticProtobufSerializer.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* 静态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;
|
||||
}
|
||||
}
|
||||
7
src/Utils/Serialization/index.ts
Normal file
7
src/Utils/Serialization/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 序列化模块导出
|
||||
*/
|
||||
|
||||
export * from './ProtobufDecorators';
|
||||
export * from './ProtobufSerializer';
|
||||
export * from './StaticProtobufSerializer';
|
||||
Reference in New Issue
Block a user