Feature/runtime cdn and plugin loader (#240)

* feat(ui): 完善 UI 布局系统和编辑器可视化工具

* refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统

* fix: 修复 CodeQL 警告并提升测试覆盖率

* refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题

* fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤

* docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明

* fix(ci): 修复 type-check 失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖

* fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖

* fix(ci): platform-web 添加缺失的 behavior-tree 依赖

* fix(lint): 移除正则表达式中不必要的转义字符
This commit is contained in:
YHH
2025-11-27 20:42:46 +08:00
committed by GitHub
parent 71869b1a58
commit 107439d70c
367 changed files with 10661 additions and 12473 deletions

View File

@@ -0,0 +1,355 @@
/**
* 插件加载器接口
* Plugin loader interfaces
*/
import type { IScene, ServiceContainer, ComponentRegistry } from '@esengine/ecs-framework';
import type { PluginDescriptor } from './PluginDescriptor';
/**
* 系统创建上下文
* System creation context
*/
export interface SystemContext {
/** 是否为编辑器模式 | Is editor mode */
isEditor: boolean;
/** 引擎桥接(如有) | Engine bridge (if available) */
engineBridge?: any;
/** 渲染系统(如有) | Render system (if available) */
renderSystem?: any;
/** 其他已创建的系统引用 | Other created system references */
[key: string]: any;
}
/**
* 运行时模块加载器
* Runtime module loader
*/
export interface IRuntimeModuleLoader {
/**
* 注册组件到 ComponentRegistry
* Register components to ComponentRegistry
*/
registerComponents(registry: typeof ComponentRegistry): void;
/**
* 注册服务到 ServiceContainer
* Register services to ServiceContainer
*/
registerServices?(services: ServiceContainer): void;
/**
* 为场景创建系统
* Create systems for scene
*/
createSystems?(scene: IScene, context: SystemContext): void;
/**
* 模块初始化完成回调
* Module initialization complete callback
*/
onInitialize?(): Promise<void>;
/**
* 模块销毁回调
* Module destroy callback
*/
onDestroy?(): void;
}
/**
* 面板位置
* Panel position
*/
export enum PanelPosition {
Left = 'left',
Right = 'right',
Bottom = 'bottom',
Center = 'center'
}
/**
* 面板描述符
* Panel descriptor
*/
export interface PanelDescriptor {
/** 面板ID | Panel ID */
id: string;
/** 面板标题 | Panel title */
title: string;
/** 面板图标 | Panel icon */
icon?: string;
/** 面板位置 | Panel position */
position: PanelPosition;
/** 渲染组件 | Render component */
component?: any;
/** 渲染函数 | Render function */
render?: () => any;
/** 默认大小 | Default size */
defaultSize?: number;
/** 是否可调整大小 | Is resizable */
resizable?: boolean;
/** 是否可关闭 | Is closable */
closable?: boolean;
/** 排序权重 | Order weight */
order?: number;
/** 是否为动态面板 | Is dynamic panel */
isDynamic?: boolean;
}
/**
* 菜单项描述符
* Menu item descriptor
*/
export interface MenuItemDescriptor {
/** 菜单ID | Menu ID */
id: string;
/** 菜单标签 | Menu label */
label: string;
/** 父菜单ID | Parent menu ID */
parentId?: string;
/** 图标 | Icon */
icon?: string;
/** 快捷键 | Shortcut */
shortcut?: string;
/** 执行函数 | Execute function */
execute?: () => void;
/** 子菜单 | Submenu items */
children?: MenuItemDescriptor[];
}
/**
* 工具栏项描述符
* Toolbar item descriptor
*/
export interface ToolbarItemDescriptor {
/** 工具栏项ID | Toolbar item ID */
id: string;
/** 标签 | Label */
label: string;
/** 图标 | Icon */
icon: string;
/** 提示 | Tooltip */
tooltip?: string;
/** 执行函数 | Execute function */
execute: () => void;
}
/**
* 组件检视器提供者(简化版)
* Component inspector provider (simplified)
*/
export interface ComponentInspectorProviderDef {
/** 组件类型名 | Component type name */
componentType: string;
/** 优先级 | Priority */
priority?: number;
/** 渲染函数 | Render function */
render: (component: any, entity: any, onChange: (key: string, value: any) => void) => any;
}
/**
* Gizmo 提供者注册
* Gizmo provider registration
*/
export interface GizmoProviderRegistration {
/** 组件类型 | Component type */
componentType: any;
/** 获取 Gizmo 数据 | Get gizmo data */
getGizmoData: (component: any, entity: any, isSelected: boolean) => any[];
}
/**
* 文件操作处理器
* File action handler
*/
export interface FileActionHandler {
/** 支持的文件扩展名 | Supported file extensions */
extensions: string[];
/** 双击处理 | Double click handler */
onDoubleClick?: (filePath: string) => void | Promise<void>;
/** 打开处理 | Open handler */
onOpen?: (filePath: string) => void | Promise<void>;
/** 获取上下文菜单 | Get context menu */
getContextMenuItems?: (filePath: string, parentPath: string) => any[];
}
/**
* 实体创建模板
* Entity creation template
*/
export interface EntityCreationTemplate {
/** 模板ID | Template ID */
id: string;
/** 标签 | Label */
label: string;
/** 图标组件 | Icon component */
icon?: any;
/** 分类 | Category */
category?: string;
/** 排序权重 | Order weight */
order?: number;
/** 创建函数 | Create function */
create: (parentEntityId?: number) => number | Promise<number>;
}
/**
* 组件操作
* Component action
*/
export interface ComponentAction {
/** 操作ID | Action ID */
id: string;
/** 组件名 | Component name */
componentName: string;
/** 标签 | Label */
label: string;
/** 图标 | Icon */
icon?: any;
/** 排序权重 | Order weight */
order?: number;
/** 执行函数 | Execute function */
execute: (component: any, entity: any) => void | Promise<void>;
}
/**
* 序列化器接口
* Serializer interface
*/
export interface ISerializer<T = any> {
/** 获取支持的类型 | Get supported type */
getSupportedType(): string;
/** 序列化数据 | Serialize data */
serialize(data: T): Uint8Array;
/** 反序列化数据 | Deserialize data */
deserialize(data: Uint8Array): T;
}
/**
* 编辑器模块加载器
* Editor module loader
*/
export interface IEditorModuleLoader {
/**
* 安装编辑器模块
* Install editor module
*/
install(services: ServiceContainer): Promise<void>;
/**
* 卸载编辑器模块
* Uninstall editor module
*/
uninstall?(): Promise<void>;
/**
* 返回面板描述列表
* Get panel descriptors
*/
getPanels?(): PanelDescriptor[];
/**
* 返回菜单项列表
* Get menu items
*/
getMenuItems?(): MenuItemDescriptor[];
/**
* 返回工具栏项列表
* Get toolbar items
*/
getToolbarItems?(): ToolbarItemDescriptor[];
/**
* 返回检视器提供者列表
* Get inspector providers
*/
getInspectorProviders?(): ComponentInspectorProviderDef[];
/**
* 返回 Gizmo 提供者列表
* Get gizmo providers
*/
getGizmoProviders?(): GizmoProviderRegistration[];
/**
* 返回文件操作处理器列表
* Get file action handlers
*/
getFileActionHandlers?(): FileActionHandler[];
/**
* 返回实体创建模板列表
* Get entity creation templates
*/
getEntityCreationTemplates?(): EntityCreationTemplate[];
/**
* 返回组件操作列表
* Get component actions
*/
getComponentActions?(): ComponentAction[];
/**
* 返回文件创建模板列表
* Get file creation templates
*/
getFileCreationTemplates?(): FileCreationTemplate[];
// ===== 生命周期钩子 | Lifecycle hooks =====
/** 编辑器就绪 | Editor ready */
onEditorReady?(): void | Promise<void>;
/** 项目打开 | Project open */
onProjectOpen?(projectPath: string): void | Promise<void>;
/** 项目关闭 | Project close */
onProjectClose?(): void | Promise<void>;
/** 场景加载 | Scene loaded */
onSceneLoaded?(scenePath: string): void;
/** 场景保存前 | Before scene save */
onSceneSaving?(scenePath: string): boolean | void;
/** 设置语言 | Set locale */
setLocale?(locale: string): void;
}
/**
* 统一插件加载器
* Unified plugin loader
*/
export interface IPluginLoader {
/** 插件描述符 | Plugin descriptor */
readonly descriptor: PluginDescriptor;
/** 运行时模块(可选) | Runtime module (optional) */
readonly runtimeModule?: IRuntimeModuleLoader;
/** 编辑器模块(可选) | Editor module (optional) */
readonly editorModule?: IEditorModuleLoader;
}
/**
* 文件创建模板
* File creation template
*/
export interface FileCreationTemplate {
/** 模板ID | Template ID */
id: string;
/** 标签 | Label */
label: string;
/** 扩展名 | Extension */
extension: string;
/** 图标 | Icon */
icon?: string;
/** 分类 | Category */
category?: string;
/** 创建函数 | Create function */
create: (filePath: string) => Promise<void>;
}

View File

@@ -0,0 +1,163 @@
/**
* 插件系统类型定义
* Plugin system type definitions
*/
/**
* 插件类别
* Plugin category
*/
export type PluginCategory =
| 'core' // 核心功能 | Core functionality
| 'rendering' // 渲染相关 | Rendering
| 'ui' // UI 系统 | UI System
| 'ai' // AI/行为树 | AI/Behavior
| 'physics' // 物理引擎 | Physics
| 'audio' // 音频系统 | Audio
| 'networking' // 网络功能 | Networking
| 'tools' // 工具/编辑器扩展 | Tools/Editor extensions
| 'content'; // 内容/资源 | Content/Assets
/**
* 加载阶段 - 控制插件模块的加载顺序
* Loading phase - controls the loading order of plugin modules
*/
export type LoadingPhase =
| 'earliest' // 最早加载(核心模块) | Earliest (core modules)
| 'preDefault' // 默认之前 | Before default
| 'default' // 默认阶段 | Default phase
| 'postDefault' // 默认之后 | After default
| 'postEngine'; // 引擎初始化后 | After engine init
/**
* 模块类型
* Module type
*/
export type ModuleType = 'runtime' | 'editor';
/**
* 模块描述符 - 描述插件内的一个模块
* Module descriptor - describes a module within a plugin
*/
export interface ModuleDescriptor {
/** 模块名称 | Module name */
name: string;
/** 模块类型 | Module type */
type: ModuleType;
/** 加载阶段 | Loading phase */
loadingPhase?: LoadingPhase;
/** 模块入口文件(相对路径) | Module entry file (relative path) */
entry?: string;
// ===== 运行时模块配置 | Runtime module config =====
/** 导出的组件类名列表 | Exported component class names */
components?: string[];
/** 导出的系统类名列表 | Exported system class names */
systems?: string[];
/** 导出的服务类名列表 | Exported service class names */
services?: string[];
// ===== 编辑器模块配置 | Editor module config =====
/** 注册的面板ID列表 | Registered panel IDs */
panels?: string[];
/** 注册的检视器类型列表 | Registered inspector types */
inspectors?: string[];
/** 注册的 Gizmo 提供者列表 | Registered Gizmo providers */
gizmoProviders?: string[];
/** 注册的编译器列表 | Registered compilers */
compilers?: string[];
/** 注册的文件处理器扩展名 | Registered file handler extensions */
fileHandlers?: string[];
}
/**
* 插件依赖
* Plugin dependency
*/
export interface PluginDependency {
/** 依赖的插件ID | Dependent plugin ID */
id: string;
/** 版本要求semver | Version requirement (semver) */
version?: string;
/** 是否可选 | Optional */
optional?: boolean;
}
/**
* 插件描述符 - 对应 plugin.json 文件
* Plugin descriptor - corresponds to plugin.json
*/
export interface PluginDescriptor {
/** 插件唯一标识符,如 "@esengine/tilemap" | Unique plugin ID */
id: string;
/** 显示名称 | Display name */
name: string;
/** 版本号 | Version */
version: string;
/** 描述 | Description */
description?: string;
/** 作者 | Author */
author?: string;
/** 许可证 | License */
license?: string;
/** 插件类别 | Plugin category */
category: PluginCategory;
/** 标签(用于搜索) | Tags (for search) */
tags?: string[];
/** 图标Lucide 图标名) | Icon (Lucide icon name) */
icon?: string;
/** 是否默认启用 | Enabled by default */
enabledByDefault: boolean;
/** 是否可以包含内容资产 | Can contain content assets */
canContainContent: boolean;
/** 是否为引擎内置插件 | Is engine built-in plugin */
isEnginePlugin: boolean;
/** 是否为核心插件(不可禁用) | Is core plugin (cannot be disabled) */
isCore?: boolean;
/** 模块列表 | Module list */
modules: ModuleDescriptor[];
/** 依赖列表 | Dependency list */
dependencies?: PluginDependency[];
/** 平台要求 | Platform requirements */
platforms?: ('web' | 'desktop' | 'mobile')[];
}
/**
* 插件状态
* Plugin state
*/
export type PluginState =
| 'unloaded' // 未加载 | Not loaded
| 'loading' // 加载中 | Loading
| 'loaded' // 已加载 | Loaded
| 'active' // 已激活 | Active
| 'error' // 错误 | Error
| 'disabled'; // 已禁用 | Disabled

View File

@@ -0,0 +1,775 @@
/**
* 统一插件管理器
* 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
} from './PluginDescriptor';
import type {
IPluginLoader,
SystemContext
} from './IPluginLoader';
import { EntityCreationRegistry } from '../Services/EntityCreationRegistry';
import { ComponentActionRegistry } from '../Services/ComponentActionRegistry';
import { FileActionRegistry } from '../Services/FileActionRegistry';
import { UIRegistry } from '../Services/UIRegistry';
const logger = createLogger('PluginManager');
/**
* PluginManager 服务标识符
* PluginManager service identifier
*/
export const IPluginManager = Symbol.for('IPluginManager');
/**
* 已注册的插件信息
* Registered plugin info
*/
export interface RegisteredPlugin {
/** 插件加载器 | Plugin loader */
loader: IPluginLoader;
/** 插件状态 | Plugin state */
state: PluginState;
/** 错误信息 | Error info */
error?: Error;
/** 是否启用 | Is enabled */
enabled: boolean;
/** 加载时间 | Load time */
loadedAt?: number;
/** 激活时间 | Activation time */
activatedAt?: number;
}
/**
* 插件配置
* 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;
constructor() {}
/**
* 释放资源
* Dispose resources
*/
dispose(): void {
this.reset();
}
/**
* 注册插件
* Register plugin
*/
register(loader: IPluginLoader): void {
if (!loader) {
logger.error('Cannot register plugin: loader is null or undefined');
return;
}
if (!loader.descriptor) {
logger.error('Cannot register plugin: descriptor is null or undefined', loader);
return;
}
const { id } = loader.descriptor;
if (!id) {
logger.error('Cannot register plugin: descriptor.id is null or undefined', loader.descriptor);
return;
}
if (this.plugins.has(id)) {
logger.warn(`Plugin ${id} is already registered, skipping`);
return;
}
const enabled = loader.descriptor.isCore || loader.descriptor.enabledByDefault;
this.plugins.set(id, {
loader,
state: 'loaded',
enabled,
loadedAt: Date.now()
});
logger.info(`Plugin registered: ${id} (${loader.descriptor.name})`);
}
/**
* 启用插件
* Enable plugin
*/
enable(pluginId: string): boolean {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (plugin.loader.descriptor.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled/enabled`);
return false;
}
// 检查依赖
const deps = plugin.loader.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 = 'loaded';
logger.info(`Plugin enabled: ${pluginId}`);
return true;
}
/**
* 禁用插件
* Disable plugin
*/
disable(pluginId: string): boolean {
const plugin = this.plugins.get(pluginId);
if (!plugin) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (plugin.loader.descriptor.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled`);
return false;
}
// 检查是否有其他插件依赖此插件
for (const [id, p] of this.plugins) {
if (!p.enabled || id === pluginId) continue;
const deps = p.loader.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;
}
}
plugin.enabled = false;
plugin.state = 'disabled';
logger.info(`Plugin disabled: ${pluginId}`);
return true;
}
/**
* 检查插件是否启用
* 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.loader.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;
}
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.loader.runtimeModule;
if (runtimeModule) {
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.loader.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.loader.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 {
logger.info('Creating systems for scene...');
const sortedPlugins = this.sortByLoadingPhase('runtime');
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled || plugin.state === 'error') continue;
const runtimeModule = plugin.loader.runtimeModule;
if (runtimeModule?.createSystems) {
try {
runtimeModule.createSystems(scene, context);
logger.debug(`Systems created for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to create 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;
}
logger.info('Initializing editor modules...');
const sortedPlugins = this.sortByLoadingPhase('editor');
// 获取注册表服务 | Get registry services
const entityCreationRegistry = services.tryResolve(EntityCreationRegistry);
const componentActionRegistry = services.tryResolve(ComponentActionRegistry);
const fileActionRegistry = services.tryResolve(FileActionRegistry);
const uiRegistry = services.tryResolve(UIRegistry);
for (const pluginId of sortedPlugins) {
const plugin = this.plugins.get(pluginId);
if (!plugin?.enabled) continue;
const editorModule = plugin.loader.editorModule;
if (editorModule) {
try {
// 安装编辑器模块 | Install editor module
await editorModule.install(services);
logger.debug(`Editor module installed: ${pluginId}`);
// 注册实体创建模板 | Register entity creation templates
if (entityCreationRegistry && editorModule.getEntityCreationTemplates) {
const templates = editorModule.getEntityCreationTemplates();
if (templates && templates.length > 0) {
entityCreationRegistry.registerMany(templates);
logger.debug(`Registered ${templates.length} entity templates from: ${pluginId}`);
}
}
// 注册组件操作 | Register component actions
if (componentActionRegistry && editorModule.getComponentActions) {
const actions = editorModule.getComponentActions();
if (actions && actions.length > 0) {
for (const action of actions) {
componentActionRegistry.register(action);
}
logger.debug(`Registered ${actions.length} component actions from: ${pluginId}`);
}
}
// 注册文件操作处理器 | Register file action handlers
if (fileActionRegistry && editorModule.getFileActionHandlers) {
const handlers = editorModule.getFileActionHandlers();
if (handlers && handlers.length > 0) {
for (const handler of handlers) {
fileActionRegistry.registerActionHandler(handler);
}
logger.debug(`Registered ${handlers.length} file action handlers from: ${pluginId}`);
}
}
// 注册文件创建模板 | Register file creation templates
if (fileActionRegistry && editorModule.getFileCreationTemplates) {
const templates = editorModule.getFileCreationTemplates();
if (templates && templates.length > 0) {
for (const template of templates) {
fileActionRegistry.registerCreationTemplate(template);
}
logger.debug(`Registered ${templates.length} file creation templates from: ${pluginId}`);
}
}
// 注册面板 | Register panels
if (uiRegistry && editorModule.getPanels) {
const panels = editorModule.getPanels();
if (panels && panels.length > 0) {
uiRegistry.registerPanels(panels);
logger.debug(`Registered ${panels.length} panels from: ${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;
}
const editorModule = plugin.loader.editorModule;
if (!editorModule) {
return;
}
// 获取注册表服务 | Get registry services
const entityCreationRegistry = services.tryResolve(EntityCreationRegistry);
const componentActionRegistry = services.tryResolve(ComponentActionRegistry);
const fileActionRegistry = services.tryResolve(FileActionRegistry);
const uiRegistry = services.tryResolve(UIRegistry);
try {
// 安装编辑器模块 | Install editor module
await editorModule.install(services);
logger.debug(`Editor module installed: ${pluginId}`);
// 注册实体创建模板 | Register entity creation templates
if (entityCreationRegistry && editorModule.getEntityCreationTemplates) {
const templates = editorModule.getEntityCreationTemplates();
if (templates && templates.length > 0) {
entityCreationRegistry.registerMany(templates);
logger.debug(`Registered ${templates.length} entity templates from: ${pluginId}`);
}
}
// 注册组件操作 | Register component actions
if (componentActionRegistry && editorModule.getComponentActions) {
const actions = editorModule.getComponentActions();
if (actions && actions.length > 0) {
for (const action of actions) {
componentActionRegistry.register(action);
}
logger.debug(`Registered ${actions.length} component actions from: ${pluginId}`);
}
}
// 注册文件操作处理器 | Register file action handlers
if (fileActionRegistry && editorModule.getFileActionHandlers) {
const handlers = editorModule.getFileActionHandlers();
if (handlers && handlers.length > 0) {
for (const handler of handlers) {
fileActionRegistry.registerActionHandler(handler);
}
logger.debug(`Registered ${handlers.length} file action handlers from: ${pluginId}`);
}
}
// 注册文件创建模板 | Register file creation templates
if (fileActionRegistry && editorModule.getFileCreationTemplates) {
const templates = editorModule.getFileCreationTemplates();
if (templates && templates.length > 0) {
for (const template of templates) {
fileActionRegistry.registerCreationTemplate(template);
}
logger.debug(`Registered ${templates.length} file creation templates from: ${pluginId}`);
}
}
// 注册面板 | Register panels
if (uiRegistry && editorModule.getPanels) {
const panels = editorModule.getPanels();
if (panels && panels.length > 0) {
uiRegistry.registerPanels(panels);
logger.debug(`Registered ${panels.length} panels from: ${pluginId}`);
}
}
// 调用 onEditorReady如果编辑器已就绪
if (this.editorInitialized && editorModule.onEditorReady) {
await editorModule.onEditorReady();
}
} 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.loader.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.loader.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.loader.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.loader.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.loader.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.loader.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.loader.descriptor.isCore) {
enabledPlugins.push(id);
}
}
return { enabledPlugins };
}
/**
* 加载配置
* Load configuration
*/
loadConfig(config: PluginConfig): void {
const { enabledPlugins } = config;
for (const [id, plugin] of this.plugins) {
if (plugin.loader.descriptor.isCore) {
plugin.enabled = true;
} else {
plugin.enabled = enabledPlugins.includes(id);
plugin.state = plugin.enabled ? 'loaded' : 'disabled';
}
}
logger.info(`Loaded config: ${enabledPlugins.length} plugins enabled`);
}
/**
* 按加载阶段排序
* 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?.loader.descriptor.modules.find(m => m.type === 'runtime')
: pluginA?.loader.descriptor.modules.find(m => m.type === 'editor');
const moduleB = moduleType === 'runtime'
? pluginB?.loader.descriptor.modules.find(m => m.type === 'runtime')
: pluginB?.loader.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.loader.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.loader.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');
}
}

View File

@@ -0,0 +1,8 @@
/**
* 插件系统
* Plugin System
*/
export * from './PluginDescriptor';
export * from './IPluginLoader';
export * from './PluginManager';

View File

@@ -1,479 +0,0 @@
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';
import { FileActionRegistry } from '../Services/FileActionRegistry';
import { EntityCreationRegistry } from '../Services/EntityCreationRegistry';
import { ComponentActionRegistry } from '../Services/ComponentActionRegistry';
import { pluginRegistry } from './PluginRegistry';
import type { EditorPluginDefinition } from './PluginTypes';
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;
private fileActionRegistry: FileActionRegistry | null = null;
private entityCreationRegistry: EntityCreationRegistry | null = null;
private componentActionRegistry: ComponentActionRegistry | 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);
this.fileActionRegistry = services.resolve(FileActionRegistry);
this.entityCreationRegistry = services.resolve(EntityCreationRegistry);
this.componentActionRegistry = services.resolve(ComponentActionRegistry);
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.registerFileActionHandlers && this.fileActionRegistry) {
const handlers = plugin.registerFileActionHandlers();
for (const handler of handlers) {
this.fileActionRegistry.registerActionHandler(handler);
}
logger.debug(`Registered ${handlers.length} file action handlers for ${plugin.name}`);
}
if (plugin.registerFileCreationTemplates && this.fileActionRegistry) {
const templates = plugin.registerFileCreationTemplates();
for (const template of templates) {
this.fileActionRegistry.registerCreationTemplate(template);
}
logger.debug(`Registered ${templates.length} file creation templates for ${plugin.name}`);
}
if (plugin.registerEntityCreationTemplates && this.entityCreationRegistry) {
const templates = plugin.registerEntityCreationTemplates();
this.entityCreationRegistry.registerMany(templates);
logger.debug(`Registered ${templates.length} entity creation templates for ${plugin.name}`);
}
if (plugin.registerComponentActions && this.componentActionRegistry) {
const actions = plugin.registerComponentActions();
this.componentActionRegistry.registerMany(actions);
logger.debug(`Registered ${actions.length} component actions 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);
}
}
if (plugin.registerFileActionHandlers && this.fileActionRegistry) {
const handlers = plugin.registerFileActionHandlers();
for (const handler of handlers) {
this.fileActionRegistry.unregisterActionHandler(handler);
}
}
if (plugin.registerFileCreationTemplates && this.fileActionRegistry) {
const templates = plugin.registerFileCreationTemplates();
for (const template of templates) {
this.fileActionRegistry.unregisterCreationTemplate(template);
}
}
if (plugin.registerEntityCreationTemplates && this.entityCreationRegistry) {
const templates = plugin.registerEntityCreationTemplates();
for (const template of templates) {
this.entityCreationRegistry.unregister(template.id);
}
}
if (plugin.registerComponentActions && this.componentActionRegistry) {
const actions = plugin.registerComponentActions();
for (const action of actions) {
this.componentActionRegistry.unregister(action.componentName, action.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);
}
/**
* 获取所有插件元数据
*
* 实时从插件实例获取 displayName 和 description以支持多语言切换
*/
public getAllPluginMetadata(): IEditorPluginMetadata[] {
const metadataList: IEditorPluginMetadata[] = [];
for (const [name, metadata] of this.pluginMetadata.entries()) {
const plugin = this.editorPlugins.get(name);
// 如果插件实例存在,使用实时的 displayName 和 description
if (plugin) {
metadataList.push({
...metadata,
displayName: plugin.displayName,
description: plugin.description
});
} else {
// 回退到缓存的元数据
metadataList.push(metadata);
}
}
return metadataList;
}
/**
* 按类别获取插件
*/
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 });
}
/**
* 使用声明式 API 注册插件
* Register plugin using declarative API
*/
public async registerPlugin(definition: EditorPluginDefinition): Promise<void> {
logger.info(`Registering plugin with declarative API: ${definition.id}`);
try {
// 使用 PluginRegistry 注册
await pluginRegistry.register(definition);
// 同步到旧的元数据系统以保持兼容性
const metadata: IEditorPluginMetadata = {
name: definition.id,
displayName: definition.name,
version: definition.version || '1.0.0',
category: EditorPluginCategory.Tool,
description: definition.description,
enabled: true,
installedAt: Date.now()
};
this.pluginMetadata.set(definition.id, metadata);
// 注册实体创建模板
if (definition.entityTemplates && this.entityCreationRegistry) {
for (const template of definition.entityTemplates) {
this.entityCreationRegistry.register({
id: `${definition.id}:${template.id}`,
label: template.label,
icon: template.icon,
order: template.priority,
create: template.create
});
}
}
// 注册组件操作
if (definition.components && this.componentActionRegistry) {
for (const comp of definition.components) {
if (comp.actions) {
for (const action of comp.actions) {
this.componentActionRegistry.register({
id: action.id,
componentName: comp.type.name,
label: action.label,
icon: action.icon,
execute: action.execute as unknown as (component: any, entity: any) => void | Promise<void>
});
}
}
}
}
await this.messageHub?.publish('plugin:installed', {
name: definition.id,
displayName: definition.name,
category: EditorPluginCategory.Tool
});
logger.info(`Plugin ${definition.id} registered successfully`);
} catch (error) {
logger.error(`Failed to register plugin ${definition.id}:`, error);
throw error;
}
}
/**
* 获取 PluginRegistry 实例
* Get PluginRegistry instance
*/
public getPluginRegistry() {
return pluginRegistry;
}
/**
* 释放资源
*/
public override dispose(): void {
super.dispose();
this.editorPlugins.clear();
this.pluginMetadata.clear();
this.uiRegistry = null;
this.messageHub = null;
this.serializerRegistry = null;
this.fileActionRegistry = null;
this.entityCreationRegistry = null;
this.componentActionRegistry = null;
logger.info('EditorPluginManager disposed');
}
}

View File

@@ -1,286 +0,0 @@
import type { IPlugin } from '@esengine/ecs-framework';
import type { MenuItem, ToolbarItem, PanelDescriptor, EntityCreationTemplate } from '../Types/UITypes';
import type { ComponentAction } from '../Services/ComponentActionRegistry';
import type { ReactNode } from 'react';
/**
* 编辑器插件类别
*/
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 FileContextMenuItem {
/**
* 菜单项标签
*/
label: string;
/**
* 图标
*/
icon?: ReactNode;
/**
* 点击处理函数
*/
onClick: (filePath: string, parentPath: string) => void | Promise<void>;
/**
* 是否禁用
*/
disabled?: boolean;
/**
* 是否为分隔符
*/
separator?: boolean;
}
/**
* 文件创建模板
*/
export interface FileCreationTemplate {
/**
* 模板名称
*/
label: string;
/**
* 文件扩展名(不含点)
*/
extension: string;
/**
* 默认文件名
*/
defaultFileName: string;
/**
* 图标
*/
icon?: ReactNode;
/**
* 创建文件内容的函数
*/
createContent: (fileName: string) => string | Promise<string>;
}
/**
* 文件操作处理器
*/
export interface FileActionHandler {
/**
* 支持的文件扩展名列表
*/
extensions: string[];
/**
* 双击处理函数
*/
onDoubleClick?: (filePath: string) => void | Promise<void>;
/**
* 打开文件处理函数
*/
onOpen?: (filePath: string) => void | Promise<void>;
/**
* 获取上下文菜单项
*/
getContextMenuItems?: (filePath: string, parentPath: string) => FileContextMenuItem[];
}
/**
* 编辑器插件接口
*
* 扩展了运行时插件接口,添加了编辑器特定的功能。
*/
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>;
/**
* 设置插件语言
*/
setLocale?(locale: string): void;
/**
* 获取行为树节点模板
*/
getNodeTemplates?(): any[];
/**
* 注册文件操作处理器
*/
registerFileActionHandlers?(): FileActionHandler[];
/**
* 注册文件创建模板
*/
registerFileCreationTemplates?(): FileCreationTemplate[];
/**
* 注册实体创建模板
*/
registerEntityCreationTemplates?(): EntityCreationTemplate[];
/**
* 注册组件操作
*/
registerComponentActions?(): ComponentAction[];
}
/**
* 编辑器插件元数据
*/
export interface IEditorPluginMetadata {
/**
* 插件名称
*/
name: string;
/**
* 显示名称
*/
displayName: string;
/**
* 版本
*/
version: string;
/**
* 类别
*/
category: EditorPluginCategory;
/**
* 描述
*/
description?: string;
/**
* 图标
*/
icon?: string;
/**
* 是否已启用
*/
enabled: boolean;
/**
* 安装时间戳
*/
installedAt?: number;
}

View File

@@ -1,528 +0,0 @@
/**
* 统一插件注册中心
* Unified plugin registry
*/
import type { ComponentType } from '@esengine/ecs-framework';
import type { ComponentType as ReactComponentType } from 'react';
import {
EditorPluginDefinition,
RegisteredPlugin,
PluginState,
ComponentRegistration,
MenuItemRegistration,
PanelRegistration,
ToolbarItemRegistration,
AssetHandlerRegistration,
MenuTreeNode,
ComponentActionDefinition
} from './PluginTypes';
/**
* 插件注册中心
* Plugin registry - central hub for all plugin registrations
*/
export class PluginRegistry {
private static _instance: PluginRegistry | null = null;
/** 已注册的插件 */
private plugins: Map<string, RegisteredPlugin> = new Map();
/** 组件到检查器的映射 */
private componentInspectors: Map<string, ReactComponentType<any>> = new Map();
/** 组件元数据 */
private componentMeta: Map<string, ComponentRegistration> = new Map();
/** 组件操作 */
private componentActions: Map<string, ComponentActionDefinition[]> = new Map();
/** 菜单树 */
private menuTree: MenuTreeNode = {
name: 'root',
path: '',
children: new Map()
};
/** 面板注册 */
private panels: Map<string, PanelRegistration & { pluginId: string }> = new Map();
/** 工具栏项 */
private toolbarItems: Map<string, ToolbarItemRegistration & { pluginId: string }> = new Map();
/** 资产处理器 */
private assetHandlers: Map<string, AssetHandlerRegistration & { pluginId: string }> = new Map();
/** 事件监听器 */
private listeners: Map<string, Set<Function>> = new Map();
private constructor() {}
/**
* 获取单例实例
*/
static getInstance(): PluginRegistry {
if (!PluginRegistry._instance) {
PluginRegistry._instance = new PluginRegistry();
}
return PluginRegistry._instance;
}
/**
* 注册插件
* Register a plugin
*/
async register(definition: EditorPluginDefinition): Promise<void> {
if (this.plugins.has(definition.id)) {
console.warn(`Plugin ${definition.id} is already registered`);
return;
}
// 检查依赖
if (definition.dependencies) {
for (const dep of definition.dependencies) {
if (!this.plugins.has(dep)) {
throw new Error(`Plugin ${definition.id} depends on ${dep}, which is not registered`);
}
const depPlugin = this.plugins.get(dep)!;
if (depPlugin.state !== 'active') {
throw new Error(`Plugin ${definition.id} depends on ${dep}, which is not active`);
}
}
}
// 创建注册记录
const registered: RegisteredPlugin = {
definition,
state: 'inactive'
};
this.plugins.set(definition.id, registered);
// 激活插件
await this.activatePlugin(definition.id);
}
/**
* 激活插件
*/
private async activatePlugin(pluginId: string): Promise<void> {
const registered = this.plugins.get(pluginId);
if (!registered) return;
const { definition } = registered;
registered.state = 'activating';
try {
// 注册组件
if (definition.components) {
for (const comp of definition.components) {
this.registerComponent(pluginId, comp);
}
}
// 注册菜单项
if (definition.menuItems) {
for (const item of definition.menuItems) {
this.registerMenuItem(pluginId, item);
}
}
// 注册面板
if (definition.panels) {
for (const panel of definition.panels) {
this.registerPanel(pluginId, panel);
}
}
// 注册工具栏项
if (definition.toolbarItems) {
for (const item of definition.toolbarItems) {
this.registerToolbarItem(pluginId, item);
}
}
// 注册资产处理器
if (definition.assetHandlers) {
for (const handler of definition.assetHandlers) {
this.registerAssetHandler(pluginId, handler);
}
}
// 调用激活钩子
if (definition.onActivate) {
await definition.onActivate();
}
registered.state = 'active';
registered.activatedAt = Date.now();
this.emit('plugin:activated', { pluginId });
} catch (error) {
registered.state = 'error';
registered.error = error instanceof Error ? error.message : String(error);
console.error(`Failed to activate plugin ${pluginId}:`, error);
throw error;
}
}
/**
* 停用插件
*/
async deactivate(pluginId: string): Promise<void> {
const registered = this.plugins.get(pluginId);
if (!registered || registered.state !== 'active') return;
const { definition } = registered;
registered.state = 'deactivating';
try {
// 调用停用钩子
if (definition.onDeactivate) {
definition.onDeactivate();
}
// 清理注册的资源
this.unregisterPluginResources(pluginId);
registered.state = 'inactive';
this.emit('plugin:deactivated', { pluginId });
} catch (error) {
registered.state = 'error';
registered.error = error instanceof Error ? error.message : String(error);
throw error;
}
}
/**
* 注册组件
*/
private registerComponent(pluginId: string, config: ComponentRegistration): void {
const typeName = config.type.name;
// 保存元数据
this.componentMeta.set(typeName, config);
// 注册检查器
if (config.inspector) {
this.componentInspectors.set(typeName, config.inspector);
}
// 注册操作
if (config.actions) {
this.componentActions.set(typeName, config.actions);
}
this.emit('component:registered', { pluginId, typeName, config });
}
/**
* 注册菜单项
*/
private registerMenuItem(pluginId: string, item: MenuItemRegistration): void {
const parts = item.path.split('/');
let current = this.menuTree;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
const isLast = i === parts.length - 1;
if (!current.children.has(part)) {
current.children.set(part, {
name: part,
path: parts.slice(0, i + 1).join('/'),
children: new Map()
});
}
current = current.children.get(part)!;
if (isLast) {
current.item = item;
current.pluginId = pluginId;
}
}
this.emit('menu:registered', { pluginId, path: item.path });
}
/**
* 注册面板
*/
private registerPanel(pluginId: string, panel: PanelRegistration): void {
this.panels.set(panel.id, { ...panel, pluginId });
this.emit('panel:registered', { pluginId, panelId: panel.id });
}
/**
* 注册工具栏项
*/
private registerToolbarItem(pluginId: string, item: ToolbarItemRegistration): void {
this.toolbarItems.set(item.id, { ...item, pluginId });
this.emit('toolbar:registered', { pluginId, itemId: item.id });
}
/**
* 注册资产处理器
*/
private registerAssetHandler(pluginId: string, handler: AssetHandlerRegistration): void {
for (const ext of handler.extensions) {
this.assetHandlers.set(ext.toLowerCase(), { ...handler, pluginId });
}
this.emit('assetHandler:registered', { pluginId, extensions: handler.extensions });
}
/**
* 清理插件资源
*/
private unregisterPluginResources(pluginId: string): void {
const definition = this.plugins.get(pluginId)?.definition;
if (!definition) return;
// 清理组件
if (definition.components) {
for (const comp of definition.components) {
const typeName = comp.type.name;
this.componentMeta.delete(typeName);
this.componentInspectors.delete(typeName);
this.componentActions.delete(typeName);
}
}
// 清理面板
if (definition.panels) {
for (const panel of definition.panels) {
this.panels.delete(panel.id);
}
}
// 清理工具栏项
if (definition.toolbarItems) {
for (const item of definition.toolbarItems) {
this.toolbarItems.delete(item.id);
}
}
// 清理资产处理器
if (definition.assetHandlers) {
for (const handler of definition.assetHandlers) {
for (const ext of handler.extensions) {
this.assetHandlers.delete(ext.toLowerCase());
}
}
}
// 清理菜单项(需要遍历树)
this.removeMenuItemsForPlugin(pluginId, this.menuTree);
}
/**
* 递归移除插件的菜单项
*/
private removeMenuItemsForPlugin(pluginId: string, node: MenuTreeNode): void {
for (const [key, child] of node.children) {
if (child.pluginId === pluginId) {
node.children.delete(key);
} else {
this.removeMenuItemsForPlugin(pluginId, child);
}
}
}
// === 查询 API ===
/**
* 获取组件的检查器
*/
getComponentInspector(typeName: string): ReactComponentType<any> | undefined {
return this.componentInspectors.get(typeName);
}
/**
* 获取组件元数据
*/
getComponentMeta(typeName: string): ComponentRegistration | undefined {
return this.componentMeta.get(typeName);
}
/**
* 获取组件操作
*/
getComponentActionDefinitions(typeName: string): ComponentActionDefinition[] {
return this.componentActions.get(typeName) || [];
}
/**
* 获取所有已注册的组件
*/
getAllComponents(): Map<string, ComponentRegistration> {
return new Map(this.componentMeta);
}
/**
* 获取菜单树
*/
getMenuTree(): MenuTreeNode {
return this.menuTree;
}
/**
* 获取指定路径下的菜单项
*/
getMenuItems(parentPath: string): MenuItemRegistration[] {
const parts = parentPath ? parentPath.split('/') : [];
let current = this.menuTree;
for (const part of parts) {
if (!current.children.has(part)) {
return [];
}
current = current.children.get(part)!;
}
const items: MenuItemRegistration[] = [];
for (const child of current.children.values()) {
if (child.item) {
items.push(child.item);
}
}
return items.sort((a, b) => (a.priority || 100) - (b.priority || 100));
}
/**
* 获取所有面板
*/
getAllPanels(): Map<string, PanelRegistration & { pluginId: string }> {
return new Map(this.panels);
}
/**
* 获取面板
*/
getPanel(panelId: string): (PanelRegistration & { pluginId: string }) | undefined {
return this.panels.get(panelId);
}
/**
* 获取所有工具栏项
*/
getAllToolbarItems(): (ToolbarItemRegistration & { pluginId: string })[] {
return Array.from(this.toolbarItems.values())
.sort((a, b) => (a.priority || 100) - (b.priority || 100));
}
/**
* 获取资产处理器
*/
getAssetHandler(extension: string): (AssetHandlerRegistration & { pluginId: string }) | undefined {
return this.assetHandlers.get(extension.toLowerCase());
}
/**
* 获取插件状态
*/
getPluginState(pluginId: string): PluginState | undefined {
return this.plugins.get(pluginId)?.state;
}
/**
* 获取所有已注册插件
*/
getAllPlugins(): Map<string, RegisteredPlugin> {
return new Map(this.plugins);
}
/**
* 检查插件是否已激活
*/
isPluginActive(pluginId: string): boolean {
return this.plugins.get(pluginId)?.state === 'active';
}
// === 事件系统 ===
/**
* 添加事件监听器
*/
on(event: string, listener: Function): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(listener);
}
/**
* 移除事件监听器
*/
off(event: string, listener: Function): void {
this.listeners.get(event)?.delete(listener);
}
/**
* 发射事件
*/
private emit(event: string, data: any): void {
const listeners = this.listeners.get(event);
if (listeners) {
for (const listener of listeners) {
try {
listener(data);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
}
}
}
/**
* 通知编辑器准备就绪
*/
notifyEditorReady(): void {
for (const [pluginId, registered] of this.plugins) {
if (registered.state === 'active' && registered.definition.onEditorReady) {
try {
registered.definition.onEditorReady();
} catch (error) {
console.error(`Error in onEditorReady for plugin ${pluginId}:`, error);
}
}
}
}
/**
* 通知场景加载
*/
notifySceneLoaded(scenePath: string): void {
for (const [pluginId, registered] of this.plugins) {
if (registered.state === 'active' && registered.definition.onSceneLoaded) {
try {
registered.definition.onSceneLoaded(scenePath);
} catch (error) {
console.error(`Error in onSceneLoaded for plugin ${pluginId}:`, error);
}
}
}
}
/**
* 通知场景保存
*/
notifySceneSaving(scenePath: string): boolean {
for (const [pluginId, registered] of this.plugins) {
if (registered.state === 'active' && registered.definition.onSceneSaving) {
try {
const result = registered.definition.onSceneSaving(scenePath);
if (result === false) {
return false; // 取消保存
}
} catch (error) {
console.error(`Error in onSceneSaving for plugin ${pluginId}:`, error);
}
}
}
return true;
}
}
// 导出单例访问
export const pluginRegistry = PluginRegistry.getInstance();

View File

@@ -1,227 +0,0 @@
/**
* 统一插件类型定义
* Unified plugin type definitions
*/
import type { ComponentType } from '@esengine/ecs-framework';
import type { ComponentType as ReactComponentType } from 'react';
/**
* 组件注册配置
* Component registration configuration
*/
export interface ComponentRegistration {
/** 组件类型 */
type: ComponentType;
/** 自定义检查器组件 */
inspector?: ReactComponentType<any>;
/** 图标名称 */
icon?: string;
/** 分类 */
category?: string;
/** 显示名称 */
displayName?: string;
/** 组件操作(如右键菜单) */
actions?: ComponentActionDefinition[];
}
/**
* 组件操作定义(用于声明式 API
* Component action definition (for declarative API)
*/
export interface ComponentActionDefinition {
/** 操作ID */
id: string;
/** 显示标签 */
label: string;
/** 图标 */
icon?: string;
/** 执行函数 */
execute: (componentData: any, entityId: number) => void | Promise<void>;
}
/**
* 菜单项注册配置
* Menu item registration configuration
*/
export interface MenuItemRegistration {
/** 菜单路径,如 "GameObject/2D Object/Tilemap" */
path: string;
/** 执行动作 */
action: () => void | number | Promise<void> | Promise<number>;
/** 图标 */
icon?: string;
/** 快捷键 */
shortcut?: string;
/** 优先级(数字越小越靠前) */
priority?: number;
}
/**
* 面板注册配置
* Panel registration configuration
*/
export interface PanelRegistration {
/** 面板ID */
id: string;
/** 面板组件 */
component: ReactComponentType<any>;
/** 标题 */
title: string;
/** 图标 */
icon?: string;
/** 默认位置 */
defaultPosition?: 'left' | 'right' | 'bottom' | 'float';
/** 默认是否可见 */
defaultVisible?: boolean;
}
/**
* 工具栏项注册配置
* Toolbar item registration configuration
*/
export interface ToolbarItemRegistration {
/** 工具ID */
id: string;
/** 显示标签 */
label: string;
/** 图标 */
icon: string;
/** 工具提示 */
tooltip?: string;
/** 执行动作 */
action: () => void;
/** 分组 */
group?: string;
/** 优先级 */
priority?: number;
}
/**
* 实体创建模板注册配置
*/
export interface EntityTemplateRegistration {
/** 模板ID */
id: string;
/** 显示名称 */
label: string;
/** 分类路径,如 "2D Object" */
category?: string;
/** 图标 */
icon?: string;
/** 排序优先级 */
priority?: number;
/** 创建实体函数返回实体ID */
create: (parentEntityId?: number) => number | Promise<number>;
}
/**
* 资产处理器注册配置
* Asset handler registration configuration
*/
export interface AssetHandlerRegistration {
/** 文件扩展名列表 */
extensions: string[];
/** 处理器名称 */
name: string;
/** 图标 */
icon?: string;
/** 打开资产 */
onOpen?: (assetPath: string) => void | Promise<void>;
/** 预览资产 */
onPreview?: (assetPath: string) => ReactComponentType<any> | null;
/** 创建资产 */
onCreate?: () => Promise<string | null>;
}
/**
* 编辑器插件定义
* Editor plugin definition
*/
export interface EditorPluginDefinition {
/** 插件唯一ID */
id: string;
/** 插件显示名称 */
name: string;
/** 版本号 */
version?: string;
/** 描述 */
description?: string;
/** 依赖的其他插件ID */
dependencies?: string[];
// === 注册配置 ===
/** 组件注册 */
components?: ComponentRegistration[];
/** 菜单项注册 */
menuItems?: MenuItemRegistration[];
/** 面板注册 */
panels?: PanelRegistration[];
/** 工具栏项注册 */
toolbarItems?: ToolbarItemRegistration[];
/** 资产处理器注册 */
assetHandlers?: AssetHandlerRegistration[];
/** 实体创建模板 */
entityTemplates?: EntityTemplateRegistration[];
// === 生命周期钩子 ===
/** 插件激活时调用 */
onActivate?: () => void | Promise<void>;
/** 插件停用时调用 */
onDeactivate?: () => void;
/** 编辑器准备就绪时调用 */
onEditorReady?: () => void | Promise<void>;
/** 场景加载后调用 */
onSceneLoaded?: (scenePath: string) => void;
/** 场景保存前调用 */
onSceneSaving?: (scenePath: string) => boolean | void;
}
/**
* 插件状态
* Plugin state
*/
export type PluginState = 'inactive' | 'activating' | 'active' | 'deactivating' | 'error';
/**
* 已注册的插件信息
* Registered plugin info
*/
export interface RegisteredPlugin {
/** 插件定义 */
definition: EditorPluginDefinition;
/** 当前状态 */
state: PluginState;
/** 错误信息(如果有) */
error?: string;
/** 激活时间 */
activatedAt?: number;
}
/**
* 菜单树节点
* Menu tree node
*/
export interface MenuTreeNode {
/** 节点名称 */
name: string;
/** 完整路径 */
path: string;
/** 子节点 */
children: Map<string, MenuTreeNode>;
/** 菜单项(叶子节点) */
item?: MenuItemRegistration;
/** 来源插件ID */
pluginId?: string;
}

View File

@@ -5,17 +5,10 @@
*/
import { injectable } from 'tsyringe';
import type { IService, Component, Entity } from '@esengine/ecs-framework';
import type { ReactNode } from 'react';
export interface ComponentAction {
id: string;
componentName: string;
label: string;
icon?: ReactNode;
order?: number;
execute: (component: Component, entity: Entity) => void | Promise<void>;
}
import type { IService } from '@esengine/ecs-framework';
import type { ComponentAction } from '../Plugin/IPluginLoader';
// Re-export ComponentAction type from Plugin system
export type { ComponentAction } from '../Plugin/IPluginLoader';
@injectable()
export class ComponentActionRegistry implements IService {

View File

@@ -1,5 +1,8 @@
import { IService } from '@esengine/ecs-framework';
import { FileActionHandler, FileCreationTemplate } from '../Plugins/IEditorPlugin';
import type { FileActionHandler, FileCreationTemplate } from '../Plugin/IPluginLoader';
// Re-export for backwards compatibility
export type { FileCreationTemplate } from '../Plugin/IPluginLoader';
/**
* 文件操作注册表服务

View File

@@ -6,7 +6,7 @@ import type { IFileAPI } from '../Types/IFileAPI';
const logger = createLogger('ProjectService');
export type ProjectType = 'cocos' | 'laya' | 'unknown';
export type ProjectType = 'esengine' | 'unknown';
export interface ProjectInfo {
path: string;
@@ -15,6 +15,17 @@ export interface ProjectInfo {
configPath?: string;
}
/**
* UI 设计分辨率配置
* UI Design Resolution Configuration
*/
export interface UIDesignResolution {
/** 设计宽度 / Design width */
width: number;
/** 设计高度 / Design height */
height: number;
}
export interface ProjectConfig {
projectType?: ProjectType;
componentsPath?: string;
@@ -22,6 +33,8 @@ export interface ProjectConfig {
buildOutput?: string;
scenesPath?: string;
defaultScene?: string;
/** UI 设计分辨率 / UI design resolution */
uiDesignResolution?: UIDesignResolution;
}
@Injectable()
@@ -47,11 +60,11 @@ export class ProjectService implements IService {
}
const config: ProjectConfig = {
projectType: 'cocos',
projectType: 'esengine',
componentsPath: 'components',
componentPattern: '**/*.ts',
buildOutput: 'temp/editor-components',
scenesPath: 'ecs-scenes',
scenesPath: 'scenes',
defaultScene: 'main.ecs'
};
@@ -176,7 +189,7 @@ export class ProjectService implements IService {
try {
projectInfo.configPath = configPath;
projectInfo.type = 'cocos';
projectInfo.type = 'esengine';
} catch (error) {
logger.warn('No ecs-editor.config.json found, using defaults');
}
@@ -185,14 +198,86 @@ export class ProjectService implements IService {
}
private async loadConfig(configPath: string): Promise<ProjectConfig> {
return {
projectType: 'cocos',
componentsPath: '',
componentPattern: '**/*.ts',
buildOutput: 'temp/editor-components',
scenesPath: 'ecs-scenes',
defaultScene: 'main.ecs'
try {
const content = await this.fileAPI.readFileContent(configPath);
const config = JSON.parse(content) as ProjectConfig;
return {
projectType: config.projectType || 'esengine',
componentsPath: config.componentsPath || '',
componentPattern: config.componentPattern || '**/*.ts',
buildOutput: config.buildOutput || 'temp/editor-components',
scenesPath: config.scenesPath || 'scenes',
defaultScene: config.defaultScene || 'main.ecs',
uiDesignResolution: config.uiDesignResolution
};
} catch (error) {
logger.warn('Failed to load config, using defaults', error);
return {
projectType: 'esengine',
componentsPath: '',
componentPattern: '**/*.ts',
buildOutput: 'temp/editor-components',
scenesPath: 'scenes',
defaultScene: 'main.ecs'
};
}
}
/**
* 保存项目配置
*/
public async saveConfig(): Promise<void> {
if (!this.currentProject?.configPath || !this.projectConfig) {
logger.warn('No project or config to save');
return;
}
try {
const content = JSON.stringify(this.projectConfig, null, 2);
await this.fileAPI.writeFileContent(this.currentProject.configPath, content);
logger.info('Project config saved');
} catch (error) {
logger.error('Failed to save project config', error);
throw error;
}
}
/**
* 更新项目配置
*/
public async updateConfig(updates: Partial<ProjectConfig>): Promise<void> {
if (!this.projectConfig) {
logger.warn('No project config to update');
return;
}
this.projectConfig = {
...this.projectConfig,
...updates
};
await this.saveConfig();
await this.messageHub.publish('project:configUpdated', { config: this.projectConfig });
}
/**
* 获取 UI 设计分辨率
* Get UI design resolution
*
* @returns UI design resolution, defaults to 1920x1080 if not set
*/
public getUIDesignResolution(): UIDesignResolution {
return this.projectConfig?.uiDesignResolution || { width: 1920, height: 1080 };
}
/**
* 设置 UI 设计分辨率
* Set UI design resolution
*
* @param resolution - The new design resolution
*/
public async setUIDesignResolution(resolution: UIDesignResolution): Promise<void> {
await this.updateConfig({ uiDesignResolution: resolution });
}
public dispose(): void {

View File

@@ -5,6 +5,7 @@ import type { MessageHub } from './MessageHub';
import type { IFileAPI } from '../Types/IFileAPI';
import type { ProjectService } from './ProjectService';
import type { EntityStoreService } from './EntityStoreService';
import { SceneTemplateRegistry } from './SceneTemplateRegistry';
const logger = createLogger('SceneManagerService');
@@ -45,7 +46,13 @@ export class SceneManagerService implements IService {
this.sceneResourceManager = manager;
}
public async newScene(): Promise<void> {
/**
* 创建新场景
* Create a new scene
*
* @param templateName - 场景模板名称,不传则使用默认模板 / Scene template name, uses default if not specified
*/
public async newScene(templateName?: string): Promise<void> {
if (!await this.canClose()) {
return;
}
@@ -54,11 +61,15 @@ export class SceneManagerService implements IService {
if (!scene) {
throw new Error('No active scene');
}
// 只移除实体,保留系统(系统由模块管理)
// Only remove entities, preserve systems (systems managed by modules)
scene.entities.removeAllEntities();
const systems = [...scene.systems];
for (const system of systems) {
scene.removeEntityProcessor(system);
}
// 使用场景模板创建默认实体
// Create default entities using scene template
const createdEntities = SceneTemplateRegistry.createDefaultEntities(scene, templateName);
logger.debug(`Created ${createdEntities.length} default entities from template`);
this.sceneState = {
currentScenePath: null,
@@ -67,7 +78,16 @@ export class SceneManagerService implements IService {
isSaved: false
};
// 同步到 EntityStore
// Sync to EntityStore
this.entityStore?.syncFromScene();
// 通知创建的实体
// Notify about created entities
for (const entity of createdEntities) {
await this.messageHub.publish('entity:added', { entity });
}
await this.messageHub.publish('scene:new', {});
logger.info('New scene created');
}

View File

@@ -0,0 +1,128 @@
import type { Scene, Entity } from '@esengine/ecs-framework';
/**
* 默认实体创建函数类型
* Default entity creator function type
*/
export type DefaultEntityCreator = (scene: Scene) => Entity | null;
/**
* 场景模板配置
* Scene template configuration
*/
export interface SceneTemplate {
/** 模板名称 / Template name */
name: string;
/** 模板描述 / Template description */
description?: string;
/** 默认实体创建器列表 / Default entity creators */
defaultEntities: DefaultEntityCreator[];
}
/**
* 场景模板注册表
* Registry for scene templates that define default entities
*
* This allows the editor-app to register what entities should be created
* when a new scene is created, without editor-core needing to know about
* specific components like CameraComponent.
*
* 这允许 editor-app 注册新建场景时应该创建的实体,
* 而 editor-core 不需要知道具体的组件如 CameraComponent。
*/
export class SceneTemplateRegistry {
private static templates: Map<string, SceneTemplate> = new Map();
private static defaultTemplateName = 'default';
/**
* 注册场景模板
* Register a scene template
*/
static registerTemplate(template: SceneTemplate): void {
this.templates.set(template.name, template);
}
/**
* 注册默认实体创建器到默认模板
* Register a default entity creator to the default template
*/
static registerDefaultEntity(creator: DefaultEntityCreator): void {
let defaultTemplate = this.templates.get(this.defaultTemplateName);
if (!defaultTemplate) {
defaultTemplate = {
name: this.defaultTemplateName,
description: 'Default scene template',
defaultEntities: []
};
this.templates.set(this.defaultTemplateName, defaultTemplate);
}
defaultTemplate.defaultEntities.push(creator);
}
/**
* 获取场景模板
* Get a scene template by name
*/
static getTemplate(name: string): SceneTemplate | undefined {
return this.templates.get(name);
}
/**
* 获取默认模板
* Get the default template
*/
static getDefaultTemplate(): SceneTemplate | undefined {
return this.templates.get(this.defaultTemplateName);
}
/**
* 设置默认模板名称
* Set the default template name
*/
static setDefaultTemplateName(name: string): void {
this.defaultTemplateName = name;
}
/**
* 获取所有模板名称
* Get all template names
*/
static getTemplateNames(): string[] {
return Array.from(this.templates.keys());
}
/**
* 为场景创建默认实体
* Create default entities for a scene using a template
*
* @param scene - 目标场景 / Target scene
* @param templateName - 模板名称,默认使用默认模板 / Template name, uses default if not specified
* @returns 创建的实体列表 / List of created entities
*/
static createDefaultEntities(scene: Scene, templateName?: string): Entity[] {
const template = templateName
? this.templates.get(templateName)
: this.getDefaultTemplate();
if (!template) {
return [];
}
const entities: Entity[] = [];
for (const creator of template.defaultEntities) {
const entity = creator(scene);
if (entity) {
entities.push(entity);
}
}
return entities;
}
/**
* 清除所有模板
* Clear all templates
*/
static clear(): void {
this.templates.clear();
}
}

View File

@@ -1,7 +1,7 @@
import type { IService } from '@esengine/ecs-framework';
import { Injectable } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
import type { ISerializer } from '../Plugins/IEditorPlugin';
import type { ISerializer } from '../Plugin/IPluginLoader';
const logger = createLogger('SerializerRegistry');

View File

@@ -1,6 +1,6 @@
import { Injectable, IService } from '@esengine/ecs-framework';
export type SettingType = 'string' | 'number' | 'boolean' | 'select' | 'color' | 'range';
export type SettingType = 'string' | 'number' | 'boolean' | 'select' | 'color' | 'range' | 'pluginList';
export interface SettingOption {
label: string;

View File

@@ -88,70 +88,8 @@ export interface ToolbarItem {
order?: number;
}
/**
* 面板位置
*/
export enum PanelPosition {
Left = 'left',
Right = 'right',
Bottom = 'bottom',
Center = 'center'
}
/**
* 面板描述符
*/
export interface PanelDescriptor {
/**
* 面板唯一标识
*/
id: string;
/**
* 显示标题
*/
title: string;
/**
* 面板位置
*/
position: PanelPosition;
/**
* 渲染组件或HTML
*/
component?: any;
/**
* 默认宽度/高度(像素)
*/
defaultSize?: number;
/**
* 是否可调整大小
*/
resizable?: boolean;
/**
* 是否可关闭
*/
closable?: boolean;
/**
* 图标
*/
icon?: string;
/**
* 排序权重
*/
order?: number;
/**
* 是否为动态面板(不默认显示,需要手动打开)
*/
isDynamic?: boolean;
}
// Re-export PanelPosition and PanelDescriptor from Plugin system
export { PanelPosition, type PanelDescriptor } from '../Plugin/IPluginLoader';
/**
* UI 扩展点类型
@@ -164,39 +102,5 @@ export enum UIExtensionType {
StatusBar = 'statusbar'
}
/**
* 实体创建模板
*/
export interface EntityCreationTemplate {
/**
* 模板唯一标识
*/
id: string;
/**
* 显示名称
*/
label: string;
/**
* 图标组件
*/
icon?: any;
/**
* 分类 (如 'basic', 'rendering', 'ui', 'physics' 等)
*/
category?: string;
/**
* 排序权重(数字越小越靠前)
*/
order?: number;
/**
* 创建实体的函数
* @param parentEntityId 父实体ID可选
* @returns 创建的实体ID
*/
create: (parentEntityId?: number) => number | Promise<number>;
}
// Re-export EntityCreationTemplate from Plugin system
export type { EntityCreationTemplate } from '../Plugin/IPluginLoader';

View File

@@ -4,10 +4,8 @@
* Plugin-based editor framework for ECS Framework
*/
export * from './Plugins/IEditorPlugin';
export * from './Plugins/EditorPluginManager';
export * from './Plugins/PluginTypes';
export * from './Plugins/PluginRegistry';
// 新插件系统 | New plugin system
export * from './Plugin';
export * from './Services/UIRegistry';
export * from './Services/MessageHub';
@@ -21,6 +19,7 @@ export * from './Services/ComponentDiscoveryService';
export * from './Services/LogService';
export * from './Services/SettingsRegistry';
export * from './Services/SceneManagerService';
export * from './Services/SceneTemplateRegistry';
export * from './Services/FileActionRegistry';
export * from './Services/EntityCreationRegistry';
export * from './Services/CompilerRegistry';
@@ -38,8 +37,8 @@ export * from './Services/IPropertyRenderer';
export * from './Services/PropertyRendererRegistry';
export * from './Services/IFieldEditor';
export * from './Services/FieldEditorRegistry';
export * from './Services/ComponentActionRegistry';
export * from './Services/ComponentInspectorRegistry';
export * from './Services/ComponentActionRegistry';
export * from './Gizmos';
@@ -49,5 +48,5 @@ export * from './Module/IPanelRegistry';
export * from './Module/IModuleContext';
export * from './Module/IEditorModule';
export * from './Types/UITypes';
export * from './Types/IFileAPI';
export * from './Types/UITypes';