refactor(render): 抽象图形后端并迁移渲染器 (#313)

* refactor(render): 抽象图形后端并迁移渲染器

- 新增 engine-shared 包,定义 GraphicsBackend trait 抽象层
- 实现 WebGL2Backend 作为首个后端实现
- 迁移 Renderer2D、SpriteBatch、GridRenderer、GizmoRenderer 使用新抽象
- 修复 VAO 创建时索引缓冲区绑定状态泄漏问题
- 新增 create_vertex_buffer_sized 方法支持预分配缓冲区

* fix(serialization): 修复序列化循环引用导致栈溢出

- 在 serializeValue 添加 WeakSet 检测循环引用
- 跳过已访问对象避免无限递归

* refactor(serialization): 提取 ValueSerializer 统一序列化逻辑

- 新增 ValueSerializer 模块,函数式设计
- 支持可扩展类型处理器注册
- 移除 ComponentSerializer/SceneSerializer 重复代码
- 内置 Date/Map/Set 类型支持

* fix: CodeQL 类型检查警告
This commit is contained in:
YHH
2025-12-19 22:46:33 +08:00
committed by GitHub
parent 4b74db3f2d
commit 96b5403d14
31 changed files with 6096 additions and 2114 deletions

View File

@@ -1,363 +1,130 @@
/**
* 组件序列化器
*
* 负责组件的序列化和反序列化操作
* Component serializer for ECS components.
*/
import { Component } from '../Component';
import { ComponentType } from '../Core/ComponentStorage';
import { getComponentTypeName, isEntityRefProperty } from '../Decorators';
import {
getSerializationMetadata
} from './SerializationDecorators';
import { getSerializationMetadata } from './SerializationDecorators';
import { ValueSerializer, SerializableValue } from './ValueSerializer';
import type { Entity } from '../Entity';
import type { SerializationContext, SerializedEntityRef } from './SerializationContext';
/**
* 可序列化的值类型
*/
export type SerializableValue =
| string
| number
| boolean
| null
| undefined
| SerializableValue[]
| { [key: string]: SerializableValue }
| { __type: 'Date'; value: string }
| { __type: 'Map'; value: Array<[SerializableValue, SerializableValue]> }
| { __type: 'Set'; value: SerializableValue[] }
| { __entityRef: SerializedEntityRef };
export type { SerializableValue } from './ValueSerializer';
/**
* 序列化后的组件数据
*/
export interface SerializedComponent {
/**
* 组件类型名称
*/
type: string;
/**
* 序列化版本
*/
version: number;
/**
* 组件数据
*/
data: Record<string, SerializableValue>;
}
/**
* 组件序列化器类
*/
export class ComponentSerializer {
/**
* 序列化单个组件
*
* @param component 要序列化的组件实例
* @returns 序列化后的组件数据如果组件不可序列化则返回null
*/
public static serialize(component: Component): SerializedComponent | null {
static serialize(component: Component): SerializedComponent | null {
const metadata = getSerializationMetadata(component);
if (!metadata) {
// 组件没有使用@Serializable装饰器不可序列化
return null;
}
if (!metadata) return null;
const componentType = component.constructor as ComponentType;
const typeName = metadata.options.typeId || getComponentTypeName(componentType);
const data: Record<string, SerializableValue> = {};
// 序列化标记的字段
for (const [fieldName, options] of metadata.fields) {
if (metadata.ignoredFields.has(fieldName)) continue;
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
const value = (component as unknown as Record<string | symbol, unknown>)[fieldName];
// 跳过忽略的字段
if (metadata.ignoredFields.has(fieldName)) {
continue;
}
let serializedValue: SerializableValue;
// 检查是否为 EntityRef 属性
if (isEntityRefProperty(component, fieldKey)) {
serializedValue = this.serializeEntityRef(value as Entity | null);
} else if (options.serializer) {
// 使用自定义序列化器
serializedValue = options.serializer(value);
} else {
// 使用默认序列化
serializedValue = this.serializeValue(value as SerializableValue);
serializedValue = ValueSerializer.serialize(value);
}
// 使用别名或原始字段名
const key = options.alias || fieldKey;
data[key] = serializedValue;
data[options.alias || fieldKey] = serializedValue;
}
return {
type: typeName,
version: metadata.options.version,
data
};
return { type: typeName, version: metadata.options.version, data };
}
/**
* 反序列化组件
*
* @param serializedData 序列化的组件数据
* @param componentRegistry 组件类型注册表 (类型名 -> 构造函数)
* @param context 序列化上下文(可选,用于解析 EntityRef
* @returns 反序列化后的组件实例如果失败则返回null
*/
public static deserialize(
static deserialize(
serializedData: SerializedComponent,
componentRegistry: Map<string, ComponentType>,
context?: SerializationContext
): Component | null {
const componentClass = componentRegistry.get(serializedData.type);
if (!componentClass) {
console.warn(`未找到组件类型: ${serializedData.type}`);
console.warn(`Component type not found: ${serializedData.type}`);
return null;
}
const metadata = getSerializationMetadata(componentClass);
if (!metadata) {
console.warn(`组件 ${serializedData.type} 不可序列化`);
console.warn(`Component ${serializedData.type} is not serializable`);
return null;
}
// 创建组件实例
const component = new componentClass();
// 反序列化字段
for (const [fieldName, options] of metadata.fields) {
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
const key = options.alias || fieldKey;
const serializedValue = serializedData.data[key];
if (serializedValue === undefined) {
continue; // 字段不存在于序列化数据中
}
if (serializedValue === undefined) continue;
// 检查是否为序列化的 EntityRef
if (this.isSerializedEntityRef(serializedValue)) {
// EntityRef 需要延迟解析
if (context) {
const ref = serializedValue.__entityRef;
context.registerPendingRef(component, fieldKey, ref.id, ref.guid);
}
// 暂时设为 null后续由 context.resolveAllReferences() 填充
(component as unknown as Record<string | symbol, unknown>)[fieldName] = null;
continue;
}
// 使用自定义反序列化器或默认反序列化
const value = options.deserializer
? options.deserializer(serializedValue)
: this.deserializeValue(serializedValue);
: ValueSerializer.deserialize(serializedValue);
(component as unknown as Record<string | symbol, SerializableValue>)[fieldName] = value;
(component as unknown as Record<string | symbol, unknown>)[fieldName] = value;
}
return component;
}
/**
* 批量序列化组件
*
* @param components 组件数组
* @returns 序列化后的组件数据数组
*/
public static serializeComponents(components: Component[]): SerializedComponent[] {
const result: SerializedComponent[] = [];
for (const component of components) {
const serialized = this.serialize(component);
if (serialized) {
result.push(serialized);
}
}
return result;
static serializeComponents(components: Component[]): SerializedComponent[] {
return components
.map(c => this.serialize(c))
.filter((s): s is SerializedComponent => s !== null);
}
/**
* 批量反序列化组件
*
* @param serializedComponents 序列化的组件数据数组
* @param componentRegistry 组件类型注册表
* @param context 序列化上下文(可选,用于解析 EntityRef
* @returns 反序列化后的组件数组
*/
public static deserializeComponents(
static deserializeComponents(
serializedComponents: SerializedComponent[],
componentRegistry: Map<string, ComponentType>,
context?: SerializationContext
): Component[] {
const result: Component[] = [];
for (const serialized of serializedComponents) {
const component = this.deserialize(serialized, componentRegistry, context);
if (component) {
result.push(component);
}
}
return result;
return serializedComponents
.map(s => this.deserialize(s, componentRegistry, context))
.filter((c): c is Component => c !== null);
}
/**
* 默认值序列化
*
* 处理基本类型、数组、对象等的序列化
*/
private static serializeValue(value: SerializableValue): SerializableValue {
if (value === null || value === undefined) {
return value;
}
// 基本类型
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return value;
}
// 日期
if (value instanceof Date) {
return {
__type: 'Date',
value: value.toISOString()
};
}
// 数组
if (Array.isArray(value)) {
return value.map((item) => this.serializeValue(item));
}
// Map (如果没有使用@SerializeMap装饰器)
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 (type === 'object' && typeof value === 'object' && !Array.isArray(value)) {
const result: Record<string, SerializableValue> = {};
const obj = value as Record<string, SerializableValue>;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = this.serializeValue(obj[key]);
}
}
return result;
}
// 其他类型(函数等)不序列化
return undefined;
}
/**
* 默认值反序列化
*/
private static deserializeValue(value: SerializableValue): SerializableValue {
if (value === null || value === undefined) {
return value;
}
// 基本类型直接返回
const type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return value;
}
// 处理特殊类型标记
if (type === 'object' && typeof value === 'object' && '__type' in value) {
const typedValue = value as { __type: string; value: SerializableValue };
switch (typedValue.__type) {
case 'Date':
return { __type: 'Date', value: typeof typedValue.value === 'string' ? typedValue.value : String(typedValue.value) };
case 'Map':
return { __type: 'Map', value: typedValue.value as Array<[SerializableValue, SerializableValue]> };
case 'Set':
return { __type: 'Set', value: typedValue.value as SerializableValue[] };
}
}
// 数组
if (Array.isArray(value)) {
return value.map((item) => this.deserializeValue(item));
}
// 普通对象
if (type === 'object' && typeof value === 'object' && !Array.isArray(value)) {
const result: Record<string, SerializableValue> = {};
const obj = value as Record<string, SerializableValue>;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = this.deserializeValue(obj[key]);
}
}
return result;
}
return value;
}
/**
* 验证序列化数据的版本
*
* @param serializedData 序列化数据
* @param expectedVersion 期望的版本号
* @returns 版本是否匹配
*/
public static validateVersion(
serializedData: SerializedComponent,
expectedVersion: number
): boolean {
static validateVersion(serializedData: SerializedComponent, expectedVersion: number): boolean {
return serializedData.version === expectedVersion;
}
/**
* 获取组件的序列化信息
*
* @param component 组件实例或组件类
* @returns 序列化信息对象,包含类型名、版本、可序列化字段列表
*/
public static getSerializationInfo(component: Component | ComponentType): {
static getSerializationInfo(component: Component | ComponentType): {
type: string;
version: number;
fields: string[];
ignoredFields: string[];
isSerializable: boolean;
} | null {
} {
const metadata = getSerializationMetadata(component);
if (!metadata) {
return {
type: 'unknown',
version: 0,
fields: [],
ignoredFields: [],
isSerializable: false
};
return { type: 'unknown', version: 0, fields: [], ignoredFields: [], isSerializable: false };
}
const componentType = typeof component === 'function'
@@ -367,50 +134,18 @@ export class ComponentSerializer {
return {
type: metadata.options.typeId || getComponentTypeName(componentType),
version: metadata.options.version,
fields: Array.from(metadata.fields.keys()).map((k) =>
typeof k === 'symbol' ? k.toString() : k
),
ignoredFields: Array.from(metadata.ignoredFields).map((k) =>
typeof k === 'symbol' ? k.toString() : k
),
fields: Array.from(metadata.fields.keys()).map(k => typeof k === 'symbol' ? k.toString() : k),
ignoredFields: Array.from(metadata.ignoredFields).map(k => typeof k === 'symbol' ? k.toString() : k),
isSerializable: true
};
}
/**
* 序列化 Entity 引用
*
* Serialize an Entity reference to a portable format.
*
* @param entity Entity 实例或 null
* @returns 序列化的引用格式
*/
public static serializeEntityRef(entity: Entity | null): SerializableValue {
if (!entity) {
return null;
}
return {
__entityRef: {
id: entity.id,
guid: entity.persistentId
}
};
static serializeEntityRef(entity: Entity | null): SerializableValue {
if (!entity) return null;
return { __entityRef: { id: entity.id, guid: entity.persistentId } };
}
/**
* 检查值是否为序列化的 EntityRef
*
* Check if a value is a serialized EntityRef.
*
* @param value 要检查的值
* @returns 如果是 EntityRef 返回 true
*/
public static isSerializedEntityRef(value: unknown): value is { __entityRef: SerializedEntityRef } {
return (
typeof value === 'object' &&
value !== null &&
'__entityRef' in value
);
static isSerializedEntityRef(value: unknown): value is { __entityRef: SerializedEntityRef } {
return typeof value === 'object' && value !== null && '__entityRef' in value;
}
}

View File

@@ -14,6 +14,7 @@ import { BinarySerializer } from '../../Utils/BinarySerializer';
import { HierarchySystem } from '../Systems/HierarchySystem';
import { HierarchyComponent } from '../Components/HierarchyComponent';
import { SerializationContext } from './SerializationContext';
import { ValueSerializer, SerializableValue } from './ValueSerializer';
/**
* 场景序列化格式
@@ -387,131 +388,21 @@ export class SceneSerializer {
}
}
/**
* 序列化场景自定义数据
*
* 将 Map<string, any> 转换为普通对象
*/
private static serializeSceneData(sceneData: Map<string, any>): Record<string, any> {
const result: Record<string, any> = {};
private static serializeSceneData(sceneData: Map<string, unknown>): Record<string, unknown> {
const result: Record<string, unknown> = {};
for (const [key, value] of sceneData) {
result[key] = this.serializeValue(value);
result[key] = ValueSerializer.serialize(value);
}
return result;
}
/**
* 反序列化场景自定义数据
*
* 将普通对象还原为 Map<string, any>
*/
private static deserializeSceneData(
data: Record<string, any>,
targetMap: Map<string, any>
): void {
private static deserializeSceneData(data: Record<string, unknown>, targetMap: Map<string, unknown>): void {
targetMap.clear();
for (const [key, value] of Object.entries(data)) {
targetMap.set(key, this.deserializeValue(value));
targetMap.set(key, ValueSerializer.deserialize(value as SerializableValue));
}
}
/**
* 序列化单个值
*/
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;
}
/**
* 过滤要序列化的实体和组件
*/

View File

@@ -0,0 +1,113 @@
/**
* 值序列化器
*
* Value serializer with circular reference detection and extensible type handlers.
*/
export type PrimitiveValue = string | number | boolean | null | undefined;
export type SerializableValue =
| PrimitiveValue
| SerializableValue[]
| { readonly [key: string]: SerializableValue }
| { readonly __type: string; readonly value: unknown };
type Serializer<T> = (value: T, serialize: (v: unknown) => SerializableValue) => SerializableValue;
type Deserializer<T> = (data: { __type: string; value: unknown }) => T;
interface TypeDef<T = unknown> {
check: (value: unknown) => value is T;
serialize: Serializer<T>;
deserialize: Deserializer<T>;
}
const types = new Map<string, TypeDef>();
function registerType<T>(name: string, def: TypeDef<T>): void {
types.set(name, def as TypeDef);
}
// 内置类型
registerType<Date>('Date', {
check: (v): v is Date => v instanceof Date,
serialize: (v) => ({ __type: 'Date', value: v.toISOString() }),
deserialize: (d) => new Date(d.value as string)
});
registerType<Map<unknown, unknown>>('Map', {
check: (v): v is Map<unknown, unknown> => v instanceof Map,
serialize: (v, ser) => ({ __type: 'Map', value: [...v].map(([k, val]) => [ser(k), ser(val)]) }),
deserialize: (d) => new Map(d.value as Array<[unknown, unknown]>)
});
registerType<Set<unknown>>('Set', {
check: (v): v is Set<unknown> => v instanceof Set,
serialize: (v, ser) => ({ __type: 'Set', value: [...v].map(ser) }),
deserialize: (d) => new Set(d.value as unknown[])
});
function serialize(value: unknown, seen = new WeakSet<object>()): SerializableValue {
if (value == null) return value as null | undefined;
const t = typeof value;
if (t === 'string' || t === 'number' || t === 'boolean') return value as PrimitiveValue;
if (t === 'function') return undefined;
const obj = value as object;
if (seen.has(obj)) return undefined;
seen.add(obj);
for (const [, def] of types) {
if (def.check(value)) {
return def.serialize(value, (v) => serialize(v, seen));
}
}
if (Array.isArray(value)) {
return value.map((v) => serialize(v, seen));
}
const result: Record<string, SerializableValue> = {};
for (const k of Object.keys(value as object)) {
result[k] = serialize((value as Record<string, unknown>)[k], seen);
}
return result;
}
function deserialize(value: SerializableValue): unknown {
if (value == null) return value;
const t = typeof value;
if (t === 'string' || t === 'number' || t === 'boolean') return value;
if (isTypedValue(value)) {
const def = types.get(value.__type);
return def ? def.deserialize(value) : value;
}
if (Array.isArray(value)) {
return value.map(deserialize);
}
const result: Record<string, unknown> = {};
for (const k of Object.keys(value)) {
result[k] = deserialize((value as Record<string, SerializableValue>)[k]);
}
return result;
}
function isTypedValue(v: unknown): v is { __type: string; value: unknown } {
if (v === null || typeof v !== 'object') {
return false;
}
return '__type' in v;
}
export const ValueSerializer = {
serialize,
deserialize,
register: registerType
} as const;
export type { TypeDef as TypeHandler };
export type TypedValue = { readonly __type: string; readonly value: unknown };

View File

@@ -24,6 +24,10 @@ export type {
SerializationMetadata
} from './SerializationDecorators';
// 值序列化器
export { ValueSerializer } from './ValueSerializer';
export type { SerializableValue, TypeHandler, TypedValue } from './ValueSerializer';
// 组件序列化器
export { ComponentSerializer } from './ComponentSerializer';
export type { SerializedComponent } from './ComponentSerializer';