feat(editor-core): 添加用户系统自动注册功能 (#283)
* feat(editor-core): 添加用户系统自动注册功能 - IUserCodeService 新增 registerSystems/unregisterSystems/getRegisteredSystems 方法 - UserCodeService 实现系统检测、实例化和场景注册逻辑 - ServiceRegistry 在预览开始时注册用户系统,停止时移除 - 热更新时自动重新加载用户系统 - 更新 System 脚本模板添加 @ECSSystem 装饰器 * feat(editor-core): 添加编辑器脚本支持(Inspector/Gizmo) - registerEditorExtensions 实际注册用户 Inspector 和 Gizmo - 添加 unregisterEditorExtensions 方法 - ServiceRegistry 在项目加载时编译并加载编辑器脚本 - 项目关闭时自动清理编辑器扩展 - 添加 Inspector 和 Gizmo 脚本创建模板
This commit is contained in:
@@ -295,26 +295,39 @@ export class ServiceRegistry {
|
||||
PluginSDKRegistry.initialize();
|
||||
|
||||
try {
|
||||
// Compile runtime scripts | 编译运行时脚本
|
||||
const compileResult = await userCodeService.compile({
|
||||
// 1. 编译运行时脚本 | Compile runtime scripts
|
||||
const runtimeResult = await userCodeService.compile({
|
||||
projectPath: projectPath,
|
||||
target: UserCodeTarget.Runtime
|
||||
});
|
||||
|
||||
if (compileResult.success && compileResult.outputPath) {
|
||||
// Load compiled module | 加载编译后的模块
|
||||
const module = await userCodeService.load(compileResult.outputPath, UserCodeTarget.Runtime);
|
||||
|
||||
// Register user components to editor | 注册用户组件到编辑器
|
||||
if (runtimeResult.success && runtimeResult.outputPath) {
|
||||
const module = await userCodeService.load(runtimeResult.outputPath, UserCodeTarget.Runtime);
|
||||
userCodeService.registerComponents(module, componentRegistry);
|
||||
|
||||
// Notify that user code has been reloaded | 通知用户代码已重新加载
|
||||
messageHub.publish('usercode:reloaded', {
|
||||
projectPath,
|
||||
exports: Object.keys(module.exports)
|
||||
});
|
||||
} else if (compileResult.errors.length > 0) {
|
||||
console.warn('[UserCodeService] Compilation errors:', compileResult.errors);
|
||||
} else if (runtimeResult.errors.length > 0) {
|
||||
console.warn('[UserCodeService] Runtime compilation errors:', runtimeResult.errors);
|
||||
}
|
||||
|
||||
// 2. 编译编辑器脚本 | Compile editor scripts
|
||||
const editorResult = await userCodeService.compile({
|
||||
projectPath: projectPath,
|
||||
target: UserCodeTarget.Editor
|
||||
});
|
||||
|
||||
if (editorResult.success && editorResult.outputPath) {
|
||||
const editorModule = await userCodeService.load(editorResult.outputPath, UserCodeTarget.Editor);
|
||||
userCodeService.registerEditorExtensions(editorModule, componentInspectorRegistry);
|
||||
messageHub.publish('usercode:editor-reloaded', {
|
||||
projectPath,
|
||||
exports: Object.keys(editorModule.exports)
|
||||
});
|
||||
} else if (editorResult.errors.length > 0) {
|
||||
// 编辑器脚本编译错误只记录,不影响运行时
|
||||
console.warn('[UserCodeService] Editor compilation errors:', editorResult.errors);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[UserCodeService] Failed to compile/load:', error);
|
||||
@@ -333,17 +346,20 @@ export class ServiceRegistry {
|
||||
console.log('[UserCodeService] Hot reload event:', event.changedFiles);
|
||||
|
||||
if (event.newModule) {
|
||||
// 1. Register new/updated components to registries
|
||||
// 1. 注册新的/更新的组件到注册表
|
||||
userCodeService.registerComponents(event.newModule, componentRegistry);
|
||||
|
||||
// 2. Hot reload: update prototype chain of existing instances
|
||||
// 2. 热更新:更新现有实例的原型链
|
||||
const updatedCount = userCodeService.hotReloadInstances(event.newModule);
|
||||
console.log(`[UserCodeService] Hot reloaded ${updatedCount} component instances`);
|
||||
|
||||
// 3. Notify that user code has been reloaded
|
||||
// 3. 通知用户代码已重新加载
|
||||
// 3. 如果正在预览,热更新用户系统
|
||||
const scene = Core.scene;
|
||||
if (scene && !scene.isEditorMode) {
|
||||
userCodeService.hotReloadSystems(event.newModule, scene);
|
||||
}
|
||||
|
||||
// 4. 通知用户代码已重新加载
|
||||
messageHub.publish('usercode:reloaded', {
|
||||
projectPath: data.path,
|
||||
exports: Object.keys(event.newModule.exports),
|
||||
@@ -355,11 +371,12 @@ export class ServiceRegistry {
|
||||
});
|
||||
});
|
||||
|
||||
// Subscribe to project:closed to stop watching
|
||||
// 订阅 project:closed 以停止监视
|
||||
// Subscribe to project:closed to stop watching and cleanup
|
||||
// 订阅 project:closed 以停止监视和清理
|
||||
messageHub.subscribe('project:closed', async () => {
|
||||
currentProjectPath = null;
|
||||
await userCodeService.stopWatch();
|
||||
userCodeService.unregisterEditorExtensions(componentInspectorRegistry);
|
||||
});
|
||||
|
||||
// Subscribe to script file changes (create/delete) from editor operations
|
||||
@@ -378,6 +395,27 @@ export class ServiceRegistry {
|
||||
}
|
||||
});
|
||||
|
||||
// 预览开始时注册用户系统
|
||||
// Register user systems when preview starts
|
||||
messageHub.subscribe('preview:start', () => {
|
||||
const runtimeModule = userCodeService.getModule(UserCodeTarget.Runtime);
|
||||
if (runtimeModule) {
|
||||
const scene = Core.scene;
|
||||
if (scene) {
|
||||
userCodeService.registerSystems(runtimeModule, scene);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 预览停止时移除用户系统
|
||||
// Unregister user systems when preview stops
|
||||
messageHub.subscribe('preview:stop', () => {
|
||||
const scene = Core.scene;
|
||||
if (scene) {
|
||||
userCodeService.unregisterSystems(scene);
|
||||
}
|
||||
});
|
||||
|
||||
// 注册默认场景模板 - 创建默认相机
|
||||
// Register default scene template - creates default camera
|
||||
this.registerDefaultSceneTemplate();
|
||||
|
||||
@@ -275,31 +275,22 @@ export class ${className} extends Component {
|
||||
category: 'Script',
|
||||
getContent: (fileName: string) => {
|
||||
const className = fileName.replace(/\.ts$/, '');
|
||||
return `import { EntitySystem, Matcher, type Entity } from '@esengine/ecs-framework';
|
||||
return `import { EntitySystem, Matcher, ECSSystem, type Entity } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* ${className}
|
||||
*/
|
||||
@ECSSystem('${className}')
|
||||
export class ${className} extends EntitySystem {
|
||||
// 定义系统处理的组件类型
|
||||
// Define component types this system processes
|
||||
protected getMatcher(): Matcher {
|
||||
// 返回匹配器,指定需要哪些组件
|
||||
// Return matcher specifying required components
|
||||
// return Matcher.all(SomeComponent);
|
||||
return Matcher.empty();
|
||||
constructor() {
|
||||
// 定义系统处理的组件类型 | Define component types this system processes
|
||||
// super(Matcher.all(SomeComponent));
|
||||
super(Matcher.empty());
|
||||
}
|
||||
|
||||
protected updateEntity(entity: Entity, deltaTime: number): void {
|
||||
// 处理每个实体
|
||||
// Process each entity
|
||||
// 处理每个实体 | Process each entity
|
||||
}
|
||||
|
||||
// 可选:系统初始化
|
||||
// Optional: System initialization
|
||||
// onInitialize(): void {
|
||||
// super.onInitialize();
|
||||
// }
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -320,6 +311,86 @@ export function ${name.charAt(0).toLowerCase() + name.slice(1)}(): void {
|
||||
// 在这里编写代码
|
||||
// Write your code here
|
||||
}
|
||||
`;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ts-inspector',
|
||||
label: 'Inspector',
|
||||
extension: '.ts',
|
||||
icon: 'FileCode',
|
||||
category: 'Editor',
|
||||
getContent: (fileName: string) => {
|
||||
const className = fileName.replace(/\.ts$/, '');
|
||||
return `import React from 'react';
|
||||
import type { Component } from '@esengine/ecs-framework';
|
||||
import type { IComponentInspector, ComponentInspectorContext } from '@esengine/editor-core';
|
||||
|
||||
/**
|
||||
* ${className}
|
||||
*
|
||||
* 自定义组件检查器 | Custom component inspector
|
||||
* 放置在 scripts/editor/ 目录下 | Place in scripts/editor/ directory
|
||||
*/
|
||||
export class ${className} implements IComponentInspector {
|
||||
readonly id = '${className.toLowerCase()}';
|
||||
readonly name = '${className}';
|
||||
readonly priority = 10;
|
||||
// 目标组件类型名称 | Target component type names
|
||||
readonly targetComponents = ['YourComponent'];
|
||||
|
||||
canHandle(component: Component): boolean {
|
||||
return this.targetComponents.includes(component.constructor.name);
|
||||
}
|
||||
|
||||
render(context: ComponentInspectorContext): React.ReactElement {
|
||||
const { component } = context;
|
||||
|
||||
return React.createElement('div', { className: 'custom-inspector' },
|
||||
React.createElement('h4', null, '${className}'),
|
||||
React.createElement('pre', null, JSON.stringify(component, null, 2))
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ts-gizmo',
|
||||
label: 'Gizmo',
|
||||
extension: '.ts',
|
||||
icon: 'FileCode',
|
||||
category: 'Editor',
|
||||
getContent: (fileName: string) => {
|
||||
const className = fileName.replace(/\.ts$/, '');
|
||||
return `import type { Component, Entity } from '@esengine/ecs-framework';
|
||||
import type { IGizmoRenderData } from '@esengine/editor-core';
|
||||
|
||||
/**
|
||||
* ${className}
|
||||
*
|
||||
* 自定义 Gizmo 提供者 | Custom Gizmo provider
|
||||
* 放置在 scripts/editor/ 目录下 | Place in scripts/editor/ directory
|
||||
*/
|
||||
export class ${className} {
|
||||
// 目标组件类型 | Target component type
|
||||
// 需要替换为实际的组件类 | Replace with actual component class
|
||||
readonly targetComponent = null; // YourComponent
|
||||
|
||||
draw(component: Component, entity: Entity, isSelected: boolean): IGizmoRenderData[] {
|
||||
// 返回要绘制的 Gizmo 数据 | Return gizmo data to draw
|
||||
return [
|
||||
{
|
||||
type: 'circle',
|
||||
x: 0,
|
||||
y: 0,
|
||||
radius: 10,
|
||||
strokeColor: isSelected ? '#00ff00' : '#ffffff',
|
||||
strokeWidth: 2
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user