Files
XCEngine/docs/api/XCEngine/Editor/Application/Application.md

232 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)` 会把程序集目录固定到:
- `<Project>/Library/ScriptAssemblies/XCEngine.ScriptCore.dll`
- `<Project>/Library/ScriptAssemblies/GameScripts.dll`
- `<Project>/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)