fix(sync): use GlobalComponentRegistry for network sync decoding (#392)
- Decoder.ts now uses GlobalComponentRegistry.getComponentType() instead of local registry - @sync decorator uses getComponentTypeName() to get @ECSComponent decorator name - @ECSComponent decorator updates SYNC_METADATA.typeId when defined - Removed deprecated registerSyncComponent/autoRegisterSyncComponent functions - Updated ComponentSync.ts in network package to use GlobalComponentRegistry - Updated tests to use correct @ECSComponent type names This ensures that components decorated with @ECSComponent are automatically available for network sync decoding without any manual registration.
This commit is contained in:
@@ -10,10 +10,16 @@ import { Int32 } from './Core/SoAStorage';
|
||||
* @en Components in ECS architecture should be pure data containers.
|
||||
* All game logic should be implemented in EntitySystem, not inside components.
|
||||
*
|
||||
* @zh **重要:所有 Component 子类都必须使用 @ECSComponent 装饰器!**
|
||||
* @zh 该装饰器用于注册组件类型名称,是序列化、网络同步等功能正常工作的前提。
|
||||
* @en **IMPORTANT: All Component subclasses MUST use the @ECSComponent decorator!**
|
||||
* @en This decorator registers the component type name, which is required for serialization, network sync, etc.
|
||||
*
|
||||
* @example
|
||||
* @zh 推荐做法:纯数据组件
|
||||
* @en Recommended: Pure data component
|
||||
* @zh 正确做法:使用 @ECSComponent 装饰器
|
||||
* @en Correct: Use @ECSComponent decorator
|
||||
* ```typescript
|
||||
* @ECSComponent('HealthComponent')
|
||||
* class HealthComponent extends Component {
|
||||
* public health: number = 100;
|
||||
* public maxHealth: number = 100;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type ComponentEditorOptions,
|
||||
type ComponentType
|
||||
} from '../Core/ComponentStorage/ComponentTypeUtils';
|
||||
import { SYNC_METADATA, type SyncMetadata } from '../Sync/types';
|
||||
|
||||
/**
|
||||
* 存储系统类型名称的Symbol键
|
||||
@@ -138,6 +139,14 @@ export function ECSComponent(typeName: string, options?: ComponentOptions) {
|
||||
metadata[COMPONENT_EDITOR_OPTIONS] = options.editor;
|
||||
}
|
||||
|
||||
// 更新 @sync 装饰器创建的 SYNC_METADATA.typeId(如果存在)
|
||||
// Update SYNC_METADATA.typeId created by @sync decorator (if exists)
|
||||
// Property decorators execute before class decorators, so @sync may have used constructor.name
|
||||
const syncMeta = (target as any)[SYNC_METADATA] as SyncMetadata | undefined;
|
||||
if (syncMeta) {
|
||||
syncMeta.typeId = typeName;
|
||||
}
|
||||
|
||||
// 自动注册到全局 ComponentRegistry,使组件可以通过名称查找
|
||||
// Auto-register to GlobalComponentRegistry, enabling lookup by name
|
||||
GlobalComponentRegistry.register(target);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import type { SyncType, SyncFieldMetadata, SyncMetadata } from './types';
|
||||
import { SYNC_METADATA, CHANGE_TRACKER } from './types';
|
||||
import { ChangeTracker } from './ChangeTracker';
|
||||
import { getComponentTypeName } from '../Core/ComponentStorage/ComponentTypeUtils';
|
||||
|
||||
/**
|
||||
* @zh 获取或创建组件的同步元数据
|
||||
@@ -31,8 +32,9 @@ function getOrCreateSyncMetadata(target: any): SyncMetadata {
|
||||
const inheritedMetadata: SyncMetadata | undefined = constructor[SYNC_METADATA];
|
||||
|
||||
// Create new metadata (copy from inherited if exists)
|
||||
// Use getComponentTypeName to get @ECSComponent decorator name, or fall back to constructor.name
|
||||
const metadata: SyncMetadata = {
|
||||
typeId: constructor.name,
|
||||
typeId: getComponentTypeName(constructor),
|
||||
fields: inheritedMetadata ? [...inheritedMetadata.fields] : [],
|
||||
fieldIndexMap: inheritedMetadata ? new Map(inheritedMetadata.fieldIndexMap) : new Map()
|
||||
};
|
||||
|
||||
@@ -12,39 +12,7 @@ import type { Scene } from '../../Scene';
|
||||
import type { SyncType, SyncMetadata } from '../types';
|
||||
import { SyncOperation, SYNC_METADATA } from '../types';
|
||||
import { BinaryReader } from './BinaryReader';
|
||||
|
||||
/**
|
||||
* @zh 组件类型注册表
|
||||
* @en Component type registry
|
||||
*/
|
||||
const componentRegistry = new Map<string, new () => Component>();
|
||||
|
||||
/**
|
||||
* @zh 注册组件类型
|
||||
* @en Register component type
|
||||
*
|
||||
* @param typeId - @zh 组件类型 ID @en Component type ID
|
||||
* @param componentClass - @zh 组件类 @en Component class
|
||||
*/
|
||||
export function registerSyncComponent<T extends Component>(
|
||||
typeId: string,
|
||||
componentClass: new () => T
|
||||
): void {
|
||||
componentRegistry.set(typeId, componentClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 从 @ECSComponent 装饰器自动注册
|
||||
* @en Auto-register from @ECSComponent decorator
|
||||
*
|
||||
* @param componentClass - @zh 组件类 @en Component class
|
||||
*/
|
||||
export function autoRegisterSyncComponent(componentClass: new () => Component): void {
|
||||
const metadata: SyncMetadata | undefined = (componentClass as any)[SYNC_METADATA];
|
||||
if (metadata) {
|
||||
componentRegistry.set(metadata.typeId, componentClass);
|
||||
}
|
||||
}
|
||||
import { GlobalComponentRegistry } from '../../Core/ComponentStorage/ComponentRegistry';
|
||||
|
||||
/**
|
||||
* @zh 解码字段值
|
||||
@@ -166,8 +134,8 @@ export function decodeEntity(
|
||||
const typeId = reader.readString();
|
||||
componentTypes.push(typeId);
|
||||
|
||||
// Find component class from registry
|
||||
const componentClass = componentRegistry.get(typeId);
|
||||
// Find component class from GlobalComponentRegistry
|
||||
const componentClass = GlobalComponentRegistry.getComponentType(typeId) as (new () => Component) | null;
|
||||
if (!componentClass) {
|
||||
console.warn(`Unknown component type: ${typeId}`);
|
||||
// Skip component data - we need to read it to advance the reader
|
||||
@@ -306,7 +274,7 @@ export function decodeSpawn(
|
||||
const typeId = reader.readString();
|
||||
componentTypes.push(typeId);
|
||||
|
||||
const componentClass = componentRegistry.get(typeId);
|
||||
const componentClass = GlobalComponentRegistry.getComponentType(typeId) as (new () => Component) | null;
|
||||
if (!componentClass) {
|
||||
console.warn(`Unknown component type: ${typeId}`);
|
||||
// Try to skip
|
||||
@@ -322,7 +290,7 @@ export function decodeSpawn(
|
||||
continue;
|
||||
}
|
||||
|
||||
const component = entity.addComponent(new componentClass());
|
||||
const component = entity.addComponent(new (componentClass as new () => Component)());
|
||||
decodeComponent(component, metadata, reader);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ export {
|
||||
|
||||
// Decoder
|
||||
export {
|
||||
registerSyncComponent,
|
||||
autoRegisterSyncComponent,
|
||||
decodeComponent,
|
||||
decodeEntity,
|
||||
decodeSnapshot,
|
||||
|
||||
Reference in New Issue
Block a user