feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)

* feat(platform-common): 添加WASM加载器和环境检测API

* feat(rapier2d): 新增Rapier2D WASM绑定包

* feat(physics-rapier2d): 添加跨平台WASM加载器

* feat(asset-system): 添加运行时资产目录和bundle格式

* feat(asset-system-editor): 新增编辑器资产管理包

* feat(editor-core): 添加构建系统和模块管理

* feat(editor-app): 重构浏览器预览使用import maps

* feat(platform-web): 添加BrowserRuntime和资产读取

* feat(engine): 添加材质系统和着色器管理

* feat(material): 新增材质系统和着色器编辑器

* feat(tilemap): 增强tilemap编辑器和动画系统

* feat(modules): 添加module.json配置

* feat(core): 添加module.json和类型定义更新

* chore: 更新依赖和构建配置

* refactor(plugins): 更新插件模板使用ModuleManifest

* chore: 添加第三方依赖库

* chore: 移除BehaviourTree-ai和ecs-astar子模块

* docs: 更新README和文档主题样式

* fix: 修复Rust文档测试和添加rapier2d WASM绑定

* fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题

* feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea)

* fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖

* fix: 添加缺失的包依赖修复CI构建

* fix: 修复CodeQL检测到的代码问题

* fix: 修复构建错误和缺失依赖

* fix: 修复类型检查错误

* fix(material-system): 修复tsconfig配置支持TypeScript项目引用

* fix(editor-core): 修复Rollup构建配置添加tauri external

* fix: 修复CodeQL检测到的代码问题

* fix: 修复CodeQL检测到的代码问题
This commit is contained in:
YHH
2025-12-03 22:15:22 +08:00
committed by GitHub
parent caf7622aa0
commit 63f006ab62
496 changed files with 77601 additions and 4067 deletions

View File

@@ -10,15 +10,14 @@ import type { ServiceContainer } from '@esengine/ecs-framework';
// 从 PluginDescriptor 重新导出(来源于 engine-core
export type {
PluginCategory,
LoadingPhase,
ModuleType,
ModuleDescriptor,
PluginDependency,
PluginDescriptor,
SystemContext,
IRuntimeModule,
IPlugin
IPlugin,
ModuleManifest,
ModuleCategory,
ModulePlatform,
ModuleExports
} from './PluginDescriptor';
// ============================================================================

View File

@@ -1,22 +1,21 @@
/**
* 插件描述符类型
* Plugin descriptor types
* 插件/模块类型定义
* Plugin/Module type definitions
*
* 从 @esengine/engine-core 重新导出基础类型,并添加编辑器专用类型
* Re-export base types from @esengine/engine-core, and add editor-specific types.
* 从 @esengine/engine-core 重新导出基础类型。
* Re-export base types from @esengine/engine-core.
*/
// 从 engine-core 重新导出所有插件相关类型
// 从 engine-core 重新导出所有类型
export type {
PluginCategory,
LoadingPhase,
ModuleType,
ModuleDescriptor,
PluginDependency,
PluginDescriptor,
SystemContext,
IRuntimeModule,
IPlugin
IPlugin,
ModuleManifest,
ModuleCategory,
ModulePlatform,
ModuleExports
} from '@esengine/engine-core';
/**

View File

@@ -6,11 +6,10 @@
import { createLogger, ComponentRegistry } from '@esengine/ecs-framework';
import type { IScene, ServiceContainer, IService } from '@esengine/ecs-framework';
import type {
PluginDescriptor,
PluginState,
PluginCategory,
LoadingPhase,
IPlugin
ModuleManifest,
IPlugin,
ModuleCategory,
PluginState
} from './PluginDescriptor';
import type {
SystemContext,
@@ -21,6 +20,7 @@ import { ComponentActionRegistry } from '../Services/ComponentActionRegistry';
import { FileActionRegistry } from '../Services/FileActionRegistry';
import { UIRegistry } from '../Services/UIRegistry';
import { MessageHub } from '../Services/MessageHub';
import { moduleRegistry } from '../Services/Module/ModuleRegistry';
const logger = createLogger('PluginManager');
@@ -31,24 +31,29 @@ const logger = createLogger('PluginManager');
export const IPluginManager = Symbol.for('IPluginManager');
/**
* 标准化后的插件描述符(所有字段都有值)
* Normalized plugin descriptor (all fields have values)
* 标准化后的模块清单(所有字段都有值)
* Normalized module manifest (all fields have values)
*/
export interface NormalizedPluginDescriptor {
export interface NormalizedManifest {
id: string;
name: string;
displayName: string;
version: string;
description: string;
category: PluginCategory;
category: ModuleCategory;
tags: string[];
icon?: string;
enabledByDefault: boolean;
defaultEnabled: boolean;
canContainContent: boolean;
isEnginePlugin: boolean;
isEngineModule: boolean;
isCore: boolean;
modules: Array<{ name: string; type: 'runtime' | 'editor'; loadingPhase: LoadingPhase }>;
dependencies: Array<{ id: string; version?: string; optional?: boolean }>;
dependencies: string[];
exports: { components?: string[]; systems?: string[]; loaders?: string[]; other?: string[] };
platforms: ('web' | 'desktop' | 'mobile')[];
editorPackage?: string;
jsSize?: number;
wasmSize?: number;
requiresWasm?: boolean;
}
/**
@@ -56,7 +61,7 @@ export interface NormalizedPluginDescriptor {
* Normalized plugin (internal use)
*/
export interface NormalizedPlugin {
descriptor: NormalizedPluginDescriptor;
manifest: NormalizedManifest;
runtimeModule?: IPlugin['runtimeModule'];
editorModule?: IEditorModuleLoader;
}
@@ -112,18 +117,6 @@ export interface PluginConfig {
enabledPlugins: string[];
}
/**
* 加载阶段顺序
* Loading phase order
*/
const LOADING_PHASE_ORDER: LoadingPhase[] = [
'earliest',
'preDefault',
'default',
'postDefault',
'postEngine'
];
/**
* 统一插件管理器
* Unified Plugin Manager
@@ -168,31 +161,49 @@ export class PluginManager implements IService {
}
/**
* 标准化插件描述符,填充默认值
* Normalize plugin descriptor, fill in defaults
* 解析依赖 ID 为完整的插件 ID
* Resolve dependency ID to full plugin ID
*
* 支持两种格式:
* - 短 ID: "core" -> "@esengine/core"
* - 完整 ID: "@esengine/core" -> "@esengine/core"
*/
private resolveDependencyId(depId: string): string {
// 如果已经是完整 ID直接返回
if (depId.startsWith('@')) {
return depId;
}
// 短 ID 转换为完整 ID
return `@esengine/${depId}`;
}
/**
* 标准化模块清单,填充默认值
* Normalize module manifest, fill in defaults
*/
private normalizePlugin(input: IPlugin): NormalizedPlugin {
const d = input.descriptor;
const m = input.manifest;
return {
descriptor: {
id: d.id,
name: d.name,
version: d.version,
description: d.description ?? '',
category: d.category ?? 'tools',
tags: d.tags ?? [],
icon: d.icon,
enabledByDefault: d.enabledByDefault ?? false,
canContainContent: d.canContainContent ?? false,
isEnginePlugin: d.isEnginePlugin ?? true,
isCore: d.isCore ?? false,
modules: (d.modules ?? [{ name: 'Runtime', type: 'runtime' as const, loadingPhase: 'default' as const }]).map((m: { name: string; type: 'runtime' | 'editor'; loadingPhase?: LoadingPhase }) => ({
name: m.name,
type: m.type,
loadingPhase: m.loadingPhase ?? 'default' as LoadingPhase
})),
dependencies: d.dependencies ?? [],
platforms: d.platforms ?? ['web', 'desktop']
manifest: {
id: m.id,
name: m.name,
displayName: m.displayName,
version: m.version,
description: m.description ?? '',
category: m.category ?? 'Other',
tags: m.tags ?? [],
icon: m.icon,
defaultEnabled: m.defaultEnabled ?? false,
canContainContent: m.canContainContent ?? false,
isEngineModule: m.isEngineModule ?? true,
isCore: m.isCore ?? false,
dependencies: m.dependencies ?? [],
exports: m.exports ?? {},
platforms: m.platforms ?? ['web', 'desktop'],
editorPackage: m.editorPackage,
jsSize: m.jsSize,
wasmSize: m.wasmSize,
requiresWasm: m.requiresWasm
},
runtimeModule: input.runtimeModule,
editorModule: input.editorModule as IEditorModuleLoader | undefined
@@ -212,15 +223,15 @@ export class PluginManager implements IService {
return;
}
if (!plugin.descriptor) {
logger.error('Cannot register plugin: descriptor is null or undefined', plugin);
if (!plugin.manifest) {
logger.error('Cannot register plugin: manifest is null or undefined', plugin);
return;
}
const { id } = plugin.descriptor;
const { id } = plugin.manifest;
if (!id) {
logger.error('Cannot register plugin: descriptor.id is null or undefined', plugin.descriptor);
logger.error('Cannot register plugin: manifest.id is null or undefined', plugin.manifest);
return;
}
@@ -230,7 +241,7 @@ export class PluginManager implements IService {
}
const normalized = this.normalizePlugin(plugin);
const enabled = normalized.descriptor.isCore || normalized.descriptor.enabledByDefault;
const enabled = normalized.manifest.isCore || normalized.manifest.defaultEnabled;
this.plugins.set(id, {
plugin: normalized,
@@ -239,7 +250,7 @@ export class PluginManager implements IService {
loadedAt: Date.now()
});
logger.info(`Plugin registered: ${id} (${normalized.descriptor.name})`);
logger.info(`Plugin registered: ${id} (${normalized.manifest.displayName})`);
}
/**
@@ -253,7 +264,7 @@ export class PluginManager implements IService {
return false;
}
if (plugin.plugin.descriptor.isCore) {
if (plugin.plugin.manifest.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled/enabled`);
return false;
}
@@ -263,13 +274,33 @@ export class PluginManager implements IService {
return true;
}
// 检查依赖
const deps = plugin.plugin.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`);
// 检查依赖(支持短 ID 和完整 ID
// Check dependencies (supports both short ID and full ID)
const deps = plugin.plugin.manifest.dependencies;
for (const depId of deps) {
const resolvedDepId = this.resolveDependencyId(depId);
const depPlugin = this.plugins.get(resolvedDepId);
// 如果依赖不在 plugins 中,检查是否是核心模块
// If dependency is not in plugins, check if it's a core module
if (!depPlugin) {
// 核心模块(如 engine-core, core, math不作为插件注册
// Core modules (like engine-core, core, math) are not registered as plugins
// 它们总是可用的,所以跳过检查
// They are always available, so skip the check
const shortId = depId.startsWith('@esengine/') ? depId.replace('@esengine/', '') : depId;
// 动态查询 moduleRegistry 判断是否是核心模块
// Dynamically query moduleRegistry to check if it's a core module
const moduleEntry = moduleRegistry.getModule(shortId);
if (moduleEntry?.isCore) {
continue;
}
logger.error(`Cannot enable ${pluginId}: dependency ${depId} (resolved: ${resolvedDepId}) is not registered`);
return false;
}
if (!depPlugin.enabled) {
logger.error(`Cannot enable ${pluginId}: dependency ${depId} (resolved: ${resolvedDepId}) is not enabled`);
return false;
}
}
@@ -312,7 +343,7 @@ export class PluginManager implements IService {
return false;
}
if (plugin.plugin.descriptor.isCore) {
if (plugin.plugin.manifest.isCore) {
logger.warn(`Core plugin ${pluginId} cannot be disabled`);
return false;
}
@@ -322,12 +353,13 @@ export class PluginManager implements IService {
return true;
}
// 检查是否有其他插件依赖此插件
// 检查是否有其他插件依赖此插件(支持短 ID 和完整 ID
for (const [id, p] of this.plugins) {
if (!p.enabled || id === pluginId) continue;
const deps = p.plugin.descriptor.dependencies;
const hasDep = deps.some(d => d.id === pluginId && !d.optional);
if (hasDep) {
const deps = p.plugin.manifest.dependencies;
// 将每个依赖解析为完整 ID 后检查
const resolvedDeps = deps.map(d => this.resolveDependencyId(d));
if (resolvedDeps.includes(pluginId)) {
logger.error(`Cannot disable ${pluginId}: plugin ${id} depends on it`);
return false;
}
@@ -684,9 +716,9 @@ export class PluginManager implements IService {
* 按类别获取插件
* Get plugins by category
*/
getPluginsByCategory(category: PluginCategory): RegisteredPlugin[] {
getPluginsByCategory(category: ModuleCategory): RegisteredPlugin[] {
return this.getAllPlugins().filter(
p => p.plugin.descriptor.category === category
p => p.plugin.manifest.category === category
);
}
@@ -1015,7 +1047,7 @@ export class PluginManager implements IService {
exportConfig(): PluginConfig {
const enabledPlugins: string[] = [];
for (const [id, plugin] of this.plugins) {
if (plugin.enabled && !plugin.plugin.descriptor.isCore) {
if (plugin.enabled && !plugin.plugin.manifest.isCore) {
enabledPlugins.push(id);
}
}
@@ -1040,7 +1072,7 @@ export class PluginManager implements IService {
const toDisable: string[] = [];
for (const [id, plugin] of this.plugins) {
if (plugin.plugin.descriptor.isCore) {
if (plugin.plugin.manifest.isCore) {
continue; // 核心插件始终启用
}
@@ -1068,35 +1100,13 @@ export class PluginManager implements IService {
}
/**
* 按加载阶段排序
* Sort by loading phase
* 按依赖排序(拓扑排序)
* Sort by dependencies (topological sort)
*/
private sortByLoadingPhase(moduleType: 'runtime' | 'editor'): string[] {
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?.plugin.descriptor.modules.find(m => m.type === 'runtime')
: pluginA?.plugin.descriptor.modules.find(m => m.type === 'editor');
const moduleB = moduleType === 'runtime'
? pluginB?.plugin.descriptor.modules.find(m => m.type === 'runtime')
: pluginB?.plugin.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;
// 按依赖拓扑排序
return this.topologicalSort(pluginIds);
}
/**
@@ -1113,10 +1123,12 @@ export class PluginManager implements IService {
const plugin = this.plugins.get(id);
if (plugin) {
const deps = plugin.plugin.descriptor.dependencies || [];
for (const dep of deps) {
if (pluginIds.includes(dep.id)) {
visit(dep.id);
const deps = plugin.plugin.manifest.dependencies || [];
for (const depId of deps) {
// 解析短 ID 为完整 ID
const resolvedDepId = this.resolveDependencyId(depId);
if (pluginIds.includes(resolvedDepId)) {
visit(resolvedDepId);
}
}
}