Files
XCEngine/docs/plan/xcui_editor_windowing_architecture_plan.md

10 KiB
Raw Blame 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/Editorlegacy 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. 目标架构

目标状态下,窗口系统的数据流应当是单向的:

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 真正收口”之后。