refactor(editor): 统一配置管理、完善插件卸载和热更新同步 (#298)
主要变更: 1. 统一配置管理 (EditorConfig) - 新增 EditorConfig 集中管理路径、文件名、全局变量等配置 - 添加 SDK 模块配置系统 (ISDKModuleConfig) - 重构 PluginSDKRegistry 使用配置而非硬编码 2. 完善插件卸载机制 - 扩展 PluginRegisteredResources 追踪运行时资源 - 实现完整的 deactivatePluginRuntime 清理流程 - ComponentRegistry 添加 unregister/getRegisteredComponents 方法 3. 热更新同步机制 - 新增 HotReloadCoordinator 协调热更新过程 - 热更新期间暂停 ECS 循环避免竞态条件 - 支持超时保护和失败恢复
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
/**
|
||||
* Hot Reload Coordinator
|
||||
* 热更新协调器
|
||||
*
|
||||
* Coordinates the hot reload process to ensure safe code updates
|
||||
* without causing race conditions or inconsistent state.
|
||||
*
|
||||
* 协调热更新过程,确保代码更新安全,不会导致竞态条件或状态不一致。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const coordinator = new HotReloadCoordinator();
|
||||
* await coordinator.performHotReload(async () => {
|
||||
* // Recompile and reload user code
|
||||
* await userCodeService.compile(options);
|
||||
* await userCodeService.load(outputPath, target);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import type { HotReloadEvent } from './IUserCodeService';
|
||||
|
||||
const logger = createLogger('HotReloadCoordinator');
|
||||
|
||||
/**
|
||||
* Hot reload phase enumeration
|
||||
* 热更新阶段枚举
|
||||
*/
|
||||
export const enum EHotReloadPhase {
|
||||
/** Idle state, no hot reload in progress | 空闲状态,没有热更新进行中 */
|
||||
Idle = 'idle',
|
||||
/** Preparing for hot reload, pausing systems | 准备热更新,暂停系统 */
|
||||
Preparing = 'preparing',
|
||||
/** Compiling user code | 编译用户代码 */
|
||||
Compiling = 'compiling',
|
||||
/** Loading new modules | 加载新模块 */
|
||||
Loading = 'loading',
|
||||
/** Updating component instances | 更新组件实例 */
|
||||
UpdatingInstances = 'updating-instances',
|
||||
/** Updating systems | 更新系统 */
|
||||
UpdatingSystems = 'updating-systems',
|
||||
/** Resuming systems | 恢复系统 */
|
||||
Resuming = 'resuming',
|
||||
/** Hot reload complete | 热更新完成 */
|
||||
Complete = 'complete',
|
||||
/** Hot reload failed | 热更新失败 */
|
||||
Failed = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* Hot reload status interface
|
||||
* 热更新状态接口
|
||||
*/
|
||||
export interface IHotReloadStatus {
|
||||
/** Current phase | 当前阶段 */
|
||||
phase: EHotReloadPhase;
|
||||
/** Error message if failed | 失败时的错误信息 */
|
||||
error?: string;
|
||||
/** Timestamp when current phase started | 当前阶段开始时间戳 */
|
||||
startTime: number;
|
||||
/** Number of updated instances | 更新的实例数量 */
|
||||
updatedInstances?: number;
|
||||
/** Number of updated systems | 更新的系统数量 */
|
||||
updatedSystems?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hot reload options
|
||||
* 热更新选项
|
||||
*/
|
||||
export interface IHotReloadOptions {
|
||||
/**
|
||||
* Timeout for hot reload process in milliseconds.
|
||||
* 热更新过程的超时时间(毫秒)。
|
||||
*
|
||||
* @default 30000
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* Whether to restore previous state on failure.
|
||||
* 失败时是否恢复到之前的状态。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
restoreOnFailure?: boolean;
|
||||
|
||||
/**
|
||||
* Callback for phase changes.
|
||||
* 阶段变化回调。
|
||||
*/
|
||||
onPhaseChange?: (phase: EHotReloadPhase) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hot Reload Coordinator
|
||||
* 热更新协调器
|
||||
*
|
||||
* Manages the hot reload process lifecycle:
|
||||
* 1. Pause ECS update loop
|
||||
* 2. Execute hot reload tasks (compile, load, update)
|
||||
* 3. Resume ECS update loop
|
||||
*
|
||||
* 管理热更新过程生命周期:
|
||||
* 1. 暂停 ECS 更新循环
|
||||
* 2. 执行热更新任务(编译、加载、更新)
|
||||
* 3. 恢复 ECS 更新循环
|
||||
*/
|
||||
export class HotReloadCoordinator {
|
||||
private _status: IHotReloadStatus = {
|
||||
phase: EHotReloadPhase.Idle,
|
||||
startTime: 0
|
||||
};
|
||||
|
||||
private _coreReference: any = null;
|
||||
private _previousPausedState: boolean = false;
|
||||
private _hotReloadPromise: Promise<void> | null = null;
|
||||
private _onPhaseChange?: (phase: EHotReloadPhase) => void;
|
||||
|
||||
/**
|
||||
* Get current hot reload status.
|
||||
* 获取当前热更新状态。
|
||||
*/
|
||||
public get status(): Readonly<IHotReloadStatus> {
|
||||
return { ...this._status };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if hot reload is in progress.
|
||||
* 检查热更新是否进行中。
|
||||
*/
|
||||
public get isInProgress(): boolean {
|
||||
return this._status.phase !== EHotReloadPhase.Idle &&
|
||||
this._status.phase !== EHotReloadPhase.Complete &&
|
||||
this._status.phase !== EHotReloadPhase.Failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize coordinator with Core reference.
|
||||
* 使用 Core 引用初始化协调器。
|
||||
*
|
||||
* @param coreModule - ECS Framework Core module | ECS 框架 Core 模块
|
||||
*/
|
||||
public initialize(coreModule: any): void {
|
||||
this._coreReference = coreModule;
|
||||
logger.info('HotReloadCoordinator initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a coordinated hot reload.
|
||||
* 执行协调的热更新。
|
||||
*
|
||||
* This method ensures the ECS loop is paused during hot reload
|
||||
* and properly resumed afterward, even if an error occurs.
|
||||
*
|
||||
* 此方法确保 ECS 循环在热更新期间暂停,并在之后正确恢复,即使发生错误。
|
||||
*
|
||||
* @param reloadTask - Async function that performs the actual reload | 执行实际重载的异步函数
|
||||
* @param options - Hot reload options | 热更新选项
|
||||
* @returns Promise that resolves when hot reload is complete | 热更新完成时解析的 Promise
|
||||
*/
|
||||
public async performHotReload(
|
||||
reloadTask: () => Promise<HotReloadEvent | void>,
|
||||
options: IHotReloadOptions = {}
|
||||
): Promise<HotReloadEvent | void> {
|
||||
// Prevent concurrent hot reloads | 防止并发热更新
|
||||
if (this._hotReloadPromise) {
|
||||
logger.warn('Hot reload already in progress, waiting for completion | 热更新已在进行中,等待完成');
|
||||
await this._hotReloadPromise;
|
||||
}
|
||||
|
||||
const {
|
||||
timeout = 30000,
|
||||
restoreOnFailure = true,
|
||||
onPhaseChange
|
||||
} = options;
|
||||
|
||||
this._onPhaseChange = onPhaseChange;
|
||||
this._status = {
|
||||
phase: EHotReloadPhase.Idle,
|
||||
startTime: Date.now()
|
||||
};
|
||||
|
||||
let result: HotReloadEvent | void = undefined;
|
||||
|
||||
this._hotReloadPromise = (async () => {
|
||||
try {
|
||||
// Phase 1: Prepare - Pause ECS | 阶段 1:准备 - 暂停 ECS
|
||||
this._setPhase(EHotReloadPhase.Preparing);
|
||||
this._pauseECS();
|
||||
|
||||
// Create timeout promise | 创建超时 Promise
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`Hot reload timed out after ${timeout}ms | 热更新超时 ${timeout}ms`));
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
// Phase 2-5: Execute reload task with timeout | 阶段 2-5:带超时执行重载任务
|
||||
this._setPhase(EHotReloadPhase.Compiling);
|
||||
result = await Promise.race([
|
||||
reloadTask(),
|
||||
timeoutPromise
|
||||
]);
|
||||
|
||||
// Phase 6: Resume ECS | 阶段 6:恢复 ECS
|
||||
this._setPhase(EHotReloadPhase.Resuming);
|
||||
this._resumeECS();
|
||||
|
||||
// Phase 7: Complete | 阶段 7:完成
|
||||
this._setPhase(EHotReloadPhase.Complete);
|
||||
logger.info('Hot reload completed successfully | 热更新成功完成', {
|
||||
duration: Date.now() - this._status.startTime
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this._status.error = errorMessage;
|
||||
this._setPhase(EHotReloadPhase.Failed);
|
||||
|
||||
logger.error('Hot reload failed | 热更新失败:', error);
|
||||
|
||||
// Always resume ECS on failure | 失败时始终恢复 ECS
|
||||
if (restoreOnFailure) {
|
||||
this._resumeECS();
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
this._hotReloadPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
await this._hotReloadPromise;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update hot reload status with instance count.
|
||||
* 更新热更新状态的实例数量。
|
||||
*
|
||||
* @param instanceCount - Number of updated instances | 更新的实例数量
|
||||
*/
|
||||
public reportInstanceUpdate(instanceCount: number): void {
|
||||
this._status.updatedInstances = instanceCount;
|
||||
this._setPhase(EHotReloadPhase.UpdatingInstances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update hot reload status with system count.
|
||||
* 更新热更新状态的系统数量。
|
||||
*
|
||||
* @param systemCount - Number of updated systems | 更新的系统数量
|
||||
*/
|
||||
public reportSystemUpdate(systemCount: number): void {
|
||||
this._status.updatedSystems = systemCount;
|
||||
this._setPhase(EHotReloadPhase.UpdatingSystems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause ECS update loop.
|
||||
* 暂停 ECS 更新循环。
|
||||
*/
|
||||
private _pauseECS(): void {
|
||||
if (!this._coreReference) {
|
||||
logger.warn('Core reference not set, cannot pause ECS | Core 引用未设置,无法暂停 ECS');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store previous paused state to restore later | 存储之前的暂停状态以便后续恢复
|
||||
this._previousPausedState = this._coreReference.paused ?? false;
|
||||
|
||||
// Pause ECS | 暂停 ECS
|
||||
this._coreReference.paused = true;
|
||||
|
||||
logger.debug('ECS paused for hot reload | ECS 已暂停以进行热更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume ECS update loop.
|
||||
* 恢复 ECS 更新循环。
|
||||
*/
|
||||
private _resumeECS(): void {
|
||||
if (!this._coreReference) {
|
||||
logger.warn('Core reference not set, cannot resume ECS | Core 引用未设置,无法恢复 ECS');
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore previous paused state | 恢复之前的暂停状态
|
||||
this._coreReference.paused = this._previousPausedState;
|
||||
|
||||
logger.debug('ECS resumed after hot reload | 热更新后 ECS 已恢复', {
|
||||
paused: this._coreReference.paused
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current phase and notify listener.
|
||||
* 设置当前阶段并通知监听器。
|
||||
*/
|
||||
private _setPhase(phase: EHotReloadPhase): void {
|
||||
this._status.phase = phase;
|
||||
|
||||
if (this._onPhaseChange) {
|
||||
try {
|
||||
this._onPhaseChange(phase);
|
||||
} catch (error) {
|
||||
logger.warn('Error in phase change callback | 阶段变化回调错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Hot reload phase: ${phase} | 热更新阶段: ${phase}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset coordinator state.
|
||||
* 重置协调器状态。
|
||||
*/
|
||||
public reset(): void {
|
||||
this._status = {
|
||||
phase: EHotReloadPhase.Idle,
|
||||
startTime: 0
|
||||
};
|
||||
this._hotReloadPromise = null;
|
||||
this._onPhaseChange = undefined;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@
|
||||
* - scripts/editor/ -> Editor-only code (inspectors, gizmos, panels)
|
||||
*/
|
||||
|
||||
import type { IHotReloadOptions } from './HotReloadCoordinator';
|
||||
|
||||
/**
|
||||
* User code target environment.
|
||||
* 用户代码目标环境。
|
||||
@@ -280,8 +282,13 @@ export interface IUserCodeService {
|
||||
*
|
||||
* @param projectPath - Project root path | 项目根路径
|
||||
* @param onReload - Callback when code is reloaded | 代码重新加载时的回调
|
||||
* @param options - Hot reload options | 热更新选项
|
||||
*/
|
||||
watch(projectPath: string, onReload: (event: HotReloadEvent) => void): Promise<void>;
|
||||
watch(
|
||||
projectPath: string,
|
||||
onReload: (event: HotReloadEvent) => void,
|
||||
options?: IHotReloadOptions
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Stop watching for file changes.
|
||||
@@ -296,20 +303,36 @@ export interface IUserCodeService {
|
||||
isWatching(): boolean;
|
||||
}
|
||||
|
||||
import { EditorConfig } from '../../Config';
|
||||
|
||||
/**
|
||||
* Default scripts directory name.
|
||||
* 默认脚本目录名称。
|
||||
*
|
||||
* @deprecated Use EditorConfig.paths.scripts instead
|
||||
*/
|
||||
export const SCRIPTS_DIR = 'scripts';
|
||||
export const SCRIPTS_DIR = EditorConfig.paths.scripts;
|
||||
|
||||
/**
|
||||
* Editor scripts subdirectory name.
|
||||
* 编辑器脚本子目录名称。
|
||||
*
|
||||
* @deprecated Use EditorConfig.paths.editorScripts instead
|
||||
*/
|
||||
export const EDITOR_SCRIPTS_DIR = 'editor';
|
||||
export const EDITOR_SCRIPTS_DIR = EditorConfig.paths.editorScripts;
|
||||
|
||||
/**
|
||||
* Default output directory for compiled user code.
|
||||
* 编译后用户代码的默认输出目录。
|
||||
*
|
||||
* @deprecated Use EditorConfig.paths.compiled instead
|
||||
*/
|
||||
export const USER_CODE_OUTPUT_DIR = '.esengine/compiled';
|
||||
export const USER_CODE_OUTPUT_DIR = EditorConfig.paths.compiled;
|
||||
|
||||
// Re-export hot reload coordinator types
|
||||
// 重新导出热更新协调器类型
|
||||
export {
|
||||
EHotReloadPhase,
|
||||
type IHotReloadStatus,
|
||||
type IHotReloadOptions
|
||||
} from './HotReloadCoordinator';
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
*/
|
||||
|
||||
import type { IService } from '@esengine/ecs-framework';
|
||||
import { Injectable, createLogger, PlatformDetector, ComponentRegistry as CoreComponentRegistry } from '@esengine/ecs-framework';
|
||||
import {
|
||||
Injectable,
|
||||
createLogger,
|
||||
PlatformDetector,
|
||||
ComponentRegistry as CoreComponentRegistry,
|
||||
COMPONENT_TYPE_NAME,
|
||||
SYSTEM_TYPE_NAME
|
||||
} from '@esengine/ecs-framework';
|
||||
import type {
|
||||
IUserCodeService,
|
||||
UserScriptInfo,
|
||||
@@ -15,7 +22,8 @@ import type {
|
||||
UserCodeCompileResult,
|
||||
CompileError,
|
||||
UserCodeModule,
|
||||
HotReloadEvent
|
||||
HotReloadEvent,
|
||||
IHotReloadOptions
|
||||
} from './IUserCodeService';
|
||||
import {
|
||||
UserCodeTarget,
|
||||
@@ -23,6 +31,8 @@ import {
|
||||
EDITOR_SCRIPTS_DIR,
|
||||
USER_CODE_OUTPUT_DIR
|
||||
} from './IUserCodeService';
|
||||
import { HotReloadCoordinator, EHotReloadPhase } from './HotReloadCoordinator';
|
||||
import { EditorConfig } from '../../Config';
|
||||
import type { IFileSystem, FileEntry } from '../IFileSystem';
|
||||
import type { ComponentInspectorRegistry, IComponentInspector } from '../ComponentInspectorRegistry';
|
||||
import { GizmoRegistry } from '../../Gizmos/GizmoRegistry';
|
||||
@@ -64,8 +74,15 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
*/
|
||||
private _registeredGizmoTypes: any[] = [];
|
||||
|
||||
/**
|
||||
* 热更新协调器
|
||||
* Hot reload coordinator
|
||||
*/
|
||||
private _hotReloadCoordinator: HotReloadCoordinator;
|
||||
|
||||
constructor(fileSystem: IFileSystem) {
|
||||
this._fileSystem = fileSystem;
|
||||
this._hotReloadCoordinator = new HotReloadCoordinator();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,8 +177,8 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
|
||||
// Determine output file name | 确定输出文件名
|
||||
const outputFileName = options.target === UserCodeTarget.Runtime
|
||||
? 'user-runtime.js'
|
||||
: 'user-editor.js';
|
||||
? EditorConfig.output.runtimeBundle
|
||||
: EditorConfig.output.editorBundle;
|
||||
const outputPath = `${outputDir}${sep}${outputFileName}`;
|
||||
|
||||
// Build entry point content | 构建入口点内容
|
||||
@@ -176,8 +193,8 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
|
||||
// Determine global name for IIFE output | 确定 IIFE 输出的全局名称
|
||||
const globalName = options.target === UserCodeTarget.Runtime
|
||||
? '__USER_RUNTIME_EXPORTS__'
|
||||
: '__USER_EDITOR_EXPORTS__';
|
||||
? EditorConfig.globals.userRuntimeExports
|
||||
: EditorConfig.globals.userEditorExports;
|
||||
|
||||
// Build alias map for framework dependencies | 构建框架依赖的别名映射
|
||||
const shimPath = `${outputDir}${sep}_shim_ecs_framework.js`.replace(/\\/g, '/');
|
||||
@@ -421,7 +438,8 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
|
||||
// Access scene through Core.scene
|
||||
// 通过 Core.scene 访问场景
|
||||
const Core = (window as any).__ESENGINE__?.ecsFramework?.Core;
|
||||
const sdkGlobal = (window as any)[EditorConfig.globals.sdk];
|
||||
const Core = sdkGlobal?.ecsFramework?.Core;
|
||||
const scene = Core?.scene;
|
||||
if (!scene || !scene.entities) {
|
||||
logger.warn('No active scene for hot reload | 没有活动场景用于热更新');
|
||||
@@ -526,9 +544,10 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
systemInstance.enabled = enabled;
|
||||
}
|
||||
|
||||
// 标记为用户系统,便于后续识别和移除 | Mark as user system for later identification and removal
|
||||
systemInstance.__isUserSystem__ = true;
|
||||
systemInstance.__userSystemName__ = name;
|
||||
// 标记为用户系统,便于后续识别和移除
|
||||
// Mark as user system for later identification and removal
|
||||
systemInstance[EditorConfig.typeMarkers.userSystem] = true;
|
||||
systemInstance[EditorConfig.typeMarkers.userSystemName] = name;
|
||||
|
||||
// 添加到场景 | Add to scene
|
||||
scene.addSystem(systemInstance);
|
||||
@@ -565,9 +584,11 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
for (const system of this._registeredSystems) {
|
||||
try {
|
||||
scene.removeSystem(system);
|
||||
logger.debug(`Removed user system: ${system.__userSystemName__} | 移除用户系统: ${system.__userSystemName__}`);
|
||||
const systemName = system[EditorConfig.typeMarkers.userSystemName];
|
||||
logger.debug(`Removed user system: ${systemName} | 移除用户系统: ${systemName}`);
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to remove system ${system.__userSystemName__}:`, err);
|
||||
const systemName = system[EditorConfig.typeMarkers.userSystemName];
|
||||
logger.warn(`Failed to remove system ${systemName}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,8 +713,13 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
*
|
||||
* @param projectPath - Project root path | 项目根路径
|
||||
* @param onReload - Callback when code is reloaded | 代码重新加载时的回调
|
||||
* @param options - Hot reload options | 热更新选项
|
||||
*/
|
||||
async watch(projectPath: string, onReload: (event: HotReloadEvent) => void): Promise<void> {
|
||||
async watch(
|
||||
projectPath: string,
|
||||
onReload: (event: HotReloadEvent) => void,
|
||||
options?: IHotReloadOptions
|
||||
): Promise<void> {
|
||||
if (this._watching) {
|
||||
this._watchCallbacks.push(onReload);
|
||||
return;
|
||||
@@ -701,6 +727,16 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
|
||||
this._currentProjectPath = projectPath;
|
||||
|
||||
// Initialize hot reload coordinator with Core reference
|
||||
// 使用 Core 引用初始化热更新协调器
|
||||
const sdkGlobal = (window as any)[EditorConfig.globals.sdk];
|
||||
const Core = sdkGlobal?.ecsFramework?.Core;
|
||||
if (Core) {
|
||||
this._hotReloadCoordinator.initialize(Core);
|
||||
} else {
|
||||
logger.warn('Core not available for HotReloadCoordinator | Core 不可用于热更新协调器');
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if we're in Tauri environment | 检查是否在 Tauri 环境
|
||||
if (PlatformDetector.isTauriEnvironment()) {
|
||||
@@ -731,27 +767,44 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
// Get previous module | 获取之前的模块
|
||||
const previousModule = this.getModule(target);
|
||||
|
||||
// Recompile the affected target | 重新编译受影响的目标
|
||||
const compileResult = await this.compile({
|
||||
projectPath,
|
||||
target
|
||||
});
|
||||
// Use coordinator for synchronized hot reload
|
||||
// 使用协调器进行同步热更新
|
||||
try {
|
||||
const hotReloadEvent = await this._hotReloadCoordinator.performHotReload(
|
||||
async () => {
|
||||
// Recompile the affected target | 重新编译受影响的目标
|
||||
const compileResult = await this.compile({
|
||||
projectPath,
|
||||
target
|
||||
});
|
||||
|
||||
if (compileResult.success && compileResult.outputPath) {
|
||||
// Reload the module | 重新加载模块
|
||||
const newModule = await this.load(compileResult.outputPath, target);
|
||||
if (!compileResult.success || !compileResult.outputPath) {
|
||||
throw new Error(
|
||||
`Hot reload compilation failed: ${compileResult.errors.map(e => e.message).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
// Create hot reload event | 创建热更新事件
|
||||
const hotReloadEvent: HotReloadEvent = {
|
||||
target,
|
||||
changedFiles: paths,
|
||||
previousModule,
|
||||
newModule
|
||||
};
|
||||
// Reload the module | 重新加载模块
|
||||
const newModule = await this.load(compileResult.outputPath, target);
|
||||
|
||||
this._notifyHotReload(hotReloadEvent);
|
||||
} else {
|
||||
logger.error('Hot reload compilation failed | 热更新编译失败', compileResult.errors);
|
||||
// Create hot reload event | 创建热更新事件
|
||||
const reloadEvent: HotReloadEvent = {
|
||||
target,
|
||||
changedFiles: paths,
|
||||
previousModule,
|
||||
newModule
|
||||
};
|
||||
|
||||
return reloadEvent;
|
||||
},
|
||||
options
|
||||
) as HotReloadEvent;
|
||||
|
||||
if (hotReloadEvent) {
|
||||
this._notifyHotReload(hotReloadEvent);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Hot reload failed | 热更新失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -774,6 +827,16 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hot reload coordinator.
|
||||
* 获取热更新协调器。
|
||||
*
|
||||
* @returns Hot reload coordinator instance | 热更新协调器实例
|
||||
*/
|
||||
getHotReloadCoordinator(): HotReloadCoordinator {
|
||||
return this._hotReloadCoordinator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching for file changes.
|
||||
* 停止监视文件变更。
|
||||
@@ -971,12 +1034,13 @@ export class UserCodeService implements IService, IUserCodeService {
|
||||
const shimPaths: string[] = [];
|
||||
|
||||
// Create shim for @esengine/ecs-framework | 为 @esengine/ecs-framework 创建 shim
|
||||
// This uses window.__ESENGINE__.ecsFramework set by PluginSDKRegistry
|
||||
// 这使用 PluginSDKRegistry 设置的 window.__ESENGINE__.ecsFramework
|
||||
// This uses window[EditorConfig.globals.sdk].ecsFramework set by PluginSDKRegistry
|
||||
// 这使用 PluginSDKRegistry 设置的 window[EditorConfig.globals.sdk].ecsFramework
|
||||
const ecsShimPath = `${outputDir}${sep}_shim_ecs_framework.js`;
|
||||
const sdkGlobalName = EditorConfig.globals.sdk;
|
||||
const ecsShimContent = `// Shim for @esengine/ecs-framework
|
||||
// Maps to window.__ESENGINE__.ecsFramework set by PluginSDKRegistry
|
||||
module.exports = (typeof window !== 'undefined' && window.__ESENGINE__ && window.__ESENGINE__.ecsFramework) || {};
|
||||
// Maps to window.${sdkGlobalName}.ecsFramework set by PluginSDKRegistry
|
||||
module.exports = (typeof window !== 'undefined' && window.${sdkGlobalName} && window.${sdkGlobalName}.ecsFramework) || {};
|
||||
`;
|
||||
await this._fileSystem.writeFile(ecsShimPath, ecsShimContent);
|
||||
shimPaths.push(ecsShimPath);
|
||||
@@ -1101,8 +1165,8 @@ module.exports = (typeof window !== 'undefined' && window.__ESENGINE__ && window
|
||||
): Promise<Record<string, any>> {
|
||||
// Determine global name based on target | 根据目标确定全局名称
|
||||
const globalName = target === UserCodeTarget.Runtime
|
||||
? '__USER_RUNTIME_EXPORTS__'
|
||||
: '__USER_EDITOR_EXPORTS__';
|
||||
? EditorConfig.globals.userRuntimeExports
|
||||
: EditorConfig.globals.userEditorExports;
|
||||
|
||||
// Clear any previous exports | 清除之前的导出
|
||||
(window as any)[globalName] = undefined;
|
||||
@@ -1150,47 +1214,45 @@ module.exports = (typeof window !== 'undefined' && window.__ESENGINE__ && window
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a class extends Component.
|
||||
* 检查类是否继承自 Component。
|
||||
* Check if a class is decorated with @ECSComponent.
|
||||
* 检查类是否使用了 @ECSComponent 装饰器。
|
||||
*
|
||||
* Uses the actual Component class from the global framework to check inheritance.
|
||||
* 使用全局框架中的实际 Component 类来检查继承关系。
|
||||
* 用户组件必须使用 @ECSComponent 装饰器才能被识别。
|
||||
* User components must use @ECSComponent decorator to be recognized.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @ECSComponent('MyComponent')
|
||||
* class MyComponent extends Component {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private _isComponentClass(cls: any): boolean {
|
||||
// Get Component class from global framework | 从全局框架获取 Component 类
|
||||
const framework = (window as any).__ESENGINE__?.ecsFramework;
|
||||
|
||||
if (!framework?.Component) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use instanceof or prototype chain check | 使用 instanceof 或原型链检查
|
||||
try {
|
||||
const ComponentClass = framework.Component;
|
||||
|
||||
// Check if cls.prototype is an instance of Component
|
||||
// 检查 cls.prototype 是否是 Component 的实例
|
||||
return cls.prototype instanceof ComponentClass ||
|
||||
ComponentClass.prototype.isPrototypeOf(cls.prototype);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 检查是否有 @ECSComponent 装饰器(通过 Symbol 键)
|
||||
// Check if class has @ECSComponent decorator (via Symbol key)
|
||||
return cls?.[COMPONENT_TYPE_NAME] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a class extends System.
|
||||
* 检查类是否继承自 System。
|
||||
* Check if a class is decorated with @ECSSystem.
|
||||
* 检查类是否使用了 @ECSSystem 装饰器。
|
||||
*
|
||||
* 用户系统必须使用 @ECSSystem 装饰器才能被识别。
|
||||
* User systems must use @ECSSystem decorator to be recognized.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @ECSSystem('MySystem')
|
||||
* class MySystem extends EntitySystem {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private _isSystemClass(cls: any): boolean {
|
||||
let proto = cls.prototype;
|
||||
while (proto) {
|
||||
const name = proto.constructor?.name;
|
||||
if (name === 'System' || name === 'EntityProcessingSystem') {
|
||||
return true;
|
||||
}
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
return false;
|
||||
// 检查是否有 @ECSSystem 装饰器(通过 Symbol 键)
|
||||
// Check if class has @ECSSystem decorator (via Symbol key)
|
||||
return cls?.[SYSTEM_TYPE_NAME] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,14 +102,19 @@ export type {
|
||||
UserCodeCompileResult,
|
||||
CompileError,
|
||||
UserCodeModule,
|
||||
HotReloadEvent
|
||||
HotReloadEvent,
|
||||
IHotReloadStatus,
|
||||
IHotReloadOptions
|
||||
} from './IUserCodeService';
|
||||
|
||||
export {
|
||||
UserCodeTarget,
|
||||
SCRIPTS_DIR,
|
||||
EDITOR_SCRIPTS_DIR,
|
||||
USER_CODE_OUTPUT_DIR
|
||||
USER_CODE_OUTPUT_DIR,
|
||||
EHotReloadPhase
|
||||
} from './IUserCodeService';
|
||||
|
||||
export { UserCodeService } from './UserCodeService';
|
||||
|
||||
export { HotReloadCoordinator } from './HotReloadCoordinator';
|
||||
|
||||
Reference in New Issue
Block a user