Files
esengine/packages/network/src/Serialization/TsrpcSerializer.ts

179 lines
5.9 KiB
TypeScript
Raw Normal View History

2025-08-09 18:56:19 +08:00
import { Component, createLogger } from '@esengine/ecs-framework';
import { SerializedData } from './SerializationTypes';
import {
TsrpcComponentMetadata,
TsrpcFieldMetadata,
TsrpcSerializationStats,
TsrpcSerializable
} from './TsrpcTypes';
import {
TsrpcRegistry,
isTsrpcSerializable,
getTsrpcMetadata,
validateTsrpcComponent
} from './TsrpcDecorators';
const logger = createLogger('TsrpcSerializer');
export class TsrpcSerializer {
private static _instance: TsrpcSerializer | null = null;
private _registry: TsrpcRegistry;
private _stats: TsrpcSerializationStats;
constructor() {
this._registry = TsrpcRegistry.getInstance();
this._stats = {
serializeCount: 0,
deserializeCount: 0,
totalSerializeTime: 0,
totalDeserializeTime: 0,
averageSerializedSize: 0,
errorCount: 0,
cacheHits: 0,
cacheMisses: 0
};
}
public static getInstance(): TsrpcSerializer {
if (!TsrpcSerializer._instance) {
TsrpcSerializer._instance = new TsrpcSerializer();
}
return TsrpcSerializer._instance;
}
public serialize(component: Component): SerializedData | null {
if (!isTsrpcSerializable(component)) return null;
const metadata = getTsrpcMetadata(component.constructor);
if (!metadata) return null;
try {
const data = this.extractSerializableData(component, metadata);
const jsonString = JSON.stringify(data);
const serialized = new TextEncoder().encode(jsonString);
this._stats.serializeCount++;
this._stats.averageSerializedSize =
(this._stats.averageSerializedSize * (this._stats.serializeCount - 1) + serialized.length)
/ this._stats.serializeCount;
return {
type: 'tsrpc',
componentType: metadata.componentType,
data: serialized,
size: serialized.length,
schema: metadata.componentType,
version: metadata.version
};
} catch (error) {
this._stats.errorCount++;
return null;
}
}
public deserialize<T extends Component>(
serializedData: SerializedData,
ComponentClass?: new (...args: any[]) => T
): T | null {
if (serializedData.type !== 'tsrpc') return null;
let metadata: TsrpcComponentMetadata | undefined;
if (ComponentClass) {
metadata = getTsrpcMetadata(ComponentClass);
} else {
metadata = this._registry.getByName(serializedData.componentType);
}
if (!metadata) return null;
try {
const jsonString = new TextDecoder().decode(serializedData.data as Uint8Array);
const data = JSON.parse(jsonString);
const component = new metadata.constructor();
this.applySerializableData(component as T, data, metadata);
this._stats.deserializeCount++;
return component as T;
} catch (error) {
this._stats.errorCount++;
return null;
}
}
private extractSerializableData(component: Component, metadata: TsrpcComponentMetadata): any {
const data: any = {};
for (const [fieldName, fieldMetadata] of metadata.fields) {
const value = (component as any)[fieldName];
if (value !== undefined || fieldMetadata.typeInfo.isOptional) {
data[fieldName] = this.processFieldValue(value, fieldMetadata);
}
}
return data;
}
private applySerializableData(component: Component, data: any, metadata: TsrpcComponentMetadata): void {
for (const [fieldName, fieldMetadata] of metadata.fields) {
if (fieldName in data) {
const value = this.processFieldValue(data[fieldName], fieldMetadata, true);
(component as any)[fieldName] = value;
}
}
if (this.isTsrpcSerializableInstance(component)) {
component.applyTsrpcData(data);
}
}
private processFieldValue(value: any, fieldMetadata: TsrpcFieldMetadata, isDeserializing = false): any {
if (value === null || value === undefined) return value;
const { typeInfo } = fieldMetadata;
if (['boolean', 'number', 'string'].includes(typeInfo.typeName)) {
return value;
}
if (typeInfo.typeName === 'date') {
return isDeserializing ? new Date(value) : value.toISOString();
}
if (typeInfo.isArray && Array.isArray(value)) {
return value.map(item => this.processFieldValue(item, fieldMetadata, isDeserializing));
}
if (typeInfo.typeName === 'object' && typeof value === 'object') {
return isDeserializing ? structuredClone(value) : value;
}
return value;
}
private isTsrpcSerializableInstance(component: any): component is TsrpcSerializable {
return typeof component.getTsrpcData === 'function' &&
typeof component.applyTsrpcData === 'function';
}
public getStats(): TsrpcSerializationStats {
return { ...this._stats };
}
public resetStats(): void {
this._stats = {
serializeCount: 0,
deserializeCount: 0,
totalSerializeTime: 0,
totalDeserializeTime: 0,
averageSerializedSize: 0,
errorCount: 0,
cacheHits: 0,
cacheMisses: 0
};
}
public getSupportedTypes(): string[] {
return this._registry.getAllComponents().map(comp => comp.componentType);
}
}
export const tsrpcSerializer = TsrpcSerializer.getInstance();