Files
XCEngine/docs/plan/xcui_editor_windowing_architecture_plan.md

185 lines
10 KiB
Markdown
Raw Normal View History

# XCUIEditor Windowing Architecture Plan
更新日期: `2026-04-26`
状态: `Phase 1 completed, Phase 2 completed, Subplan 3A planned`
## 1. Plan 管理规则
- `docs/plan` 现在只保留总 plan。
- 已完成 subplan 的结论直接并回总 plan不再保留阶段性执行文档。
- 新的 subplan 必须保持小切口;一次只收口一条主链,不把 projection、destroy、drag/drop 等多条链路捆在同一阶段。
## 2. 顶层架构问题
当前 `XCUIEditorApp` 最严重的架构问题仍然是: 多窗口工作区状态存在双真相源。
这两个真相源目前分别落在:
- `editor/app/Windowing/System/EditorWindowSystem.*`
- `editor/app/Composition/EditorWindowWorkspaceStore.*`
- live `EditorWindow`
- `editor/app/Windowing/Content/EditorWorkspaceWindowContentController.*`
- `editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*`
虽然已经把“同步 diff 规划”和“标题策略”从 `Win32` 层切回了 app 层,但当前仍然存在以下事实:
- live `EditorWindow` / `EditorWorkspaceWindowContentController` 里的 `UIEditorWorkspaceController` 仍然是可变状态容器。
- `EditorWorkspaceWindowContentController::UpdateAndAppend(...)` 已经产出显式 `workspaceMutation` request但 request 仍然来自 live controller 的本地副本。
- `EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests(...)` 收到 `workspaceMutation` 后,仍通过 `CommitLiveWindowMutation(sourceWindow)` 回读 `window.GetWorkspaceController()`,而不是直接消费 request payload。
- `EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan(...)` 仍同时承担 host 执行与 authoritative commit 的时序编排。
这会带来四个直接后果:
1. app 层和 host 层都能改 `UIEditorWindowWorkspaceSet`,单一真相源名义存在、实际不存在。
2. frame 内部交互修改依赖平台回写兜底,边界方向不稳定。
3. 关闭、拖拽、分离、primary 切换这些跨窗口时序很难纯粹按领域规则推导。
4. `editor/app/Platform/Win32` 继续承担了不属于 host adapter 的领域编排责任。
## 3. 已完成收口
当前已完成两阶段收口,结果如下:
- 新增 app 层同步模型:
- `EditorWindowSynchronizationPlan`
- `EditorWindowSynchronizationPlanner`
- `EditorWindowPresentationPolicy`
- `EditorWindowSystem` 新增 `BuildSynchronizationPlan(...)`,开始由 app 层产出 authoritative sync plan。
- `EditorWindowWorkspaceCoordinator` 不再持有窗口集 diff 算法和标题策略定义,只保留 host snapshot 采集与 plan 执行。
- `EditorWindowTransferRequests` 新增显式 workspace mutation requestframe 内工作区变化不再依赖平台隐式回写 authority。
- `EditorWindowSystem` 收口 live window mutation / native destroy observation / authoritative commit 语义入口。
- `EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(...)` 不再直接删除 authority store entry而是回到 app 层做 destroy reconciliation。
- `editor/app/Platform/Win32/**` 已移除对 authority raw write 接口的直接依赖。
- 新增聚焦验证:
- `tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp`
- `editor_windowing_phase1_tests`
- editor 本体已完成构建与 `12s` 启动冒烟验证。
- editor 测试入口回到 `tests/UI/Editor`legacy `tests/editor` 已从当前活动构建图移除。
这两阶段的目标分别是:
- Phase 1: 先把“领域决策”和“Win32 执行”切开。
- Phase 2: 删除“平台回写 authority”链路把 authority mutation 收口回 `editor/app/Windowing`
这两个目标都已经达成。
## 4. 当前剩余缺口
原计划中针对 `authority writeback` 的核心缺口已经完成收口,但还有一段“半收口”链路仍然存在:
- `EditorWindowTransferRequests` 已有 `EditorWindowWorkspaceMutationRequest`,但 `EditorWorkspaceWindowContentController::UpdateAndAppend(...)` 目前只填 `workspace/session`,没有把稳定的 `windowId` 一并带出。
- `EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests(...)` 目前拿到 request 后并不直接消费它,而是再次回读 `sourceWindow` 上的 live projection。
- 这意味着 frame 内交互虽然已经显式化为 request但 request 还没有真正成为这条链路的唯一语义输入。
这一段正好适合作为下一个小 subplan在它完成之前不适合继续展开更大的 live projection 去状态化。
## 5. 目标架构
目标状态下,窗口系统的数据流应当是单向的:
```text
authoritative window set (app/windowing)
-> mutation validation
-> synchronization plan
-> Win32 execution
-> host observation / lifecycle event
-> app-layer semantic callback
```
这里的关键约束是:
- authority 只在 `editor/app/Windowing` 侧维护。
- `Win32` 层只能消费 plan、上报 observation/event不能直接写 authority store。
- live content controller 只能是 projection 或受控 mutation producer不能再充当隐式 authority 副本。
## 6. 后续路线
后续按“小 subplan”推进每个 subplan 只解决一条主链。下一个 subplan 只收口 frame 内显式 `workspaceMutation` request不同时处理 live projection 去状态化、native destroy reconciliation 或 global tab drag。
### 6.1 Subplan 3A: 显式 workspace mutation request 收口
#### 6.1.1 阶段目标
- 让 frame 内 workspace 变更通过 `EditorWindowWorkspaceMutationRequest` 进入 app 层,而不是由 coordinator 再回读 live `UIEditorWorkspaceController`
- 把“显式 request 已存在但未真正成为主输入”的半收口状态补完整。
- 保持本阶段足够小,只处理 live workspace mutation commit 这条链。
#### 6.1.2 建议改动范围
- `editor/app/Windowing/Frame/EditorWindowTransferRequests.h`
- `editor/app/Windowing/Content/EditorWorkspaceWindowContentController.*`
- `editor/app/Windowing/System/EditorWindowSystem.*`
- `editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*`
- `tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp`
- 必要时补 `tests/UI/Editor/unit` 下新的 focused test若必须经过 host 协调器,再补 `editor_app_feature_tests`
#### 6.1.3 详细执行步骤
1. 先把 `workspaceMutation` request payload 补完整。
- `EditorWorkspaceWindowContentController::UpdateAndAppend(...)` 在生成 `EditorWindowWorkspaceMutationRequest` 时补齐 `windowState.windowId`
- request 的有效性以“`windowId` 非空且 workspace state 可用”为准,避免继续依赖 source window 隐式补信息。
2. 再把 app 层计划入口改成显式 request 驱动。
-`EditorWindowSystem` 中新增或收口一个直接消费 `EditorWindowWorkspaceMutationRequest` 的 plan builder。
- 该入口只做三件事: 校验 `windowId`、把 request 中的 `workspace/session` 合并进当前 authoritative window set、复用 `BuildPlanForWindowSet(...)`
- `BuildPlanForLiveWindowMutation(...)` 如果还有旧调用方,可暂时降级成薄封装;本阶段不再新增新的调用点依赖它。
3. 最后切换 `EditorWindowWorkspaceCoordinator` 的 live commit 路径。
- `HandleWindowFrameTransferRequests(...)` 直接把 `transferRequests.workspace.workspaceMutation` 传给 commit 路径。
- `CommitLiveWindowMutation(...)` 改成基于 request 的实现,或者删除旧的 `EditorWindow&` 回读版本。
- 本阶段完成后live workspace mutation commit 不再依赖 `window.GetWorkspaceController()`;该接口只允许继续服务 projection / title / UI 查询。
4. 补 focused 验证,保证这是小收口而不是行为漂移。
- planner/system 侧至少覆盖“显式 request 改 active panel 后可生成并提交有效 plan”。
- 增加“unknown windowId request 被拒绝”的负向验证。
- 如果 unit 无法覆盖 coordinator 消费 request 的行为,再补一条最小 host-side focused test而不是直接上重型运行时验证。
#### 6.1.4 本阶段验收标准
- `workspaceMutation` request 自身携带稳定 `windowId`,不再需要 source window 兜底补齐。
- `EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests(...)` 不再忽略 request payload。
- live frame mutation 的 authoritative commit 路径不再回读 `sourceWindow.GetWorkspaceController()`
- 现有 detach / dock / close / primary switch 行为不因本阶段发生语义漂移。
- `editor_windowing_phase1_tests` 继续通过;若新增 host-side focused test对应测试目标也应通过。
#### 6.1.5 本阶段非目标
- 不在本阶段删除 `EditorWindow` / content controller 上的 `TryGetWorkspaceController()``ReplaceWorkspaceController()`
- 不在本阶段处理 native destroy reconciliation。
- 不在本阶段重做 global tab drag / detach 相关编排。
- 不在本阶段把 live content controller 改成完全只读 projection。
## 7. 验收标准
整个计划完成后,应至少满足:
1. `editor/app/Platform/Win32/**` 不再直接调用 authority store 原始写接口。
2. `EditorWindowSystem` 成为窗口工作区 authoritative mutation 的唯一 app 层入口。
3. 任意多窗口变化都先形成 app 层语义上的 mutation / transition再形成 sync plan再由 host 执行。
4. native close / destroy 不再通过平台层“直接删 store entry”完成状态收口。
5. `EditorWorkspaceWindowContentController` 不再被当作 authority 副本,而只是 projection 或受控 mutation source。
6. detach / dock / close / primary switch 至少具备稳定 unit coverage并对关键 host 时序具备 focused integration coverage。
## 8. 验证策略
验证入口仍然以 `tests/UI/Editor` 为主,不重新启用旧 `tests/editor`
优先级如下:
- `tests/UI/Editor/unit`
- 必要时补 `tests/UI/Editor/smoke`
- 只有在 unit / focused integration 无法覆盖的 host 时序下,才考虑更重的运行时验证
当前已建立的验证基线:
- `cmake --build build --config Debug --target editor_windowing_phase1_tests`
- `build/tests/UI/Editor/unit/Debug/editor_windowing_phase1_tests.exe`
## 9. 非目标
本计划当前不直接解决以下事项:
-`XCUIEditorApp` 立即改造成跨平台窗口系统
- 全量重写 shell / viewport / message dispatcher
- 顺手重构所有 inspector、panel、runtime 业务逻辑
- 在 authority 收口前提前合并全部 Win32 coordinator
这些都必须排在“窗口领域 authority 真正收口”之后。