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:
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { X, BarChart3, Maximize2, Minimize2 } from 'lucide-react';
|
||||
import { ProfilerService } from '../services/ProfilerService';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { ProfilerServiceToken, type IProfilerService } from '../services/tokens';
|
||||
import { AdvancedProfiler } from './AdvancedProfiler';
|
||||
import '../styles/ProfilerWindow.css';
|
||||
|
||||
@@ -8,19 +9,19 @@ interface AdvancedProfilerWindowProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface WindowWithProfiler extends Window {
|
||||
__PROFILER_SERVICE__?: ProfilerService;
|
||||
}
|
||||
|
||||
export function AdvancedProfilerWindow({ onClose }: AdvancedProfilerWindowProps) {
|
||||
const [profilerService, setProfilerService] = useState<ProfilerService | null>(null);
|
||||
const [profilerService, setProfilerService] = useState<IProfilerService | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const service = (window as WindowWithProfiler).__PROFILER_SERVICE__;
|
||||
if (service) {
|
||||
setProfilerService(service);
|
||||
try {
|
||||
const service = Core.pluginServices.get(ProfilerServiceToken);
|
||||
if (service) {
|
||||
setProfilerService(service);
|
||||
}
|
||||
} catch {
|
||||
// Core 可能还没有初始化
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -40,22 +40,13 @@ import {
|
||||
AlertTriangle
|
||||
} from 'lucide-react';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { MessageHub, FileActionRegistry, AssetRegistryService, type FileCreationTemplate } from '@esengine/editor-core';
|
||||
import { MessageHub, FileActionRegistry, AssetRegistryService, MANAGED_ASSET_DIRECTORIES, type FileCreationTemplate } from '@esengine/editor-core';
|
||||
import { TauriAPI, DirectoryEntry } from '../api/tauri';
|
||||
import { SettingsService } from '../services/SettingsService';
|
||||
import { ContextMenu, ContextMenuItem } from './ContextMenu';
|
||||
import { PromptDialog } from './PromptDialog';
|
||||
import '../styles/ContentBrowser.css';
|
||||
|
||||
/**
|
||||
* Directories managed by asset registry (GUID system)
|
||||
* 被资产注册表(GUID 系统)管理的目录
|
||||
*
|
||||
* Note: This is duplicated from AssetRegistryService to avoid build dependency issues.
|
||||
* Keep in sync with MANAGED_ASSET_DIRECTORIES in AssetRegistryService.ts
|
||||
*/
|
||||
const MANAGED_ASSET_DIRECTORIES = ['assets', 'scripts', 'scenes'] as const;
|
||||
|
||||
interface AssetItem {
|
||||
name: string;
|
||||
path: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { X, Server, WifiOff, Wifi } from 'lucide-react';
|
||||
import { SettingsService } from '../services/SettingsService';
|
||||
import { ProfilerService } from '../services/ProfilerService';
|
||||
import { getProfilerService } from '../services/getService';
|
||||
import '../styles/PortManager.css';
|
||||
|
||||
interface PortManagerProps {
|
||||
@@ -58,7 +58,7 @@ export function PortManager({ onClose }: PortManagerProps) {
|
||||
const handleStopServer = async () => {
|
||||
setIsStopping(true);
|
||||
try {
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
if (profilerService) {
|
||||
await profilerService.manualStopServer();
|
||||
setIsServerRunning(false);
|
||||
@@ -73,7 +73,7 @@ export function PortManager({ onClose }: PortManagerProps) {
|
||||
const handleStartServer = async () => {
|
||||
setIsStarting(true);
|
||||
try {
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
if (profilerService) {
|
||||
await profilerService.manualStartServer();
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Activity, Cpu, Layers, Package, Wifi, WifiOff, Maximize2, Pause, Play, BarChart3 } from 'lucide-react';
|
||||
import { ProfilerService, ProfilerData } from '../services/ProfilerService';
|
||||
import type { ProfilerData } from '../services/tokens';
|
||||
import { SettingsService } from '../services/SettingsService';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { MessageHub } from '@esengine/editor-core';
|
||||
import { getProfilerService } from '../services/getService';
|
||||
import '../styles/ProfilerDockPanel.css';
|
||||
|
||||
export function ProfilerDockPanel() {
|
||||
@@ -32,7 +33,7 @@ export function ProfilerDockPanel() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
|
||||
if (!profilerService) {
|
||||
console.warn('[ProfilerDockPanel] ProfilerService not available - plugin may be disabled');
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Core } from '@esengine/ecs-framework';
|
||||
import { Activity, BarChart3, Clock, Cpu, RefreshCw, Pause, Play, X, Wifi, WifiOff, Server, Search, Table2, TreePine } from 'lucide-react';
|
||||
import { ProfilerService } from '../services/ProfilerService';
|
||||
import { SettingsService } from '../services/SettingsService';
|
||||
import { getProfilerService } from '../services/getService';
|
||||
import '../styles/ProfilerWindow.css';
|
||||
|
||||
interface SystemPerformanceData {
|
||||
@@ -59,7 +60,7 @@ export function ProfilerWindow({ onClose }: ProfilerWindowProps) {
|
||||
|
||||
// Check ProfilerService connection status
|
||||
useEffect(() => {
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
|
||||
if (!profilerService) {
|
||||
return;
|
||||
@@ -186,7 +187,7 @@ export function ProfilerWindow({ onClose }: ProfilerWindowProps) {
|
||||
useEffect(() => {
|
||||
if (dataSource !== 'remote') return;
|
||||
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
|
||||
if (!profilerService) {
|
||||
console.warn('[ProfilerWindow] ProfilerService not available');
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
Eye, Star, Lock, Settings, Filter, Folder, Sun, Cloud, Mountain, Flag,
|
||||
SquareStack, FolderPlus
|
||||
} from 'lucide-react';
|
||||
import { ProfilerService, RemoteEntity } from '../services/ProfilerService';
|
||||
import type { RemoteEntity } from '../services/tokens';
|
||||
import { getProfilerService } from '../services/getService';
|
||||
import { confirm } from '@tauri-apps/plugin-dialog';
|
||||
import { CreateEntityCommand, DeleteEntityCommand, ReparentEntityCommand, DropPosition } from '../application/commands/entity';
|
||||
import '../styles/SceneHierarchy.css';
|
||||
@@ -264,7 +265,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager, isProf
|
||||
|
||||
// Subscribe to remote entity data from ProfilerService
|
||||
useEffect(() => {
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
|
||||
if (!profilerService) {
|
||||
return;
|
||||
@@ -444,7 +445,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager, isProf
|
||||
const handleRemoteEntityClick = (entity: RemoteEntity) => {
|
||||
setSelectedIds(new Set([entity.id]));
|
||||
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
||||
const profilerService = getProfilerService();
|
||||
if (profilerService) {
|
||||
profilerService.requestEntityDetails(entity.id);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { X, Search, Folder, FolderOpen, File, Image, FileText, Music, Video, Database, AlertTriangle } from 'lucide-react';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { ProjectService, AssetRegistryService } from '@esengine/editor-core';
|
||||
import { ProjectService, AssetRegistryService, MANAGED_ASSET_DIRECTORIES } from '@esengine/editor-core';
|
||||
import { TauriFileSystemService } from '../../services/TauriFileSystemService';
|
||||
import './AssetPickerDialog.css';
|
||||
|
||||
/**
|
||||
* Directories managed by asset registry (GUID system)
|
||||
* Only files in these directories can be selected
|
||||
*
|
||||
* Note: Keep in sync with MANAGED_ASSET_DIRECTORIES in AssetRegistryService.ts
|
||||
*/
|
||||
const MANAGED_ASSET_DIRECTORIES = ['assets', 'scripts', 'scenes'] as const;
|
||||
|
||||
interface AssetPickerDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user