# Editor 架构说明 ## 1. 当前目标 当前这一轮 editor 重构,目标不是继续在各个 panel 上零散修 UI,而是先把 editor 自身的架构层次收稳: - 把视觉样式、交互路由、编辑命令、dock 布局、面板壳层拆开。 - 把 `Hierarchy / Project / Inspector / Console / MenuBar` 统一到同一套 shared UI 和 action 语义上。 - 把 `Scene / Game` viewport 保持在当前真实主链上:`ViewportHostService -> Rendering + RHI -> ImGui panel`。 - 把 `Assets + .meta + Library` 项目工作流、脚本程序集构建与运行时状态纳入 editor 正式分层,而不是继续当外围脚本。 这意味着当前 editor 的重点已经不是“先把外壳搭出来”,而是: - 在现有外壳分层上继续收口真实可运行的 viewport / project / scripting 主链。 - 让 panel、viewport helper、commands、managers 和引擎侧 `Rendering / Resources / Scene / Scripting` 的边界继续清晰化。 ## 2. 总体分层 当前 editor 推荐按下面的依赖方向理解: `Application -> EditorLayer -> EditorWorkspace -> Panels/Viewport -> Actions -> Commands -> Managers/Core` 同时还有一条横向的共享 UI 层: `Panels / Actions / ComponentEditors -> UI` 以及一条 inspector 专用扩展链路: `InspectorPanel -> ComponentEditorRegistry -> IComponentEditor` 以及一条已经落地的 editor 到引擎主链: `Viewport / Managers / Scripting -> Rendering / Resources / Scene / Scripting / RHI` 允许依赖的基本原则: - `UI` 只负责样式 token、共享控件、popup/property-grid 等表现层能力,不承担业务语义。 - `Actions` 负责把 button/menu/shortcut/context menu 这些 UI 意图转换为命令调用、事件请求或共享状态更新。 - `Commands` 负责真正修改 scene/project/component 数据,并处理 dirty/undo/selection 的业务边界。 - `Panels` 只保留最小的渲染壳层和少量局部瞬时状态,不直接堆业务逻辑。 - `Layout` 只负责 dockspace 和布局持久化,不处理 scene/project 业务。 - `Core/Managers` 提供 editor 运行时共享状态与数据入口。 - `Viewport` 是 editor 宿主和引擎渲染主链之间的正式桥接层,不是 panel 内随手拼的辅助代码。 不允许继续扩散的方向: - 不要在 panel 里直接散落 scene/project 业务修改。 - 不要把菜单、快捷键、右键菜单逻辑分别复制到不同 panel。 - 不要把样式常量重新写回 panel 本地。 - 不要让 `UI` 反向依赖 `Commands` 或具体 panel。 ## 3. 主要模块职责 ### 3.1 Application / Platform 关键文件: - `editor/src/Application.cpp` - `editor/src/Platform/Win32EditorHost.h` - `editor/src/Platform/D3D12WindowRenderer.h` - `editor/src/UI/ImGuiSession.h` - `editor/src/UI/ImGuiBackendBridge.h` 职责: - 创建窗口、D3D12 renderer、ImGui session 与 backend bridge。 - 初始化 `EditorContext`。 - 监听 editor 级退出事件。 - 驱动 layer attach/detach/update/render 主循环。 - 更新窗口标题。 边界: - 这里是 editor 的宿主壳层,不负责具体 panel 交互。 - 这里可以处理平台与渲染 backend 生命周期,但不应该继续写 editor 业务逻辑。 ### 3.2 EditorLayer / EditorWorkspace 关键文件: - `editor/src/Layers/EditorLayer.cpp` - `editor/src/Core/EditorWorkspace.h` 职责: - `EditorLayer` 只做 layer 生命周期转发。 - `EditorWorkspace` 负责组装 panel 集合、初始化 project panel、加载启动场景、挂接 dock layout controller。 - 统一调度 panel 的 attach/detach/update/render/event。 边界: - `EditorLayer` 不能回到“自己直接维护一堆 panel 生命周期”的旧结构。 - 新增 panel 时,优先在 `EditorWorkspace` 里做装配,不回写到 `Application`。 ### 3.3 UI Shared Layer 关键文件: - `editor/src/UI/BaseTheme.h` - `editor/src/UI/StyleTokens.h` - `editor/src/UI/DockHostStyle.h` - `editor/src/UI/PanelChrome.h` - `editor/src/UI/Widgets.h` - `editor/src/UI/PopupState.h` - `editor/src/UI/PropertyGrid.h` - `editor/src/UI/UI.h` 职责: - 提供 editor 统一主题、尺寸、留白、颜色和面板 chrome。 - 提供 toolbar、tab、popup、dialog、property row、asset tile、empty state 等共享控件。 - 提供 inspector/property-grid 的公共表现层。 边界: - 这一层可以知道 ImGui,但不应该知道 scene/project 业务对象如何变化。 - 这一层输出的是“怎样画”,不是“点了以后改什么”。 ### 3.4 Actions / Routers 关键文件: - `editor/src/Actions/EditorActions.h` - `editor/src/Actions/ActionBinding.h` - `editor/src/Actions/ActionRouting.h` - `editor/src/Actions/EditActionRouter.h` - `editor/src/Actions/MainMenuActionRouter.h` - `editor/src/Actions/HierarchyActionRouter.h` - `editor/src/Actions/ProjectActionRouter.h` - `editor/src/Actions/InspectorActionRouter.h` - `editor/src/Actions/ConsoleActionRouter.h` 职责: - 定义 editor 内部动作项的文案、快捷键、可用状态。 - 统一菜单点击、快捷键触发、toolbar 按钮、右键菜单动作。 - 处理 active route/focused route,把 `Edit` 类动作路由到正确 panel。 - 处理 popup 请求、rename 请求、局部状态切换等“轻量交互编排”。 边界: - `Actions` 可以依赖 `Commands`、`EventBus`、`UI`,但不负责底层数据结构修改细节。 - `Actions` 不负责持久保存复杂状态;复杂状态应留给 manager 或共享 state object。 - `Actions` 不应该包含大量 panel 私有布局代码。 判断标准: - 一个交互如果同时被 menu、shortcut、context menu、toolbar 复用,它应先进入 router。 - 一个交互如果只是“请求 panel 开一个 popup/进入 rename”,更适合走 `Actions + EventBus/shared state`。 ### 3.5 Commands 关键文件: - `editor/src/Commands/SceneCommands.h` - `editor/src/Commands/EntityCommands.h` - `editor/src/Commands/ProjectCommands.h` - `editor/src/Commands/ComponentCommands.h` 职责: - 处理 scene/project/component 的实际编辑行为。 - 统一 undo 快照、dirty 标记、selection reset 等业务边界。 - 统一保存、加载、复制、粘贴、重命名、重挂接、资源移动等操作。 边界: - `Commands` 不依赖 ImGui。 - `Commands` 不直接绘制 UI。 - `Commands` 返回的是业务结果,不承载 popup 绘制和菜单拼装。 经验规则: - 只要一个操作会改 scene/project 数据,就优先考虑落到 `Commands`。 - 只要一个操作涉及 undo/redo,就不要留在 panel 本地手写。 ### 3.6 Core / Managers 关键文件: - `editor/src/Core/EditorContext.h` - `editor/src/Core/EventBus.h` - `editor/src/Core/SelectionManager.h` - `editor/src/Core/UndoManager.h` - `editor/src/Managers/SceneManager.h` - `editor/src/Managers/ProjectManager.h` 职责: - `EditorContext` 聚合 event bus、selection、scene、project、undo、active action route。 - `SceneManager` 持有场景对象、根节点、场景路径、dirty 状态、clipboard。 - `ProjectManager` 扫描 `Assets`、维护当前目录、选择索引、文件夹导航。 - `SelectionManager` 统一发布 selection changed 事件。 - `UndoManager` 负责快照历史、interactive change 边界、undo/redo。 所有权约定: - selection 所有权在 `SelectionManager`。 - undo 历史所有权在 `UndoManager`。 - scene dirty 与当前场景路径所有权在 `SceneManager`。 - 当前活动编辑路由所有权在 `EditorContext`。 ### 3.7 Layout 关键文件: - `editor/src/Layout/DockLayoutController.h` 职责: - 创建 dockspace。 - 应用默认布局。 - 响应 reset layout 请求。 - 持久化 ImGui layout 到项目目录下的 `.xceditor/imgui_layout.ini`。 边界: - layout controller 不应该知道 hierarchy/project/scene 的编辑业务。 - panel 是否存在由 workspace 决定,不由 layout controller 反向创建。 ### 3.8 Panels 关键文件: - `editor/src/panels/MenuBar.cpp` - `editor/src/panels/HierarchyPanel.cpp` - `editor/src/panels/ProjectPanel.cpp` - `editor/src/panels/InspectorPanel.cpp` - `editor/src/panels/ConsolePanel.cpp` 当前 panel 应保留的内容: - 窗口壳层。 - 生命周期订阅与退订。 - 非共享的极少量局部瞬时状态。 - 调用 shared UI/widget 和 action router 进行拼装。 当前 panel 不应该再承载的内容: - undo/dirty/selection 的业务判断。 - 菜单和快捷键的重复定义。 - 资源/实体操作的底层命令执行细节。 - 大量散落的视觉常量。 ### 3.9 ComponentEditors 关键文件: - `editor/src/ComponentEditors/IComponentEditor.h` - `editor/src/ComponentEditors/ComponentEditorRegistry.h` - `editor/src/ComponentEditors/ComponentEditorRegistry.cpp` - `editor/src/ComponentEditors/TransformComponentEditor.h` - `editor/src/ComponentEditors/CameraComponentEditor.h` - `editor/src/ComponentEditors/LightComponentEditor.h` 职责: - 为某一类 component 提供 inspector 内容绘制。 - 定义该 component 的显示名、是否能添加、是否能删除、添加禁用原因。 - 通过 registry 统一注册到 inspector 与 component commands。 当前注册机制: - `ComponentEditorRegistry` 是 editor 侧单例注册表。 - 注册发生在 `ComponentEditorRegistry` 构造函数内。 - `RegisterEditor(std::unique_ptr)` 将 editor 同时放入顺序列表和按类型名索引表。 - inspector 绘制时通过 component 实例类型名查找 editor。 - Add Component 菜单通过遍历 registry 的已注册 editor 生成。 - `ComponentCommands` 通过相同 registry 复用 add/remove 规则,而不是在 inspector 单独硬编码。 这套机制的意义: - inspector 的“显示逻辑”和“组件增删能力”在同一处收口。 - 新增一个 component editor 时,不需要去 panel 里到处补分支。 - 后续如果要做脚本组件、自定义组件 inspector,优先扩展 registry,而不是污染 panel。 新增 component editor 的推荐步骤: 1. 新建一个实现 `IComponentEditor` 的 editor。 2. 在其中定义 `GetComponentTypeName / GetDisplayName / Render / CanAddTo / CanRemove`。 3. 在 `ComponentEditorRegistry.cpp` 注册。 4. 如果涉及复杂交互,优先复用 `UI::PropertyGrid` 和 undo interactive change 机制。 ### 3.10 Viewport 关键文件: - `editor/src/Viewport/ViewportHostService.h` - `editor/src/Viewport/IViewportHostService.h` - `editor/src/Viewport/SceneViewportChrome.h` - `editor/src/Viewport/SceneViewportInteractionFrame.h` - `editor/src/Viewport/SceneViewportNavigation.h` - `editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h` - `editor/src/Viewport/SceneViewportOverlayBuilder.h` - `editor/src/Viewport/SceneViewportOverlayFrameCache.h` - `editor/src/Viewport/SceneViewportOverlaySpriteResources.h` - `editor/src/Viewport/SceneViewportResourcePaths.h` - `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h` 职责: - 作为 editor 宿主与引擎 `Rendering + RHI` 的桥接层,维护 Scene / Game viewport 的离屏渲染接线。 - 维护当前 Scene View helper 主链: - `SceneViewportChrome` - `SceneViewportInteractionFrame` - `SceneViewportNavigation` - `SceneViewportTransformGizmoCoordinator` - `ViewportHostService` - 负责 overlay builder、overlay frame cache、sprite 资源准备、object-id picking、grid、outline 和 gizmo overlay state。 边界: - `Viewport` 是正式子系统,不再是“等待未来回归”的空位。 - panel 不应自己拼接一套独立渲染路径;新的 viewport 行为优先落到 helper、host service 或 overlay pass。 - `SceneViewportShaderPaths.h` 当前主要是兼容 include;资源路径真实 owner 已经转到 `SceneViewportResourcePaths.h`。 ### 3.11 Scripting / Project Workflow 关键文件: - `editor/src/Scripting/EditorScriptAssemblyBuilder.h` - `editor/src/Scripting/EditorScriptRuntimeStatus.h` - `editor/src/Managers/ProjectManager.h` - `editor/src/Managers/SceneManager.h` - `editor/src/Utils/ProjectFileUtils.h` 职责: - 解析项目根目录、读写 `Project.xcproject`,并把仓库内 `project/` 或 `--project ` 解析为当前工程。 - 驱动 `Assets + .meta + Library` 风格项目 workflow,包括项目资源浏览、脚本程序集重建入口和运行时状态反馈。 - 把项目脚本程序集与 editor/runtime 实际消费的 `Library/ScriptAssemblies/` 目录连接起来。 边界: - 与项目资产、`.meta`、`Library`、脚本程序集相关的问题,不应再按“editor 仍处于无工程状态”理解。 - `ProjectManager`、`SceneManager`、`EditorScriptAssemblyBuilder` 与引擎侧 `ResourceManager / AssetImportService / ScriptEngine` 是协作关系,不应在 panel 里各自复制一层状态机。 ## 4. EventBus 使用规则 `EventBus` 现在主要承担两类职责: - editor 范围的状态通知,例如 selection changed、entity created/deleted、scene changed。 - UI 请求类事件,例如 rename request、exit request、reset layout request。 推荐规则: - “请求某个 panel 进入某种 UI 状态”时,优先用事件或共享 popup state。 - “真正修改数据”时,优先走 command,不要只发 event 期待别人去改数据。 - 事件用于解耦,不用于隐藏业务路径。 一个简单判断: - 如果动作需要 undo/dirty,通常应该先有 `Command`。 - 如果动作只是让某个 panel 弹窗或进入 rename,通常可以只发 `Event`。 ## 5. Undo / Dirty / Selection 约定 这是 editor 最容易再次失控的地方,必须保持统一: - scene 数据修改通过 `Commands` 收口。 - `UndoUtils::ExecuteSceneCommand(...)` 负责把一次编辑包成可回退命令。 - inspector 连续拖拽这类交互,使用 interactive change 边界,不在 panel 里手搓多次提交。 - 场景切换、新建场景、加载场景后,selection 与 undo 历史由 scene command 统一重置。 - scene dirty 由 `SceneManager` 维护,保存成功后归零。 禁止事项: - 不要在 panel 里直接偷偷 `MarkSceneDirty()` 来替代 command。 - 不要让某个 panel 自己保存一份 selection。 - 不要让快捷键直接跨过 command 改 manager。 ## 6. 快捷键与菜单路由规则 当前 editor 已形成两条主路由: - `MainMenuActionRouter` 负责 `File / View / Help / global shortcut`。 - `EditActionRouter` 负责 `Edit` 菜单和 panel-focused edit shortcut。 约定: - `Global` 类快捷键由 menu bar 统一分发。 - `Hierarchy / Project` 等焦点相关动作,先根据 `EditorActionRoute` 解析目标,再执行对应 action。 - panel 只负责声明自己何时成为 active route,不负责重复实现整套 edit 菜单。 这样做的收益: - 同一个动作不会再出现“菜单能点、快捷键失效、右键菜单又是另一套”的问题。 - 后续增加新 panel 的编辑语义时,只需要接入 route,不必复制整套菜单逻辑。 ## 7. 当前已完成与明确暂缓的部分 已完成的重点: - 统一 UI token、panel chrome、popup/property-grid/shared widgets。 - menu/shortcut/context menu/action 的大部分共享路由。 - scene/project/entity/component 的主要编辑命令收口。 - dock layout controller 与 editor workspace 装配。 - inspector 的 component editor registry 接入。 - editor 级回归测试基础框架与关键命令测试。 - `Scene / Game` viewport 已经重新接回当前正式主链:引擎 `Rendering + RHI` 离屏输出 -> `ViewportHostService` -> ImGui panel。 - `Assets + .meta + Library` 项目工作流、`Project.xcproject`、脚本程序集重建与脚本运行时状态已经进入 editor 正式工作流。 - viewport helper 已按 `Chrome -> InteractionFrame -> Navigation -> TransformGizmoCoordinator -> ViewportHostService` 显式拆层。 当前明确暂缓: - 把 editor 主线程上残留的同步资源兜底点继续清理到更严格的异步消费模型。 - 把 `Library bootstrap / scene streaming / explicit import` 的状态模型继续正式化到 UI。 - 继续减少 panel 内局部瞬时状态与 helper 间重复规则。 原因很明确: - 前三项已经不是“没接回来的未来工作”,而是已经落地、但还要继续收口的当前主线。 ## 8. 后续新增功能时的落点原则 如果以后继续扩 editor,推荐按下面的判断落点: - 新视觉样式或共享控件:放 `UI` - 新菜单项/快捷键/右键菜单复用:放 `Actions` - 新的 scene/project/component 编辑行为:放 `Commands` - 新 inspector 组件面板:放 `ComponentEditors` - 新 dock/window 布局控制:放 `Layout` - 新 editor 全局状态:放 `Core/Managers` - 新 viewport host / overlay / interaction helper:放 `Viewport` - 只是把已有能力拼进某个窗口:放 `Panels` 如果一个功能不知道放哪,一般先问自己: - 它是不是只关乎“怎么画”? - 它是不是多个入口共享的交互? - 它是不是会真正改数据并进入 undo? 这三个问题基本能把落点判断清楚。 ## 9. 当前收尾阶段剩余事项 从 UI 架构角度看,当前已经不是“推倒重来”阶段,而是最后的封口阶段。剩余事项主要有: - 继续压缩少量 panel 本地瞬时状态,能下沉的继续下沉。 - 继续补命令/路由回归测试,尤其是 inspector interactive undo 边界。 - 继续补 viewport helper、project workflow、script assembly builder 与 scene streaming 相关回归测试。 - 把 `Library bootstrap`、显式导入、后台 scene asset streaming 这三类状态在 editor UI 上分开表达。 结论: editor 当前已经形成稳定分层,而且这套分层已经承接了真实的 viewport / project / scripting 工作流。后面再做功能迭代时,应坚持“先看边界,再落代码”,不要回到 panel 内部堆逻辑的旧路线。