refactor: reorganize package structure and decouple framework packages (#338)

* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 deletions

View File

@@ -0,0 +1,601 @@
/**
* @zh 运行时插件管理器
* @en Runtime Plugin Manager
*
* @zh 提供插件生命周期管理的核心实现。
* @en Provides core implementation for plugin lifecycle management.
*
* @zh 设计原则 | Design principles:
* @en
* 1. 最小依赖 - 只依赖 ecs-framework 和 engine-core
* 2. 状态跟踪 - 详细的插件状态用于调试和 UI
* 3. 依赖验证 - 确保加载顺序正确
* 4. 错误隔离 - 单个插件失败不影响其他
*/
import { GlobalComponentRegistry, ServiceContainer, createLogger } from '@esengine/ecs-framework';
import type { IScene } from '@esengine/ecs-framework';
import type { IRuntimePlugin, IRuntimeModule, SystemContext, ModuleManifest } from '@esengine/engine-core';
import {
topologicalSort,
resolveDependencyId,
getReverseDependencies,
type IDependable
} from './utils/DependencyUtils';
import type { PluginState } from './PluginState';
export type { IRuntimePlugin, IRuntimeModule, SystemContext, ModuleManifest };
const logger = createLogger('PluginManager');
// ============================================================================
// 类型定义 | Type Definitions
// ============================================================================
// PluginState 从 ./PluginState 重新导出
// PluginState is re-exported from ./PluginState
/**
* @zh 已注册的插件信息
* @en Registered plugin info
*/
export interface RegisteredPluginInfo {
/**
* @zh 插件实例
* @en Plugin instance
*/
plugin: IRuntimePlugin;
/**
* @zh 插件状态
* @en Plugin state
*/
state: PluginState;
/**
* @zh 是否启用
* @en Whether enabled
*/
enabled: boolean;
/**
* @zh 错误信息
* @en Error message
*/
error?: Error;
/**
* @zh 注册时间
* @en Registration time
*/
registeredAt: number;
/**
* @zh 激活时间
* @en Activation time
*/
activatedAt?: number;
/**
* @zh 创建的系统实例(用于清理)
* @en Created system instances (for cleanup)
*/
systemInstances?: any[];
}
/**
* @zh 插件配置
* @en Plugin configuration
*/
export interface RuntimePluginConfig {
/**
* @zh 启用的插件 ID 列表
* @en Enabled plugin ID list
*/
enabledPlugins: string[];
}
// ============================================================================
// RuntimePluginManager
// ============================================================================
/**
* @zh 运行时插件管理器
* @en Runtime Plugin Manager
*
* @zh 管理运行时插件的注册、初始化和生命周期。
* @en Manages registration, initialization, and lifecycle of runtime plugins.
*/
export class RuntimePluginManager {
private _plugins = new Map<string, RegisteredPluginInfo>();
private _initialized = false;
private _currentScene: IScene | null = null;
private _currentContext: SystemContext | null = null;
// ============================================================================
// 注册 | Registration
// ============================================================================
/**
* @zh 注册插件
* @en Register plugin
*
* @param plugin - @zh 插件实例 @en Plugin instance
*/
register(plugin: IRuntimePlugin): void {
if (!plugin?.manifest?.id) {
logger.error('Cannot register plugin: invalid manifest');
return;
}
const id = plugin.manifest.id;
if (this._plugins.has(id)) {
logger.warn(`Plugin ${id} is already registered, skipping`);
return;
}
const enabled = plugin.manifest.isCore === true ||
plugin.manifest.isEngineModule === true ||
plugin.manifest.defaultEnabled !== false;
this._plugins.set(id, {
plugin,
state: 'loading', // 已加载但未初始化
enabled,
registeredAt: Date.now()
});
logger.debug(`Plugin registered: ${id}`, {
enabled,
isCore: plugin.manifest.isCore,
isEngineModule: plugin.manifest.isEngineModule
});
}
/**
* @zh 批量注册插件
* @en Register multiple plugins
*
* @param plugins - @zh 插件列表 @en Plugin list
*/
registerMany(plugins: IRuntimePlugin[]): void {
for (const plugin of plugins) {
this.register(plugin);
}
}
// ============================================================================
// 启用/禁用 | Enable/Disable
// ============================================================================
/**
* @zh 启用插件
* @en Enable plugin
*
* @param pluginId - @zh 插件 ID @en Plugin ID
* @returns @zh 是否成功 @en Whether successful
*/
enable(pluginId: string): boolean {
const info = this._plugins.get(pluginId);
if (!info) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (info.plugin.manifest.isCore) {
logger.warn(`Core plugin ${pluginId} is always enabled`);
return true;
}
// 检查依赖 | Check dependencies
const deps = info.plugin.manifest.dependencies || [];
for (const dep of deps) {
const depId = resolveDependencyId(dep);
const depInfo = this._plugins.get(depId);
if (!depInfo?.enabled) {
logger.error(`Cannot enable ${pluginId}: dependency ${dep} is not enabled`);
return false;
}
}
info.enabled = true;
logger.info(`Plugin enabled: ${pluginId}`);
return true;
}
/**
* @zh 禁用插件
* @en Disable plugin
*
* @param pluginId - @zh 插件 ID @en Plugin ID
* @returns @zh 是否成功 @en Whether successful
*/
disable(pluginId: string): boolean {
const info = this._plugins.get(pluginId);
if (!info) {
logger.error(`Plugin ${pluginId} not found`);
return false;
}
if (info.plugin.manifest.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled`);
return false;
}
// 检查是否有其他插件依赖此插件(使用统一工具)
// Check if other plugins depend on this (using unified util)
const reverseDeps = this._getReverseDependencies(pluginId);
const enabledReverseDeps = Array.from(reverseDeps).filter(
id => this._plugins.get(id)?.enabled
);
if (enabledReverseDeps.length > 0) {
logger.error(`Cannot disable ${pluginId}: plugins ${enabledReverseDeps.join(', ')} depend on it`);
return false;
}
// 清理系统实例 | Cleanup system instances
if (info.systemInstances && this._currentScene) {
for (const system of info.systemInstances) {
try {
this._currentScene.removeSystem(system);
} catch (e) {
logger.warn(`Failed to remove system from ${pluginId}:`, e);
}
}
info.systemInstances = [];
}
info.enabled = false;
info.state = 'disabled';
logger.info(`Plugin disabled: ${pluginId}`);
return true;
}
/**
* @zh 检查插件是否启用
* @en Check if plugin is enabled
*/
isEnabled(pluginId: string): boolean {
return this._plugins.get(pluginId)?.enabled ?? false;
}
/**
* @zh 加载配置
* @en Load configuration
*
* @param config - @zh 插件配置 @en Plugin configuration
*/
loadConfig(config: RuntimePluginConfig): void {
const { enabledPlugins } = config;
for (const [id, info] of this._plugins) {
if (info.plugin.manifest.isCore || info.plugin.manifest.isEngineModule) {
info.enabled = true;
continue;
}
const shouldEnable = enabledPlugins.includes(id) ||
info.plugin.manifest.defaultEnabled === true;
info.enabled = shouldEnable;
}
logger.info('Plugin configuration loaded', {
enabled: Array.from(this._plugins.values()).filter(p => p.enabled).length,
total: this._plugins.size
});
}
// ============================================================================
// 初始化 | Initialization
// ============================================================================
/**
* @zh 初始化所有启用的插件
* @en Initialize all enabled plugins
*
* @param services - @zh 服务容器 @en Service container
*/
async initializeRuntime(services: ServiceContainer): Promise<void> {
if (this._initialized) {
logger.warn('Runtime already initialized');
return;
}
const startTime = Date.now();
const sortedPlugins = this._topologicalSort();
// Phase 1: 注册组件 | Register components
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled) continue;
const mod = info.plugin.runtimeModule;
if (mod?.registerComponents) {
try {
info.state = 'initializing';
mod.registerComponents(GlobalComponentRegistry);
logger.debug(`Components registered for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to register components for ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
}
}
// Phase 2: 注册服务 | Register services
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.registerServices) {
try {
mod.registerServices(services);
logger.debug(`Services registered for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to register services for ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
}
}
// Phase 3: 初始化回调 | Initialize callbacks
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.onInitialize) {
try {
await mod.onInitialize();
info.state = 'active';
info.activatedAt = Date.now();
logger.debug(`Initialized: ${pluginId}`);
} catch (e) {
logger.error(`Failed to initialize ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
} else {
info.state = 'active';
info.activatedAt = Date.now();
}
}
this._initialized = true;
const duration = Date.now() - startTime;
const activeCount = Array.from(this._plugins.values())
.filter(p => p.state === 'active').length;
logger.info(`Runtime initialized | 运行时初始化完成`, {
active: activeCount,
total: this._plugins.size,
duration: `${duration}ms`
});
}
/**
* @zh 为场景创建系统
* @en Create systems for scene
*
* @param scene - @zh 场景 @en Scene
* @param context - @zh 系统上下文 @en System context
*/
createSystemsForScene(scene: IScene, context: SystemContext): void {
this._currentScene = scene;
this._currentContext = context;
const sortedPlugins = this._topologicalSort();
// Phase 1: 创建系统 | Create systems
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.createSystems) {
try {
const systemsBefore = scene.systems.length;
mod.createSystems(scene, context);
// 跟踪创建的系统 | Track created systems
const systemsAfter = scene.systems;
info.systemInstances = [];
for (let i = systemsBefore; i < systemsAfter.length; i++) {
info.systemInstances.push(systemsAfter[i]);
}
logger.debug(`Systems created for: ${pluginId}`, {
count: info.systemInstances.length
});
} catch (e) {
logger.error(`Failed to create systems for ${pluginId}:`, e);
info.state = 'error';
info.error = e as Error;
}
}
}
// Phase 2: 系统创建后回调 | Post-creation callbacks
for (const pluginId of sortedPlugins) {
const info = this._plugins.get(pluginId);
if (!info?.enabled || info.state === 'error') continue;
const mod = info.plugin.runtimeModule;
if (mod?.onSystemsCreated) {
try {
mod.onSystemsCreated(scene, context);
logger.debug(`Systems wired for: ${pluginId}`);
} catch (e) {
logger.error(`Failed to wire systems for ${pluginId}:`, e);
}
}
}
logger.info('Systems created for scene | 场景系统创建完成');
}
// ============================================================================
// 查询 | Query
// ============================================================================
/**
* @zh 获取插件
* @en Get plugin
*/
getPlugin(id: string): IRuntimePlugin | undefined {
return this._plugins.get(id)?.plugin;
}
/**
* @zh 获取插件信息
* @en Get plugin info
*/
getPluginInfo(id: string): RegisteredPluginInfo | undefined {
return this._plugins.get(id);
}
/**
* @zh 获取所有插件
* @en Get all plugins
*/
getPlugins(): IRuntimePlugin[] {
return Array.from(this._plugins.values()).map(p => p.plugin);
}
/**
* @zh 获取所有启用的插件
* @en Get all enabled plugins
*/
getEnabledPlugins(): IRuntimePlugin[] {
return Array.from(this._plugins.values())
.filter(p => p.enabled)
.map(p => p.plugin);
}
/**
* @zh 获取插件状态
* @en Get plugin state
*/
getState(pluginId: string): PluginState | undefined {
return this._plugins.get(pluginId)?.state;
}
/**
* @zh 获取失败的插件
* @en Get failed plugins
*/
getFailedPlugins(): Array<{ id: string; error: Error }> {
const failed: Array<{ id: string; error: Error }> = [];
for (const [id, info] of this._plugins) {
if (info.state === 'error' && info.error) {
failed.push({ id, error: info.error });
}
}
return failed;
}
/**
* @zh 是否已初始化
* @en Whether initialized
*/
get initialized(): boolean {
return this._initialized;
}
// ============================================================================
// 生命周期 | Lifecycle
// ============================================================================
/**
* @zh 清理场景系统
* @en Clear scene systems
*/
clearSceneSystems(): void {
for (const [pluginId, info] of this._plugins) {
if (!info.enabled) continue;
const mod = info.plugin.runtimeModule;
if (mod?.onDestroy) {
try {
mod.onDestroy();
} catch (e) {
logger.error(`Error in ${pluginId}.onDestroy:`, e);
}
}
info.systemInstances = [];
}
this._currentScene = null;
this._currentContext = null;
logger.debug('Scene systems cleared');
}
/**
* @zh 重置管理器
* @en Reset manager
*/
reset(): void {
this.clearSceneSystems();
this._plugins.clear();
this._initialized = false;
logger.info('PluginManager reset');
}
// ============================================================================
// 私有方法 | Private Methods
// ============================================================================
/**
* @zh 拓扑排序(使用统一的 DependencyUtils
* @en Topological sort (using unified DependencyUtils)
*/
private _topologicalSort(): string[] {
// 转换为 IDependable 格式
const items: IDependable[] = Array.from(this._plugins.entries()).map(
([id, info]) => ({
id,
dependencies: info.plugin.manifest.dependencies
})
);
const result = topologicalSort(items, {
algorithm: 'dfs',
resolveId: resolveDependencyId
});
if (result.hasCycles) {
logger.warn(`Circular dependencies detected: ${result.cycleIds?.join(', ')}`);
}
return result.sorted.map(item => item.id);
}
/**
* @zh 获取反向依赖(使用统一的 DependencyUtils
* @en Get reverse dependencies (using unified DependencyUtils)
*/
private _getReverseDependencies(pluginId: string): Set<string> {
const items: IDependable[] = Array.from(this._plugins.entries()).map(
([id, info]) => ({
id,
dependencies: info.plugin.manifest.dependencies
})
);
return getReverseDependencies(pluginId, items, {
resolveId: resolveDependencyId
});
}
}
/**
* @zh 全局运行时插件管理器实例
* @en Global runtime plugin manager instance
*/
export const runtimePluginManager = new RuntimePluginManager();