refactor(plugin): 重构插件系统架构,统一类型导入路径 (#296)

* refactor(plugin): 重构插件系统架构,统一类型导入路径

## 主要更改

### 新增 @esengine/plugin-types 包
- 提供打破循环依赖的最小类型定义
- 包含 ServiceToken, createServiceToken, PluginServiceRegistry, IEditorModuleBase

### engine-core 类型统一
- IPlugin<T> 泛型参数改为 T = unknown
- 所有运行时类型(IRuntimeModule, ModuleManifest, SystemContext)在此定义
- 新增 PluginServiceRegistry.ts 导出服务令牌相关类型

### editor-core 类型优化
- 重命名 IPluginLoader.ts 为 EditorModule.ts
- 新增 IEditorPlugin = IPlugin<IEditorModuleLoader> 类型别名
- 移除弃用别名 IPluginLoader, IRuntimeModuleLoader

### 编辑器插件更新
- 所有 9 个编辑器插件使用 IEditorPlugin 类型
- 统一从 editor-core 导入编辑器类型

### 服务令牌规范
- 各模块在 tokens.ts 中定义自己的服务接口和令牌
- 遵循"谁定义接口,谁导出 Token"原则

## 导入规范

| 场景 | 导入来源 |
|------|---------|
| 运行时模块 | @esengine/engine-core |
| 编辑器插件 | @esengine/editor-core |
| 服务令牌 | @esengine/engine-core |

* refactor(plugin): 完善服务令牌规范,统一运行时模块

## 更改内容

### 运行时模块优化
- particle: 使用 PluginServiceRegistry 获取依赖服务
- physics-rapier2d: 通过服务令牌注册/获取物理查询接口
- tilemap: 使用服务令牌获取物理系统依赖
- sprite: 导出服务令牌
- ui: 导出服务令牌
- behavior-tree: 使用服务令牌系统

### 资产系统增强
- IAssetManager 接口扩展
- 加载器使用服务令牌获取依赖

### 运行时核心
- GameRuntime 使用 PluginServiceRegistry
- 导出服务令牌相关类型

### 编辑器服务
- EngineService 适配新的服务令牌系统
- AssetRegistryService 优化

* fix: 修复 editor-app 和 behavior-tree-editor 中的类型引用

- editor-app/PluginLoader.ts: 使用 IPlugin 替代 IPluginLoader
- behavior-tree-editor: 使用 IEditorPlugin 替代 IPluginLoader

* fix(ui): 添加缺失的 ecs-engine-bindgen 依赖

UIRuntimeModule 使用 EngineBridgeToken,需要声明对 ecs-engine-bindgen 的依赖

* fix(type): 解决 ServiceToken 跨包类型兼容性问题

- 在 engine-core 中直接定义 ServiceToken 和 PluginServiceRegistry
  而不是从 plugin-types 重新导出,确保 tsup 生成的类型声明
  以 engine-core 作为类型来源
- 移除 RuntimeResolver.ts 中的硬编码模块 ID 检查,
  改用 module.json 中的 name 配置
- 修复 pnpm-lock.yaml 中的依赖记录

* refactor(arch): 改进架构设计,移除硬编码

- 统一类型导出:editor-core 从 engine-core 导入 IEditorModuleBase
- RuntimeResolver: 将硬编码路径改为配置常量和搜索路径列表
- 添加跨平台安装路径支持(Windows/macOS/Linux)
- 使用 ENGINE_WASM_CONFIG 配置引擎 WASM 文件信息
- IBundlePackOptions 添加 preloadBundles 配置项

* fix(particle): 添加缺失的 ecs-engine-bindgen 依赖

ParticleRuntimeModule 导入了 @esengine/ecs-engine-bindgen 的 tokens,
但 package.json 中未声明该依赖,导致 CI 构建失败。

* fix(physics-rapier2d): 移除不存在的 PhysicsSystemContext 导出

PhysicsRuntimeModule 中不存在该类型,导致 type-check 失败
This commit is contained in:
YHH
2025-12-08 21:10:57 +08:00
committed by GitHub
parent 2476379af1
commit c3b7250f85
86 changed files with 1813 additions and 551 deletions

View File

@@ -31,6 +31,7 @@
"license": "MIT",
"devDependencies": {
"@esengine/ecs-framework": "workspace:*",
"@esengine/engine-core": "workspace:*",
"@esengine/build-config": "workspace:*",
"rimraf": "^5.0.0",
"tsup": "^8.0.0",

View File

@@ -204,6 +204,12 @@ export interface IBundlePackOptions {
groupByType?: boolean;
/** Include asset names in bundle | 在包中包含资产名称 */
includeNames?: boolean;
/**
* 需要预加载的包名列表 | List of bundle names to preload
* 如果未指定,默认预加载 'core' 和 'main' 包
* If not specified, defaults to preloading 'core' and 'main' bundles
*/
preloadBundles?: string[];
}
/**

View File

@@ -10,6 +10,9 @@
* For editor-side functionality (meta files, packing), use @esengine/asset-system-editor
*/
// Service tokens (谁定义接口,谁导出 Token)
export { AssetManagerToken, type IAssetManager } from './tokens';
// Types
export * from './types/AssetTypes';

View File

@@ -15,8 +15,9 @@ import {
IAssetLoadProgress,
IAssetCatalog
} from '../types/AssetTypes';
import { IAssetLoader } from './IAssetLoader';
import { IAssetLoader, IAssetLoaderFactory } from './IAssetLoader';
import { IAssetReader } from './IAssetReader';
import type { AssetDatabase } from '../core/AssetDatabase';
/**
* Asset manager interface
@@ -167,6 +168,24 @@ export interface IAssetManager {
* 从目录加载资产元数据,用于运行时资产解析。
*/
initializeFromCatalog(catalog: IAssetCatalog): void;
/**
* Get the asset database
* 获取资产数据库
*/
getDatabase(): AssetDatabase;
/**
* Get the loader factory
* 获取加载器工厂
*/
getLoaderFactory(): IAssetLoaderFactory;
/**
* Set project root path
* 设置项目根路径
*/
setProjectRoot(path: string): void;
}
/**

View File

@@ -27,7 +27,14 @@ export class AudioLoader implements IAssetLoader<IAudioAsset> {
*/
private static getAudioContext(): AudioContext {
if (!AudioLoader._audioContext) {
AudioLoader._audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
// 兼容旧版 Safari 的 webkitAudioContext
// Support legacy Safari webkitAudioContext
const AudioContextClass = window.AudioContext ||
(window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
if (!AudioContextClass) {
throw new Error('AudioContext is not supported in this browser');
}
AudioLoader._audioContext = new AudioContextClass();
}
return AudioLoader._audioContext;
}

View File

@@ -38,6 +38,8 @@ export class BinaryLoader implements IAssetLoader<IBinaryAsset> {
* 释放已加载的资产
*/
dispose(asset: IBinaryAsset): void {
(asset as any).data = null;
// 释放二进制数据引用以允许垃圾回收
// Release binary data reference to allow garbage collection
(asset as { data: ArrayBuffer | null }).data = null;
}
}

View File

@@ -35,6 +35,7 @@ export class JsonLoader implements IAssetLoader<IJsonAsset> {
* 释放已加载的资产
*/
dispose(asset: IJsonAsset): void {
(asset as any).data = null;
// 清空 JSON 数据 | Clear JSON data
asset.data = null;
}
}

View File

@@ -50,6 +50,7 @@ export class TextLoader implements IAssetLoader<ITextAsset> {
* 释放已加载的资产
*/
dispose(asset: ITextAsset): void {
(asset as any).content = '';
// 清空文本内容 | Clear text content
asset.content = '';
}
}

View File

@@ -7,6 +7,26 @@ import { AssetType } from '../types/AssetTypes';
import { IAssetLoader, ITextureAsset, IAssetParseContext } from '../interfaces/IAssetLoader';
import { IAssetContent, AssetContentType } from '../interfaces/IAssetReader';
/**
* 全局引擎桥接接口(运行时挂载到 window
* Global engine bridge interface (mounted to window at runtime)
*/
interface IEngineBridgeGlobal {
loadTexture?(textureId: number, path: string): Promise<void>;
unloadTexture?(textureId: number): void;
}
/**
* 获取全局引擎桥接
* Get global engine bridge
*/
function getEngineBridge(): IEngineBridgeGlobal | undefined {
if (typeof window !== 'undefined' && 'engineBridge' in window) {
return (window as Window & { engineBridge?: IEngineBridgeGlobal }).engineBridge;
}
return undefined;
}
/**
* Texture loader implementation
* 纹理加载器实现
@@ -39,35 +59,23 @@ export class TextureLoader implements IAssetLoader<ITextureAsset> {
};
// Upload to GPU if bridge exists.
if (typeof window !== 'undefined' && (window as any).engineBridge) {
await this.uploadToGPU(textureAsset, context.metadata.path);
const bridge = getEngineBridge();
if (bridge?.loadTexture) {
await bridge.loadTexture(textureAsset.textureId, context.metadata.path);
}
return textureAsset;
}
/**
* Upload texture to GPU
* 上传纹理到GPU
*/
private async uploadToGPU(textureAsset: ITextureAsset, path: string): Promise<void> {
const bridge = (window as any).engineBridge;
if (bridge && bridge.loadTexture) {
await bridge.loadTexture(textureAsset.textureId, path);
}
}
/**
* Dispose loaded asset
* 释放已加载的资产
*/
dispose(asset: ITextureAsset): void {
// Release GPU resources.
if (typeof window !== 'undefined' && (window as any).engineBridge) {
const bridge = (window as any).engineBridge;
if (bridge.unloadTexture) {
bridge.unloadTexture(asset.textureId);
}
const bridge = getEngineBridge();
if (bridge?.unloadTexture) {
bridge.unloadTexture(asset.textureId);
}
// Clean up image data.

View File

@@ -0,0 +1,32 @@
/**
* Asset System 服务令牌
* Asset System service tokens
*
* 定义 asset-system 模块导出的服务令牌和接口。
* Defines service tokens and interfaces exported by asset-system module.
*
* @example
* ```typescript
* // 消费方导入 Token | Consumer imports Token
* import { AssetManagerToken, type IAssetManager } from '@esengine/asset-system';
*
* // 获取服务 | Get service
* const assetManager = context.services.get(AssetManagerToken);
* ```
*/
import { createServiceToken } from '@esengine/engine-core';
import type { IAssetManager } from './interfaces/IAssetManager';
// 重新导出接口方便使用 | Re-export interface for convenience
export type { IAssetManager } from './interfaces/IAssetManager';
export type { IAssetLoadResult } from './types/AssetTypes';
/**
* 资产管理器服务令牌
* Asset manager service token
*
* 用于注册和获取资产管理器服务。
* For registering and getting asset manager service.
*/
export const AssetManagerToken = createServiceToken<IAssetManager>('assetManager');