14 KiB
Editor Architecture And Workflow
先建立正确心智模型
docs/api/XCEngine/Editor/** 对应的是编辑器应用层,而不是引擎运行时 public API。
当前这套 Editor 已经不是“只有窗口壳和几块早期 ImGui 面板”的状态。按 editor/src/**、project/ 和当前脚本程序集目录来看,它已经形成了一条真实闭环:
Application -> EditorContext -> Panels / Actions / Commands -> ViewportHostService -> Rendering / ResourceManager / ScriptEngine
同时还和项目目录结构直接对接:
Project.xcprojectAssets/.metaLibrary/SourceAssetDBLibrary/ArtifactDBLibrary/ArtifactsLibrary/ScriptAssemblies
因此这组文档的重点,不是“介绍一个理想中的未来编辑器”,而是解释当前 checkout 下这条链路已经怎么工作。
顶层宿主是怎样启动的
Application 是编辑器进程的组合根。
它的启动顺序大致是:
- 安装崩溃过滤器并把日志重定向到可执行目录。
- 通过
ResolveEditorProjectRootUtf8()解析项目根目录。 - 若命令行带
--project或-p,优先使用覆盖路径。 - 否则在工作目录或可执行目录向上查找 workspace 根,再优先落到
<workspace>/project/。 - 初始化主窗口渲染器、
ResourceManager、EditorContext、脚本运行时、ImGui 和ViewportHostService。 - 最后挂接
EditorLayer,开始每帧驱动。
这里最关键的一点是:项目根目录不是由某个面板临时决定,而是宿主层在启动时统一解析并固定下来。当前仓库默认就是随仓维护的 project/ 示例工程。
当前 Editor 的六层结构
可以把当前实现理解成六层。
1. 宿主层
ApplicationPlatformD3D12WindowRendererImGuiBackendBridgeImGuiSession
这一层负责窗口、SwapChain、ImGui 帧边界、项目切换和脚本运行时重载。它不直接实现 Hierarchy、Inspector 或 Project Browser 的业务规则。
2. 上下文与共享服务层
EditorContextEventBusSelectionManagerSceneManagerProjectManagerUndoManager
EditorContext 是当前 Editor 的状态容器。它把事件、选择、场景、项目、撤销和 ViewportHostService 指针聚合起来,供面板和动作层统一访问。
3. 面板与布局层
PanelCollectionHierarchyPanelProjectPanelInspectorPanelSceneViewPanelGameViewPanelConsolePanel
这一层负责“用户看到什么”,以及把即时模式 UI 交互采样成更稳定的意图。
其中 GameViewPanel 还有一条额外职责:它会把视口里的键鼠状态采样成 GameViewInputFrameEvent,再通过 PlaySessionController 桥接到运行时 InputManager。这一条链路可以单独看:
4. 动作与命令层
Actions::*ActionRouterCommands::*
这层负责把“菜单点击、快捷键、右键菜单、拖放、行内重命名”翻译成统一的编辑语义。典型例子是:
HierarchyActionRouterEntityCommandsProjectCommands
5. 视口宿主层
ViewportHostServiceSceneViewportOverlayProvidersSceneViewportOverlayBuilderSceneViewportEditorOverlayPassSceneViewportPicker
它把 Scene/Game 视口真正接到引擎的 Rendering + RHI 上,同时承接 object-id picking、outline、grid 和世界空间 overlay。
6. 引擎与资源层
RenderingResourceManagerAssetDatabaseScriptEngineMonoScriptRuntime
这一层不属于 Editor 自身,但 Editor 当前已经大量依赖它们提供真实能力,而不是自己维护一套独立“简化版运行时”。
EditorActionRoute 为什么重要
很多旧文档容易忽略 EditorActionRoute,但它对当前 Inspector 和焦点行为非常关键。
当前只有三个 route:
NoneHierarchyProject
这不是菜单状态,而是“最近一次明确的编辑焦点来自哪里”。
它直接影响 InspectorPanel 的检查对象:
- route 是
Hierarchy时,优先检查当前选中实体。 - route 是
Project时,优先检查当前选中资源。 - 当前只有
Material资源拥有专用 inspector;其他资源走 unsupported fallback。
所以 Inspector 现在已经不是“只给 GameObject 用”的面板,而是一个带 subject 切换能力的通用检查器。
一条典型的 Hierarchy 链路
以“在 Hierarchy 中重命名实体”为例,当前真实链路大致是:
HierarchyPanel绘制树节点和行内编辑框。- HierarchyActionRouter 负责解释点击、右键、拖放和 rename 请求。
RequestEntityRename(...)通过 EventBus 发布EntityRenameRequestedEvent。- 行内编辑结束后,
CommitEntityRename(...)调用 EntityCommands。 EntityCommands::RenameEntity(...)通过UndoUtils::ExecuteSceneCommand(...)进入撤销栈。SceneManager完成真正的场景修改。- 其他面板通过选择状态或事件观察到变化。
这条链路体现了当前 editor 的一个硬边界:
- 面板负责呈现和采样。
- router 负责交互语义。
- commands 负责稳定的编辑命令。
- manager 负责真正的数据结构修改。
一条典型的 Project 链路
ProjectPanel 不是 AssetDatabase 本身,而是 IProjectManager 的浏览器前端。
当前它的主链路是:
- 左侧目录树用
TreeView绘制 folder hierarchy。 - 右侧浏览区用
DrawAssetTile(...)绘制当前目录条目。 - 顶部工具栏只做当前目录内搜索,不做全项目索引。
- 面包屑通过
NavigateToIndex(...)回退到任意路径段。 - 重命名、创建文件夹、创建材质、移动资源、删除资源都交给
ProjectCommands。 - 打开资源、开始拖拽、背景点击等交互交给
Actions层 helper。
因此当前 ProjectPanel 的定位很清楚:
- 它负责浏览和编辑入口。
- 它不自己维护资源数据库。
- 它依赖 project manager 和命令层维持真实状态。
还要建立一个新的工作流边界认知:
- Project 面板负责资源浏览与资源级编辑
- 项目级保存和脚本程序集重建仍由主菜单触发
- 旧文档里提到的场景资源批量迁移入口已经不在当前 editor 工作流中
Inspector 已经不只是组件列表
InspectorPanel 现在至少有四种 subject mode:
NoneGameObjectMaterialAssetUnsupportedAsset
GameObject 模式
这一支仍然是最传统的 Inspector:
- 遍历实体组件列表。
- 通过
ComponentEditorRegistry找到对应IComponentEditor。 - 用
BeginComponentSection(...)组织 section UI。 - 组件 editor 返回
true时,标记场景 dirty。 Add Component弹窗走DeferredPopupState和Actionshelper。
Material 资源模式
这一支是旧文档最容易写错的地方。当前实现已经支持直接检查材质资源:
- 从
ProjectManager取当前选中的AssetItem。 - 仅当
item->type == "Material"时进入专用 inspector。 - 通过项目相对路径调用
ResourceManager::Load<Material>()。 - 当前可编辑:
- Shader 路径
- Shader Pass
- Render Queue
- Render State
- Tags
- 每次修改都会立刻写回材质文件,并尽量把变更同步到已加载的
Material资源实例。
所以现在 Inspector 已经承担了一部分资源编辑器职责,而不是单纯的 scene component inspector。
ScriptComponent 不是缺席状态
旧表述里常见“Inspector 还没有脚本组件”之类的说法,但当前这已经不成立。
ScriptComponentEditor 已经接入当前 Inspector 链路,支持:
- 脚本类选择
- 字段模型读取
- 字段 override 写回
- 运行时缺失 / 程序集缺失状态提示
Rebuild Scripts/Reload Scripts入口
脚本程序集链路已经落地
当前脚本相关的关键目录是:
managed/XCEngine.ScriptCore/managed/GameScripts/project/Assets/**/*.csproject/Library/ScriptAssemblies/
Application 在启动、切换项目和显式 reload 时,都会把脚本程序集目录固定到:
<Project>/Library/ScriptAssemblies/XCEngine.ScriptCore.dll<Project>/Library/ScriptAssemblies/GameScripts.dll<Project>/Library/ScriptAssemblies/mscorlib.dll
若程序集缺失:
- Editor 不会直接崩溃。
ScriptEngineruntime 会被清空。ScriptRuntimeStatus会保留错误消息,供 Inspector 和其他 UI 展示。
若用户触发 RebuildScriptingAssemblies():
EditorScriptAssemblyBuilder重建项目程序集。- 成功后立即
ReloadScriptingRuntime()。 - 新 runtime 会重新注册到
ScriptEngine。
这条链路说明当前脚本系统已经进入“编辑器真实工作流的一部分”,而不是实验性质的旁路功能。
视口链路也已经从面板里抽出来了
旧版 editor 很容易把 SceneView 相关逻辑直接堆进面板文件里,但当前实现已经明显走向规范化。
按当前结构,典型流程是:
Application::Render()建立主帧边界。LayerStack和具体面板产生 Scene / Game 视口请求。ViewportHostService::BeginFrame()清理并准备本帧视口状态。RenderRequestedViewports(...)真正调用引擎SceneRenderer。- Scene View 额外接入:
- 编辑器私有相机
- object-id surface
- outline
- grid
SceneViewportOverlayProvidersSceneViewportOverlayBuilderSceneViewportEditorOverlayPass
这意味着:
- editor 是渲染宿主,不是第二套 renderer。
- 新的 Scene overlay / gizmo 功能应优先沿
overlay provider -> overlay builder -> overlay pass扩展。 - 不应再把世界空间绘制逻辑继续回堆到
SceneViewPanel的临时 ImGui 路径。
更细一层的 Scene View 输入仲裁、pivot / center、global / local、HUD/world overlay 分层以及 gizmo overlay state 传播关系,可继续看 SceneView Interaction And Gizmo Model。
当前项目目录结构不是“伪工作流”
当前工程目录的几个部分都已经参与真实工作流:
project/Assets/project/Assets.metaproject/Library/project/Library/ScriptAssemblies/Project.xcproject
尤其是 project/Library/,在当前实现里并不只是可以无脑忽略的临时目录。它至少承接:
- 资源数据库缓存
- artifact 缓存
- 脚本程序集输出
因此在 editor 文档里,不应再把 .meta 和 Library/ 描述成“未来才会用到”的规划项。
如果要继续扩展当前 Editor,应该从哪层下手
新增一个面板
优先顺序通常是:
- 在
panels/下新建 panel。 - 复用已有
PanelWindowScope、PanelToolbarScope、PanelContentScope。 - 若面板要参与焦点切换,再接
EditorActionRoute。 - 若面板会修改项目或场景,优先接命令层而不是直改 manager。
给 Inspector 新增一种检查对象
优先顺序通常是:
- 先确认新的 subject 应由哪条 route 驱动。
- 在
InspectorPanel::SyncSubject()里定义切换条件。 - 为该资源或组件准备独立渲染函数。
- 若涉及文件写回或场景写回,尽量复用现有
Commands/ResourceManager/ScriptEngine。
给 Hierarchy / Project 增加新动作
优先顺序通常是:
- 在
Actions层描述入口。 - 在 router 里解释 UI 交互。
- 在
Commands层集中写实际修改逻辑。 - 需要后续 UI 同步时,再通过
EventBus发布事件。
现在不该再沿用的旧说法
以下说法已经不再适合当前活跃文档:
- “Project 面板还不是完整资产导入系统”
- “Inspector 还没有脚本组件”
- “Editor 仍然只是轻量编辑器壳层”
- “脚本程序集和项目目录结构还在规划中”
更准确的描述应当是:
- 当前 Editor 仍在持续重构,但主链路已经真实落地。
- 高层结构已经能覆盖项目根目录解析、资源浏览、材质资源检查、脚本程序集重建与重载、Scene/Game 视口宿主和 object-id picking。
- 接下来的工作重点是继续把这些现有能力文档化、模块化,而不是再把它描述成只有方向没有实现的雏形。
推荐阅读顺序
如果要快速建立当前 Editor 的整体认识,推荐按这个顺序读:
- Editor
- Application
- EventBus
- ProjectPanel
- InspectorPanel
- HierarchyActionRouter
- EntityCommands
- ViewportHostService
- SceneView Interaction And Gizmo Model
- ScriptComponentEditor
这样读下来,基本就能把“宿主层、共享状态、面板、动作命令、视口和脚本”这几条主线拼起来。