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

@@ -0,0 +1,47 @@
{
"name": "@esengine/plugin-types",
"version": "1.0.0",
"description": "Plugin system type definitions for ES Engine",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"build:watch": "tsup --watch",
"clean": "rimraf dist",
"type-check": "tsc --noEmit"
},
"keywords": [
"ecs",
"plugin",
"types",
"typescript"
],
"author": "yhh",
"license": "MIT",
"devDependencies": {
"@esengine/ecs-framework": "workspace:*",
"@esengine/build-config": "workspace:*",
"rimraf": "^5.0.0",
"tsup": "^8.0.0",
"typescript": "^5.8.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/esengine/ecs-framework.git",
"directory": "packages/plugin-types"
}
}

View File

@@ -0,0 +1,202 @@
/**
* 插件系统基础类型定义
* Plugin system base type definitions
*
* 这个包只提供用于打破循环依赖的基础接口。
* 完整的类型定义在 engine-core 中。
*
* This package only provides base interfaces to break circular dependencies.
* Complete type definitions are in engine-core.
*
* 导出内容 | Exports:
* - ServiceToken / createServiceToken / PluginServiceRegistry: 服务令牌系统
* - IEditorModuleBase: 编辑器模块基础接口(用于 IPlugin.editorModule 类型)
*
* 不导出(在 engine-core 中定义)| Not exported (defined in engine-core):
* - IPlugin, IRuntimeModule, SystemContext: 完整的插件类型
* - ModuleManifest: 完整的模块清单类型
* - LoadingPhase: 加载阶段类型
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
// ============================================================================
// 服务令牌 | Service Token
// ============================================================================
/**
* 服务令牌接口
* Service token interface
*
* 用于类型安全的服务注册和获取。
* For type-safe service registration and retrieval.
*
* 注意__phantom 是必需属性(使用 declare 避免运行时开销),
* 这确保 TypeScript 在跨包类型解析时保留泛型类型信息。
*
* Note: __phantom is a required property (using declare to avoid runtime overhead),
* which ensures 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)
*
* 使用 declare 声明,不会在运行时占用内存。
* Declared with 'declare', no runtime memory overhead.
*/
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
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();
}
}
// ============================================================================
// 编辑器模块基础接口 | Editor Module Base Interface
// ============================================================================
/**
* 编辑器模块基础接口
* Base editor module interface
*
* 定义编辑器模块的核心生命周期方法。
* 这个接口用于 IPlugin.editorModule 的类型定义,避免 engine-core 依赖 editor-core。
* 完整的 IEditorModuleLoader 接口在 editor-core 中扩展此接口。
*
* Defines core lifecycle methods for editor modules.
* This interface is used for IPlugin.editorModule type to avoid engine-core depending on editor-core.
* Full IEditorModuleLoader interface extends this in editor-core.
*/
export interface IEditorModuleBase {
/**
* 安装编辑器模块
* Install editor module
*/
install(services: ServiceContainer): Promise<void>;
/**
* 卸载编辑器模块
* Uninstall editor module
*/
uninstall?(): Promise<void>;
/**
* 编辑器就绪回调
* Editor ready callback
*/
onEditorReady?(): void | Promise<void>;
/**
* 项目打开回调
* Project open callback
*/
onProjectOpen?(projectPath: string): void | Promise<void>;
/**
* 项目关闭回调
* Project close callback
*/
onProjectClose?(): void | Promise<void>;
/**
* 场景加载回调
* Scene loaded callback
*/
onSceneLoaded?(scenePath: string): void;
/**
* 场景保存前回调
* Before scene save callback
*/
onSceneSaving?(scenePath: string): boolean | void;
/**
* 设置语言
* Set locale
*/
setLocale?(locale: string): void;
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,14 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
tsconfig: 'tsconfig.build.json',
external: [
'@esengine/ecs-framework'
]
});