Files
esengine/packages/editor-core/src/Plugin/PluginManager.ts
YHH b42a7b4e43 Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
2025-12-01 22:28:51 +08:00

1164 lines
39 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.
/**
* 统一插件管理器
* Unified Plugin Manager
*/
import { createLogger, ComponentRegistry } from '@esengine/ecs-framework';
import type { IScene, ServiceContainer, IService } from '@esengine/ecs-framework';
import type {
PluginDescriptor,
PluginState,
PluginCategory,
LoadingPhase,
IPlugin
} from './PluginDescriptor';
import type {
SystemContext,
IEditorModuleLoader
} from './IPluginLoader';
import { EntityCreationRegistry } from '../Services/EntityCreationRegistry';
import { ComponentActionRegistry } from '../Services/ComponentActionRegistry';
import { FileActionRegistry } from '../Services/FileActionRegistry';
import { UIRegistry } from '../Services/UIRegistry';
import { MessageHub } from '../Services/MessageHub';
const logger = createLogger('PluginManager');
/**
* PluginManager 服务标识符
* PluginManager service identifier
*/
export const IPluginManager = Symbol.for('IPluginManager');
/**
* 标准化后的插件描述符(所有字段都有值)
* Normalized plugin descriptor (all fields have values)
*/
export interface NormalizedPluginDescriptor {
id: string;
name: string;
version: string;
description: string;
category: PluginCategory;
tags: string[];
icon?: string;
enabledByDefault: boolean;
canContainContent: boolean;
isEnginePlugin: boolean;
isCore: boolean;
modules: Array<{ name: string; type: 'runtime' | 'editor'; loadingPhase: LoadingPhase }>;
dependencies: Array<{ id: string; version?: string; optional?: boolean }>;
platforms: ('web' | 'desktop' | 'mobile')[];
}
/**
* 标准化后的插件(内部使用)
* Normalized plugin (internal use)
*/
export interface NormalizedPlugin {
descriptor: NormalizedPluginDescriptor;
runtimeModule?: IPlugin['runtimeModule'];
editorModule?: IEditorModuleLoader;
}
/**
* 插件注册的资源(用于卸载时清理)
* Resources registered by plugin (for cleanup on unload)
*/
export interface PluginRegisteredResources {
/** 注册的面板ID | Registered panel IDs */
panelIds: string[];
/** 注册的菜单ID | Registered menu IDs */
menuIds: string[];
/** 注册的工具栏ID | Registered toolbar IDs */
toolbarIds: string[];
/** 注册的实体模板ID | Registered entity template IDs */
entityTemplateIds: string[];
/** 注册的组件操作 | Registered component actions */
componentActions: Array<{ componentName: string; actionId: string }>;
/** 注册的文件处理器 | Registered file handlers */
fileHandlers: any[];
/** 注册的文件模板 | Registered file templates */
fileTemplates: any[];
}
/**
* 已注册的插件信息
* Registered plugin info
*/
export interface RegisteredPlugin {
/** 标准化后的插件 | Normalized plugin */
plugin: NormalizedPlugin;
/** 插件状态 | Plugin state */
state: PluginState;
/** 错误信息 | Error info */
error?: Error;
/** 是否启用 | Is enabled */
enabled: boolean;
/** 加载时间 | Load time */
loadedAt?: number;
/** 激活时间 | Activation time */
activatedAt?: number;
/** 插件注册的资源 | Resources registered by plugin */
registeredResources?: PluginRegisteredResources;
}
/**
* 插件配置
* Plugin configuration
*/
export interface PluginConfig {
/** 启用的插件ID列表 | Enabled plugin IDs */
enabledPlugins: string[];
}
/**
* 加载阶段顺序
* Loading phase order
*/
const LOADING_PHASE_ORDER: LoadingPhase[] = [
'earliest',
'preDefault',
'default',
'postDefault',
'postEngine'
];
/**
* 统一插件管理器
* Unified Plugin Manager
*
* 使用方式:
* 1. 在 ServiceContainer 中注册: services.registerInstance(IPluginManager, new PluginManager())
* 2. 获取实例: services.resolve<PluginManager>(IPluginManager)
*/
export class PluginManager implements IService {
private plugins: Map<string, RegisteredPlugin> = new Map();
private initialized = false;
private editorInitialized = false;
private services: ServiceContainer | null = null;
private currentScene: IScene | null = null;
private currentContext: SystemContext | null = null;
constructor() {}
/**
* 设置服务容器(用于动态启用插件)
* Set service container (for dynamic plugin enabling)
*/
setServiceContainer(services: ServiceContainer): void {
this.services = services;
}
/**
* 设置当前场景和上下文(用于动态创建系统)
* Set current scene and context (for dynamic system creation)
*/
setSceneContext(scene: IScene, context: SystemContext): void {
this.currentScene = scene;
this.currentContext = context;
}
/**
* 释放资源
* Dispose resources
*/
dispose(): void {
this.reset();
}
/**
* 标准化插件描述符,填充默认值
* Normalize plugin descriptor, fill in defaults
*/
private normalizePlugin(input: IPlugin): NormalizedPlugin {
const d = input.descriptor;
return {
descriptor: {
id: d.id,
name: d.name,
version: d.version,
description: d.description ?? '',
category: d.category ?? 'tools',
tags: d.tags ?? [],
icon: d.icon,
enabledByDefault: d.enabledByDefault ?? false,
canContainContent: d.canContainContent ?? false,
isEnginePlugin: d.isEnginePlugin ?? true,
isCore: d.isCore ?? false,
modules: (d.modules ?? [{ name: 'Runtime', type: 'runtime' as const, loadingPhase: 'default' as const }]).map((m: { name: string; type: 'runtime' | 'editor'; loadingPhase?: LoadingPhase }) => ({
name: m.name,
type: m.type,
loadingPhase: m.loadingPhase ?? 'default' as LoadingPhase
})),
dependencies: d.dependencies ?? [],
platforms: d.platforms ?? ['web', 'desktop']
},
runtimeModule: input.runtimeModule,
editorModule: input.editorModule as IEditorModuleLoader | undefined
};
}
/**
* 注册插件
* Register plugin
*
* 接受任何符合 IPlugin 接口的插件,内部会标准化所有字段。
* Accepts any plugin conforming to IPlugin interface, normalizes all fields internally.
*/
register(plugin: IPlugin): void {
if (!plugin) {
logger.error('Cannot register plugin: plugin is null or undefined');
return;
}
if (!plugin.descriptor) {
logger.error('Cannot register plugin: descriptor is null or undefined', plugin);
return;
}
const { id } = plugin.descriptor;
if (!id) {
logger.error('Cannot register plugin: descriptor.id is null or undefined', plugin.descriptor);
return;
}
if (this.plugins.has(id)) {
logger.warn(`Plugin ${id} is already registered, skipping`);
return;
}
const normalized = this.normalizePlugin(plugin);
const enabled = normalized.descriptor.isCore || normalized.descriptor.enabledByDefault;
this.plugins.set(id, {
plugin: normalized,
state: 'loaded',
enabled,
loadedAt: Date.now()
});
logger.info(`Plugin registered: ${id} (${normalized.descriptor.name})`);
}
/**
* 启用插件(动态加载编辑器模块和运行时系统)
* Enable plugin (dynamically load editor module and runtime systems)
*/
async enable(pluginId: string): Promise<boolean> {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (plugin.plugin.descriptor.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled/enabled`);
return false;
}
if (plugin.enabled) {
logger.warn(`Plugin ${pluginId} is already enabled`);
return true;
}
// 检查依赖
const deps = plugin.plugin.descriptor.dependencies;
for (const dep of deps) {
if (dep.optional) continue;
const depPlugin = this.plugins.get(dep.id);
if (!depPlugin || !depPlugin.enabled) {
logger.error(`Cannot enable ${pluginId}: dependency ${dep.id} is not enabled`);
return false;
}
}
plugin.enabled = true;
plugin.state = 'loading';
try {
// 动态加载编辑器模块
if (this.services && this.editorInitialized) {
await this.activatePluginEditor(pluginId);
}
// 动态加载运行时模块
if (this.currentScene && this.currentContext && this.initialized) {
await this.activatePluginRuntime(pluginId);
}
plugin.state = 'active';
plugin.activatedAt = Date.now();
logger.info(`Plugin enabled and activated: ${pluginId}`);
return true;
} catch (e) {
logger.error(`Failed to activate plugin ${pluginId}:`, e);
plugin.state = 'error';
plugin.error = e as Error;
plugin.enabled = false;
return false;
}
}
/**
* 禁用插件(动态卸载编辑器模块和运行时系统)
* Disable plugin (dynamically unload editor module and runtime systems)
*/
async disable(pluginId: string): Promise<boolean> {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (plugin.plugin.descriptor.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled`);
return false;
}
if (!plugin.enabled) {
logger.warn(`Plugin ${pluginId} is already disabled`);
return true;
}
// 检查是否有其他插件依赖此插件
for (const [id, p] of this.plugins) {
if (!p.enabled || id === pluginId) continue;
const deps = p.plugin.descriptor.dependencies;
const hasDep = deps.some(d => d.id === pluginId && !d.optional);
if (hasDep) {
logger.error(`Cannot disable ${pluginId}: plugin ${id} depends on it`);
return false;
}
}
try {
// 卸载编辑器模块
if (this.services) {
await this.deactivatePluginEditor(pluginId);
}
// 卸载运行时模块(清理系统)
if (this.currentScene) {
this.deactivatePluginRuntime(pluginId);
}
plugin.enabled = false;
plugin.state = 'disabled';
plugin.registeredResources = undefined;
logger.info(`Plugin disabled: ${pluginId}`);
return true;
} catch (e) {
logger.error(`Failed to deactivate plugin ${pluginId}:`, e);
return false;
}
}
/**
* 动态激活插件的编辑器模块
* Dynamically activate plugin's editor module
*/
private async activatePluginEditor(pluginId: string): Promise<void> {
const plugin = this.plugins.get(pluginId);
if (!plugin || !this.services) {
logger.warn(`activatePluginEditor: skipping ${pluginId} (plugin=${!!plugin}, services=${!!this.services})`);
return;
}
const editorModule = plugin.plugin.editorModule;
if (!editorModule) {
logger.debug(`activatePluginEditor: ${pluginId} has no editorModule`);
return;
}
logger.info(`activatePluginEditor: activating ${pluginId}`);
// 初始化资源跟踪
const resources: PluginRegisteredResources = {
panelIds: [],
menuIds: [],
toolbarIds: [],
entityTemplateIds: [],
componentActions: [],
fileHandlers: [],
fileTemplates: []
};
// 获取注册表服务
const entityCreationRegistry = this.services.tryResolve(EntityCreationRegistry);
const componentActionRegistry = this.services.tryResolve(ComponentActionRegistry);
const fileActionRegistry = this.services.tryResolve(FileActionRegistry);
const uiRegistry = this.services.tryResolve(UIRegistry);
// 安装编辑器模块
await editorModule.install(this.services);
logger.debug(`Editor module installed: ${pluginId}`);
// 注册实体创建模板
if (entityCreationRegistry && editorModule.getEntityCreationTemplates) {
const templates = editorModule.getEntityCreationTemplates();
logger.info(`[${pluginId}] getEntityCreationTemplates returned ${templates?.length ?? 0} templates`);
if (templates && templates.length > 0) {
entityCreationRegistry.registerMany(templates);
resources.entityTemplateIds = templates.map(t => t.id);
logger.info(`Registered ${templates.length} entity templates from: ${pluginId}`, templates.map(t => t.id));
}
} else {
logger.debug(`[${pluginId}] entityCreationRegistry=${!!entityCreationRegistry}, hasGetEntityCreationTemplates=${!!editorModule.getEntityCreationTemplates}`);
}
// 注册组件操作
if (componentActionRegistry && editorModule.getComponentActions) {
const actions = editorModule.getComponentActions();
if (actions && actions.length > 0) {
for (const action of actions) {
componentActionRegistry.register(action);
resources.componentActions.push({
componentName: action.componentName,
actionId: action.id
});
}
logger.debug(`Registered ${actions.length} component actions from: ${pluginId}`);
}
}
// 注册文件操作处理器
if (fileActionRegistry && editorModule.getFileActionHandlers) {
const handlers = editorModule.getFileActionHandlers();
if (handlers && handlers.length > 0) {
for (const handler of handlers) {
fileActionRegistry.registerActionHandler(handler);
resources.fileHandlers.push(handler);
}
logger.debug(`Registered ${handlers.length} file action handlers from: ${pluginId}`);
}
}
// 注册文件创建模板
if (fileActionRegistry && editorModule.getFileCreationTemplates) {
const templates = editorModule.getFileCreationTemplates();
if (templates && templates.length > 0) {
for (const template of templates) {
fileActionRegistry.registerCreationTemplate(template);
resources.fileTemplates.push(template);
}
logger.debug(`Registered ${templates.length} file creation templates from: ${pluginId}`);
}
}
// 注册面板
if (uiRegistry && editorModule.getPanels) {
const panels = editorModule.getPanels();
if (panels && panels.length > 0) {
uiRegistry.registerPanels(panels);
resources.panelIds = panels.map(p => p.id);
logger.debug(`Registered ${panels.length} panels from: ${pluginId}`);
}
}
// 注册菜单
if (uiRegistry && editorModule.getMenuItems) {
const menuItems = editorModule.getMenuItems();
if (menuItems && menuItems.length > 0) {
for (const item of menuItems) {
// 转换 MenuItemDescriptor 到 MenuItemexecute -> onClick
const menuItem = {
...item,
onClick: item.execute
};
uiRegistry.registerMenu(menuItem as any);
resources.menuIds.push(item.id);
}
logger.debug(`Registered ${menuItems.length} menu items from: ${pluginId}`);
}
}
// 注册工具栏
if (uiRegistry && editorModule.getToolbarItems) {
const toolbarItems = editorModule.getToolbarItems();
if (toolbarItems && toolbarItems.length > 0) {
for (const item of toolbarItems) {
uiRegistry.registerToolbarItem(item as any);
resources.toolbarIds.push(item.id);
}
logger.debug(`Registered ${toolbarItems.length} toolbar items from: ${pluginId}`);
}
}
// 保存注册的资源
plugin.registeredResources = resources;
// 调用 onEditorReady
if (editorModule.onEditorReady) {
await editorModule.onEditorReady();
}
// 发布插件安装事件,通知 UI 刷新
const messageHub = this.services.tryResolve(MessageHub);
if (messageHub) {
messageHub.publish('plugin:installed', { pluginId });
}
}
/**
* 动态卸载插件的编辑器模块
* Dynamically deactivate plugin's editor module
*/
private async deactivatePluginEditor(pluginId: string): Promise<void> {
const plugin = this.plugins.get(pluginId);
if (!plugin || !this.services) return;
const editorModule = plugin.plugin.editorModule;
const resources = plugin.registeredResources;
// 获取注册表服务
const entityCreationRegistry = this.services.tryResolve(EntityCreationRegistry);
const componentActionRegistry = this.services.tryResolve(ComponentActionRegistry);
const fileActionRegistry = this.services.tryResolve(FileActionRegistry);
const uiRegistry = this.services.tryResolve(UIRegistry);
if (resources) {
// 注销面板
if (uiRegistry) {
for (const panelId of resources.panelIds) {
uiRegistry.unregisterPanel(panelId);
}
}
// 注销菜单
if (uiRegistry) {
for (const menuId of resources.menuIds) {
uiRegistry.unregisterMenu(menuId);
}
}
// 注销工具栏
if (uiRegistry) {
for (const toolbarId of resources.toolbarIds) {
uiRegistry.unregisterToolbarItem(toolbarId);
}
}
// 注销实体模板
if (entityCreationRegistry) {
for (const templateId of resources.entityTemplateIds) {
entityCreationRegistry.unregister(templateId);
}
}
// 注销组件操作
if (componentActionRegistry) {
for (const action of resources.componentActions) {
componentActionRegistry.unregister(action.componentName, action.actionId);
}
}
// 注销文件处理器
if (fileActionRegistry) {
for (const handler of resources.fileHandlers) {
fileActionRegistry.unregisterActionHandler(handler);
}
}
// 注销文件模板
if (fileActionRegistry) {
for (const template of resources.fileTemplates) {
fileActionRegistry.unregisterCreationTemplate(template);
}
}
logger.debug(`Unregistered resources for: ${pluginId}`);
}
// 调用 uninstall
if (editorModule?.uninstall) {
await editorModule.uninstall();
logger.debug(`Editor module uninstalled: ${pluginId}`);
}
// 发布插件卸载事件,通知 UI 刷新
const messageHub = this.services.tryResolve(MessageHub);
if (messageHub) {
messageHub.publish('plugin:uninstalled', { pluginId });
}
}
/**
* 动态激活插件的运行时模块
* Dynamically activate plugin's runtime module
*/
private async activatePluginRuntime(pluginId: string): Promise<void> {
const plugin = this.plugins.get(pluginId);
if (!plugin || !this.currentScene || !this.currentContext || !this.services) return;
const runtimeModule = plugin.plugin.runtimeModule;
if (!runtimeModule) return;
// 注册组件
if (runtimeModule.registerComponents) {
runtimeModule.registerComponents(ComponentRegistry);
logger.debug(`Components registered for: ${pluginId}`);
}
// 注册服务
if (runtimeModule.registerServices) {
runtimeModule.registerServices(this.services);
logger.debug(`Services registered for: ${pluginId}`);
}
// 创建系统
if (runtimeModule.createSystems) {
runtimeModule.createSystems(this.currentScene, this.currentContext);
logger.debug(`Systems created for: ${pluginId}`);
}
// 调用系统创建后回调
if (runtimeModule.onSystemsCreated) {
runtimeModule.onSystemsCreated(this.currentScene, this.currentContext);
logger.debug(`Systems wired for: ${pluginId}`);
}
// 调用初始化
if (runtimeModule.onInitialize) {
await runtimeModule.onInitialize();
logger.debug(`Runtime initialized for: ${pluginId}`);
}
}
/**
* 动态卸载插件的运行时模块
* Dynamically deactivate plugin's runtime module
*/
private deactivatePluginRuntime(pluginId: string): void {
const plugin = this.plugins.get(pluginId);
if (!plugin) return;
const runtimeModule = plugin.plugin.runtimeModule;
if (!runtimeModule) return;
// 调用销毁回调
if (runtimeModule.onDestroy) {
runtimeModule.onDestroy();
logger.debug(`Runtime destroyed for: ${pluginId}`);
}
// 注意:组件和服务无法动态注销,这是设计限制
// 系统的移除需要场景支持,暂时只调用 onDestroy
}
/**
* 检查插件是否启用
* Check if plugin is enabled
*/
isEnabled(pluginId: string): boolean {
const plugin = this.plugins.get(pluginId);
return plugin?.enabled ?? false;
}
/**
* 获取插件状态
* Get plugin state
*/
getState(pluginId: string): PluginState | undefined {
return this.plugins.get(pluginId)?.state;
}
/**
* 获取插件
* Get plugin
*/
getPlugin(pluginId: string): RegisteredPlugin | undefined {
return this.plugins.get(pluginId);
}
/**
* 获取所有插件
* Get all plugins
*/
getAllPlugins(): RegisteredPlugin[] {
return Array.from(this.plugins.values());
}
/**
* 按类别获取插件
* Get plugins by category
*/
getPluginsByCategory(category: PluginCategory): RegisteredPlugin[] {
return this.getAllPlugins().filter(
p => p.plugin.descriptor.category === category
);
}
/**
* 获取已启用的插件
* Get enabled plugins
*/
getEnabledPlugins(): RegisteredPlugin[] {
return this.getAllPlugins().filter(p => p.enabled);
}
/**
* 初始化所有运行时模块
* Initialize all runtime modules
*/
async initializeRuntime(services: ServiceContainer): Promise<void> {
if (this.initialized) {
logger.warn('Runtime already initialized');
return;
}
// 保存服务容器引用
this.services = services;
logger.info('Initializing runtime modules...');
const sortedPlugins = this.sortByLoadingPhase('runtime');
// 注册组件
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled) continue;
const runtimeModule = plugin.plugin.runtimeModule;
if (runtimeModule?.registerComponents) {
try {
runtimeModule.registerComponents(ComponentRegistry);
logger.debug(`Components registered for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to register components for ${pluginId}:`, e);
plugin.state = 'error';
plugin.error = e as Error;
}
}
}
// 注册服务
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled || plugin.state === 'error') continue;
const runtimeModule = plugin.plugin.runtimeModule;
if (runtimeModule?.registerServices) {
try {
runtimeModule.registerServices(services);
logger.debug(`Services registered for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to register services for ${pluginId}:`, e);
plugin.state = 'error';
plugin.error = e as Error;
}
}
}
// 调用初始化回调 | Call initialization callbacks
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled || plugin.state === 'error') continue;
const runtimeModule = plugin.plugin.runtimeModule;
if (runtimeModule?.onInitialize) {
try {
await runtimeModule.onInitialize();
plugin.state = 'active';
plugin.activatedAt = Date.now();
logger.debug(`Initialized: ${pluginId}`);
} catch (e) {
logger.error(`Failed to initialize ${pluginId}:`, e);
plugin.state = 'error';
plugin.error = e as Error;
}
} else {
// 没有初始化回调,直接激活 | No init callback, activate directly
plugin.state = 'active';
plugin.activatedAt = Date.now();
}
}
this.initialized = true;
logger.info('Runtime modules initialized');
}
/**
* 为场景创建系统
* Create systems for scene
*/
createSystemsForScene(scene: IScene, context: SystemContext): void {
// 保存场景和上下文引用(用于动态启用插件)
this.currentScene = scene;
this.currentContext = context;
logger.info('Creating systems for scene...');
console.log('[PluginManager] createSystemsForScene called, context.assetManager:', context.assetManager ? 'exists' : 'null');
const sortedPlugins = this.sortByLoadingPhase('runtime');
console.log('[PluginManager] Sorted plugins for runtime:', sortedPlugins);
// 第一阶段:创建所有系统
// Phase 1: Create all systems
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
console.log(`[PluginManager] Plugin ${pluginId}: enabled=${plugin?.enabled}, state=${plugin?.state}, hasRuntimeModule=${!!plugin?.plugin.runtimeModule}`);
if (!plugin?.enabled || plugin.state === 'error') continue;
const runtimeModule = plugin.plugin.runtimeModule;
if (runtimeModule?.createSystems) {
try {
console.log(`[PluginManager] Calling createSystems for: ${pluginId}`);
runtimeModule.createSystems(scene, context);
logger.debug(`Systems created for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to create systems for ${pluginId}:`, e);
}
}
}
// 第二阶段:系统创建完成后的回调(用于跨插件依赖连接)
// Phase 2: Post-creation callbacks (for cross-plugin dependency wiring)
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled || plugin.state === 'error') continue;
const runtimeModule = plugin.plugin.runtimeModule;
if (runtimeModule?.onSystemsCreated) {
try {
runtimeModule.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');
}
/**
* 初始化所有编辑器模块
* Initialize all editor modules
*/
async initializeEditor(services: ServiceContainer): Promise<void> {
if (this.editorInitialized) {
logger.warn('Editor already initialized');
return;
}
// 保存服务容器引用
this.services = services;
logger.info('Initializing editor modules...');
const sortedPlugins = this.sortByLoadingPhase('editor');
logger.info(`Sorted plugins for editor initialization: ${sortedPlugins.join(', ')}`);
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
logger.debug(`Processing plugin ${pluginId}: enabled=${plugin?.enabled}, hasEditorModule=${!!plugin?.plugin.editorModule}`);
if (!plugin?.enabled) continue;
try {
// 使用统一的激活方法,自动跟踪注册的资源
await this.activatePluginEditor(pluginId);
} catch (e) {
logger.error(`Failed to install editor module for ${pluginId}:`, e);
plugin.state = 'error';
plugin.error = e as Error;
}
}
this.editorInitialized = true;
logger.info('Editor modules initialized');
}
/**
* 初始化单个插件的编辑器模块(用于动态加载的项目插件)
* Initialize a single plugin's editor module (for dynamically loaded project plugins)
*/
async initializePluginEditor(pluginId: string, services: ServiceContainer): Promise<void> {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled) {
logger.warn(`Plugin ${pluginId} not found or not enabled`);
return;
}
// 确保服务容器已设置
if (!this.services) {
this.services = services;
}
try {
// 使用统一的激活方法
await this.activatePluginEditor(pluginId);
} catch (e) {
logger.error(`Failed to install editor module for ${pluginId}:`, e);
plugin.state = 'error';
plugin.error = e as Error;
}
}
/**
* 通知编辑器就绪
* Notify editor ready
*/
async notifyEditorReady(): Promise<void> {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const editorModule = plugin.plugin.editorModule;
if (editorModule?.onEditorReady) {
try {
await editorModule.onEditorReady();
} catch (e) {
logger.error(`Error in ${pluginId}.onEditorReady:`, e);
}
}
}
}
/**
* 通知项目打开
* Notify project open
*/
async notifyProjectOpen(projectPath: string): Promise<void> {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const editorModule = plugin.plugin.editorModule;
if (editorModule?.onProjectOpen) {
try {
await editorModule.onProjectOpen(projectPath);
} catch (e) {
logger.error(`Error in ${pluginId}.onProjectOpen:`, e);
}
}
}
}
/**
* 通知项目关闭
* Notify project close
*/
async notifyProjectClose(): Promise<void> {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const editorModule = plugin.plugin.editorModule;
if (editorModule?.onProjectClose) {
try {
await editorModule.onProjectClose();
} catch (e) {
logger.error(`Error in ${pluginId}.onProjectClose:`, e);
}
}
}
}
/**
* 通知场景加载
* Notify scene loaded
*/
notifySceneLoaded(scenePath: string): void {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const editorModule = plugin.plugin.editorModule;
if (editorModule?.onSceneLoaded) {
try {
editorModule.onSceneLoaded(scenePath);
} catch (e) {
logger.error(`Error in ${pluginId}.onSceneLoaded:`, e);
}
}
}
}
/**
* 通知场景保存
* Notify scene saving
*/
notifySceneSaving(scenePath: string): boolean {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const editorModule = plugin.plugin.editorModule;
if (editorModule?.onSceneSaving) {
try {
const result = editorModule.onSceneSaving(scenePath);
if (result === false) {
return false;
}
} catch (e) {
logger.error(`Error in ${pluginId}.onSceneSaving:`, e);
}
}
}
return true;
}
/**
* 设置所有插件的语言
* Set locale for all plugins
*/
setLocale(locale: string): void {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const editorModule = plugin.plugin.editorModule;
if (editorModule?.setLocale) {
try {
editorModule.setLocale(locale);
} catch (e) {
logger.error(`Error in ${pluginId}.setLocale:`, e);
}
}
}
}
/**
* 导出配置
* Export configuration
*/
exportConfig(): PluginConfig {
const enabledPlugins: string[] = [];
for (const [id, plugin] of this.plugins) {
if (plugin.enabled && !plugin.plugin.descriptor.isCore) {
enabledPlugins.push(id);
}
}
return { enabledPlugins };
}
/**
* 加载配置并激活插件
* Load configuration and activate plugins
*
* 此方法会:
* 1. 根据配置启用/禁用插件
* 2. 激活新启用插件的编辑器模块
* 3. 卸载新禁用插件的编辑器模块
*/
async loadConfig(config: PluginConfig): Promise<void> {
const { enabledPlugins } = config;
logger.info(`loadConfig called with: ${enabledPlugins.join(', ')}`);
// 收集状态变化的插件
const toEnable: string[] = [];
const toDisable: string[] = [];
for (const [id, plugin] of this.plugins) {
if (plugin.plugin.descriptor.isCore) {
continue; // 核心插件始终启用
}
const shouldBeEnabled = enabledPlugins.includes(id);
const wasEnabled = plugin.enabled;
if (shouldBeEnabled && !wasEnabled) {
toEnable.push(id);
} else if (!shouldBeEnabled && wasEnabled) {
toDisable.push(id);
}
}
// 禁用不再需要的插件
for (const pluginId of toDisable) {
await this.disable(pluginId);
}
// 启用新插件
for (const pluginId of toEnable) {
await this.enable(pluginId);
}
logger.info(`Config loaded and applied: ${toEnable.length} enabled, ${toDisable.length} disabled`);
}
/**
* 按加载阶段排序
* Sort by loading phase
*/
private sortByLoadingPhase(moduleType: 'runtime' | 'editor'): string[] {
const pluginIds = Array.from(this.plugins.keys());
// 先按依赖拓扑排序
const sorted = this.topologicalSort(pluginIds);
// 再按加载阶段排序(稳定排序)
sorted.sort((a, b) => {
const pluginA = this.plugins.get(a);
const pluginB = this.plugins.get(b);
const moduleA = moduleType === 'runtime'
? pluginA?.plugin.descriptor.modules.find(m => m.type === 'runtime')
: pluginA?.plugin.descriptor.modules.find(m => m.type === 'editor');
const moduleB = moduleType === 'runtime'
? pluginB?.plugin.descriptor.modules.find(m => m.type === 'runtime')
: pluginB?.plugin.descriptor.modules.find(m => m.type === 'editor');
const phaseA = moduleA?.loadingPhase || 'default';
const phaseB = moduleB?.loadingPhase || 'default';
return LOADING_PHASE_ORDER.indexOf(phaseA) - LOADING_PHASE_ORDER.indexOf(phaseB);
});
return sorted;
}
/**
* 拓扑排序(处理依赖)
* Topological sort (handle dependencies)
*/
private topologicalSort(pluginIds: string[]): string[] {
const visited = new Set<string>();
const result: string[] = [];
const visit = (id: string) => {
if (visited.has(id)) return;
visited.add(id);
const plugin = this.plugins.get(id);
if (plugin) {
const deps = plugin.plugin.descriptor.dependencies || [];
for (const dep of deps) {
if (pluginIds.includes(dep.id)) {
visit(dep.id);
}
}
}
result.push(id);
};
for (const id of pluginIds) {
visit(id);
}
return result;
}
/**
* 清理所有场景系统
* Clear scene systems
*/
clearSceneSystems(): void {
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.enabled) continue;
const runtimeModule = plugin.plugin.runtimeModule;
if (runtimeModule?.onDestroy) {
try {
runtimeModule.onDestroy();
} catch (e) {
logger.error(`Error in ${pluginId}.onDestroy:`, e);
}
}
}
}
/**
* 重置
* Reset
*/
reset(): void {
this.clearSceneSystems();
this.plugins.clear();
this.initialized = false;
this.editorInitialized = false;
logger.info('PluginManager reset');
}
}