# Application **命名空间**: `XCEngine::Editor` **类型**: `class (singleton)` **源文件**: `editor/src/Application.h` **描述**: 编辑器进程的组合根,负责主窗口渲染宿主、`EditorContext`、脚本运行时、ImGui 会话与视口宿主服务的启动、逐帧驱动和关闭。 ## 概述 `Application` 不是“又一个编辑器功能模块”,而是当前 Editor 可执行程序最外层的壳。 它把一批必须按顺序初始化、按反向顺序关闭的系统串起来: - Win32 窗口句柄与窗口标题。 - [D3D12WindowRenderer](../Platform/D3D12WindowRenderer/D3D12WindowRenderer.md)。 - [EditorContext](../Core/EditorContext/EditorContext.md) 与其内部的 manager / event bus。 - [ImGuiBackendBridge](../UI/ImGuiBackendBridge/ImGuiBackendBridge.md) 与 [ImGuiSession](../UI/ImGuiSession/ImGuiSession.md)。 - [ViewportHostService](../Viewport/ViewportHostService/ViewportHostService.md)。 - `LayerStack` 与 [EditorLayer](../Layers/EditorLayer/EditorLayer.md)。 - 全局资源系统和脚本运行时桥接。 如果把商业编辑器类比成“宿主壳 + 工具层 + 领域服务”,`Application` 对应的就是宿主壳。它自己不直接实现 Inspector、Hierarchy、Project Browser 或 Scene View 的业务规则,但它决定这些系统能否以正确顺序运行起来。 ## 前置知识 理解这个类型,先抓住两点: - 编辑器真正的业务状态主要在 [EditorContext](../Core/EditorContext/EditorContext.md) 内,而不在 `Application` 自身。 - `Application` 的核心职责是“编排顺序”而不是“承载全部逻辑”。 这也是商业级工具常见的分层方式。宿主层负责窗口、GPU 设备、UI 帧和生命周期;面板、命令和 manager 负责编辑器行为。这样项目切换、脚本热重载、布局恢复和视口渲染才不会互相缠死。 ## 公开成员 | 成员 | 说明 | |------|------| | `Get()` | 返回进程级单例。 | | [Initialize(HWND)](Initialize.md) | 初始化窗口渲染、上下文、脚本运行时、ImGui 和编辑器 layer。 | | [Shutdown()](Shutdown.md) | 按反向顺序释放所有宿主级资源。 | | [Render()](Render.md) | 推进一帧 editor update + render。 | | [OnResize(int, int)](OnResize-And-SaveProjectState.md) | 将窗口尺寸变化转发给主窗口渲染器。 | | [SwitchProject(const std::string&)](SwitchProject.md) | 切换项目根目录、资源根、脚本运行时与布局会话。 | | [`ReloadScriptingRuntime()`](ReloadScriptingRuntime.md) | 重新从当前项目的 `Library/ScriptAssemblies` 装载脚本运行时。 | | [`RebuildScriptingAssemblies()`](RebuildScriptingAssemblies.md) | 先卸载当前脚本 runtime 释放程序集锁,再触发项目脚本程序集重建,并在成功后重载运行时。 | | [`CanReimportProjectAsset(const std::string&)`](CanReimportProjectAsset.md) | 判断某个项目路径当前是否支持显式重导。 | | [`ReimportProjectAsset(const std::string&)`](ReimportProjectAsset.md) | 强制重导单个 project asset,并刷新项目资产索引。 | | [`ReimportAllProjectAssets()`](ReimportAllProjectAssets.md) | 重建整个项目 `Library` 资产缓存。 | | [`ClearProjectLibrary()`](ClearProjectLibrary.md) | 清空当前项目 `Library` 缓存目录与对应索引。 | | [`GetProjectLibraryRoot()`](GetProjectLibraryRoot.md) | 返回当前项目 `Library` 根目录。 | | [SaveProjectState()](OnResize-And-SaveProjectState.md) | 保存当前 ImGui 会话状态。 | | `GetMainRenderContext()` | 暴露主窗口 render context。 | | `GetMainRHIDevice()` / `GetMainSwapChain()` | 暴露主窗口 RHI 设备与交换链。 | | `GetViewportHostService()` | 暴露 Scene / Game 等视口宿主服务。 | | `GetEditorContext()` | 访问当前 editor context。 | | [`GetScriptRuntimeStatus()`](GetScriptRuntimeStatus.md) | 返回当前脚本运行时状态快照。 | | `IsRenderReady()` | 判断渲染宿主是否已完成初始化。 | | `GetWindowHandle()` | 返回主窗口句柄。 | ## 启动链路 按 `Application.cpp` 当前实现,[Initialize(HWND)](Initialize.md) 的真实顺序是: 1. 安装崩溃异常过滤器,并把 `stderr` 重定向到可执行目录日志。 2. 解析编辑器项目根目录,并尝试切换当前工作目录。 3. 初始化主窗口渲染器。 4. 初始化全局 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md),并把资源根设置到项目目录。 5. 创建 [EditorContext](../Core/EditorContext/EditorContext.md) 并记录项目路径。 6. 在 `EventBus` 上订阅 `EditorExitRequestedEvent`,把“请求退出”转换成 `WM_CLOSE`。 7. 初始化脚本运行时。 8. 初始化 ImGui 会话、DX12 后端、内置图标和 `ViewportHostService`。 9. 创建并挂接 [EditorLayer](../Layers/EditorLayer/EditorLayer.md)。 10. 标记 `m_renderReady = true`。 这个顺序很重要。比如: - `EditorContext` 必须先存在,ImGui 初始化时才能把 project path 和 viewport host service 注入进去。 - `ViewportHostService` 依赖 ImGui backend 和 RHI device,不能早于窗口渲染器。 - `EditorLayer` 依赖已经就绪的 context、ImGui 和视口服务,必须放在最后挂接。 ## 每帧执行链路 [`Render()`](Render.md) 当前不是单纯“画一帧 UI”,而是 Editor 主循环的最外层入口: 1. 计算 `deltaTime`。 2. 调用 `m_layerStack.onUpdate(deltaTime)`。 3. 进入 `RenderEditorFrame()`。 `RenderEditorFrame()` 的链路则是: 1. `m_windowRenderer.BeginFrame()`。 2. `m_imguiBackend.BeginFrame()`。 3. `m_viewportHostService.BeginFrame()`。 4. `m_layerStack.onImGuiRender()`。 5. `UpdateWindowTitle()`。 6. `ImGui::Render()`。 7. 调用 `m_windowRenderer.Render(...)`,并在 render callback 中执行 `m_viewportHostService.RenderRequestedViewports(...)`。 这条链路体现了当前 editor 的核心分工: - `Application` 负责帧边界。 - `LayerStack` 负责工具 UI 的组织。 - `ViewportHostService` 负责真正的视口渲染请求。 所以典型的数据流是: `Application::Render()` -> 面板与 layer 采集输入 / 生成视口请求 -> `ViewportHostService` 渲染 -> 主窗口提交。 ## 项目切换与脚本运行时 `Application` 还是当前 editor 中“项目切换”和“脚本运行时热更新”的宿主层入口。 ### 脚本运行时初始化 `InitializeScriptingRuntime(projectPath)` 会把程序集目录固定到: - `/Library/ScriptAssemblies/XCEngine.ScriptCore.dll` - `/Library/ScriptAssemblies/GameScripts.dll` - `/Library/ScriptAssemblies/mscorlib.dll` 在启用 `XCENGINE_ENABLE_MONO_SCRIPTING` 的构建里: - 缺少任一程序集时,不会崩溃,而是把 [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md) 运行时清空,并写入状态消息。 - 只有 `MonoScriptRuntime::Initialize()` 成功后,才会把 runtime 注册到 [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md)。 这意味着当前 editor 对脚本程序集采用的是“软失败”策略。项目还没编译 managed 程序集时,Inspector 和脚本类发现会退化,但编辑器主体仍可启动。 ### 项目切换 [`SwitchProject(projectPath)`](SwitchProject.md) 当前会同步完成: - 切换 `ImGuiSession` 所属项目路径。 - 更新 `EditorContext` 项目路径。 - 更新工作目录。 - 更新 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md) 的资源根。 - 重新初始化脚本运行时。 - 刷新窗口标题。 - 当目标项目没有已保存布局时,发布 `DockLayoutResetRequestedEvent`。 这和 Unity / Unreal 这类工具的思路是一致的:项目切换不是只换一个路径字符串,而是一次宿主级上下文切换。 ### 脚本程序集重建 `RebuildScriptingAssemblies()` 会先关闭当前脚本 runtime 以释放 `GameScripts.dll` 这类项目本地程序集的文件锁,再调用 editor 侧脚本程序集构建器。构建成功后,才继续走 `ReloadScriptingRuntime()`;如果构建或重载失败,editor 会留下失败状态消息,但不会继续保留旧 runtime。 如果需要分别看重载、重建和状态读取的返回值语义,可以继续阅读: - [ReloadScriptingRuntime](ReloadScriptingRuntime.md) - [RebuildScriptingAssemblies](RebuildScriptingAssemblies.md) - [GetScriptRuntimeStatus](GetScriptRuntimeStatus.md) ## 项目资产重导与 Library 管理 `Application` 现在还暴露了一组 editor 宿主级项目资产缓存入口,统一转发到全局 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)。 这几项接口的定位不是运行时按需加载,而是让菜单、命令路由和项目面板能通过宿主层执行显式缓存维护: - [`CanReimportProjectAsset(assetPath)`](CanReimportProjectAsset.md) 判断给定路径当前是否可走重导链路。 - [`ReimportProjectAsset(assetPath)`](ReimportProjectAsset.md) 强制重导单个 asset,并刷新项目资产索引。 - [`ReimportAllProjectAssets()`](ReimportAllProjectAssets.md) 清空已加载资源后重建整个项目 `Library` 缓存。 - [`ClearProjectLibrary()`](ClearProjectLibrary.md) 清空 `Library` 缓存,但不立即重建所有 artifact。 - [`GetProjectLibraryRoot()`](GetProjectLibraryRoot.md) 返回当前项目缓存目录路径。 这组能力被放在 `Application` 而不是某个单独 panel 里,是因为它们都依赖当前项目根、全局资源系统和 editor 宿主生命周期。 ## 关闭链路与所有权 [`Shutdown()`](Shutdown.md) 采用严格的反向释放顺序: 1. 停止渲染。 2. Detach `EditorLayer`。 3. 从 `EditorContext` 移除 viewport host service 引用。 4. 关闭 `ViewportHostService`。 5. 释放内置图标、ImGui backend、ImGui session。 6. 关闭脚本运行时并从 [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md) 卸下 runtime。 7. 销毁 `EditorContext`。 8. 关闭 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)。 9. 关闭主窗口渲染器。 当前所有权关系大致是: - `Application` 持有 `EditorContext`、`ViewportHostService`、ImGui backend/session 和窗口渲染器。 - `LayerStack` 拥有 `EditorLayer` 的生命周期。 - [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md) 本身是全局单例,但 runtime 实例的所有权由 `Application` 持有。 ## 设计说明 当前实现有几个很重要的设计取向: - 把顺序敏感的宿主初始化集中到 `Application`,避免散落在各个 panel / manager 中。 - 通过 `EventBus` 把退出请求、布局重置这类“宿主动作”做成事件,而不是让菜单栏直接调用 Win32 API。 - 把脚本运行时热重载放在宿主层,而不是 Inspector 或 `ScriptEngine` 内部硬编码,便于项目切换和重新编译。 这类设计的好处是明显的: - 项目切换不需要重启 editor。 - 视口渲染与 ImGui 会话有稳定的帧边界。 - 脚本运行时缺失时,editor 仍可继续工作。 ## 当前限制 - 当前主窗口宿主固定绑定 [D3D12WindowRenderer](../Platform/D3D12WindowRenderer/D3D12WindowRenderer.md) 和 Win32,不是多后端宿主抽象。 - 整个类型按单窗口、单 swap chain 设计,没有多 editor window 管理。 - 脚本运行时是否可用受编译宏和程序集产物双重限制。 - 所有入口都应视为主线程 API;当前没有显式跨线程调度。 ## 相关文档 - [EditorContext](../Core/EditorContext/EditorContext.md) - [EventBus](../Core/EventBus/EventBus.md) - [EditorEvents](../Core/EditorEvents/EditorEvents.md) - [EditorLayer](../Layers/EditorLayer/EditorLayer.md) - [ViewportHostService](../Viewport/ViewportHostService/ViewportHostService.md) - [ImGuiBackendBridge](../UI/ImGuiBackendBridge/ImGuiBackendBridge.md) - [ImGuiSession](../UI/ImGuiSession/ImGuiSession.md) - [ReloadScriptingRuntime](ReloadScriptingRuntime.md) - [RebuildScriptingAssemblies](RebuildScriptingAssemblies.md) - [GetScriptRuntimeStatus](GetScriptRuntimeStatus.md) - [CanReimportProjectAsset](CanReimportProjectAsset.md) - [ReimportProjectAsset](ReimportProjectAsset.md) - [ReimportAllProjectAssets](ReimportAllProjectAssets.md) - [ClearProjectLibrary](ClearProjectLibrary.md) - [GetProjectLibraryRoot](GetProjectLibraryRoot.md) - [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md)