Files
XCEngine/docs/plan/Editor架构说明.md

447 lines
18 KiB
Markdown
Raw Permalink 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.
# 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<IComponentEditor>)` 将 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 <path>` 解析为当前工程。
- 驱动 `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 内部堆逻辑的旧路线。