插件系统

This commit is contained in:
YHH
2025-10-11 09:26:36 +08:00
parent 279c1d9bc9
commit ae71af856b
5 changed files with 889 additions and 0 deletions

View File

@@ -10,6 +10,8 @@ import { createLogger } from './Utils/Logger';
import { SceneManager } from './ECS/SceneManager'; import { SceneManager } from './ECS/SceneManager';
import { IScene } from './ECS/IScene'; import { IScene } from './ECS/IScene';
import { ServiceContainer } from './Core/ServiceContainer'; 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 _sceneManager: SceneManager;
/**
* 插件管理器
*
* 管理所有插件的生命周期。
*/
private _pluginManager: PluginManager;
/** /**
* Core配置 * Core配置
*/ */
@@ -163,6 +172,11 @@ export class Core {
this._sceneManager = new SceneManager(); this._sceneManager = new SceneManager();
this._serviceContainer.registerInstance(SceneManager, this._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; Core.entitySystemsEnabled = this._config.enableEntitySystems ?? true;
this.debug = this._config.debug ?? true; this.debug = this._config.debug ?? true;
@@ -470,6 +484,90 @@ export class Core {
return this._instance?._config.debugConfig?.enabled || false; 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<void> {
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<void> {
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);
}
/** /**
* 初始化核心系统 * 初始化核心系统
* *

View File

@@ -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<void>;
/**
* 卸载插件
*
* 清理插件占用的资源。
* 可以是同步或异步的。
*/
uninstall(): void | Promise<void>;
}
/**
* 插件元数据
*/
export interface IPluginMetadata {
/**
* 插件名称
*/
name: string;
/**
* 插件版本
*/
version: string;
/**
* 插件状态
*/
state: PluginState;
/**
* 安装时间戳
*/
installedAt?: number;
/**
* 错误信息(如果安装失败)
*/
error?: string;
}

View File

@@ -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<string, IPlugin> = new Map();
/**
* 插件元数据
*/
private _metadata: Map<string, IPluginMetadata> = 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<void> {
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<void> {
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');
}
}

View File

@@ -8,6 +8,11 @@ export { Core } from './Core';
export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer'; export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer';
export type { IService, ServiceType } 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 { export {
Injectable, Injectable,

View File

@@ -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');
});
});
});