From ae71af856b234a6765f7af0c7a83f6f6558bead4 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Sat, 11 Oct 2025 09:26:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/Core.ts | 98 +++++ packages/core/src/Core/Plugin.ts | 124 ++++++ packages/core/src/Core/PluginManager.ts | 266 ++++++++++++ packages/core/src/index.ts | 5 + packages/core/tests/Core/PluginSystem.test.ts | 396 ++++++++++++++++++ 5 files changed, 889 insertions(+) create mode 100644 packages/core/src/Core/Plugin.ts create mode 100644 packages/core/src/Core/PluginManager.ts create mode 100644 packages/core/tests/Core/PluginSystem.test.ts diff --git a/packages/core/src/Core.ts b/packages/core/src/Core.ts index 628485ec..d7d07317 100644 --- a/packages/core/src/Core.ts +++ b/packages/core/src/Core.ts @@ -10,6 +10,8 @@ import { createLogger } from './Utils/Logger'; import { SceneManager } from './ECS/SceneManager'; import { IScene } from './ECS/IScene'; import { ServiceContainer } from './Core/ServiceContainer'; +import { PluginManager } from './Core/PluginManager'; +import { IPlugin } from './Core/Plugin'; /** * 游戏引擎核心类 @@ -120,6 +122,13 @@ export class Core { */ private _sceneManager: SceneManager; + /** + * 插件管理器 + * + * 管理所有插件的生命周期。 + */ + private _pluginManager: PluginManager; + /** * Core配置 */ @@ -163,6 +172,11 @@ export class Core { this._sceneManager = new SceneManager(); this._serviceContainer.registerInstance(SceneManager, this._sceneManager); + // 初始化插件管理器 + this._pluginManager = new PluginManager(); + this._pluginManager.initialize(this, this._serviceContainer); + this._serviceContainer.registerInstance(PluginManager, this._pluginManager); + Core.entitySystemsEnabled = this._config.enableEntitySystems ?? true; this.debug = this._config.debug ?? true; @@ -470,6 +484,90 @@ export class Core { return this._instance?._config.debugConfig?.enabled || false; } + /** + * 安装插件 + * + * @param plugin - 插件实例 + * @throws 如果Core实例未创建或插件安装失败 + * + * @example + * ```typescript + * Core.create({ debug: true }); + * + * // 安装插件 + * await Core.installPlugin(new MyPlugin()); + * ``` + */ + public static async installPlugin(plugin: IPlugin): Promise { + if (!this._instance) { + throw new Error('Core实例未创建,请先调用Core.create()'); + } + + await this._instance._pluginManager.install(plugin); + } + + /** + * 卸载插件 + * + * @param name - 插件名称 + * @throws 如果Core实例未创建或插件卸载失败 + * + * @example + * ```typescript + * await Core.uninstallPlugin('my-plugin'); + * ``` + */ + public static async uninstallPlugin(name: string): Promise { + if (!this._instance) { + throw new Error('Core实例未创建,请先调用Core.create()'); + } + + await this._instance._pluginManager.uninstall(name); + } + + /** + * 获取插件实例 + * + * @param name - 插件名称 + * @returns 插件实例,如果未安装则返回undefined + * + * @example + * ```typescript + * const myPlugin = Core.getPlugin('my-plugin'); + * if (myPlugin) { + * console.log(myPlugin.version); + * } + * ``` + */ + public static getPlugin(name: string): IPlugin | undefined { + if (!this._instance) { + return undefined; + } + + return this._instance._pluginManager.getPlugin(name); + } + + /** + * 检查插件是否已安装 + * + * @param name - 插件名称 + * @returns 是否已安装 + * + * @example + * ```typescript + * if (Core.isPluginInstalled('my-plugin')) { + * console.log('Plugin is installed'); + * } + * ``` + */ + public static isPluginInstalled(name: string): boolean { + if (!this._instance) { + return false; + } + + return this._instance._pluginManager.isInstalled(name); + } + /** * 初始化核心系统 * diff --git a/packages/core/src/Core/Plugin.ts b/packages/core/src/Core/Plugin.ts new file mode 100644 index 00000000..e00c1d55 --- /dev/null +++ b/packages/core/src/Core/Plugin.ts @@ -0,0 +1,124 @@ +import type { Core } from '../Core'; +import type { ServiceContainer } from './ServiceContainer'; + +/** + * 插件状态 + */ +export enum PluginState { + /** + * 未安装 + */ + NotInstalled = 'not_installed', + + /** + * 已安装 + */ + Installed = 'installed', + + /** + * 安装失败 + */ + Failed = 'failed' +} + +/** + * 插件接口 + * + * 所有插件都必须实现此接口。 + * 插件提供了一种扩展框架功能的标准方式。 + * + * @example + * ```typescript + * class MyPlugin implements IPlugin { + * readonly name = 'my-plugin'; + * readonly version = '1.0.0'; + * readonly dependencies = ['other-plugin']; + * + * async install(core: Core, services: ServiceContainer) { + * // 注册服务 + * services.registerSingleton(MyService); + * + * // 添加系统 + * const world = core.getWorld(); + * if (world) { + * world.addSystem(new MySystem()); + * } + * } + * + * async uninstall() { + * // 清理资源 + * } + * } + * ``` + */ +export interface IPlugin { + /** + * 插件唯一名称 + * + * 用于依赖解析和插件管理。 + */ + readonly name: string; + + /** + * 插件版本 + * + * 遵循语义化版本规范 (semver)。 + */ + readonly version: string; + + /** + * 依赖的其他插件名称列表 + * + * 这些插件必须在当前插件之前安装。 + */ + readonly dependencies?: readonly string[]; + + /** + * 安装插件 + * + * 在此方法中初始化插件,注册服务、系统等。 + * 可以是同步或异步的。 + * + * @param core - Core实例,用于访问World等 + * @param services - 服务容器,用于注册服务 + */ + install(core: Core, services: ServiceContainer): void | Promise; + + /** + * 卸载插件 + * + * 清理插件占用的资源。 + * 可以是同步或异步的。 + */ + uninstall(): void | Promise; +} + +/** + * 插件元数据 + */ +export interface IPluginMetadata { + /** + * 插件名称 + */ + name: string; + + /** + * 插件版本 + */ + version: string; + + /** + * 插件状态 + */ + state: PluginState; + + /** + * 安装时间戳 + */ + installedAt?: number; + + /** + * 错误信息(如果安装失败) + */ + error?: string; +} diff --git a/packages/core/src/Core/PluginManager.ts b/packages/core/src/Core/PluginManager.ts new file mode 100644 index 00000000..a25607c4 --- /dev/null +++ b/packages/core/src/Core/PluginManager.ts @@ -0,0 +1,266 @@ +import { IPlugin, IPluginMetadata, PluginState } from './Plugin'; +import type { IService } from './ServiceContainer'; +import type { Core } from '../Core'; +import type { ServiceContainer } from './ServiceContainer'; +import { createLogger } from '../Utils/Logger'; + +const logger = createLogger('PluginManager'); + +/** + * 插件管理器 + * + * 负责插件的注册、安装、卸载和生命周期管理。 + * 支持依赖检查和异步加载。 + * + * @example + * ```typescript + * const core = Core.create(); + * const pluginManager = core.getService(PluginManager); + * + * // 注册插件 + * await pluginManager.install(new MyPlugin()); + * + * // 查询插件 + * const plugin = pluginManager.getPlugin('my-plugin'); + * + * // 卸载插件 + * await pluginManager.uninstall('my-plugin'); + * ``` + */ +export class PluginManager implements IService { + /** + * 已安装的插件 + */ + private _plugins: Map = new Map(); + + /** + * 插件元数据 + */ + private _metadata: Map = new Map(); + + /** + * Core实例引用 + */ + private _core: Core | null = null; + + /** + * 服务容器引用 + */ + private _services: ServiceContainer | null = null; + + /** + * 初始化插件管理器 + * + * @param core - Core实例 + * @param services - 服务容器 + */ + public initialize(core: Core, services: ServiceContainer): void { + this._core = core; + this._services = services; + logger.info('PluginManager initialized'); + } + + /** + * 安装插件 + * + * 会自动检查依赖并按正确顺序安装。 + * + * @param plugin - 插件实例 + * @throws 如果依赖检查失败或安装失败 + */ + public async install(plugin: IPlugin): Promise { + if (!this._core || !this._services) { + throw new Error('PluginManager not initialized. Call initialize() first.'); + } + + // 检查是否已安装 + if (this._plugins.has(plugin.name)) { + logger.warn(`Plugin ${plugin.name} is already installed`); + return; + } + + // 检查依赖 + if (plugin.dependencies && plugin.dependencies.length > 0) { + this._checkDependencies(plugin); + } + + // 创建元数据 + const metadata: IPluginMetadata = { + name: plugin.name, + version: plugin.version, + state: PluginState.NotInstalled, + installedAt: Date.now() + }; + + this._metadata.set(plugin.name, metadata); + + try { + // 调用插件的安装方法 + logger.info(`Installing plugin: ${plugin.name} v${plugin.version}`); + await plugin.install(this._core, this._services); + + // 标记为已安装 + this._plugins.set(plugin.name, plugin); + metadata.state = PluginState.Installed; + + logger.info(`Plugin ${plugin.name} installed successfully`); + } catch (error) { + // 安装失败 + metadata.state = PluginState.Failed; + metadata.error = error instanceof Error ? error.message : String(error); + + logger.error(`Failed to install plugin ${plugin.name}:`, error); + throw error; + } + } + + /** + * 卸载插件 + * + * @param name - 插件名称 + * @throws 如果插件未安装或卸载失败 + */ + public async uninstall(name: string): Promise { + const plugin = this._plugins.get(name); + if (!plugin) { + throw new Error(`Plugin ${name} is not installed`); + } + + // 检查是否有其他插件依赖此插件 + this._checkDependents(name); + + try { + logger.info(`Uninstalling plugin: ${name}`); + await plugin.uninstall(); + + // 从注册表中移除 + this._plugins.delete(name); + this._metadata.delete(name); + + logger.info(`Plugin ${name} uninstalled successfully`); + } catch (error) { + logger.error(`Failed to uninstall plugin ${name}:`, error); + throw error; + } + } + + /** + * 获取插件实例 + * + * @param name - 插件名称 + * @returns 插件实例,如果未安装则返回undefined + */ + public getPlugin(name: string): IPlugin | undefined { + return this._plugins.get(name); + } + + /** + * 获取插件元数据 + * + * @param name - 插件名称 + * @returns 插件元数据,如果未安装则返回undefined + */ + public getMetadata(name: string): IPluginMetadata | undefined { + return this._metadata.get(name); + } + + /** + * 获取所有已安装的插件 + * + * @returns 插件列表 + */ + public getAllPlugins(): IPlugin[] { + return Array.from(this._plugins.values()); + } + + /** + * 获取所有插件元数据 + * + * @returns 元数据列表 + */ + public getAllMetadata(): IPluginMetadata[] { + return Array.from(this._metadata.values()); + } + + /** + * 检查插件是否已安装 + * + * @param name - 插件名称 + * @returns 是否已安装 + */ + public isInstalled(name: string): boolean { + return this._plugins.has(name); + } + + /** + * 检查插件依赖 + * + * @param plugin - 插件实例 + * @throws 如果依赖未满足 + */ + private _checkDependencies(plugin: IPlugin): void { + if (!plugin.dependencies) { + return; + } + + const missingDeps: string[] = []; + + for (const dep of plugin.dependencies) { + if (!this._plugins.has(dep)) { + missingDeps.push(dep); + } + } + + if (missingDeps.length > 0) { + throw new Error( + `Plugin ${plugin.name} has unmet dependencies: ${missingDeps.join(', ')}` + ); + } + } + + /** + * 检查是否有其他插件依赖指定插件 + * + * @param name - 插件名称 + * @throws 如果有其他插件依赖此插件 + */ + private _checkDependents(name: string): void { + const dependents: string[] = []; + + for (const plugin of this._plugins.values()) { + if (plugin.dependencies && plugin.dependencies.includes(name)) { + dependents.push(plugin.name); + } + } + + if (dependents.length > 0) { + throw new Error( + `Cannot uninstall plugin ${name}: it is required by ${dependents.join(', ')}` + ); + } + } + + /** + * 释放资源 + */ + public dispose(): void { + // 卸载所有插件(逆序,先卸载依赖项) + const plugins = Array.from(this._plugins.values()).reverse(); + + for (const plugin of plugins) { + try { + logger.info(`Disposing plugin: ${plugin.name}`); + plugin.uninstall(); + } catch (error) { + logger.error(`Error disposing plugin ${plugin.name}:`, error); + } + } + + this._plugins.clear(); + this._metadata.clear(); + this._core = null; + this._services = null; + + logger.info('PluginManager disposed'); + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 26da5490..4d38f37b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,6 +8,11 @@ export { Core } from './Core'; export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer'; export type { IService, ServiceType } from './Core/ServiceContainer'; +// 插件系统 +export { PluginManager } from './Core/PluginManager'; +export { PluginState } from './Core/Plugin'; +export type { IPlugin, IPluginMetadata } from './Core/Plugin'; + // 依赖注入 export { Injectable, diff --git a/packages/core/tests/Core/PluginSystem.test.ts b/packages/core/tests/Core/PluginSystem.test.ts new file mode 100644 index 00000000..2ca76066 --- /dev/null +++ b/packages/core/tests/Core/PluginSystem.test.ts @@ -0,0 +1,396 @@ +import { Core } from '../../src/Core'; +import { IPlugin } from '../../src/Core/Plugin'; +import { PluginManager } from '../../src/Core/PluginManager'; +import type { ServiceContainer } from '../../src/Core/ServiceContainer'; + +describe('插件系统', () => { + let core: Core; + + beforeEach(() => { + core = Core.create({ debug: false }); + }); + + afterEach(() => { + Core.destroy(); + }); + + describe('基本功能', () => { + it('应该能够安装插件', async () => { + class TestPlugin implements IPlugin { + readonly name = 'test-plugin'; + readonly version = '1.0.0'; + installed = false; + + install() { + this.installed = true; + } + + uninstall() { + this.installed = false; + } + } + + const plugin = new TestPlugin(); + await Core.installPlugin(plugin); + + expect(plugin.installed).toBe(true); + expect(Core.isPluginInstalled('test-plugin')).toBe(true); + }); + + it('应该能够卸载插件', async () => { + class TestPlugin implements IPlugin { + readonly name = 'test-plugin'; + readonly version = '1.0.0'; + installed = false; + + install() { + this.installed = true; + } + + uninstall() { + this.installed = false; + } + } + + const plugin = new TestPlugin(); + await Core.installPlugin(plugin); + expect(plugin.installed).toBe(true); + + await Core.uninstallPlugin('test-plugin'); + expect(plugin.installed).toBe(false); + expect(Core.isPluginInstalled('test-plugin')).toBe(false); + }); + + it('应该能够获取已安装的插件', async () => { + class TestPlugin implements IPlugin { + readonly name = 'test-plugin'; + readonly version = '1.0.0'; + + install() {} + uninstall() {} + } + + const plugin = new TestPlugin(); + await Core.installPlugin(plugin); + + const installed = Core.getPlugin('test-plugin'); + expect(installed).toBe(plugin); + expect(installed?.version).toBe('1.0.0'); + }); + + it('应该拒绝重复安装同一个插件', async () => { + class TestPlugin implements IPlugin { + readonly name = 'test-plugin'; + readonly version = '1.0.0'; + + install() {} + uninstall() {} + } + + const plugin1 = new TestPlugin(); + const plugin2 = new TestPlugin(); + + await Core.installPlugin(plugin1); + await Core.installPlugin(plugin2); + + expect(Core.getPlugin('test-plugin')).toBe(plugin1); + }); + }); + + describe('依赖管理', () => { + it('应该检查插件依赖', async () => { + class BasePlugin implements IPlugin { + readonly name = 'base-plugin'; + readonly version = '1.0.0'; + + install() {} + uninstall() {} + } + + class DependentPlugin implements IPlugin { + readonly name = 'dependent-plugin'; + readonly version = '1.0.0'; + readonly dependencies = ['base-plugin'] as const; + + install() {} + uninstall() {} + } + + const dependentPlugin = new DependentPlugin(); + + await expect(Core.installPlugin(dependentPlugin)).rejects.toThrow( + 'unmet dependencies' + ); + }); + + it('应该允许按正确顺序安装有依赖的插件', async () => { + class BasePlugin implements IPlugin { + readonly name = 'base-plugin'; + readonly version = '1.0.0'; + + install() {} + uninstall() {} + } + + class DependentPlugin implements IPlugin { + readonly name = 'dependent-plugin'; + readonly version = '1.0.0'; + readonly dependencies = ['base-plugin'] as const; + + install() {} + uninstall() {} + } + + const basePlugin = new BasePlugin(); + const dependentPlugin = new DependentPlugin(); + + await Core.installPlugin(basePlugin); + await Core.installPlugin(dependentPlugin); + + expect(Core.isPluginInstalled('base-plugin')).toBe(true); + expect(Core.isPluginInstalled('dependent-plugin')).toBe(true); + }); + + it('应该防止卸载被依赖的插件', async () => { + class BasePlugin implements IPlugin { + readonly name = 'base-plugin'; + readonly version = '1.0.0'; + + install() {} + uninstall() {} + } + + class DependentPlugin implements IPlugin { + readonly name = 'dependent-plugin'; + readonly version = '1.0.0'; + readonly dependencies = ['base-plugin'] as const; + + install() {} + uninstall() {} + } + + await Core.installPlugin(new BasePlugin()); + await Core.installPlugin(new DependentPlugin()); + + await expect(Core.uninstallPlugin('base-plugin')).rejects.toThrow( + 'required by' + ); + }); + }); + + describe('服务注册', () => { + it('应该允许插件注册服务', async () => { + class TestService { + public value = 42; + dispose() {} + } + + class ServicePlugin implements IPlugin { + readonly name = 'service-plugin'; + readonly version = '1.0.0'; + + install(core: Core, services: ServiceContainer) { + services.registerSingleton(TestService); + } + + uninstall() {} + } + + await Core.installPlugin(new ServicePlugin()); + + const service = Core.services.resolve(TestService); + expect(service.value).toBe(42); + }); + + it('应该允许插件访问Core和ServiceContainer', async () => { + let capturedCore: Core | null = null; + let capturedServices: ServiceContainer | null = null; + + class TestPlugin implements IPlugin { + readonly name = 'test-plugin'; + readonly version = '1.0.0'; + + install(core: Core, services: ServiceContainer) { + capturedCore = core; + capturedServices = services; + } + + uninstall() {} + } + + await Core.installPlugin(new TestPlugin()); + + expect(capturedCore).toBe(Core.Instance); + expect(capturedServices).toBe(Core.services); + }); + }); + + describe('异步插件', () => { + it('应该支持异步安装', async () => { + class AsyncPlugin implements IPlugin { + readonly name = 'async-plugin'; + readonly version = '1.0.0'; + initialized = false; + + async install() { + await new Promise(resolve => setTimeout(resolve, 10)); + this.initialized = true; + } + + uninstall() {} + } + + const plugin = new AsyncPlugin(); + await Core.installPlugin(plugin); + + expect(plugin.initialized).toBe(true); + }); + + it('应该支持异步卸载', async () => { + class AsyncPlugin implements IPlugin { + readonly name = 'async-plugin'; + readonly version = '1.0.0'; + cleaned = false; + + install() {} + + async uninstall() { + await new Promise(resolve => setTimeout(resolve, 10)); + this.cleaned = true; + } + } + + const plugin = new AsyncPlugin(); + await Core.installPlugin(plugin); + await Core.uninstallPlugin('async-plugin'); + + expect(plugin.cleaned).toBe(true); + }); + }); + + describe('错误处理', () => { + it('应该捕获安装错误', async () => { + class FailingPlugin implements IPlugin { + readonly name = 'failing-plugin'; + readonly version = '1.0.0'; + + install() { + throw new Error('安装失败'); + } + + uninstall() {} + } + + await expect(Core.installPlugin(new FailingPlugin())).rejects.toThrow( + '安装失败' + ); + + expect(Core.isPluginInstalled('failing-plugin')).toBe(false); + }); + + it('应该捕获卸载错误', async () => { + class FailingPlugin implements IPlugin { + readonly name = 'failing-plugin'; + readonly version = '1.0.0'; + + install() {} + + uninstall() { + throw new Error('卸载失败'); + } + } + + await Core.installPlugin(new FailingPlugin()); + + await expect(Core.uninstallPlugin('failing-plugin')).rejects.toThrow( + '卸载失败' + ); + }); + }); + + describe('PluginManager直接使用', () => { + it('应该能够从ServiceContainer获取PluginManager', () => { + const pluginManager = Core.services.resolve(PluginManager); + expect(pluginManager).toBeDefined(); + }); + + it('应该能够获取所有插件', async () => { + class Plugin1 implements IPlugin { + readonly name = 'plugin1'; + readonly version = '1.0.0'; + install() {} + uninstall() {} + } + + class Plugin2 implements IPlugin { + readonly name = 'plugin2'; + readonly version = '2.0.0'; + install() {} + uninstall() {} + } + + await Core.installPlugin(new Plugin1()); + await Core.installPlugin(new Plugin2()); + + const pluginManager = Core.services.resolve(PluginManager); + const allPlugins = pluginManager.getAllPlugins(); + + expect(allPlugins).toHaveLength(2); + expect(allPlugins.map(p => p.name)).toContain('plugin1'); + expect(allPlugins.map(p => p.name)).toContain('plugin2'); + }); + + it('应该能够获取插件元数据', async () => { + class TestPlugin implements IPlugin { + readonly name = 'test-plugin'; + readonly version = '1.0.0'; + install() {} + uninstall() {} + } + + await Core.installPlugin(new TestPlugin()); + + const pluginManager = Core.services.resolve(PluginManager); + const metadata = pluginManager.getMetadata('test-plugin'); + + expect(metadata).toBeDefined(); + expect(metadata?.name).toBe('test-plugin'); + expect(metadata?.version).toBe('1.0.0'); + expect(metadata?.state).toBe('installed'); + expect(metadata?.installedAt).toBeDefined(); + }); + }); + + describe('生命周期', () => { + it('应该在Core销毁时卸载所有插件', async () => { + const uninstallCalls: string[] = []; + + class Plugin1 implements IPlugin { + readonly name = 'plugin1'; + readonly version = '1.0.0'; + install() {} + uninstall() { + uninstallCalls.push('plugin1'); + } + } + + class Plugin2 implements IPlugin { + readonly name = 'plugin2'; + readonly version = '1.0.0'; + readonly dependencies = ['plugin1'] as const; + install() {} + uninstall() { + uninstallCalls.push('plugin2'); + } + } + + await Core.installPlugin(new Plugin1()); + await Core.installPlugin(new Plugin2()); + + Core.destroy(); + + expect(uninstallCalls).toContain('plugin1'); + expect(uninstallCalls).toContain('plugin2'); + }); + }); +});