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,2 +1,6 @@
|
||||
export { AudioSourceComponent } from './AudioSourceComponent';
|
||||
export { AudioPlugin } from './AudioPlugin';
|
||||
|
||||
// Service Tokens (reserved for future use)
|
||||
// 服务令牌(预留用于未来扩展)
|
||||
// export { AudioManagerToken, type IAudioManager } from './tokens';
|
||||
|
||||
31
packages/audio/src/tokens.ts
Normal file
31
packages/audio/src/tokens.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Audio Module Service Tokens
|
||||
* 音频模块服务令牌
|
||||
*
|
||||
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||
* Following "who defines interface, who exports Token" principle.
|
||||
*
|
||||
* 当前模块仅提供组件,暂无服务定义。
|
||||
* 此文件预留用于未来可能添加的 AudioManager 服务。
|
||||
*
|
||||
* Currently this module only provides components, no services defined yet.
|
||||
* This file is reserved for potential future AudioManager service.
|
||||
*/
|
||||
|
||||
// import { createServiceToken } from '@esengine/engine-core';
|
||||
|
||||
// ============================================================================
|
||||
// Reserved for future service tokens
|
||||
// 预留用于未来的服务令牌
|
||||
// ============================================================================
|
||||
|
||||
// export interface IAudioManager {
|
||||
// // 播放音效 | Play sound effect
|
||||
// playSound(path: string): void;
|
||||
// // 播放背景音乐 | Play background music
|
||||
// playMusic(path: string): void;
|
||||
// // 停止所有音频 | Stop all audio
|
||||
// stopAll(): void;
|
||||
// }
|
||||
|
||||
// export const AudioManagerToken = createServiceToken<IAudioManager>('audioManager');
|
||||
@@ -1,2 +1,6 @@
|
||||
export { CameraComponent, ECameraProjection, CameraProjection } from './CameraComponent';
|
||||
export { CameraPlugin } from './CameraPlugin';
|
||||
|
||||
// Service Tokens (reserved for future use)
|
||||
// 服务令牌(预留用于未来扩展)
|
||||
// export { CameraManagerToken, type ICameraManager } from './tokens';
|
||||
|
||||
31
packages/camera/src/tokens.ts
Normal file
31
packages/camera/src/tokens.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Camera Module Service Tokens
|
||||
* 相机模块服务令牌
|
||||
*
|
||||
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||
* Following "who defines interface, who exports Token" principle.
|
||||
*
|
||||
* 当前模块仅提供组件,暂无服务定义。
|
||||
* 此文件预留用于未来可能添加的 CameraManager 服务。
|
||||
*
|
||||
* Currently this module only provides components, no services defined yet.
|
||||
* This file is reserved for potential future CameraManager service.
|
||||
*/
|
||||
|
||||
// import { createServiceToken } from '@esengine/engine-core';
|
||||
|
||||
// ============================================================================
|
||||
// Reserved for future service tokens
|
||||
// 预留用于未来的服务令牌
|
||||
// ============================================================================
|
||||
|
||||
// export interface ICameraManager {
|
||||
// // 获取主相机 | Get main camera
|
||||
// getMainCamera(): CameraComponent | null;
|
||||
// // 设置主相机 | Set main camera
|
||||
// setMainCamera(camera: CameraComponent): void;
|
||||
// // 屏幕坐标转世界坐标 | Screen to world coordinates
|
||||
// screenToWorld(screenX: number, screenY: number): { x: number; y: number };
|
||||
// }
|
||||
|
||||
// export const CameraManagerToken = createServiceToken<ICameraManager>('cameraManager');
|
||||
@@ -11,6 +11,7 @@ import { SceneManager } from './ECS/SceneManager';
|
||||
import { IScene } from './ECS/IScene';
|
||||
import { ServiceContainer } from './Core/ServiceContainer';
|
||||
import { PluginManager } from './Core/PluginManager';
|
||||
import { PluginServiceRegistry } from './Core/PluginServiceRegistry';
|
||||
import { IPlugin } from './Core/Plugin';
|
||||
import { WorldManager } from './ECS/WorldManager';
|
||||
import { DebugConfigService } from './Utils/Debug/DebugConfigService';
|
||||
@@ -109,6 +110,14 @@ export class Core {
|
||||
*/
|
||||
private _pluginManager: PluginManager;
|
||||
|
||||
/**
|
||||
* 插件服务注册表
|
||||
*
|
||||
* 基于 ServiceToken 的类型安全服务注册表。
|
||||
* Type-safe service registry based on ServiceToken.
|
||||
*/
|
||||
private _pluginServiceRegistry: PluginServiceRegistry;
|
||||
|
||||
/**
|
||||
* Core配置
|
||||
*/
|
||||
@@ -168,6 +177,11 @@ export class Core {
|
||||
this._pluginManager.initialize(this, this._serviceContainer);
|
||||
this._serviceContainer.registerInstance(PluginManager, this._pluginManager);
|
||||
|
||||
// 初始化插件服务注册表
|
||||
// Initialize plugin service registry
|
||||
this._pluginServiceRegistry = new PluginServiceRegistry();
|
||||
this._serviceContainer.registerInstance(PluginServiceRegistry, this._pluginServiceRegistry);
|
||||
|
||||
this.debug = this._config.debug ?? true;
|
||||
|
||||
// 初始化调试管理器
|
||||
@@ -220,6 +234,39 @@ export class Core {
|
||||
return this._instance._serviceContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件服务注册表
|
||||
*
|
||||
* 用于基于 ServiceToken 的类型安全服务注册和获取。
|
||||
* For type-safe service registration and retrieval based on ServiceToken.
|
||||
*
|
||||
* @returns PluginServiceRegistry 实例
|
||||
* @throws 如果 Core 实例未创建
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createServiceToken } from '@esengine/ecs-framework';
|
||||
*
|
||||
* // 定义服务令牌
|
||||
* const MyServiceToken = createServiceToken<IMyService>('myService');
|
||||
*
|
||||
* // 注册服务
|
||||
* Core.pluginServices.register(MyServiceToken, myServiceInstance);
|
||||
*
|
||||
* // 获取服务(可选)
|
||||
* const service = Core.pluginServices.get(MyServiceToken);
|
||||
*
|
||||
* // 获取服务(必需,不存在则抛异常)
|
||||
* const service = Core.pluginServices.require(MyServiceToken);
|
||||
* ```
|
||||
*/
|
||||
public static get pluginServices(): PluginServiceRegistry {
|
||||
if (!this._instance) {
|
||||
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||
}
|
||||
return this._instance._pluginServiceRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取World管理器
|
||||
*
|
||||
|
||||
135
packages/core/src/Core/PluginServiceRegistry.ts
Normal file
135
packages/core/src/Core/PluginServiceRegistry.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 插件服务注册表
|
||||
* Plugin Service Registry
|
||||
*
|
||||
* 基于 ServiceToken 的类型安全服务注册表。
|
||||
* Type-safe service registry based on ServiceToken.
|
||||
*
|
||||
* 设计原则 | Design principles:
|
||||
* 1. 类型安全 - 使用 ServiceToken 携带类型信息
|
||||
* 2. 显式依赖 - 通过导入 token 明确表达依赖关系
|
||||
* 3. 可选依赖 - get 返回 undefined,require 抛异常
|
||||
* 4. 单一职责 - 只负责服务注册和查询,不涉及生命周期管理
|
||||
* 5. 谁定义接口,谁导出 Token - 各模块定义自己的接口和 Token
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// 服务令牌 | Service Token
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 服务令牌接口
|
||||
* Service token interface
|
||||
*
|
||||
* 用于类型安全的服务注册和获取。
|
||||
* For type-safe service registration and retrieval.
|
||||
*
|
||||
* 注意:__phantom 是必需属性,确保 TypeScript 在跨包类型解析时保留泛型类型信息。
|
||||
* Note: __phantom is a required property to ensure TypeScript preserves generic
|
||||
* type information across packages.
|
||||
*/
|
||||
export interface ServiceToken<T> {
|
||||
readonly id: symbol;
|
||||
readonly name: string;
|
||||
/**
|
||||
* Phantom type 标记(强制类型推断)
|
||||
* Phantom type marker (enforces type inference)
|
||||
*/
|
||||
readonly __phantom: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建服务令牌
|
||||
* Create a service token
|
||||
*
|
||||
* @param name 令牌名称 | Token name
|
||||
* @returns 服务令牌 | Service token
|
||||
*/
|
||||
export function createServiceToken<T>(name: string): ServiceToken<T> {
|
||||
// __phantom 仅用于类型推断,运行时不需要实际值
|
||||
// __phantom is only for type inference, no actual value needed at runtime
|
||||
return {
|
||||
id: Symbol(name),
|
||||
name
|
||||
} as ServiceToken<T>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 插件服务注册表 | Plugin Service Registry
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 插件服务注册表
|
||||
* Plugin service registry
|
||||
*
|
||||
* 用于跨插件共享服务的类型安全注册表。
|
||||
* Type-safe registry for sharing services between plugins.
|
||||
*/
|
||||
export class PluginServiceRegistry {
|
||||
private _services = new Map<symbol, unknown>();
|
||||
|
||||
/**
|
||||
* 注册服务
|
||||
* Register a service
|
||||
*/
|
||||
register<T>(token: ServiceToken<T>, service: T): void {
|
||||
this._services.set(token.id, service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务(可选)
|
||||
* Get a service (optional)
|
||||
*/
|
||||
get<T>(token: ServiceToken<T>): T | undefined {
|
||||
return this._services.get(token.id) as T | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务(必需)
|
||||
* Get a service (required)
|
||||
*
|
||||
* @throws 如果服务未注册 | If service is not registered
|
||||
*/
|
||||
require<T>(token: ServiceToken<T>): T {
|
||||
const service = this._services.get(token.id);
|
||||
if (service === undefined) {
|
||||
throw new Error(`Service not found: ${token.name}`);
|
||||
}
|
||||
return service as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查服务是否已注册
|
||||
* Check if a service is registered
|
||||
*/
|
||||
has<T>(token: ServiceToken<T>): boolean {
|
||||
return this._services.has(token.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销服务
|
||||
* Unregister a service
|
||||
*/
|
||||
unregister<T>(token: ServiceToken<T>): boolean {
|
||||
return this._services.delete(token.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有服务
|
||||
* Clear all services
|
||||
*/
|
||||
clear(): void {
|
||||
this._services.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
* Dispose resources
|
||||
*
|
||||
* 实现 IService 接口,在服务容器清理时调用。
|
||||
* Implements IService interface, called when service container is cleaned up.
|
||||
*/
|
||||
dispose(): void {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@ export { Core } from './Core';
|
||||
export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer';
|
||||
export type { IService, ServiceType, ServiceIdentifier } from './Core/ServiceContainer';
|
||||
|
||||
// 插件服务注册表(基于 ServiceToken 的类型安全服务管理)
|
||||
// Plugin Service Registry (type-safe service management based on ServiceToken)
|
||||
export { PluginServiceRegistry, createServiceToken } from './Core/PluginServiceRegistry';
|
||||
export type { ServiceToken } from './Core/PluginServiceRegistry';
|
||||
|
||||
// 插件系统
|
||||
export { PluginManager } from './Core/PluginManager';
|
||||
export { PluginState } from './Core/Plugin';
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as ReactDOM from 'react-dom';
|
||||
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
||||
import { Core, createLogger, Scene } from '@esengine/ecs-framework';
|
||||
import * as ECSFramework from '@esengine/ecs-framework';
|
||||
import { getProfilerService } from './services/getService';
|
||||
|
||||
// 将 React 暴露到全局,供动态加载的插件使用
|
||||
// editor-runtime.js 将 React 设为 external,需要从全局获取
|
||||
@@ -207,14 +208,15 @@ function App() {
|
||||
}, [messageHub, showToast]);
|
||||
|
||||
// 监听远程连接状态
|
||||
// Monitor remote connection status
|
||||
useEffect(() => {
|
||||
const checkConnection = () => {
|
||||
const profilerService = (window as any).__PROFILER_SERVICE__;
|
||||
const connected = profilerService && profilerService.isConnected();
|
||||
const profilerService = getProfilerService();
|
||||
const connected = !!(profilerService && profilerService.isConnected());
|
||||
|
||||
setIsRemoteConnected((prevConnected) => {
|
||||
if (connected !== prevConnected) {
|
||||
// 状态发生变化
|
||||
// 状态发生变化 | State has changed
|
||||
if (connected) {
|
||||
setStatus(t('header.status.remoteConnected'));
|
||||
} else {
|
||||
@@ -246,7 +248,8 @@ function App() {
|
||||
initRef.current = true;
|
||||
|
||||
try {
|
||||
(window as any).__ECS_FRAMEWORK__ = ECSFramework;
|
||||
// ECS Framework 已通过 PluginSDKRegistry 暴露到全局
|
||||
// ECS Framework is exposed globally via PluginSDKRegistry
|
||||
|
||||
const editorScene = new Scene();
|
||||
Core.setScene(editorScene);
|
||||
@@ -775,7 +778,7 @@ function App() {
|
||||
const Component = panelDesc.component;
|
||||
return {
|
||||
id: panelDesc.id,
|
||||
title: (panelDesc as any).titleZh && locale === 'zh' ? (panelDesc as any).titleZh : panelDesc.title,
|
||||
title: panelDesc.titleZh && locale === 'zh' ? panelDesc.titleZh : panelDesc.title,
|
||||
content: <Component key={`${panelDesc.id}-${pluginUpdateTrigger}`} projectPath={currentProjectPath} />,
|
||||
closable: panelDesc.closable ?? true
|
||||
};
|
||||
@@ -791,7 +794,7 @@ function App() {
|
||||
const panelDesc = uiRegistry.getPanel(panelId)!;
|
||||
// 优先使用动态标题,否则使用默认标题
|
||||
const customTitle = dynamicPanelTitles.get(panelId);
|
||||
const defaultTitle = (panelDesc as any).titleZh && locale === 'zh' ? (panelDesc as any).titleZh : panelDesc.title;
|
||||
const defaultTitle = panelDesc.titleZh && locale === 'zh' ? panelDesc.titleZh : panelDesc.title;
|
||||
|
||||
// 支持 component 或 render 两种方式
|
||||
let content: React.ReactNode;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { useMemo } from 'react';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
|
||||
type LucideIconName = keyof typeof LucideIcons;
|
||||
|
||||
/**
|
||||
* 动态获取 Lucide 图标组件
|
||||
* Dynamically get Lucide icon component by name
|
||||
*
|
||||
* @param iconName - 图标名称(如 'Package', 'Settings')
|
||||
* @param fallback - 找不到时的回退组件
|
||||
* @returns Lucide 图标组件
|
||||
*/
|
||||
export function useDynamicIcon(iconName?: string, fallback?: React.ComponentType) {
|
||||
return useMemo(() => {
|
||||
if (!iconName) {
|
||||
return fallback || LucideIcons.Package;
|
||||
}
|
||||
|
||||
// 动态图标查找需要使用 any,因为 lucide-react 的类型定义不支持动态索引
|
||||
// Dynamic icon lookup requires any, as lucide-react types don't support dynamic indexing
|
||||
const IconComponent = (LucideIcons as any)[iconName];
|
||||
return IconComponent || fallback || LucideIcons.Package;
|
||||
}, [iconName, fallback]);
|
||||
|
||||
@@ -1,23 +1,52 @@
|
||||
/**
|
||||
* ProfilerService Hook
|
||||
*
|
||||
* 通过 ServiceToken 获取 ProfilerService 实例。
|
||||
* Get ProfilerService instance via ServiceToken.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { ProfilerServiceToken, type IProfilerService } from '../services/tokens';
|
||||
|
||||
export interface ProfilerService {
|
||||
connect(port: number): void;
|
||||
disconnect(): void;
|
||||
isConnected(): boolean;
|
||||
requestEntityList(): void;
|
||||
requestEntityDetails(entityId: number): void;
|
||||
}
|
||||
|
||||
export function useProfilerService(): ProfilerService | undefined {
|
||||
const [service, setService] = useState<ProfilerService | undefined>(() => {
|
||||
return (window as any).__PROFILER_SERVICE__;
|
||||
/**
|
||||
* 获取 ProfilerService 实例的 Hook
|
||||
* Hook to get ProfilerService instance
|
||||
*
|
||||
* 使用 ServiceToken 从 Core.pluginServices 获取服务,
|
||||
* 提供类型安全的服务访问。
|
||||
*
|
||||
* Uses ServiceToken to get service from Core.pluginServices,
|
||||
* providing type-safe service access.
|
||||
*
|
||||
* @returns ProfilerService 实例,如果未注册则返回 undefined
|
||||
*/
|
||||
export function useProfilerService(): IProfilerService | undefined {
|
||||
const [service, setService] = useState<IProfilerService | undefined>(() => {
|
||||
try {
|
||||
return Core.pluginServices.get(ProfilerServiceToken);
|
||||
} catch {
|
||||
// Core 可能还没有初始化
|
||||
// Core might not be initialized yet
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// 定期检查服务是否可用(处理服务延迟注册的情况)
|
||||
// Periodically check if service is available (handles delayed service registration)
|
||||
const checkService = () => {
|
||||
const newService = (window as any).__PROFILER_SERVICE__;
|
||||
if (newService !== service) {
|
||||
setService(newService);
|
||||
try {
|
||||
const newService = Core.pluginServices.get(ProfilerServiceToken);
|
||||
if (newService !== service) {
|
||||
setService(newService);
|
||||
}
|
||||
} catch {
|
||||
// Core 可能还没有初始化
|
||||
// Core might not be initialized yet
|
||||
if (service !== undefined) {
|
||||
setService(undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IPlugin,
|
||||
IEditorModuleLoader,
|
||||
@@ -12,6 +13,7 @@ import type {
|
||||
} from '@esengine/editor-core';
|
||||
import { MessageHub, SettingsRegistry } from '@esengine/editor-core';
|
||||
import { ProfilerService } from '../../services/ProfilerService';
|
||||
import { ProfilerServiceToken } from '../../services/tokens';
|
||||
|
||||
/**
|
||||
* Profiler 编辑器模块
|
||||
@@ -87,15 +89,21 @@ class ProfilerEditorModule implements IEditorModuleLoader {
|
||||
});
|
||||
|
||||
this.profilerService = new ProfilerService();
|
||||
(window as any).__PROFILER_SERVICE__ = this.profilerService;
|
||||
|
||||
// 使用 ServiceToken 注册服务(类型安全)
|
||||
// Register service using ServiceToken (type-safe)
|
||||
Core.pluginServices.register(ProfilerServiceToken, this.profilerService);
|
||||
}
|
||||
|
||||
async uninstall(): Promise<void> {
|
||||
// 从服务注册表注销
|
||||
// Unregister from service registry
|
||||
Core.pluginServices.unregister(ProfilerServiceToken);
|
||||
|
||||
if (this.profilerService) {
|
||||
this.profilerService.destroy();
|
||||
this.profilerService = null;
|
||||
}
|
||||
delete (window as any).__PROFILER_SERVICE__;
|
||||
}
|
||||
|
||||
getMenuItems(): MenuItemDescriptor[] {
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { SettingsService } from './SettingsService';
|
||||
import { LogLevel } from '@esengine/ecs-framework';
|
||||
|
||||
export interface SystemPerformanceData {
|
||||
name: string;
|
||||
executionTime: number;
|
||||
entityCount: number;
|
||||
averageTime: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export interface RemoteEntity {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
active: boolean;
|
||||
activeInHierarchy: boolean;
|
||||
componentCount: number;
|
||||
componentTypes: string[];
|
||||
parentId: number | null;
|
||||
childIds: number[];
|
||||
depth: number;
|
||||
tag: number;
|
||||
updateOrder: number;
|
||||
}
|
||||
import type {
|
||||
IProfilerService,
|
||||
ProfilerData,
|
||||
SystemPerformanceData,
|
||||
RemoteEntity,
|
||||
AdvancedProfilerDataPayload
|
||||
} from './tokens';
|
||||
|
||||
export interface RemoteComponentDetail {
|
||||
typeName: string;
|
||||
@@ -45,29 +29,10 @@ export interface RemoteEntityDetails {
|
||||
parentName: string | null;
|
||||
}
|
||||
|
||||
export interface ProfilerData {
|
||||
totalFrameTime: number;
|
||||
systems: SystemPerformanceData[];
|
||||
entityCount: number;
|
||||
componentCount: number;
|
||||
fps: number;
|
||||
entities?: RemoteEntity[];
|
||||
}
|
||||
|
||||
type ProfilerDataListener = (data: ProfilerData) => void;
|
||||
|
||||
/**
|
||||
* 高级性能数据结构(用于高级性能分析器)
|
||||
*/
|
||||
export interface AdvancedProfilerDataPayload {
|
||||
advancedProfiler?: any;
|
||||
performance?: any;
|
||||
systems?: any;
|
||||
}
|
||||
|
||||
type AdvancedProfilerDataListener = (data: AdvancedProfilerDataPayload) => void;
|
||||
|
||||
export class ProfilerService {
|
||||
export class ProfilerService implements IProfilerService {
|
||||
private ws: WebSocket | null = null;
|
||||
private isServerRunning = false;
|
||||
private wsPort: number;
|
||||
|
||||
36
packages/editor-app/src/services/getService.ts
Normal file
36
packages/editor-app/src/services/getService.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 服务获取辅助函数
|
||||
* Service getter helper functions
|
||||
*
|
||||
* 提供类型安全的服务获取,避免直接访问全局变量。
|
||||
* Provides type-safe service access, avoiding direct global variable access.
|
||||
*/
|
||||
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import type { ServiceToken } from '@esengine/engine-core';
|
||||
import { ProfilerServiceToken, type IProfilerService } from './tokens';
|
||||
|
||||
/**
|
||||
* 安全获取插件服务
|
||||
* Safely get plugin service
|
||||
*
|
||||
* 在 Core 未初始化时返回 undefined 而非抛出异常。
|
||||
* Returns undefined instead of throwing when Core is not initialized.
|
||||
*/
|
||||
export function getPluginService<T>(token: ServiceToken<T>): T | undefined {
|
||||
try {
|
||||
return Core.pluginServices.get(token);
|
||||
} catch {
|
||||
// Core 可能还没有初始化
|
||||
// Core might not be initialized yet
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ProfilerService 实例
|
||||
* Get ProfilerService instance
|
||||
*/
|
||||
export function getProfilerService(): IProfilerService | undefined {
|
||||
return getPluginService(ProfilerServiceToken);
|
||||
}
|
||||
114
packages/editor-app/src/services/tokens.ts
Normal file
114
packages/editor-app/src/services/tokens.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 编辑器服务令牌
|
||||
* Editor Service Tokens
|
||||
*
|
||||
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||
* 这些服务定义在 editor-app 中,所以 Token 也在这里定义。
|
||||
*
|
||||
* Following "who defines interface, who exports Token" principle.
|
||||
* These services are defined in editor-app, so Tokens are also defined here.
|
||||
*/
|
||||
|
||||
import { createServiceToken } from '@esengine/engine-core';
|
||||
|
||||
// ============================================================================
|
||||
// Profiler Data Types (定义在这里以避免循环依赖)
|
||||
// ============================================================================
|
||||
|
||||
export interface SystemPerformanceData {
|
||||
name: string;
|
||||
executionTime: number;
|
||||
entityCount: number;
|
||||
averageTime: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export interface RemoteEntity {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
active: boolean;
|
||||
activeInHierarchy: boolean;
|
||||
componentCount: number;
|
||||
componentTypes: string[];
|
||||
parentId: number | null;
|
||||
childIds: number[];
|
||||
depth: number;
|
||||
tag: number;
|
||||
updateOrder: number;
|
||||
}
|
||||
|
||||
export interface ProfilerData {
|
||||
totalFrameTime: number;
|
||||
systems: SystemPerformanceData[];
|
||||
entityCount: number;
|
||||
componentCount: number;
|
||||
fps: number;
|
||||
entities?: RemoteEntity[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 高级性能数据结构(用于高级性能分析器)
|
||||
* Advanced profiler data structure
|
||||
*/
|
||||
export interface AdvancedProfilerDataPayload {
|
||||
advancedProfiler?: any;
|
||||
performance?: any;
|
||||
systems?: any;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Profiler Service Token
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* ProfilerService 接口(用于类型检查)
|
||||
* ProfilerService interface (for type checking)
|
||||
*
|
||||
* 提供远程性能分析功能,包括:
|
||||
* - WebSocket 连接管理
|
||||
* - 性能数据收集和分发
|
||||
* - 远程日志接收
|
||||
*
|
||||
* Provides remote profiling capabilities including:
|
||||
* - WebSocket connection management
|
||||
* - Performance data collection and distribution
|
||||
* - Remote log reception
|
||||
*/
|
||||
export interface IProfilerService {
|
||||
/** 检查是否已连接 | Check if connected */
|
||||
isConnected(): boolean;
|
||||
|
||||
/** 检查服务器是否运行 | Check if server is running */
|
||||
isServerActive(): boolean;
|
||||
|
||||
/** 手动启动服务器 | Manually start server */
|
||||
manualStartServer(): Promise<void>;
|
||||
|
||||
/** 手动停止服务器 | Manually stop server */
|
||||
manualStopServer(): Promise<void>;
|
||||
|
||||
/** 订阅数据更新 | Subscribe to data updates */
|
||||
subscribe(callback: (data: ProfilerData) => void): () => void;
|
||||
|
||||
/** 订阅高级数据更新 | Subscribe to advanced data updates */
|
||||
subscribeAdvanced(callback: (data: AdvancedProfilerDataPayload) => void): () => void;
|
||||
|
||||
/** 请求实体详情 | Request entity details */
|
||||
requestEntityDetails(entityId: number): void;
|
||||
|
||||
/** 请求高级性能分析数据 | Request advanced profiler data */
|
||||
requestAdvancedProfilerData(): void;
|
||||
|
||||
/** 设置选中的函数 | Set selected function */
|
||||
setProfilerSelectedFunction(functionName: string | null): void;
|
||||
|
||||
/** 销毁服务 | Destroy service */
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfilerService 的服务令牌
|
||||
* Service token for ProfilerService
|
||||
*/
|
||||
export const ProfilerServiceToken = createServiceToken<IProfilerService>('profilerService');
|
||||
@@ -47,6 +47,8 @@ export interface PanelDescriptor {
|
||||
id: string;
|
||||
/** 面板标题 | Panel title */
|
||||
title: string;
|
||||
/** 面板中文标题 | Panel title in Chinese */
|
||||
titleZh?: string;
|
||||
/** 面板图标 | Panel icon */
|
||||
icon?: string;
|
||||
/** 面板位置 | Panel position */
|
||||
|
||||
@@ -54,3 +54,8 @@ export type { IShaderAssetData, ShaderFileFormat } from './loaders/ShaderLoader'
|
||||
// 运行时模块。
|
||||
export { MaterialRuntimeModule, materialRuntimeModule, MaterialSystemPlugin } from './MaterialSystemPlugin';
|
||||
export type { IMaterialRuntimeModule } from './MaterialSystemPlugin';
|
||||
|
||||
// Service Tokens.
|
||||
// 服务令牌。
|
||||
export { MaterialManagerToken } from './tokens';
|
||||
export type { IMaterialManager } from './tokens';
|
||||
|
||||
174
packages/material-system/src/tokens.ts
Normal file
174
packages/material-system/src/tokens.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Material System Service Tokens
|
||||
* 材质系统服务令牌
|
||||
*
|
||||
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||
* Following "who defines interface, who exports Token" principle.
|
||||
*/
|
||||
|
||||
import { createServiceToken } from '@esengine/engine-core';
|
||||
import type { Material } from './Material';
|
||||
import type { Shader } from './Shader';
|
||||
import type { IEngineBridge } from './MaterialManager';
|
||||
import type { IAssetManager } from '@esengine/asset-system';
|
||||
|
||||
// ============================================================================
|
||||
// Material Manager Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* MaterialManager 接口
|
||||
* MaterialManager interface
|
||||
*
|
||||
* 提供材质和着色器管理功能。
|
||||
* Provides material and shader management functionality.
|
||||
*/
|
||||
export interface IMaterialManager {
|
||||
// ========== Initialization | 初始化 ==========
|
||||
|
||||
/**
|
||||
* 设置引擎桥接
|
||||
* Set engine bridge for Rust communication
|
||||
*/
|
||||
setEngineBridge(bridge: IEngineBridge): void;
|
||||
|
||||
/**
|
||||
* 设置资产管理器
|
||||
* Set asset manager for loading assets
|
||||
*/
|
||||
setAssetManager(assetManager: IAssetManager): void;
|
||||
|
||||
/**
|
||||
* 初始化内置材质
|
||||
* Initialize built-in materials
|
||||
*/
|
||||
initializeBuiltInMaterials(): Promise<void>;
|
||||
|
||||
// ========== Shader Management | 着色器管理 ==========
|
||||
|
||||
/**
|
||||
* 注册着色器
|
||||
* Register a shader
|
||||
*/
|
||||
registerShader(shader: Shader): Promise<number>;
|
||||
|
||||
/**
|
||||
* 通过 ID 获取着色器
|
||||
* Get shader by ID
|
||||
*/
|
||||
getShader(id: number): Shader | undefined;
|
||||
|
||||
/**
|
||||
* 通过名称获取着色器
|
||||
* Get shader by name
|
||||
*/
|
||||
getShaderByName(name: string): Shader | undefined;
|
||||
|
||||
/**
|
||||
* 移除着色器
|
||||
* Remove a shader
|
||||
*/
|
||||
removeShader(id: number): boolean;
|
||||
|
||||
/**
|
||||
* 从路径加载着色器
|
||||
* Load shader from path
|
||||
*/
|
||||
loadShaderByPath(path: string): Promise<number>;
|
||||
|
||||
// ========== Material Management | 材质管理 ==========
|
||||
|
||||
/**
|
||||
* 注册材质
|
||||
* Register a material
|
||||
*/
|
||||
registerMaterial(material: Material): Promise<number>;
|
||||
|
||||
/**
|
||||
* 通过 ID 获取材质
|
||||
* Get material by ID
|
||||
*/
|
||||
getMaterial(id: number): Material | undefined;
|
||||
|
||||
/**
|
||||
* 通过名称获取材质
|
||||
* Get material by name
|
||||
*/
|
||||
getMaterialByName(name: string): Material | undefined;
|
||||
|
||||
/**
|
||||
* 移除材质
|
||||
* Remove a material
|
||||
*/
|
||||
removeMaterial(id: number): boolean;
|
||||
|
||||
/**
|
||||
* 从路径加载材质
|
||||
* Load material from path
|
||||
*/
|
||||
loadMaterialByPath(path: string): Promise<number>;
|
||||
|
||||
/**
|
||||
* 克隆材质
|
||||
* Clone a material
|
||||
*/
|
||||
cloneMaterial(materialId: number, newName?: string): Promise<Material | null>;
|
||||
|
||||
// ========== Built-in Materials | 内置材质 ==========
|
||||
|
||||
/**
|
||||
* 获取默认材质 ID
|
||||
* Get default material ID
|
||||
*/
|
||||
getDefaultMaterialId(): number;
|
||||
|
||||
/**
|
||||
* 获取灰度材质 ID
|
||||
* Get grayscale material ID
|
||||
*/
|
||||
getGrayscaleMaterialId(): number;
|
||||
|
||||
/**
|
||||
* 获取着色材质 ID
|
||||
* Get tint material ID
|
||||
*/
|
||||
getTintMaterialId(): number;
|
||||
|
||||
/**
|
||||
* 获取闪烁材质 ID
|
||||
* Get flash material ID
|
||||
*/
|
||||
getFlashMaterialId(): number;
|
||||
|
||||
/**
|
||||
* 获取轮廓材质 ID
|
||||
* Get outline material ID
|
||||
*/
|
||||
getOutlineMaterialId(): number;
|
||||
|
||||
// ========== Uniform Management | Uniform 管理 ==========
|
||||
|
||||
/**
|
||||
* 设置材质 uniform 值
|
||||
* Set material uniform value
|
||||
*/
|
||||
setMaterialUniform(materialId: number, name: string, value: any): boolean;
|
||||
|
||||
// ========== Lifecycle | 生命周期 ==========
|
||||
|
||||
/**
|
||||
* 销毁管理器,释放所有资源
|
||||
* Destroy manager and release all resources
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Token
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* MaterialManager 服务令牌
|
||||
* MaterialManager service token
|
||||
*/
|
||||
export const MaterialManagerToken = createServiceToken<IMaterialManager>('materialManager');
|
||||
@@ -22,9 +22,11 @@ import {
|
||||
Physics2DSystemToken,
|
||||
Physics2DWorldToken,
|
||||
PhysicsConfigToken,
|
||||
CollisionLayerConfigToken,
|
||||
type IPhysics2DQuery,
|
||||
type PhysicsConfig
|
||||
} from './tokens';
|
||||
import { CollisionLayerConfig } from './services/CollisionLayerConfig';
|
||||
|
||||
// 注册 Rapier2D 加载器
|
||||
import './loaders';
|
||||
@@ -35,6 +37,7 @@ export {
|
||||
Physics2DSystemToken,
|
||||
Physics2DWorldToken,
|
||||
PhysicsConfigToken,
|
||||
CollisionLayerConfigToken,
|
||||
type IPhysics2DQuery,
|
||||
type PhysicsConfig
|
||||
} from './tokens';
|
||||
@@ -144,6 +147,7 @@ class PhysicsRuntimeModule implements IRuntimeModule {
|
||||
context.services.register(Physics2DSystemToken, physicsSystem);
|
||||
context.services.register(Physics2DWorldToken, physicsSystem.world);
|
||||
context.services.register(Physics2DQueryToken, physicsSystem);
|
||||
context.services.register(CollisionLayerConfigToken, CollisionLayerConfig.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,9 @@ export {
|
||||
Physics2DSystemToken,
|
||||
Physics2DWorldToken,
|
||||
PhysicsConfigToken,
|
||||
CollisionLayerConfigToken,
|
||||
type IPhysics2DQuery,
|
||||
type IPhysics2DWorld,
|
||||
type ICollisionLayerConfig,
|
||||
type PhysicsConfig
|
||||
} from './tokens';
|
||||
|
||||
@@ -136,6 +136,33 @@ export interface PhysicsConfig {
|
||||
timestep?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 碰撞层配置接口
|
||||
* Collision layer config interface
|
||||
*
|
||||
* 跨模块共享的碰撞层配置契约。
|
||||
* Cross-module shared collision layer config contract.
|
||||
*/
|
||||
export interface ICollisionLayerConfig {
|
||||
/**
|
||||
* 获取所有层定义
|
||||
* Get all layer definitions
|
||||
*/
|
||||
getLayers(): ReadonlyArray<{ name: string }>;
|
||||
|
||||
/**
|
||||
* 添加监听器
|
||||
* Add listener
|
||||
*/
|
||||
addListener(callback: () => void): void;
|
||||
|
||||
/**
|
||||
* 移除监听器
|
||||
* Remove listener
|
||||
*/
|
||||
removeListener(callback: () => void): void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 服务令牌 | Service Tokens
|
||||
// ============================================================================
|
||||
@@ -175,3 +202,12 @@ export const Physics2DSystemToken = createServiceToken<Physics2DSystem>('physics
|
||||
* For passing physics configuration (gravity, timestep, etc.).
|
||||
*/
|
||||
export const PhysicsConfigToken = createServiceToken<PhysicsConfig>('physicsConfig');
|
||||
|
||||
/**
|
||||
* 碰撞层配置令牌
|
||||
* Collision layer config token
|
||||
*
|
||||
* 用于获取碰撞层配置服务。
|
||||
* For getting collision layer config service.
|
||||
*/
|
||||
export const CollisionLayerConfigToken = createServiceToken<ICollisionLayerConfig>('collisionLayerConfig');
|
||||
|
||||
Reference in New Issue
Block a user