编辑器核心框架
This commit is contained in:
315
packages/editor-core/src/Plugins/EditorPluginManager.ts
Normal file
315
packages/editor-core/src/Plugins/EditorPluginManager.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import { PluginManager } from '@esengine/ecs-framework';
|
||||
import type { Core, ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { Injectable } from '@esengine/ecs-framework';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type { IEditorPlugin, IEditorPluginMetadata } from './IEditorPlugin';
|
||||
import { EditorPluginCategory } from './IEditorPlugin';
|
||||
import { UIRegistry } from '../Services/UIRegistry';
|
||||
import { MessageHub } from '../Services/MessageHub';
|
||||
import { SerializerRegistry } from '../Services/SerializerRegistry';
|
||||
|
||||
const logger = createLogger('EditorPluginManager');
|
||||
|
||||
/**
|
||||
* 编辑器插件管理器
|
||||
*
|
||||
* 扩展运行时插件管理器,提供编辑器特定的插件管理功能。
|
||||
*/
|
||||
@Injectable()
|
||||
export class EditorPluginManager extends PluginManager {
|
||||
private editorPlugins: Map<string, IEditorPlugin> = new Map();
|
||||
private pluginMetadata: Map<string, IEditorPluginMetadata> = new Map();
|
||||
private uiRegistry: UIRegistry | null = null;
|
||||
private messageHub: MessageHub | null = null;
|
||||
private serializerRegistry: SerializerRegistry | null = null;
|
||||
|
||||
/**
|
||||
* 初始化编辑器插件管理器
|
||||
*/
|
||||
public override initialize(core: Core, services: ServiceContainer): void {
|
||||
super.initialize(core, services);
|
||||
|
||||
this.uiRegistry = services.resolve(UIRegistry);
|
||||
this.messageHub = services.resolve(MessageHub);
|
||||
this.serializerRegistry = services.resolve(SerializerRegistry);
|
||||
|
||||
logger.info('EditorPluginManager initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装编辑器插件
|
||||
*/
|
||||
public async installEditor(plugin: IEditorPlugin): Promise<void> {
|
||||
if (!this.uiRegistry || !this.messageHub || !this.serializerRegistry) {
|
||||
throw new Error('EditorPluginManager not initialized. Call initialize() first.');
|
||||
}
|
||||
|
||||
logger.info(`Installing editor plugin: ${plugin.name} (${plugin.displayName})`);
|
||||
|
||||
await super.install(plugin);
|
||||
|
||||
this.editorPlugins.set(plugin.name, plugin);
|
||||
|
||||
const metadata: IEditorPluginMetadata = {
|
||||
name: plugin.name,
|
||||
displayName: plugin.displayName,
|
||||
version: plugin.version,
|
||||
category: plugin.category,
|
||||
description: plugin.description,
|
||||
icon: plugin.icon,
|
||||
enabled: true,
|
||||
installedAt: Date.now()
|
||||
};
|
||||
this.pluginMetadata.set(plugin.name, metadata);
|
||||
|
||||
try {
|
||||
if (plugin.registerMenuItems) {
|
||||
const menuItems = plugin.registerMenuItems();
|
||||
this.uiRegistry.registerMenus(menuItems);
|
||||
logger.debug(`Registered ${menuItems.length} menu items for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerToolbar) {
|
||||
const toolbarItems = plugin.registerToolbar();
|
||||
this.uiRegistry.registerToolbarItems(toolbarItems);
|
||||
logger.debug(`Registered ${toolbarItems.length} toolbar items for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.registerPanels) {
|
||||
const panels = plugin.registerPanels();
|
||||
this.uiRegistry.registerPanels(panels);
|
||||
logger.debug(`Registered ${panels.length} panels for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.getSerializers) {
|
||||
const serializers = plugin.getSerializers();
|
||||
this.serializerRegistry.registerMultiple(plugin.name, serializers);
|
||||
logger.debug(`Registered ${serializers.length} serializers for ${plugin.name}`);
|
||||
}
|
||||
|
||||
if (plugin.onEditorReady) {
|
||||
await plugin.onEditorReady();
|
||||
}
|
||||
|
||||
await this.messageHub.publish('plugin:installed', {
|
||||
name: plugin.name,
|
||||
displayName: plugin.displayName,
|
||||
category: plugin.category
|
||||
});
|
||||
|
||||
logger.info(`Editor plugin ${plugin.name} installed successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to install editor plugin ${plugin.name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载编辑器插件
|
||||
*/
|
||||
public async uninstallEditor(name: string): Promise<void> {
|
||||
const plugin = this.editorPlugins.get(name);
|
||||
if (!plugin) {
|
||||
throw new Error(`Editor plugin ${name} is not installed`);
|
||||
}
|
||||
|
||||
logger.info(`Uninstalling editor plugin: ${name}`);
|
||||
|
||||
try {
|
||||
if (plugin.registerMenuItems) {
|
||||
const menuItems = plugin.registerMenuItems();
|
||||
for (const item of menuItems) {
|
||||
this.uiRegistry?.unregisterMenu(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerToolbar) {
|
||||
const toolbarItems = plugin.registerToolbar();
|
||||
for (const item of toolbarItems) {
|
||||
this.uiRegistry?.unregisterToolbarItem(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.registerPanels) {
|
||||
const panels = plugin.registerPanels();
|
||||
for (const panel of panels) {
|
||||
this.uiRegistry?.unregisterPanel(panel.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.serializerRegistry?.unregisterAll(name);
|
||||
|
||||
await super.uninstall(name);
|
||||
|
||||
this.editorPlugins.delete(name);
|
||||
this.pluginMetadata.delete(name);
|
||||
|
||||
await this.messageHub?.publish('plugin:uninstalled', { name });
|
||||
|
||||
logger.info(`Editor plugin ${name} uninstalled successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to uninstall editor plugin ${name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编辑器插件
|
||||
*/
|
||||
public getEditorPlugin(name: string): IEditorPlugin | undefined {
|
||||
return this.editorPlugins.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有编辑器插件
|
||||
*/
|
||||
public getAllEditorPlugins(): IEditorPlugin[] {
|
||||
return Array.from(this.editorPlugins.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件元数据
|
||||
*/
|
||||
public getPluginMetadata(name: string): IEditorPluginMetadata | undefined {
|
||||
return this.pluginMetadata.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有插件元数据
|
||||
*/
|
||||
public getAllPluginMetadata(): IEditorPluginMetadata[] {
|
||||
return Array.from(this.pluginMetadata.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类别获取插件
|
||||
*/
|
||||
public getPluginsByCategory(category: EditorPluginCategory): IEditorPlugin[] {
|
||||
return this.getAllEditorPlugins().filter(plugin => plugin.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用插件
|
||||
*/
|
||||
public async enablePlugin(name: string): Promise<void> {
|
||||
const metadata = this.pluginMetadata.get(name);
|
||||
if (!metadata) {
|
||||
throw new Error(`Plugin ${name} not found`);
|
||||
}
|
||||
|
||||
if (metadata.enabled) {
|
||||
logger.warn(`Plugin ${name} is already enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
metadata.enabled = true;
|
||||
await this.messageHub?.publish('plugin:enabled', { name });
|
||||
logger.info(`Plugin ${name} enabled`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用插件
|
||||
*/
|
||||
public async disablePlugin(name: string): Promise<void> {
|
||||
const metadata = this.pluginMetadata.get(name);
|
||||
if (!metadata) {
|
||||
throw new Error(`Plugin ${name} not found`);
|
||||
}
|
||||
|
||||
if (!metadata.enabled) {
|
||||
logger.warn(`Plugin ${name} is already disabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
metadata.enabled = false;
|
||||
await this.messageHub?.publish('plugin:disabled', { name });
|
||||
logger.info(`Plugin ${name} disabled`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目打开通知
|
||||
*/
|
||||
public async notifyProjectOpen(projectPath: string): Promise<void> {
|
||||
logger.info(`Notifying plugins of project open: ${projectPath}`);
|
||||
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onProjectOpen) {
|
||||
try {
|
||||
await plugin.onProjectOpen(projectPath);
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onProjectOpen:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('project:opened', { path: projectPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目关闭通知
|
||||
*/
|
||||
public async notifyProjectClose(): Promise<void> {
|
||||
logger.info('Notifying plugins of project close');
|
||||
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onProjectClose) {
|
||||
try {
|
||||
await plugin.onProjectClose();
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onProjectClose:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('project:closed', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件保存前通知
|
||||
*/
|
||||
public async notifyBeforeSave(filePath: string, data: any): Promise<void> {
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onBeforeSave) {
|
||||
try {
|
||||
await plugin.onBeforeSave(filePath, data);
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onBeforeSave:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('file:beforeSave', { path: filePath, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件保存后通知
|
||||
*/
|
||||
public async notifyAfterSave(filePath: string): Promise<void> {
|
||||
for (const plugin of this.editorPlugins.values()) {
|
||||
if (plugin.onAfterSave) {
|
||||
try {
|
||||
await plugin.onAfterSave(filePath);
|
||||
} catch (error) {
|
||||
logger.error(`Error in ${plugin.name}.onAfterSave:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.messageHub?.publish('file:afterSave', { path: filePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.editorPlugins.clear();
|
||||
this.pluginMetadata.clear();
|
||||
this.uiRegistry = null;
|
||||
this.messageHub = null;
|
||||
this.serializerRegistry = null;
|
||||
|
||||
logger.info('EditorPluginManager disposed');
|
||||
}
|
||||
}
|
||||
169
packages/editor-core/src/Plugins/IEditorPlugin.ts
Normal file
169
packages/editor-core/src/Plugins/IEditorPlugin.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import type { IPlugin } from '@esengine/ecs-framework';
|
||||
import type { MenuItem, ToolbarItem, PanelDescriptor } from '../Types/UITypes';
|
||||
|
||||
/**
|
||||
* 编辑器插件类别
|
||||
*/
|
||||
export enum EditorPluginCategory {
|
||||
/**
|
||||
* 工具插件
|
||||
*/
|
||||
Tool = 'tool',
|
||||
|
||||
/**
|
||||
* 窗口插件
|
||||
*/
|
||||
Window = 'window',
|
||||
|
||||
/**
|
||||
* 检视器插件
|
||||
*/
|
||||
Inspector = 'inspector',
|
||||
|
||||
/**
|
||||
* 系统插件
|
||||
*/
|
||||
System = 'system',
|
||||
|
||||
/**
|
||||
* 导入导出插件
|
||||
*/
|
||||
ImportExport = 'import-export'
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化器接口
|
||||
*/
|
||||
export interface ISerializer<T = any> {
|
||||
/**
|
||||
* 序列化为二进制数据
|
||||
*/
|
||||
serialize(data: T): Uint8Array;
|
||||
|
||||
/**
|
||||
* 从二进制数据反序列化
|
||||
*/
|
||||
deserialize(data: Uint8Array): T;
|
||||
|
||||
/**
|
||||
* 获取序列化器支持的数据类型
|
||||
*/
|
||||
getSupportedType(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器插件接口
|
||||
*
|
||||
* 扩展了运行时插件接口,添加了编辑器特定的功能。
|
||||
*/
|
||||
export interface IEditorPlugin extends IPlugin {
|
||||
/**
|
||||
* 插件显示名称
|
||||
*/
|
||||
readonly displayName: string;
|
||||
|
||||
/**
|
||||
* 插件类别
|
||||
*/
|
||||
readonly category: EditorPluginCategory;
|
||||
|
||||
/**
|
||||
* 插件描述
|
||||
*/
|
||||
readonly description?: string;
|
||||
|
||||
/**
|
||||
* 插件图标
|
||||
*/
|
||||
readonly icon?: string;
|
||||
|
||||
/**
|
||||
* 注册菜单项
|
||||
*/
|
||||
registerMenuItems?(): MenuItem[];
|
||||
|
||||
/**
|
||||
* 注册工具栏项
|
||||
*/
|
||||
registerToolbar?(): ToolbarItem[];
|
||||
|
||||
/**
|
||||
* 注册面板
|
||||
*/
|
||||
registerPanels?(): PanelDescriptor[];
|
||||
|
||||
/**
|
||||
* 提供序列化器
|
||||
*/
|
||||
getSerializers?(): ISerializer[];
|
||||
|
||||
/**
|
||||
* 编辑器就绪回调
|
||||
*/
|
||||
onEditorReady?(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 项目打开回调
|
||||
*/
|
||||
onProjectOpen?(projectPath: string): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 项目关闭回调
|
||||
*/
|
||||
onProjectClose?(): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 文件保存前回调
|
||||
*/
|
||||
onBeforeSave?(filePath: string, data: any): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 文件保存后回调
|
||||
*/
|
||||
onAfterSave?(filePath: string): void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器插件元数据
|
||||
*/
|
||||
export interface IEditorPluginMetadata {
|
||||
/**
|
||||
* 插件名称
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 显示名称
|
||||
*/
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
version: string;
|
||||
|
||||
/**
|
||||
* 类别
|
||||
*/
|
||||
category: EditorPluginCategory;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* 是否已启用
|
||||
*/
|
||||
enabled: boolean;
|
||||
|
||||
/**
|
||||
* 安装时间戳
|
||||
*/
|
||||
installedAt?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user