Files
esengine/packages/engine/runtime-core/src/PluginManager.ts
YHH 155411e743 refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
2025-12-26 14:50:35 +08:00

602 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @zh 运行时插件管理器
* @en Runtime Plugin Manager
*
* @zh 提供插件生命周期管理的核心实现。
* @en Provides core implementation for plugin lifecycle management.
*
* @zh 设计原则 | Design principles:
* @en
* 1. 最小依赖 - 只依赖 ecs-framework 和 engine-core
* 2. 状态跟踪 - 详细的插件状态用于调试和 UI
* 3. 依赖验证 - 确保加载顺序正确
* 4. 错误隔离 - 单个插件失败不影响其他
*/
import { GlobalComponentRegistry, ServiceContainer, createLogger } from '@esengine/ecs-framework';
import type { IScene } from '@esengine/ecs-framework';
import type { IRuntimePlugin, IRuntimeModule, SystemContext, ModuleManifest } from '@esengine/engine-core';
import {
topologicalSort,
resolveDependencyId,
getReverseDependencies,
type IDependable
} from './utils/DependencyUtils';
import type { PluginState } from './PluginState';
export type { IRuntimePlugin, IRuntimeModule, SystemContext, ModuleManifest };
const logger = createLogger('PluginManager');
// ============================================================================
// 类型定义 | Type Definitions
// ============================================================================
// PluginState 从 ./PluginState 重新导出
// PluginState is re-exported from ./PluginState
/**
* @zh 已注册的插件信息
* @en Registered plugin info
*/
export interface RegisteredPluginInfo {
/**
* @zh 插件实例
* @en Plugin instance
*/
plugin: IRuntimePlugin;
/**
* @zh 插件状态
* @en Plugin state
*/
state: PluginState;
/**
* @zh 是否启用
* @en Whether enabled
*/
enabled: boolean;
/**
* @zh 错误信息
* @en Error message
*/
error?: Error;
/**
* @zh 注册时间
* @en Registration time
*/
registeredAt: number;
/**
* @zh 激活时间
* @en Activation time
*/
activatedAt?: number;
/**
* @zh 创建的系统实例(用于清理)
* @en Created system instances (for cleanup)
*/
systemInstances?: any[];
}
/**
* @zh 插件配置
* @en Plugin configuration
*/
export interface RuntimePluginConfig {
/**
* @zh 启用的插件 ID 列表
* @en Enabled plugin ID list
*/
enabledPlugins: string[];
}
// ============================================================================
// RuntimePluginManager
// ============================================================================
/**
* @zh 运行时插件管理器
* @en Runtime Plugin Manager
*
* @zh 管理运行时插件的注册、初始化和生命周期。
* @en Manages registration, initialization, and lifecycle of runtime plugins.
*/
export class RuntimePluginManager {
private _plugins = new Map<string, RegisteredPluginInfo>();
private _initialized = false;
private _currentScene: IScene | null = null;
private _currentContext: SystemContext | null = null;
// ============================================================================
// 注册 | Registration
// ============================================================================
/**
* @zh 注册插件
* @en Register plugin
*
* @param plugin - @zh 插件实例 @en Plugin instance
*/
register(plugin: IRuntimePlugin): void {
if (!plugin?.manifest?.id) {
logger.error('Cannot register plugin: invalid manifest');
return;
}
const id = plugin.manifest.id;
if (this._plugins.has(id)) {
logger.warn(`Plugin ${id} is already registered, skipping`);
return;
}
const enabled = plugin.manifest.isCore === true ||
plugin.manifest.isEngineModule === true ||
plugin.manifest.defaultEnabled !== false;
this._plugins.set(id, {
plugin,
state: 'loading', // 已加载但未初始化
enabled,
registeredAt: Date.now()
});
logger.debug(`Plugin registered: ${id}`, {
enabled,
isCore: plugin.manifest.isCore,
isEngineModule: plugin.manifest.isEngineModule
});
}
/**
* @zh 批量注册插件
* @en Register multiple plugins
*
* @param plugins - @zh 插件列表 @en Plugin list
*/
registerMany(plugins: IRuntimePlugin[]): void {
for (const plugin of plugins) {
this.register(plugin);
}
}
// ============================================================================
// 启用/禁用 | Enable/Disable
// ============================================================================
/**
* @zh 启用插件
* @en Enable plugin
*
* @param pluginId - @zh 插件 ID @en Plugin ID
* @returns @zh 是否成功 @en Whether successful
*/
enable(pluginId: string): boolean {
const info = this._plugins.get(pluginId);
if (!info) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (info.plugin.manifest.isCore) {
logger.warn(`Core plugin ${pluginId} is always enabled`);
return true;
}
// 检查依赖 | Check dependencies
const deps = info.plugin.manifest.dependencies || [];
for (const dep of deps) {
const depId = resolveDependencyId(dep);
const depInfo = this._plugins.get(depId);
if (!depInfo?.enabled) {
logger.error(`Cannot enable ${pluginId}: dependency ${dep} is not enabled`);
return false;
}
}
info.enabled = true;
logger.info(`Plugin enabled: ${pluginId}`);
return true;
}
/**
* @zh 禁用插件
* @en Disable plugin
*
* @param pluginId - @zh 插件 ID @en Plugin ID
* @returns @zh 是否成功 @en Whether successful
*/
disable(pluginId: string): boolean {
const info = this._plugins.get(pluginId);
if (!info) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (info.plugin.manifest.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled`);
return false;
}
// 检查是否有其他插件依赖此插件(使用统一工具)
// Check if other plugins depend on this (using unified util)
const reverseDeps = this._getReverseDependencies(pluginId);
const enabledReverseDeps = Array.from(reverseDeps).filter(
id => this._plugins.get(id)?.enabled
);
if (enabledReverseDeps.length > 0) {
logger.error(`Cannot disable ${pluginId}: plugins ${enabledReverseDeps.join(', ')} depend on it`);
return false;
}
// 清理系统实例 | Cleanup system instances
if (info.systemInstances && this._currentScene) {
for (const system of info.systemInstances) {
try {
this._currentScene.removeSystem(system);
} catch (e) {
logger.warn(`Failed to remove system from ${pluginId}:`, e);
}
}
info.systemInstances = [];
}
info.enabled = false;
info.state = 'disabled';
logger.info(`Plugin disabled: ${pluginId}`);
return true;
}
/**
* @zh 检查插件是否启用
* @en Check if plugin is enabled
*/
isEnabled(pluginId: string): boolean {
return this._plugins.get(pluginId)?.enabled ?? false;
}
/**
* @zh 加载配置
* @en Load configuration
*
* @param config - @zh 插件配置 @en Plugin configuration
*/
loadConfig(config: RuntimePluginConfig): void {
const { enabledPlugins } = config;
for (const [id, info] of this._plugins) {
if (info.plugin.manifest.isCore || info.plugin.manifest.isEngineModule) {
info.enabled = true;
continue;
}
const shouldEnable = enabledPlugins.includes(id) ||
info.plugin.manifest.defaultEnabled === true;
info.enabled = shouldEnable;
}
logger.info('Plugin configuration loaded', {
enabled: Array.from(this._plugins.values()).filter(p => p.enabled).length,
total: this._plugins.size
});
}
// ============================================================================
// 初始化 | Initialization
// ============================================================================
/**
* @zh 初始化所有启用的插件
* @en Initialize all enabled plugins
*
* @param services - @zh 服务容器 @en Service container
*/
async initializeRuntime(services: ServiceContainer): Promise<void> {
if (this._initialized) {
logger.warn('Runtime already initialized');
return;
}
const startTime = Date.now();
const sortedPlugins = this._topologicalSort();
// Phase 1: 注册组件 | Register components
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled) continue;
const mod = info.plugin.runtimeModule;
if (mod?.registerComponents) {
try {
info.state = 'initializing';
mod.registerComponents(GlobalComponentRegistry);
logger.debug(`Components registered for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to register components for ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
}
}
// Phase 2: 注册服务 | Register services
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.registerServices) {
try {
mod.registerServices(services);
logger.debug(`Services registered for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to register services for ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
}
}
// Phase 3: 初始化回调 | Initialize callbacks
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.onInitialize) {
try {
await mod.onInitialize();
info.state = 'active';
info.activatedAt = Date.now();
logger.debug(`Initialized: ${pluginId}`);
} catch (e) {
logger.error(`Failed to initialize ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
} else {
info.state = 'active';
info.activatedAt = Date.now();
}
}
this._initialized = true;
const duration = Date.now() - startTime;
const activeCount = Array.from(this._plugins.values())
.filter(p => p.state === 'active').length;
logger.info(`Runtime initialized | 运行时初始化完成`, {
active: activeCount,
total: this._plugins.size,
duration: `${duration}ms`
});
}
/**
* @zh 为场景创建系统
* @en Create systems for scene
*
* @param scene - @zh 场景 @en Scene
* @param context - @zh 系统上下文 @en System context
*/
createSystemsForScene(scene: IScene, context: SystemContext): void {
this._currentScene = scene;
this._currentContext = context;
const sortedPlugins = this._topologicalSort();
// Phase 1: 创建系统 | Create systems
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.createSystems) {
try {
const systemsBefore = scene.systems.length;
mod.createSystems(scene, context);
// 跟踪创建的系统 | Track created systems
const systemsAfter = scene.systems;
info.systemInstances = [];
for (let i = systemsBefore; i < systemsAfter.length; i++) {
info.systemInstances.push(systemsAfter[i]);
}
logger.debug(`Systems created for: ${pluginId}`, {
count: info.systemInstances.length
});
} catch (e) {
logger.error(`Failed to create systems for ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
}
}
// Phase 2: 系统创建后回调 | Post-creation callbacks
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.onSystemsCreated) {
try {
mod.onSystemsCreated(scene, context);
logger.debug(`Systems wired for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to wire systems for ${pluginId}:`, e);
}
}
}
logger.info('Systems created for scene | 场景系统创建完成');
}
// ============================================================================
// 查询 | Query
// ============================================================================
/**
* @zh 获取插件
* @en Get plugin
*/
getPlugin(id: string): IRuntimePlugin | undefined {
return this._plugins.get(id)?.plugin;
}
/**
* @zh 获取插件信息
* @en Get plugin info
*/
getPluginInfo(id: string): RegisteredPluginInfo | undefined {
return this._plugins.get(id);
}
/**
* @zh 获取所有插件
* @en Get all plugins
*/
getPlugins(): IRuntimePlugin[] {
return Array.from(this._plugins.values()).map(p => p.plugin);
}
/**
* @zh 获取所有启用的插件
* @en Get all enabled plugins
*/
getEnabledPlugins(): IRuntimePlugin[] {
return Array.from(this._plugins.values())
.filter(p => p.enabled)
.map(p => p.plugin);
}
/**
* @zh 获取插件状态
* @en Get plugin state
*/
getState(pluginId: string): PluginState | undefined {
return this._plugins.get(pluginId)?.state;
}
/**
* @zh 获取失败的插件
* @en Get failed plugins
*/
getFailedPlugins(): Array<{ id: string; error: Error }> {
const failed: Array<{ id: string; error: Error }> = [];
for (const [id, info] of this._plugins) {
if (info.state === 'error' && info.error) {
failed.push({ id, error: info.error });
}
}
return failed;
}
/**
* @zh 是否已初始化
* @en Whether initialized
*/
get initialized(): boolean {
return this._initialized;
}
// ============================================================================
// 生命周期 | Lifecycle
// ============================================================================
/**
* @zh 清理场景系统
* @en Clear scene systems
*/
clearSceneSystems(): void {
for (const [pluginId, info] of this._plugins) {
if (!info.enabled) continue;
const mod = info.plugin.runtimeModule;
if (mod?.onDestroy) {
try {
mod.onDestroy();
} catch (e) {
logger.error(`Error in ${pluginId}.onDestroy:`, e);
}
}
info.systemInstances = [];
}
this._currentScene = null;
this._currentContext = null;
logger.debug('Scene systems cleared');
}
/**
* @zh 重置管理器
* @en Reset manager
*/
reset(): void {
this.clearSceneSystems();
this._plugins.clear();
this._initialized = false;
logger.info('PluginManager reset');
}
// ============================================================================
// 私有方法 | Private Methods
// ============================================================================
/**
* @zh 拓扑排序(使用统一的 DependencyUtils
* @en Topological sort (using unified DependencyUtils)
*/
private _topologicalSort(): string[] {
// 转换为 IDependable 格式
const items: IDependable[] = Array.from(this._plugins.entries()).map(
([id, info]) => ({
id,
dependencies: info.plugin.manifest.dependencies
})
);
const result = topologicalSort(items, {
algorithm: 'dfs',
resolveId: resolveDependencyId
});
if (result.hasCycles) {
logger.warn(`Circular dependencies detected: ${result.cycleIds?.join(', ')}`);
}
return result.sorted.map(item => item.id);
}
/**
* @zh 获取反向依赖(使用统一的 DependencyUtils
* @en Get reverse dependencies (using unified DependencyUtils)
*/
private _getReverseDependencies(pluginId: string): Set<string> {
const items: IDependable[] = Array.from(this._plugins.entries()).map(
([id, info]) => ({
id,
dependencies: info.plugin.manifest.dependencies
})
);
return getReverseDependencies(pluginId, items, {
resolveId: resolveDependencyId
});
}
}
/**
* @zh 全局运行时插件管理器实例
* @en Global runtime plugin manager instance
*/
export const runtimePluginManager = new RuntimePluginManager();