refactor(arch): 改进 ServiceToken 设计,统一服务获取模式 (#300)

* refactor(arch): 移除全局变量,使用 ServiceToken 模式

- 创建 PluginServiceRegistry 类,提供类型安全的服务注册/获取
- 添加 ProfilerServiceToken 和 CollisionLayerConfigToken
- 重构所有 __PROFILER_SERVICE__ 全局变量访问为 getProfilerService()
- 重构 __PHYSICS_RAPIER2D__ 全局变量访问为 CollisionLayerConfigToken
- 在 Core 类添加 pluginServices 静态属性
- 添加 getService.ts 辅助模块简化服务获取

这是 ServiceToken 模式重构的第一阶段,移除了最常用的两个全局变量。
后续可继续应用到其他模块(Camera/Audio 等)。

* refactor(arch): 改进 ServiceToken 设计,移除重复常量

- tokens.ts: 从 engine-core 导入 createServiceToken(符合规范)
- tokens.ts: Token 使用接口 IProfilerService 而非具体类
- 移除 AssetPickerDialog 和 ContentBrowser 中重复的 MANAGED_ASSET_DIRECTORIES
- 统一从 editor-core 导入 MANAGED_ASSET_DIRECTORIES

* fix(type): 修复 IProfilerService 接口与实现类型不匹配

- 将 ProfilerData 等数据类型移到 tokens.ts 以避免循环依赖
- ProfilerService 显式实现 IProfilerService 接口
- 更新使用方使用 IProfilerService 接口类型而非具体类

* refactor(type): 移除类型重导出,改进类型安全

- 删除 ProfilerService.ts 中的类型重导出,消费方直接从 tokens.ts 导入
- PanelDescriptor 接口添加 titleZh 属性,移除 App.tsx 中的 as any
- 改进 useDynamicIcon.ts 的类型安全,使用正确的 Record 类型

* refactor(arch): 为模块添加 ServiceToken 支持

- Material System: 创建 tokens.ts,定义 IMaterialManager 接口和 MaterialManagerToken
- Audio: 创建预留 tokens.ts 文件,为未来 AudioManager 服务扩展做准备
- Camera: 创建预留 tokens.ts 文件,为未来 CameraManager 服务扩展做准备

遵循"谁定义接口,谁导出 Token"原则,统一服务访问模式
This commit is contained in:
YHH
2025-12-09 11:07:44 +08:00
committed by GitHub
parent c71a47f2b0
commit 995fa2d514
31 changed files with 1024 additions and 210 deletions

View File

@@ -4,18 +4,15 @@
*/
import React, { useState, useEffect, useRef, useCallback } from 'react';
/**
* 碰撞层配置接口(用于获取自定义层名称)
*/
interface CollisionLayerConfigAPI {
getLayers(): Array<{ name: string }>;
addListener(callback: () => void): void;
removeListener(callback: () => void): void;
}
import { Core } from '@esengine/ecs-framework';
import {
CollisionLayerConfigToken,
type ICollisionLayerConfig
} from '@esengine/physics-rapier2d';
/**
* 默认层名称(当 CollisionLayerConfig 不可用时使用)
* Default layer names (used when CollisionLayerConfig is unavailable)
*/
const DEFAULT_LAYER_NAMES = [
'Default', 'Player', 'Enemy', 'Projectile',
@@ -24,25 +21,18 @@ const DEFAULT_LAYER_NAMES = [
'Layer12', 'Layer13', 'Layer14', 'Layer15',
];
let cachedConfig: CollisionLayerConfigAPI | null = null;
/**
* 尝试获取 CollisionLayerConfig 实例
* Try to get CollisionLayerConfig instance
*/
function getCollisionConfig(): CollisionLayerConfigAPI | null {
if (cachedConfig) return cachedConfig;
function getCollisionConfig(): ICollisionLayerConfig | undefined {
try {
// 动态导入以避免循环依赖
const physicsModule = (window as any).__PHYSICS_RAPIER2D__;
if (physicsModule?.CollisionLayerConfig) {
cachedConfig = physicsModule.CollisionLayerConfig.getInstance();
return cachedConfig;
}
return Core.pluginServices.get(CollisionLayerConfigToken);
} catch {
// 忽略错误
// Core 可能还没有初始化
// Core might not be initialized yet
return undefined;
}
return null;
}
interface CollisionLayerFieldProps {

View File

@@ -1,4 +1,6 @@
import { Core } from '@esengine/ecs-framework';
import { ComponentData } from './types';
import { ProfilerServiceToken, type IProfilerService } from '../../services/tokens';
export function formatNumber(value: number, decimalPlaces: number): string {
if (decimalPlaces < 0) {
@@ -10,13 +12,21 @@ export function formatNumber(value: number, decimalPlaces: number): string {
return value.toFixed(decimalPlaces);
}
export interface ProfilerService {
requestEntityDetails(entityId: number): void;
subscribe(callback: () => void): () => void;
}
export function getProfilerService(): ProfilerService | undefined {
return (window as any).__PROFILER_SERVICE__;
/**
* 获取 ProfilerService 实例
* Get ProfilerService instance
*
* 使用 ServiceToken 从 Core.pluginServices 获取服务。
* Uses ServiceToken to get service from Core.pluginServices.
*/
export function getProfilerService(): IProfilerService | undefined {
try {
return Core.pluginServices.get(ProfilerServiceToken);
} catch {
// Core 可能还没有初始化
// Core might not be initialized yet
return undefined;
}
}
export function isComponentData(value: unknown): value is ComponentData {