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,128 @@
/**
* Editor Appearance Plugin
* 编辑器外观插件
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
import type { IPluginLoader, IEditorModuleLoader, PluginDescriptor } from '@esengine/editor-core';
import { SettingsRegistry } from '@esengine/editor-core';
import { SettingsService } from '../../services/SettingsService';
const logger = createLogger('EditorAppearancePlugin');
/**
* Editor Appearance 编辑器模块
*/
class EditorAppearanceEditorModule implements IEditorModuleLoader {
async install(services: ServiceContainer): Promise<void> {
const settingsRegistry = services.resolve(SettingsRegistry);
settingsRegistry.registerCategory({
id: 'appearance',
title: '外观',
description: '配置编辑器的外观设置',
sections: [
{
id: 'font',
title: '字体设置',
description: '配置编辑器字体样式',
settings: [
{
key: 'editor.fontSize',
label: '字体大小 (px)',
type: 'range',
defaultValue: 13,
description: '编辑器界面的字体大小',
min: 11,
max: 18,
step: 1
}
]
},
{
id: 'inspector',
title: '检视器设置',
description: '配置属性检视器显示',
settings: [
{
key: 'inspector.decimalPlaces',
label: '数字小数位数',
type: 'number',
defaultValue: 4,
description: '数字类型属性显示的小数位数,设置为 -1 表示不限制',
min: -1,
max: 10,
step: 1
}
]
}
]
});
this.applyFontSettings();
this.setupSettingsListener();
logger.info('Installed');
}
async uninstall(): Promise<void> {
logger.info('Uninstalled');
}
async onEditorReady(): Promise<void> {
logger.info('Editor is ready');
}
private applyFontSettings(): void {
const settings = SettingsService.getInstance();
const baseFontSize = settings.get<number>('editor.fontSize', 13);
logger.info(`Applying font size: ${baseFontSize}px`);
const root = document.documentElement;
root.style.setProperty('--font-size-xs', `${baseFontSize - 2}px`);
root.style.setProperty('--font-size-sm', `${baseFontSize - 1}px`);
root.style.setProperty('--font-size-base', `${baseFontSize}px`);
root.style.setProperty('--font-size-md', `${baseFontSize + 1}px`);
root.style.setProperty('--font-size-lg', `${baseFontSize + 3}px`);
root.style.setProperty('--font-size-xl', `${baseFontSize + 5}px`);
}
private setupSettingsListener(): void {
window.addEventListener('settings:changed', ((event: CustomEvent) => {
const changedSettings = event.detail;
logger.info('Settings changed event received', changedSettings);
if ('editor.fontSize' in changedSettings) {
logger.info('Font size changed, applying...');
this.applyFontSettings();
}
}) as EventListener);
}
}
const descriptor: PluginDescriptor = {
id: '@esengine/editor-appearance',
name: 'Editor Appearance',
version: '1.0.0',
description: 'Configure editor appearance settings',
category: 'tools',
icon: 'Palette',
enabledByDefault: true,
canContainContent: false,
isEnginePlugin: true,
isCore: true,
modules: [
{
name: 'EditorAppearanceEditor',
type: 'editor',
loadingPhase: 'earliest'
}
]
};
export const EditorAppearancePlugin: IPluginLoader = {
descriptor,
editorModule: new EditorAppearanceEditorModule()
};

View File

@@ -0,0 +1,50 @@
/**
* Gizmo Plugin
* Gizmo 插件
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
import type { IPluginLoader, IEditorModuleLoader, PluginDescriptor, GizmoProviderRegistration } from '@esengine/editor-core';
import { registerSpriteGizmo } from '../../gizmos';
/**
* Gizmo 编辑器模块
*/
class GizmoEditorModule implements IEditorModuleLoader {
async install(_services: ServiceContainer): Promise<void> {
registerSpriteGizmo();
}
async uninstall(): Promise<void> {
// Uninstalled
}
getGizmoProviders(): GizmoProviderRegistration[] {
return [];
}
}
const descriptor: PluginDescriptor = {
id: '@esengine/gizmo',
name: 'Gizmo System',
version: '1.0.0',
description: 'Provides gizmo support for editor components',
category: 'tools',
icon: 'Move',
enabledByDefault: true,
canContainContent: false,
isEnginePlugin: true,
isCore: true,
modules: [
{
name: 'GizmoEditor',
type: 'editor',
loadingPhase: 'preDefault'
}
]
};
export const GizmoPlugin: IPluginLoader = {
descriptor,
editorModule: new GizmoEditorModule()
};

View File

@@ -0,0 +1,77 @@
/**
* Plugin Config Plugin
* 插件配置插件
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
import type { IPluginLoader, IEditorModuleLoader, PluginDescriptor } from '@esengine/editor-core';
import { SettingsRegistry } from '@esengine/editor-core';
const logger = createLogger('PluginConfigPlugin');
/**
* Plugin Config 编辑器模块
*/
class PluginConfigEditorModule implements IEditorModuleLoader {
async install(services: ServiceContainer): Promise<void> {
const settingsRegistry = services.resolve(SettingsRegistry);
settingsRegistry.registerCategory({
id: 'plugins',
title: '插件',
description: '管理项目使用的插件',
sections: [
{
id: 'engine-plugins',
title: '插件管理',
description: '启用或禁用项目需要的插件。禁用不需要的插件可以减少打包体积。',
settings: [
{
key: 'project.enabledPlugins',
label: '',
type: 'pluginList',
defaultValue: [],
description: ''
}
]
}
]
});
logger.info('Installed');
}
async uninstall(): Promise<void> {
logger.info('Uninstalled');
}
async onEditorReady(): Promise<void> {
logger.info('Editor is ready');
}
}
const descriptor: PluginDescriptor = {
id: '@esengine/plugin-config',
name: 'Plugin Config',
version: '1.0.0',
description: 'Configure engine plugins',
category: 'tools',
icon: 'Package',
enabledByDefault: true,
canContainContent: false,
isEnginePlugin: true,
isCore: true,
modules: [
{
name: 'PluginConfigEditor',
type: 'editor',
loadingPhase: 'postDefault'
}
]
};
export const PluginConfigPlugin: IPluginLoader = {
descriptor,
editorModule: new PluginConfigEditorModule()
};

View File

@@ -0,0 +1,155 @@
/**
* Profiler Plugin
* 性能分析器插件
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
import type {
IPluginLoader,
IEditorModuleLoader,
PluginDescriptor,
PanelDescriptor,
MenuItemDescriptor
} from '@esengine/editor-core';
import { MessageHub, SettingsRegistry, PanelPosition } from '@esengine/editor-core';
import { ProfilerDockPanel } from '../../components/ProfilerDockPanel';
import { ProfilerService } from '../../services/ProfilerService';
/**
* Profiler 编辑器模块
*/
class ProfilerEditorModule implements IEditorModuleLoader {
private messageHub: MessageHub | null = null;
private profilerService: ProfilerService | null = null;
async install(services: ServiceContainer): Promise<void> {
this.messageHub = services.resolve(MessageHub);
const settingsRegistry = services.resolve(SettingsRegistry);
settingsRegistry.registerCategory({
id: 'profiler',
title: '性能分析器',
description: '配置性能分析器的行为和显示选项',
sections: [
{
id: 'connection',
title: '连接设置',
description: '配置WebSocket服务器连接参数',
settings: [
{
key: 'profiler.port',
label: '监听端口',
type: 'number',
defaultValue: 8080,
description: '性能分析器WebSocket服务器监听的端口号',
placeholder: '8080',
min: 1024,
max: 65535,
validator: {
validate: (value: number) => value >= 1024 && value <= 65535,
errorMessage: '端口号必须在1024到65535之间'
}
},
{
key: 'profiler.autoStart',
label: '自动启动服务器',
type: 'boolean',
defaultValue: true,
description: '编辑器启动时自动启动性能分析器服务器'
}
]
},
{
id: 'display',
title: '显示设置',
description: '配置性能数据的显示选项',
settings: [
{
key: 'profiler.refreshInterval',
label: '刷新间隔 (毫秒)',
type: 'range',
defaultValue: 100,
description: '性能数据刷新的时间间隔',
min: 50,
max: 1000,
step: 50
},
{
key: 'profiler.maxDataPoints',
label: '最大数据点数',
type: 'number',
defaultValue: 100,
description: '图表中保留的最大历史数据点数量',
min: 10,
max: 500
}
]
}
]
});
this.profilerService = new ProfilerService();
(window as any).__PROFILER_SERVICE__ = this.profilerService;
}
async uninstall(): Promise<void> {
if (this.profilerService) {
this.profilerService.destroy();
this.profilerService = null;
}
delete (window as any).__PROFILER_SERVICE__;
}
getPanels(): PanelDescriptor[] {
return [
{
id: 'profiler-monitor',
title: 'Performance Monitor',
position: PanelPosition.Center,
closable: false,
component: ProfilerDockPanel,
order: 200
}
];
}
getMenuItems(): MenuItemDescriptor[] {
return [
{
id: 'window.profiler',
label: 'Profiler',
parentId: 'window',
execute: () => {
this.messageHub?.publish('ui:openWindow', { windowId: 'profiler' });
}
}
];
}
async onEditorReady(): Promise<void> {}
}
const descriptor: PluginDescriptor = {
id: '@esengine/profiler',
name: 'Performance Profiler',
version: '1.0.0',
description: 'Real-time performance monitoring for ECS systems',
category: 'tools',
icon: 'BarChart3',
enabledByDefault: true,
canContainContent: false,
isEnginePlugin: true,
modules: [
{
name: 'ProfilerEditor',
type: 'editor',
loadingPhase: 'postDefault',
panels: ['profiler-monitor']
}
]
};
export const ProfilerPlugin: IPluginLoader = {
descriptor,
editorModule: new ProfilerEditorModule()
};

View File

@@ -0,0 +1,173 @@
/**
* Project Settings Plugin
* 项目设置插件
*
* Registers project-level settings like UI design resolution.
* 注册项目级别的设置,如 UI 设计分辨率。
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
import { createLogger, Core } from '@esengine/ecs-framework';
import type { IPluginLoader, IEditorModuleLoader, PluginDescriptor } from '@esengine/editor-core';
import { SettingsRegistry, ProjectService } from '@esengine/editor-core';
import EngineService from '../../services/EngineService';
const logger = createLogger('ProjectSettingsPlugin');
/**
* Common UI design resolution presets
* 常见的 UI 设计分辨率预设
*/
export const UI_RESOLUTION_PRESETS = [
{ label: '1920 x 1080 (Full HD)', value: { width: 1920, height: 1080 } },
{ label: '1280 x 720 (HD)', value: { width: 1280, height: 720 } },
{ label: '1366 x 768 (HD+)', value: { width: 1366, height: 768 } },
{ label: '2560 x 1440 (2K)', value: { width: 2560, height: 1440 } },
{ label: '3840 x 2160 (4K)', value: { width: 3840, height: 2160 } },
{ label: '750 x 1334 (iPhone 6/7/8)', value: { width: 750, height: 1334 } },
{ label: '1125 x 2436 (iPhone X/11/12)', value: { width: 1125, height: 2436 } },
{ label: '1080 x 1920 (Mobile Portrait)', value: { width: 1080, height: 1920 } },
{ label: '800 x 600 (SVGA)', value: { width: 800, height: 600 } },
{ label: '1024 x 768 (XGA)', value: { width: 1024, height: 768 } },
];
/**
* Project Settings 编辑器模块
*/
class ProjectSettingsEditorModule implements IEditorModuleLoader {
private settingsListener: ((event: Event) => void) | null = null;
async install(services: ServiceContainer): Promise<void> {
const settingsRegistry = services.resolve(SettingsRegistry);
// Setup listener for UI design resolution changes
this.setupSettingsListener();
settingsRegistry.registerCategory({
id: 'project',
title: '项目',
description: '项目级别的配置',
sections: [
{
id: 'ui-settings',
title: 'UI 设置',
description: '配置 UI 系统的基础参数',
settings: [
{
key: 'project.uiDesignResolution.width',
label: '设计宽度',
type: 'number',
defaultValue: 1920,
description: 'UI 画布的设计宽度(像素)',
min: 320,
max: 7680,
step: 1
},
{
key: 'project.uiDesignResolution.height',
label: '设计高度',
type: 'number',
defaultValue: 1080,
description: 'UI 画布的设计高度(像素)',
min: 240,
max: 4320,
step: 1
},
{
key: 'project.uiDesignResolution.preset',
label: '分辨率预设',
type: 'select',
defaultValue: '1920x1080',
description: '选择常见的分辨率预设',
options: UI_RESOLUTION_PRESETS.map(p => ({
label: p.label,
value: `${p.value.width}x${p.value.height}`
}))
}
]
}
]
});
logger.info('Installed');
}
async uninstall(): Promise<void> {
// Remove settings listener
if (this.settingsListener) {
window.removeEventListener('settings:changed', this.settingsListener);
this.settingsListener = null;
}
logger.info('Uninstalled');
}
async onEditorReady(): Promise<void> {
logger.info('Editor is ready');
}
/**
* Setup listener for settings changes
* 设置设置变化监听器
*/
private setupSettingsListener(): void {
this.settingsListener = ((event: CustomEvent) => {
const changedSettings = event.detail;
// Check if UI design resolution changed
if ('project.uiDesignResolution.width' in changedSettings ||
'project.uiDesignResolution.height' in changedSettings ||
'project.uiDesignResolution.preset' in changedSettings) {
logger.info('UI design resolution changed, applying...');
this.applyUIDesignResolution();
}
}) as EventListener;
window.addEventListener('settings:changed', this.settingsListener);
}
/**
* Apply UI design resolution from ProjectService
* 从 ProjectService 应用 UI 设计分辨率
*/
private applyUIDesignResolution(): void {
const projectService = Core.services.tryResolve<ProjectService>(ProjectService);
if (!projectService) {
logger.warn('ProjectService not available');
return;
}
const resolution = projectService.getUIDesignResolution();
const engineService = EngineService.getInstance();
if (engineService.isInitialized()) {
engineService.setUICanvasSize(resolution.width, resolution.height);
logger.info(`Applied UI design resolution: ${resolution.width}x${resolution.height}`);
}
}
}
const descriptor: PluginDescriptor = {
id: '@esengine/project-settings',
name: 'Project Settings',
version: '1.0.0',
description: 'Configure project-level settings',
category: 'tools',
icon: 'Settings',
enabledByDefault: true,
canContainContent: false,
isEnginePlugin: true,
isCore: true,
modules: [
{
name: 'ProjectSettingsEditor',
type: 'editor',
loadingPhase: 'postDefault'
}
]
};
export const ProjectSettingsPlugin: IPluginLoader = {
descriptor,
editorModule: new ProjectSettingsEditorModule()
};

View File

@@ -0,0 +1,199 @@
/**
* Scene Inspector Plugin
* 场景检视器插件
*/
import { Core, Entity } from '@esengine/ecs-framework';
import type { ServiceContainer } from '@esengine/ecs-framework';
import type {
IPluginLoader,
IEditorModuleLoader,
PluginDescriptor,
PanelDescriptor,
MenuItemDescriptor,
ToolbarItemDescriptor,
EntityCreationTemplate
} from '@esengine/editor-core';
import { PanelPosition, EntityStoreService, MessageHub } from '@esengine/editor-core';
import { TransformComponent, SpriteComponent, SpriteAnimatorComponent, CameraComponent } from '@esengine/ecs-components';
/**
* Scene Inspector 编辑器模块
*/
class SceneInspectorEditorModule implements IEditorModuleLoader {
async install(_services: ServiceContainer): Promise<void> {
// Installed
}
async uninstall(): Promise<void> {
// Uninstalled
}
getPanels(): PanelDescriptor[] {
return [
{
id: 'panel-scene-hierarchy',
title: 'Scene Hierarchy',
position: PanelPosition.Left,
defaultSize: 250,
resizable: true,
closable: false,
icon: 'List',
order: 10
},
{
id: 'panel-entity-inspector',
title: 'Entity Inspector',
position: PanelPosition.Right,
defaultSize: 300,
resizable: true,
closable: false,
icon: 'Search',
order: 10
}
];
}
getMenuItems(): MenuItemDescriptor[] {
return [
{
id: 'view-scene-inspector',
label: 'Scene Inspector',
parentId: 'view',
shortcut: 'Ctrl+Shift+I'
},
{
id: 'scene-create-entity',
label: 'Create Entity',
parentId: 'scene',
shortcut: 'Ctrl+N'
}
];
}
getToolbarItems(): ToolbarItemDescriptor[] {
return [
{
id: 'toolbar-create-entity',
label: 'New Entity',
icon: 'Plus',
tooltip: 'Create new entity',
execute: () => {}
},
{
id: 'toolbar-delete-entity',
label: 'Delete Entity',
icon: 'Trash2',
tooltip: 'Delete selected entity',
execute: () => {}
}
];
}
getEntityCreationTemplates(): EntityCreationTemplate[] {
return [
// Sprite
{
id: 'create-sprite-entity',
label: 'Sprite',
icon: 'Image',
category: 'rendering',
order: 10,
create: (): number => {
return this.createEntity('Sprite', (entity) => {
entity.addComponent(new TransformComponent());
entity.addComponent(new SpriteComponent());
});
}
},
// Animated Sprite
{
id: 'create-animated-sprite-entity',
label: '动画 Sprite',
icon: 'Film',
category: 'rendering',
order: 11,
create: (): number => {
return this.createEntity('AnimatedSprite', (entity) => {
entity.addComponent(new TransformComponent());
entity.addComponent(new SpriteAnimatorComponent());
});
}
},
// Camera
{
id: 'create-camera-entity',
label: '相机',
icon: 'Camera',
category: 'rendering',
order: 12,
create: (): number => {
return this.createEntity('Camera', (entity) => {
entity.addComponent(new TransformComponent());
entity.addComponent(new CameraComponent());
});
}
}
];
}
private createEntity(baseName: string, setupFn: (entity: Entity) => void): number {
const scene = Core.scene;
if (!scene) {
throw new Error('Scene not available');
}
const entityStore = Core.services.resolve(EntityStoreService);
const messageHub = Core.services.resolve(MessageHub);
if (!entityStore || !messageHub) {
throw new Error('EntityStoreService or MessageHub not available');
}
// 计数已有同类实体
const count = entityStore.getAllEntities()
.filter((e: Entity) => e.name.startsWith(`${baseName} `)).length;
const entityName = `${baseName} ${count + 1}`;
const entity = scene.createEntity(entityName);
setupFn(entity);
entityStore.addEntity(entity);
messageHub.publish('entity:added', { entity });
messageHub.publish('scene:modified', {});
entityStore.selectEntity(entity);
return entity.id;
}
async onEditorReady(): Promise<void> {}
async onProjectOpen(_projectPath: string): Promise<void> {}
async onProjectClose(): Promise<void> {}
}
const descriptor: PluginDescriptor = {
id: '@esengine/scene-inspector',
name: 'Scene Inspector',
version: '1.0.0',
description: 'Scene hierarchy and entity inspector',
category: 'tools',
icon: 'Search',
enabledByDefault: true,
canContainContent: false,
isEnginePlugin: true,
isCore: true,
modules: [
{
name: 'SceneInspectorEditor',
type: 'editor',
loadingPhase: 'default',
panels: ['panel-scene-hierarchy', 'panel-entity-inspector'],
inspectors: ['EntityInspector']
}
]
};
export const SceneInspectorPlugin: IPluginLoader = {
descriptor,
editorModule: new SceneInspectorEditorModule()
};

View File

@@ -0,0 +1,11 @@
/**
* 内置插件
* Built-in plugins
*/
export { GizmoPlugin } from './GizmoPlugin';
export { SceneInspectorPlugin } from './SceneInspectorPlugin';
export { ProfilerPlugin } from './ProfilerPlugin';
export { EditorAppearancePlugin } from './EditorAppearancePlugin';
export { PluginConfigPlugin } from './PluginConfigPlugin';
export { ProjectSettingsPlugin } from './ProjectSettingsPlugin';