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

12 KiB
Raw Blame History

Application

命名空间: XCEngine::Editor

类型: class (singleton)

源文件: editor/src/Application.h

描述: 编辑器进程的组合根,负责主窗口渲染宿主、EditorContext、脚本运行时、ImGui 会话与视口宿主服务的启动、逐帧驱动和关闭。

概述

Application 不是“又一个编辑器功能模块”,而是当前 Editor 可执行程序最外层的壳。

它把一批必须按顺序初始化、按反向顺序关闭的系统串起来:

如果把商业编辑器类比成“宿主壳 + 工具层 + 领域服务”,Application 对应的就是宿主壳。它自己不直接实现 Inspector、Hierarchy、Project Browser 或 Scene View 的业务规则,但它决定这些系统能否以正确顺序运行起来。

前置知识

理解这个类型,先抓住两点:

  • 编辑器真正的业务状态主要在 EditorContext 内,而不在 Application 自身。
  • Application 的核心职责是“编排顺序”而不是“承载全部逻辑”。

这也是商业级工具常见的分层方式。宿主层负责窗口、GPU 设备、UI 帧和生命周期;面板、命令和 manager 负责编辑器行为。这样项目切换、脚本热重载、布局恢复和视口渲染才不会互相缠死。

公开成员

成员 说明
Get() 返回进程级单例。
Initialize(HWND) 初始化窗口渲染、上下文、脚本运行时、ImGui 和编辑器 layer。
Shutdown() 按反向顺序释放所有宿主级资源。
Render() 推进一帧 editor update + render。
OnResize(int, int) 将窗口尺寸变化转发给主窗口渲染器。
SwitchProject(const std::string&) 切换项目根目录、资源根、脚本运行时与布局会话。
ReloadScriptingRuntime() 重新从当前项目的 Library/ScriptAssemblies 装载脚本运行时。
RebuildScriptingAssemblies() 先卸载当前脚本 runtime 释放程序集锁,再触发项目脚本程序集重建,并在成功后重载运行时。
CanReimportProjectAsset(const std::string&) 判断某个项目路径当前是否支持显式重导。
ReimportProjectAsset(const std::string&) 强制重导单个 project asset并刷新项目资产索引。
ReimportAllProjectAssets() 重建整个项目 Library 资产缓存。
ClearProjectLibrary() 清空当前项目 Library 缓存目录与对应索引。
GetProjectLibraryRoot() 返回当前项目 Library 根目录。
SaveProjectState() 保存当前 ImGui 会话状态。
GetMainRenderContext() 暴露主窗口 render context。
GetMainRHIDevice() / GetMainSwapChain() 暴露主窗口 RHI 设备与交换链。
GetViewportHostService() 暴露 Scene / Game 等视口宿主服务。
GetEditorContext() 访问当前 editor context。
GetScriptRuntimeStatus() 返回当前脚本运行时状态快照。
IsRenderReady() 判断渲染宿主是否已完成初始化。
GetWindowHandle() 返回主窗口句柄。

启动链路

Application.cpp 当前实现,Initialize(HWND) 的真实顺序是:

  1. 安装崩溃异常过滤器,并把 stderr 重定向到可执行目录日志。
  2. 解析编辑器项目根目录,并尝试切换当前工作目录。
  3. 初始化主窗口渲染器。
  4. 初始化全局 ResourceManager,并把资源根设置到项目目录。
  5. 创建 EditorContext 并记录项目路径。
  6. EventBus 上订阅 EditorExitRequestedEvent,把“请求退出”转换成 WM_CLOSE
  7. 初始化脚本运行时。
  8. 初始化 ImGui 会话、DX12 后端、内置图标和 ViewportHostService
  9. 创建并挂接 EditorLayer
  10. 标记 m_renderReady = true

这个顺序很重要。比如:

  • EditorContext 必须先存在ImGui 初始化时才能把 project path 和 viewport host service 注入进去。
  • ViewportHostService 依赖 ImGui backend 和 RHI device不能早于窗口渲染器。
  • EditorLayer 依赖已经就绪的 context、ImGui 和视口服务,必须放在最后挂接。

每帧执行链路

Render() 当前不是单纯“画一帧 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 运行时清空,并写入状态消息。
  • 只有 MonoScriptRuntime::Initialize() 成功后,才会把 runtime 注册到 ScriptEngine

这意味着当前 editor 对脚本程序集采用的是“软失败”策略。项目还没编译 managed 程序集时Inspector 和脚本类发现会退化,但编辑器主体仍可启动。

项目切换

SwitchProject(projectPath) 当前会同步完成:

  • 切换 ImGuiSession 所属项目路径。
  • 更新 EditorContext 项目路径。
  • 更新工作目录。
  • 更新 ResourceManager 的资源根。
  • 重新初始化脚本运行时。
  • 刷新窗口标题。
  • 当目标项目没有已保存布局时,发布 DockLayoutResetRequestedEvent

这和 Unity / Unreal 这类工具的思路是一致的:项目切换不是只换一个路径字符串,而是一次宿主级上下文切换。

脚本程序集重建

RebuildScriptingAssemblies() 会先关闭当前脚本 runtime 以释放 GameScripts.dll 这类项目本地程序集的文件锁,再调用 editor 侧脚本程序集构建器。构建成功后,才继续走 ReloadScriptingRuntime()如果构建或重载失败editor 会留下失败状态消息,但不会继续保留旧 runtime。

如果需要分别看重载、重建和状态读取的返回值语义,可以继续阅读:

项目资产重导与 Library 管理

Application 现在还暴露了一组 editor 宿主级项目资产缓存入口,统一转发到全局 ResourceManager

这几项接口的定位不是运行时按需加载,而是让菜单、命令路由和项目面板能通过宿主层执行显式缓存维护:

这组能力被放在 Application 而不是某个单独 panel 里,是因为它们都依赖当前项目根、全局资源系统和 editor 宿主生命周期。

关闭链路与所有权

Shutdown() 采用严格的反向释放顺序:

  1. 停止渲染。
  2. Detach EditorLayer
  3. EditorContext 移除 viewport host service 引用。
  4. 关闭 ViewportHostService
  5. 释放内置图标、ImGui backend、ImGui session。
  6. 关闭脚本运行时并从 ScriptEngine 卸下 runtime。
  7. 销毁 EditorContext
  8. 关闭 ResourceManager
  9. 关闭主窗口渲染器。

当前所有权关系大致是:

  • Application 持有 EditorContextViewportHostService、ImGui backend/session 和窗口渲染器。
  • LayerStack 拥有 EditorLayer 的生命周期。
  • ScriptEngine 本身是全局单例,但 runtime 实例的所有权由 Application 持有。

设计说明

当前实现有几个很重要的设计取向:

  • 把顺序敏感的宿主初始化集中到 Application,避免散落在各个 panel / manager 中。
  • 通过 EventBus 把退出请求、布局重置这类“宿主动作”做成事件,而不是让菜单栏直接调用 Win32 API。
  • 把脚本运行时热重载放在宿主层,而不是 Inspector 或 ScriptEngine 内部硬编码,便于项目切换和重新编译。

这类设计的好处是明显的:

  • 项目切换不需要重启 editor。
  • 视口渲染与 ImGui 会话有稳定的帧边界。
  • 脚本运行时缺失时editor 仍可继续工作。

当前限制

  • 当前主窗口宿主固定绑定 D3D12WindowRenderer 和 Win32不是多后端宿主抽象。
  • 整个类型按单窗口、单 swap chain 设计,没有多 editor window 管理。
  • 脚本运行时是否可用受编译宏和程序集产物双重限制。
  • 所有入口都应视为主线程 API当前没有显式跨线程调度。

相关文档