1888 lines
48 KiB
Markdown
1888 lines
48 KiB
Markdown
# New Editor Win32 窗口架构重构总计划
|
||
|
||
## 1. 文档目的
|
||
|
||
本计划用于指导 `new_editor/app/Platform/Win32` 相关窗口系统的彻底重构。
|
||
|
||
这不是一次“把大类再拆小一点”的整理,而是一次底层架构重建。核心目标是把当前体系改造成:
|
||
|
||
**状态机 + 单向事件流 + 平台投影**
|
||
|
||
本计划覆盖:
|
||
|
||
- `new_editor/app/Platform/Win32/Windowing`
|
||
- `new_editor/app/Platform/Win32/Runtime`
|
||
- `new_editor/app/Platform/Win32/Chrome`
|
||
- `new_editor/app/Platform/Win32/Content`
|
||
- `new_editor/app/Platform/Win32/System`
|
||
- `new_editor/app/Composition` 中与窗口、workspace、utility window、全局上下文相关的部分
|
||
|
||
---
|
||
|
||
## 2. 当前系统的根因
|
||
|
||
### 2.1 根因结论
|
||
|
||
当前最根本的问题只有一个:
|
||
|
||
**`EditorWindow` 没有形成真正的状态边界,窗口相关状态被多个类共同持有、共同修改、相互穿透。**
|
||
|
||
这带来的直接后果是:
|
||
|
||
1. 单窗口状态没有唯一所有者。
|
||
2. 跨窗口状态没有唯一所有者。
|
||
3. `WndProc` 路径承担了过多业务编排职责。
|
||
4. 稳态渲染与即时渲染双轨并存,且存在重入。
|
||
5. transfer request、binding 探测、兼容状态机大量出现,本质上都在给“边界失效”打补丁。
|
||
|
||
### 2.2 当前系统的派生问题
|
||
|
||
#### 2.2.1 `EditorWindow` 变成了共享可变状态包
|
||
|
||
它同时挂着:
|
||
|
||
- lifecycle
|
||
- HWND / title / dpi / size
|
||
- input controller
|
||
- chrome controller
|
||
- runtime controller
|
||
- frame orchestrator
|
||
- queued transfer requests
|
||
|
||
但这些状态不由它自己独占修改,而是被 `MessageDispatcher`、`ChromeController`、`HostRuntime`、`LifecycleCoordinator`、`WorkspaceCoordinator` 等对象直接穿透读写。
|
||
|
||
#### 2.2.2 Win32 消息分发承担了应用层职责
|
||
|
||
当前 `WM_*` 消息路径中,除了输入翻译,还直接做了:
|
||
|
||
- 生命周期推进
|
||
- resize / dpi 响应
|
||
- 立即帧渲染
|
||
- global tab drag 编排
|
||
- workspace 事务提交
|
||
- utility window 打开
|
||
- chrome 命令执行
|
||
|
||
这说明当前平台层已经不是适配层,而是业务主链的一部分。
|
||
|
||
#### 2.2.3 渲染链路是双轨的
|
||
|
||
现状存在两条渲染主链:
|
||
|
||
- 稳态帧:主循环统一 `RenderAllWindows`
|
||
- 即时帧:消息回调里直接 render / present
|
||
|
||
这会造成:
|
||
|
||
1. 消息回调重入渲染。
|
||
2. transfer request 需要额外缓冲。
|
||
3. resize / dpi / paint 顺序变得脆弱。
|
||
|
||
#### 2.2.4 `WorkspaceCoordinator` 已经偷偷变成状态源
|
||
|
||
当前 `EditorWindowWorkspaceCoordinator` 不只是协调器,而是同时承担:
|
||
|
||
- window set 构造
|
||
- workspace 同步
|
||
- 快照回滚
|
||
- 新窗口创建
|
||
- 窗口关闭编排
|
||
- global tab drag 会话
|
||
- 标题刷新
|
||
|
||
但它并没有被正式定义为“权威状态源”,所以职责和边界都不稳定。
|
||
|
||
#### 2.2.5 全局 `EditorContext` 混入了窗口局部能力
|
||
|
||
当前每个窗口 runtime 都会把自己的局部能力挂到全局 context 上,例如:
|
||
|
||
- text measurer
|
||
- utility window request 消费
|
||
- capture 状态文本依赖
|
||
|
||
这让全局上下文和局部窗口能力耦在了一起。
|
||
|
||
### 2.3 代码层证据
|
||
|
||
下面这些不是推测,而是当前代码直接暴露出来的结构事实:
|
||
|
||
1. `new_editor/app/Platform/Win32/Windowing/EditorWindowState.h`
|
||
中所谓 `EditorWindowState` 实际只保存了 `HWND`、`windowId`、`title`、`primary`、`lifecycle`。输入、chrome、resize、dpi、capture、frame、content 相关真实状态都不在这个“状态对象”里,这说明系统从命名层面就在伪装一个并不存在的统一状态边界。
|
||
2. `new_editor/app/Platform/Win32/Windowing/EditorWindow.h`
|
||
通过 `friend class` 把 `EditorWindowChromeController`、`EditorWindowFrameDriver`、`EditorWindowHostRuntime`、`EditorWindowMessageDispatcher`、`EditorWindowLifecycleCoordinator`、`EditorWindowWorkspaceCoordinator` 全部放进 `EditorWindow` 内部。这意味着窗口对象没有封装,只是一个公开状态袋。
|
||
3. `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp`
|
||
在 `WM_DPICHANGED`、`WM_EXITSIZEMOVE`、`WM_SIZE`、`WM_PAINT` 中直接触发 `RenderAndHandleWindowFrame` 或 `OnPaintMessage`。也就是说 Win32 消息回调直接参与渲染、事务完成和跨模块编排。
|
||
4. `new_editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp`
|
||
中的 `RenderAllWindows` 不只是“遍历窗口”,它同时承担帧驱动、presentation 刷新、transfer request 聚合和后续分发。平台宿主已经兼任应用层调度器。
|
||
5. `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp`
|
||
通过 `BuildLiveWindowWorkspaceSet(...)` 从 live windows 反推当前窗口集合,再基于这个集合做同步、提交和回滚。这说明跨窗口 topology 并没有显式权威状态,只能从现场对象拼出来。
|
||
6. `new_editor/app/Platform/Win32/Content/EditorWindowContentController.h`
|
||
暴露 `TryGetWorkspaceBinding`、`TryGetDockHostBinding`、`TryGetInputFeedbackBinding`、`TryGetTitleBarBinding`。应用层必须去“探测内容层支持哪些能力”,再临时决定走哪条逻辑分支,本质上是拿 capability probing 代替正式协议。
|
||
7. `new_editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h`
|
||
同时持有 D3D12 renderer、texture host、text system、window render loop、screenshot controller、content controller、frame timing、dpi scale。render host、content host、diagnostic host 被揉成了一个类。
|
||
8. `new_editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp`
|
||
里 workspace 内容控制器一边对外暴露 binding,一边直接驱动 shell runtime 和 frame orchestrator。说明“内容输出”不是一个结构化结果,而是一堆散落接口和副作用。
|
||
|
||
因此,当前问题不是“某几个类太大”,而是根本没有形成:
|
||
|
||
- 单窗口状态所有权
|
||
- 跨窗口拓扑状态所有权
|
||
- 事件到 effect 的正式管线
|
||
- 平台层与应用层的稳定边界
|
||
|
||
---
|
||
|
||
## 3. 重构目标
|
||
|
||
### 3.1 总目标
|
||
|
||
建立以下新的权责模型:
|
||
|
||
1. **单窗口状态**
|
||
只能由 `WindowSession` 持有。
|
||
2. **跨窗口拓扑状态**
|
||
只能由 `WindowTopologyService` 持有。
|
||
3. **平台对象**
|
||
由 Win32 / D3D12 backend 持有,但只作为投影结果,不作为业务状态源。
|
||
4. **内容层**
|
||
不再通过 binding 探测链暴露能力,而是统一返回 `ContentOutput`。
|
||
5. **帧调度**
|
||
统一由 `FrameScheduler` 管理,消息回调只登记帧请求,不直接渲染。
|
||
|
||
### 3.2 完成后必须满足的硬约束
|
||
|
||
1. 新窗口架构中禁止 `friend` 穿透状态。
|
||
2. domain / application 头文件中禁止出现 `HWND`、`WPARAM`、`LPARAM`、`RECT`、`POINT`。
|
||
3. `WndProc` 路径中禁止直接 render / present。
|
||
4. `EditorWindowFrameTransferRequests` 必须删除。
|
||
5. `TryGetWorkspaceBinding` / `TryGetDockHostBinding` / `TryGetInputFeedbackBinding` / `TryGetTitleBarBinding` 必须退出主干架构。
|
||
6. 跨窗口状态必须显式保存在 `WindowTopologyState` 中,禁止从 live windows 反推。
|
||
7. `EditorContext` 中禁止挂载窗口私有能力对象。
|
||
|
||
---
|
||
|
||
## 4. 目标架构
|
||
|
||
## 4.1 分层模型
|
||
|
||
```text
|
||
Domain
|
||
状态、事件、命令、意图、effect 定义
|
||
|
||
Application
|
||
WindowSession / WindowTopologyService / FrameScheduler / Projector
|
||
|
||
Content
|
||
WorkspaceContentHost / UtilityContentHost
|
||
|
||
Platform
|
||
Win32 事件适配、native window backend、D3D12 render host
|
||
```
|
||
|
||
## 4.2 状态所有权
|
||
|
||
### 单窗口状态:`WindowState`
|
||
|
||
由 `WindowSession` 独占持有,建议至少包含:
|
||
|
||
- identity
|
||
- `windowId`
|
||
- `windowKind`
|
||
- lifecycle
|
||
- `PendingCreate`
|
||
- `NativeAttached`
|
||
- `Initializing`
|
||
- `Running`
|
||
- `Closing`
|
||
- `Destroyed`
|
||
- presentation
|
||
- `title`
|
||
- `dpi`
|
||
- `dpiScale`
|
||
- `focused`
|
||
- `visible`
|
||
- `minimized`
|
||
- size
|
||
- `clientPixelWidth`
|
||
- `clientPixelHeight`
|
||
- `predictedClientPixelWidth`
|
||
- `predictedClientPixelHeight`
|
||
- input
|
||
- `pointerCaptureOwner`
|
||
- `trackingMouseLeave`
|
||
- `modifierSnapshot`
|
||
- `pendingEvents`
|
||
- `pendingDoubleClickMask`
|
||
- chrome
|
||
- `hoveredChromeTarget`
|
||
- `pressedChromeTarget`
|
||
- `hoveredResizeEdge`
|
||
- `activeResizeEdge`
|
||
- `maximized`
|
||
- `restoreRect`
|
||
- `dragRestoreState`
|
||
- frame
|
||
- `needsFrame`
|
||
- `framePriority`
|
||
- `frameSerial`
|
||
- `resizeEpoch`
|
||
- content
|
||
- `cursorKind`
|
||
- `captureDemand`
|
||
- `titleBarMode`
|
||
- `contentStatus`
|
||
|
||
### 跨窗口状态:`WindowTopologyState`
|
||
|
||
由 `WindowTopologyService` 独占持有,至少包含:
|
||
|
||
- `primaryWindowId`
|
||
- `activeWindowId`
|
||
- `orderedWindowIds`
|
||
- `workspaceWindowIds`
|
||
- `utilityWindowIds`
|
||
- `windowProjections`
|
||
- `workspaceProjectionSnapshots`
|
||
- `workspaceSessionSnapshots`
|
||
- `globalTabDragState`
|
||
- `pendingUtilityRequests`
|
||
- `pendingDetachTransactions`
|
||
- `pendingDropTransactions`
|
||
|
||
说明:
|
||
|
||
- `WindowTopologyState` 不要求吞掉整个 workspace runtime 对象,但必须显式持有“窗口到 workspace 的权威投影真相”。
|
||
- 至少要能按 `windowId` 解释:window kind / role、workspace projection snapshot、workspace session snapshot、utility window kind、primary / active 标记、正在进行的 detach / drop / reuse 事务。
|
||
- 禁止再通过 live window、content controller、workspace controller 临时拼装当前业务 window set。
|
||
|
||
### 平台状态
|
||
|
||
平台状态由 platform backend 持有,但不作为业务真状态:
|
||
|
||
- `HWND`
|
||
- Win32 class registration
|
||
- OS capture / cursor
|
||
- D3D12 device / swapchain / renderer
|
||
|
||
---
|
||
|
||
## 5. 核心抽象
|
||
|
||
## 5.1 Domain 抽象
|
||
|
||
建议新增目录:
|
||
|
||
```text
|
||
new_editor/app/Windowing/Domain/
|
||
```
|
||
|
||
建议新增类型:
|
||
|
||
- `WindowId.h`
|
||
- `WindowState.h`
|
||
- `WindowTopologyState.h`
|
||
- `WindowEvent.h`
|
||
- `WindowIntent.h`
|
||
- `WindowCommand.h`
|
||
- `WindowEffect.h`
|
||
- `WindowCursorKind.h`
|
||
- `WindowCaptureOwner.h`
|
||
- `FramePriority.h`
|
||
|
||
### `WindowEvent`
|
||
|
||
平台层只负责把 `WM_*` 转成这些事件:
|
||
|
||
- 生命周期
|
||
- `NativeAttached`
|
||
- `NativeDestroyed`
|
||
- `CloseRequested`
|
||
- 输入
|
||
- `PointerMoved`
|
||
- `PointerLeft`
|
||
- `PointerButtonDown`
|
||
- `PointerButtonUp`
|
||
- `PointerWheel`
|
||
- `KeyDown`
|
||
- `KeyUp`
|
||
- `Character`
|
||
- `FocusGained`
|
||
- `FocusLost`
|
||
- `CaptureChanged`
|
||
- 尺寸
|
||
- `ClientResized`
|
||
- `InteractiveResizeStarted`
|
||
- `InteractiveResizeEnded`
|
||
- `DpiChanged`
|
||
- 帧
|
||
- `FrameTick`
|
||
- `PaintRequested`
|
||
- `FramePresented`
|
||
- 内容
|
||
- `ContentOutputReady`
|
||
|
||
### `WindowIntent`
|
||
|
||
内容层不再返回 transfer request,而是返回正式意图:
|
||
|
||
- `StartGlobalTabDrag`
|
||
- `DetachPanel`
|
||
- `OpenUtilityWindow`
|
||
- `RequestCursor`
|
||
- `RequestCapture`
|
||
- `ReleaseCapture`
|
||
- `RequestImmediateFrame`
|
||
- `RequestInvalidate`
|
||
- `ProposeWorkspaceMutation`
|
||
|
||
### `WindowEffect`
|
||
|
||
应用层最终向平台层发 effect:
|
||
|
||
- `CreateNativeWindow`
|
||
- `DestroyNativeWindow`
|
||
- `ShowWindow`
|
||
- `FocusWindow`
|
||
- `MoveWindow`
|
||
- `ResizeWindow`
|
||
- `SetWindowTitle`
|
||
- `SetCursor`
|
||
- `AcquireCapture`
|
||
- `ReleaseCapture`
|
||
- `TrackMouseLeave`
|
||
- `ScheduleFrame`
|
||
- `ApplyRenderResize`
|
||
|
||
---
|
||
|
||
## 6. 目标模块设计
|
||
|
||
## 6.1 `WindowSession`
|
||
|
||
职责:
|
||
|
||
- 持有 `WindowState`
|
||
- 接收 `WindowEvent`
|
||
- 演进单窗口状态
|
||
- 触发内容更新
|
||
- 生成窗口级命令和 effect
|
||
|
||
禁止:
|
||
|
||
- 直接调用 Win32 API
|
||
- 直接调用 D3D12 present
|
||
- 直接修改其它窗口状态
|
||
- 直接创建 utility window
|
||
|
||
## 6.2 `WindowTopologyService`
|
||
|
||
职责:
|
||
|
||
- 持有 `WindowTopologyState`
|
||
- 处理跨窗口事务
|
||
- 统一管理:
|
||
- primary / active window
|
||
- panel detach
|
||
- cross-window drop
|
||
- global tab drag
|
||
- utility window 去重与重用
|
||
|
||
它必须替代:
|
||
|
||
- `EditorWindowWorkspaceCoordinator`
|
||
- `EditorUtilityWindowCoordinator`
|
||
|
||
## 6.3 `FrameScheduler`
|
||
|
||
职责:
|
||
|
||
- 统一登记所有窗口帧请求
|
||
- 合并稳态帧与优先帧
|
||
- 对 resize / dpi / paint 恢复设定显式优先级
|
||
|
||
关键规则:
|
||
|
||
1. 消息回调只登记帧请求。
|
||
2. 所有 render / present 集中在调度点执行。
|
||
3. 不允许出现新的“隐式即时帧”路径。
|
||
|
||
## 6.4 `IWindowContentHost`
|
||
|
||
内容层改成统一宿主接口,而不是多 binding 探测。
|
||
|
||
输入:`WindowContentInput`
|
||
|
||
- workspace bounds
|
||
- input events
|
||
- frame context
|
||
- capture status text
|
||
- topology snapshot
|
||
- window role
|
||
|
||
输出:`WindowContentOutput`
|
||
|
||
- draw data
|
||
- cursor kind
|
||
- capture demand
|
||
- title bar mode
|
||
- workspace mutation intent
|
||
- utility window intent
|
||
- focus hints
|
||
- status text
|
||
|
||
## 6.5 `WorkspaceContentHost`
|
||
|
||
替代:
|
||
|
||
- `EditorWorkspaceWindowContentController`
|
||
|
||
职责:
|
||
|
||
- 持有 `EditorShellRuntime`
|
||
- 更新 shell
|
||
- 输出统一 `WindowContentOutput`
|
||
|
||
## 6.6 `UtilityContentHost`
|
||
|
||
替代:
|
||
|
||
- `EditorUtilityWindowContentController`
|
||
|
||
职责:
|
||
|
||
- 承载 utility panel
|
||
- 输出聚焦态、绘制结果、最小尺寸和内容状态
|
||
|
||
## 6.7 `Win32WindowProcAdapter`
|
||
|
||
替代:
|
||
|
||
- `EditorWindowMessageDispatcher`
|
||
|
||
职责:
|
||
|
||
- `WM_* -> WindowEvent`
|
||
- 不做业务决策
|
||
- 不持有业务状态
|
||
- 不 render / present
|
||
|
||
## 6.8 `Win32WindowBackend`
|
||
|
||
职责:
|
||
|
||
- `CreateWindowExW`
|
||
- `DestroyWindow`
|
||
- `SetWindowPos`
|
||
- `SetWindowTextW`
|
||
- `ShowWindow`
|
||
- `SetForegroundWindow`
|
||
- `TrackMouseEvent`
|
||
- `SetCapture / ReleaseCapture`
|
||
|
||
原则:
|
||
|
||
- 只执行 effect
|
||
- 不知道 workspace
|
||
- 不知道 topology
|
||
- 不知道 content host
|
||
|
||
## 6.9 `D3D12WindowRenderHost`
|
||
|
||
替代旧 `EditorWindowRuntimeController` 中的 D3D12 部分。
|
||
|
||
职责:
|
||
|
||
- 初始化窗口 renderer
|
||
- resize
|
||
- begin frame
|
||
- present
|
||
- viewport surface presentation capability
|
||
|
||
它是渲染宿主,不是业务控制器。
|
||
|
||
## 6.10 Chrome 子层
|
||
|
||
当前 stateful `EditorWindowChromeController` 需要拆为:
|
||
|
||
- `ChromeGeometry`
|
||
- 纯几何、纯 hit test
|
||
- `ChromeReducer`
|
||
- hover / press / resize / drag restore 状态机
|
||
- `ChromeEffectsBuilder`
|
||
- 根据状态变化生成 effect
|
||
|
||
纯 helper 如 `BorderlessWindowChrome.*`、`BorderlessWindowFrame.*` 可以保留或收缩。
|
||
|
||
## 6.11 关键接口草案
|
||
|
||
下面给的是目标结构草案,不要求首阶段一次到位,但最终主干必须收敛到类似形态。
|
||
|
||
### `WindowSession`
|
||
|
||
```cpp
|
||
class WindowSession final {
|
||
public:
|
||
explicit WindowSession(WindowId id, WindowKind kind);
|
||
|
||
const WindowState& Snapshot() const;
|
||
|
||
WindowSessionResult ApplyEvent(const WindowEvent& event);
|
||
WindowSessionResult ApplyContentOutput(const WindowContentOutput& output);
|
||
WindowSessionResult ApplyTopologyFeedback(const WindowTopologyFeedback& feedback);
|
||
|
||
private:
|
||
WindowState m_state = {};
|
||
};
|
||
```
|
||
|
||
约束:
|
||
|
||
- `WindowSession` 是单窗口真状态唯一可写者。
|
||
- 外部只能拿 snapshot,不能直接改字段。
|
||
- chrome、input、lifecycle、frame 都通过 reducer 更新,不允许 controller 私改。
|
||
|
||
### `WindowTopologyService`
|
||
|
||
```cpp
|
||
class WindowTopologyService final {
|
||
public:
|
||
const WindowTopologyState& Snapshot() const;
|
||
|
||
TopologyResult ApplyIntent(
|
||
WindowId sourceWindowId,
|
||
const WindowIntent& intent,
|
||
const WindowTopologySnapshot& snapshot);
|
||
|
||
void OnWindowClosed(WindowId windowId);
|
||
void OnWindowCreated(WindowId windowId, WindowRole role);
|
||
|
||
private:
|
||
WindowTopologyState m_state = {};
|
||
};
|
||
```
|
||
|
||
约束:
|
||
|
||
- 所有跨窗口事务统一进这里。
|
||
- 不允许在别处直接增删 `workspaceWindowIds` / `utilityWindowIds`。
|
||
- global tab drag 只能由它维护完整会话状态。
|
||
|
||
### `FrameScheduler`
|
||
|
||
```cpp
|
||
class FrameScheduler final {
|
||
public:
|
||
void RequestFrame(WindowId windowId, FramePriority priority, FrameReason reason);
|
||
bool HasPendingFrame() const;
|
||
FrameBatch DequeueReadyBatch(const WindowSessionStore& sessions);
|
||
void NotifyFramePresented(WindowId windowId, std::uint64_t frameSerial);
|
||
};
|
||
```
|
||
|
||
约束:
|
||
|
||
- 只有 `FrameScheduler` 可以决定某一帧何时真正执行。
|
||
- `WM_PAINT`、`WM_SIZE`、`WM_DPICHANGED` 只产生请求,不直接 render。
|
||
- steady frame / immediate frame 不再是两条链,而是一个调度器内的不同优先级。
|
||
|
||
### `FrameScheduler` 帧事务模型
|
||
|
||
单帧事务顺序必须固定,不允许实现时自由发挥。标准顺序定义为:
|
||
|
||
1. drain window events
|
||
2. merge frame requests
|
||
3. build `WindowContentInput`
|
||
4. update content hosts
|
||
5. submit topology intents / effect intents
|
||
6. finalize render plan
|
||
7. execute render subpasses / viewport work
|
||
8. present
|
||
9. publish `FramePresented` / frame feedback
|
||
10. validate paint / clear frame debt / decide requeue
|
||
|
||
必须同时定义以下语义:
|
||
|
||
- `WM_PAINT` 只产生 `FrameReason::PaintValidation`
|
||
- `WM_SIZE` 只产生 `FrameReason::Resize`
|
||
- `WM_DPICHANGED` 只产生 `FrameReason::DpiChange`
|
||
- steady tick 只产生 `FrameReason::SteadyTick`
|
||
- global tab drag / capture 恢复只产生显式 interaction continuation reason
|
||
|
||
优先级规则必须显式写死:
|
||
|
||
- `DpiChange` = `Critical`
|
||
- `Resize` = `High`
|
||
- `PaintValidation` = `High`
|
||
- `InteractionContinuation` = `High`
|
||
- `SteadyTick` = `Normal`
|
||
|
||
旧语义替代关系必须明确:
|
||
|
||
- `DriveFrame` -> steady tick frame request
|
||
- `DriveImmediateFrame` -> high/critical priority frame request
|
||
- `RequestSkipNextSteadyStateFrame` -> scheduler suppression rule
|
||
- queued completed immediate frame -> scheduler-owned post-present topology transaction queue
|
||
|
||
### `IWindowContentHost`
|
||
|
||
```cpp
|
||
class IWindowContentHost {
|
||
public:
|
||
virtual ~IWindowContentHost() = default;
|
||
virtual void Initialize(const WindowContentHostInitContext&) = 0;
|
||
virtual WindowContentOutput Update(const WindowContentInput&) = 0;
|
||
virtual void RenderSubpasses(const WindowRenderSubpassContext&) = 0;
|
||
virtual void Shutdown() = 0;
|
||
};
|
||
```
|
||
|
||
约束:
|
||
|
||
- 内容层统一返回 `WindowContentOutput`。
|
||
- 应用层不再 `TryGet*Binding`。
|
||
- 内容层如果需要表达 cursor、capture、detach、utility window、title bar mode,都在 output 中显式返回。
|
||
|
||
### `Win32WindowProcAdapter`
|
||
|
||
```cpp
|
||
class Win32WindowProcAdapter final {
|
||
public:
|
||
std::optional<WindowEvent> Translate(
|
||
HWND hwnd,
|
||
UINT message,
|
||
WPARAM wParam,
|
||
LPARAM lParam) const;
|
||
};
|
||
```
|
||
|
||
约束:
|
||
|
||
- 只做消息翻译,不做业务分支。
|
||
- 不调用 workspace coordinator。
|
||
- 不调用 utility coordinator。
|
||
- 不调用 render / present。
|
||
|
||
---
|
||
|
||
## 7. 旧模块到新模块的映射
|
||
|
||
| 现有模块 | 目标归宿 | 最终命运 |
|
||
| --- | --- | --- |
|
||
| `EditorWindow` | `WindowSession` + 轻量 native facade | 大幅收缩或删除 |
|
||
| `EditorWindowManager` | `WindowSessionStore` + `WindowTopologyService` + `FrameScheduler` | 删除 |
|
||
| `EditorWindowHostRuntime` | `WindowSessionStore` + `Win32WindowBackend` | 删除 |
|
||
| `EditorWindowMessageDispatcher` | `Win32WindowProcAdapter` | 删除 |
|
||
| `EditorWindowLifecycleCoordinator` | `WindowSession` + topology service | 删除 |
|
||
| `EditorWindowWorkspaceCoordinator` | `WindowTopologyService` | 删除 |
|
||
| `EditorUtilityWindowCoordinator` | `WindowTopologyService` | 删除 |
|
||
| `EditorWindowFrameDriver` | `FrameScheduler` | 删除 |
|
||
| `EditorWindowRuntimeController` | `D3D12WindowRenderHost` + content host bridge | 拆分后删除旧形态 |
|
||
| `EditorWorkspaceWindowContentController` | `WorkspaceContentHost` | 替换 |
|
||
| `EditorUtilityWindowContentController` | `UtilityContentHost` | 替换 |
|
||
| `EditorWindowTransferRequests` | `WindowIntent / WindowCommand / WindowEffect` | 删除 |
|
||
| `TryGet*Binding` 链 | `WindowContentOutput` | 删除 |
|
||
|
||
## 7.1 最终顶层编排者
|
||
|
||
最终必须引入新的顶层运行时,建议命名:
|
||
|
||
- `WindowApplicationRuntime`
|
||
|
||
它的职责只能是:
|
||
|
||
- 持有 `WindowSessionStore`
|
||
- 持有 `WindowTopologyService`
|
||
- 持有 `FrameScheduler`
|
||
- 持有 content host factory
|
||
- 持有 `Win32WindowBackend`
|
||
- 持有 `Win32WindowProjector`
|
||
- 持有 `D3D12WindowRenderHost` registry
|
||
- 驱动主循环调度
|
||
|
||
它明确禁止做的事:
|
||
|
||
- 不新增业务可写状态真相
|
||
- 不直接执行 Win32 API
|
||
- 不直接做 workspace 事务
|
||
- 不把 topology / session / frame 规则塞回 `Application`
|
||
|
||
`Application` 最终只保留:
|
||
|
||
- 进程级初始化
|
||
- crash / log / DPI bootstrap
|
||
- 创建 `WindowApplicationRuntime`
|
||
- 调用 runtime run / shutdown
|
||
|
||
---
|
||
|
||
## 8. 目录重组建议
|
||
|
||
建议新增目录:
|
||
|
||
```text
|
||
new_editor/app/Windowing/Domain/
|
||
new_editor/app/Windowing/Application/
|
||
new_editor/app/Windowing/Content/
|
||
new_editor/app/Platform/Win32/Adapter/
|
||
new_editor/app/Platform/Win32/Backend/
|
||
new_editor/app/Platform/Win32/Projection/
|
||
new_editor/app/Platform/Win32/Rendering/
|
||
```
|
||
|
||
旧目录的处理策略:
|
||
|
||
- `Platform/Win32/Windowing`
|
||
先收缩为迁移桥,最终清空或仅保留少量 native glue
|
||
- `Platform/Win32/Runtime`
|
||
D3D12 相关迁出后删除旧 driver / controller
|
||
- `Platform/Win32/Content`
|
||
迁入 `Windowing/Content`
|
||
- `Platform/Win32/Chrome`
|
||
仅保留纯 helper,删除 stateful controller
|
||
|
||
## 8.1 依赖与边界规则
|
||
|
||
重构过程中必须同时执行“依赖收口”,否则类名换了,旧问题会原样复活。
|
||
|
||
### 允许的依赖方向
|
||
|
||
```text
|
||
Domain <- Application <- Content
|
||
<- Platform Projection
|
||
Platform Backend 只被 Application/Projection 调用
|
||
Rendering Host 只被 FrameScheduler/Render Pipeline 调用
|
||
```
|
||
|
||
### 明确禁止的依赖
|
||
|
||
1. `Windowing/Domain` 禁止包含 `windows.h`、D3D12、swapchain、renderer 头文件。
|
||
2. `Windowing/Application` 禁止直接调用 `CreateWindowExW`、`SetWindowPos`、`SetCapture`、`Present`。
|
||
3. `Windowing/Content` 禁止感知 `HWND`、`RECT`、`POINT`、`WPARAM`、`LPARAM`。
|
||
4. `Platform/Win32/Adapter` 禁止包含 workspace 事务逻辑。
|
||
5. `Platform/Win32/Backend` 禁止读取 workspace/session/content 模型。
|
||
6. `WindowTopologyService` 之外禁止直接维护 global tab drag 会话。
|
||
7. `FrameScheduler` 之外禁止新增任何“临时立即帧”入口。
|
||
8. `EditorContext` 之外的全局单例禁止继续扩散;新增全局能力必须先证明不是 per-window。
|
||
|
||
### include 审核规则
|
||
|
||
审核员在代码审查时必须额外检查:
|
||
|
||
- 新建 domain 头文件是否仍然引用 Win32 类型。
|
||
- 新建 content host 是否仍然暴露 binding 探测接口。
|
||
- 新建 platform 代码是否偷偷包含 `UIEditorWorkspaceController`。
|
||
- 任何新类是否再次通过 `friend` 穿透 `WindowSession`。
|
||
|
||
## 8.2 迁移期间的反腐层规则
|
||
|
||
迁移不是一口气删光旧代码,而是分期替换。但桥接层必须受限,否则过渡层会永久化。
|
||
|
||
### 允许存在的临时桥
|
||
|
||
1. `EditorWindowManager`
|
||
只允许暂时作为启动入口和 legacy facade。
|
||
2. `EditorWindow`
|
||
过渡期允许保留,但只能逐步退化为:
|
||
- `windowId`
|
||
- native handle facade
|
||
- 指向 `WindowSession` / backend 的轻量桥
|
||
3. `EditorWindowRuntimeController`
|
||
只允许短期作为 `content host + render host` 的组合桥,后续必须拆解。
|
||
4. `EditorWindowMessageDispatcher`
|
||
只允许短期 forward 到 `Win32WindowProcAdapter`,不能继续长逻辑。
|
||
|
||
所有临时桥都必须同时满足以下硬约束:
|
||
|
||
1. 只允许做机械转发或数据形状适配。
|
||
2. 不允许持有业务可写状态。
|
||
3. 不允许做条件分支决策。
|
||
4. 不允许生成新的业务语义。
|
||
5. 不允许吞掉 effect、intent、错误或反馈。
|
||
6. 必须在计划中绑定删除阶段和删除条件。
|
||
7. 做不到以上约束的桥,不允许“先留着”,必须在当前阶段直接拆掉。
|
||
|
||
### 迁移期明确禁止
|
||
|
||
1. 禁止新增任何新的 `friend class`。
|
||
2. 禁止给 `EditorWindowFrameTransferRequests` 再加字段。
|
||
3. 禁止新增新的 `TryGet*Binding`。
|
||
4. 禁止在 `WM_*` 路径里新增 render / present / workspace 事务代码。
|
||
5. 禁止在旧 coordinator 中继续沉积新业务。
|
||
6. 禁止从 live windows 再推导新的“临时全局状态”。
|
||
|
||
### 判断一个桥是否可以删除的标准
|
||
|
||
同时满足以下条件时,桥必须进入删除阶段:
|
||
|
||
1. 新结构已经有等价状态对象。
|
||
2. 新结构已经有等价命令或 effect。
|
||
3. 旧桥只剩转发,不再做决策。
|
||
4. 已有回归覆盖该路径。
|
||
|
||
## 9.0 所有阶段通用门禁
|
||
|
||
下面这些门禁不是某一阶段独有,而是从 Phase 1 开始到 Phase 8 都必须成立:
|
||
|
||
1. 本阶段新增桥接层只能是机械转发 adapter,不能业务化。
|
||
2. 任何单窗口状态在同一时刻只能有一个可写源。
|
||
3. 任何跨窗口状态在同一时刻只能有一个可写源。
|
||
4. 新增类型不得把 Win32 类型重新包装后带回 domain / application。
|
||
5. 没有对应测试门禁和回归命令的结构,不允许宣称阶段完成。
|
||
|
||
---
|
||
|
||
## 9. 分阶段执行方案
|
||
|
||
## Phase 0:基线冻结与护栏建立
|
||
|
||
### 目标
|
||
|
||
在动架构前冻结当前行为基线。
|
||
|
||
### 工作项
|
||
|
||
1. 记录旧系统关键链路:
|
||
- 建窗
|
||
- 关闭
|
||
- steady frame
|
||
- immediate frame
|
||
- resize
|
||
- dpi change
|
||
- utility window open / reuse
|
||
- panel detach
|
||
- global tab drag
|
||
2. 补最小回归清单。
|
||
3. 建立 trace / log 基线。
|
||
4. 标记本计划的阶段门禁。
|
||
|
||
### 退出条件
|
||
|
||
- 关键行为已文档化。
|
||
- 有最小回归检查项。
|
||
- 已定义测试 target 规划、fake 对象职责和阶段门禁命令。
|
||
- 评论区已登记 Phase 0 启动记录。
|
||
|
||
## Phase 1:新协议落地
|
||
|
||
### 目标
|
||
|
||
先引入新的 domain 抽象,不替换旧主链。
|
||
|
||
### 工作项
|
||
|
||
1. 新增 `WindowState`、`WindowTopologyState`、`WindowEvent`、`WindowIntent`、`WindowEffect`。
|
||
2. 新增 `WindowContentInput`、`WindowContentOutput`。
|
||
3. 新增纯 reducer 雏形:
|
||
- lifecycle
|
||
- input
|
||
- chrome
|
||
4. 保持旧主链不动。
|
||
|
||
### 退出条件
|
||
|
||
- 新类型不依赖 Win32 类型。
|
||
- 新类型足以表达旧主链中的关键意图。
|
||
- 已把 `window_session_tests`、`window_topology_tests`、`frame_scheduler_tests`、`win32_message_translator_tests`
|
||
纳入 CMake 目标规划与目录规划。
|
||
|
||
## Phase 2:单窗口状态迁入 `WindowSession`
|
||
|
||
### 目标
|
||
|
||
解决最底层的状态 ownership 问题。
|
||
|
||
### 工作项
|
||
|
||
1. 创建 `WindowSession`。
|
||
2. 把单窗口状态迁入 `WindowSession`。
|
||
3. `EditorWindow` 退化为兼容外壳。
|
||
4. 开始消灭外部对 `EditorWindow` 内部状态的直接访问。
|
||
|
||
### 退出条件
|
||
|
||
- 单窗口真状态只在 `WindowSession` 或其 reducer 中可写。
|
||
- 旧 `EditorWindow` / `EditorWindowInputController` / `EditorWindowChromeController`
|
||
不能再持有独立业务状态真相。
|
||
- 不允许存在新旧双写路径。
|
||
- `EditorWindow` 不再是共享可变状态包。
|
||
|
||
## Phase 3:消息路径改造
|
||
|
||
### 目标
|
||
|
||
把 `WndProc` 路径变成纯事件适配。
|
||
|
||
### 工作项
|
||
|
||
1. 新建 `Win32WindowProcAdapter`。
|
||
2. 所有 `WM_*` 转成 `WindowEvent`。
|
||
3. 移出消息路径中的:
|
||
- render / present
|
||
- workspace 事务
|
||
- utility window 打开
|
||
- chrome 直接 effect
|
||
|
||
### 退出条件
|
||
|
||
- `WndProc` 中不再直接调用渲染入口。
|
||
|
||
## Phase 4:统一帧调度
|
||
|
||
### 目标
|
||
|
||
消灭稳态帧与即时帧的双轨结构。
|
||
|
||
### 工作项
|
||
|
||
1. 建立 `FrameScheduler`。
|
||
2. 消息路径只登记帧请求。
|
||
3. 稳态帧和优先帧统一调度。
|
||
4. 定义完整 frame lifecycle、`FrameReason` 和 `FramePriority`。
|
||
5. 删除 `DriveImmediateFrame` 思维模式。
|
||
|
||
### 退出条件
|
||
|
||
- 没有消息路径直接 render / present。
|
||
- `FrameScheduler` 生命周期顺序、reason 映射、priority 映射、失败/跳帧规则已落成文档并对应旧语义。
|
||
- 不再需要 queued completed immediate frame 补偿逻辑。
|
||
|
||
## Phase 5:内容层协议替换
|
||
|
||
### 目标
|
||
|
||
删除 binding 探测链。
|
||
|
||
### 工作项
|
||
|
||
1. 建立 `IWindowContentHost`。
|
||
2. 实现 `WorkspaceContentHost`。
|
||
3. 实现 `UtilityContentHost`。
|
||
4. 让内容层统一返回 `WindowContentOutput`。
|
||
5. 删除 `TryGet*Binding` 链。
|
||
|
||
### 退出条件
|
||
|
||
- 应用层不再感知内容层内部的 binding 形状。
|
||
|
||
## Phase 6:拓扑服务落地
|
||
|
||
### 目标
|
||
|
||
建立显式 `WindowTopologyState`。
|
||
|
||
### 工作项
|
||
|
||
1. 建立 `WindowTopologyService`。
|
||
2. 迁移:
|
||
- primary / active window
|
||
- panel detach
|
||
- cross-window drop
|
||
- global tab drag
|
||
- utility window 管理
|
||
3. 停止从 live windows 反推业务 window set。
|
||
|
||
### 退出条件
|
||
|
||
- 系统里只有一个跨窗口拓扑状态源。
|
||
- `WindowTopologyState` 已显式保存 window projection / workspace projection / session snapshot / 事务快照。
|
||
- `BuildLiveWindowWorkspaceSet(...)` 不再承担业务真相恢复职责。
|
||
|
||
## Phase 7:平台投影与后端收口
|
||
|
||
### 目标
|
||
|
||
把 Win32 / D3D12 执行逻辑从业务决策中彻底剥离。
|
||
|
||
### 工作项
|
||
|
||
1. 新建 `WindowApplicationRuntime`。
|
||
1. 新建 `Win32WindowBackend`。
|
||
2. 新建 `Win32WindowProjector`。
|
||
3. 新建 `D3D12WindowRenderHost`。
|
||
4. 把 `Application` 收缩为 bootstrap,主循环调度迁给 `WindowApplicationRuntime`。
|
||
5. 所有 Win32 effect 都通过 backend 执行。
|
||
|
||
### 退出条件
|
||
|
||
- `EditorWindowManager` 删除后的顶层调用链已经明确落到 `WindowApplicationRuntime`。
|
||
- 应用层不再直接调用 Win32 API。
|
||
- 会话层不再直接持有 D3D12 业务控制职责。
|
||
|
||
## Phase 8:删除旧结构与最终清理
|
||
|
||
### 目标
|
||
|
||
删掉所有旧桥、旧接口、旧控制器。
|
||
|
||
### 工作项
|
||
|
||
1. 删除旧 coordinator / driver / dispatcher / transfer request。
|
||
2. 删除旧 binding 探测链。
|
||
3. 收缩 `EditorContext`。
|
||
4. 清理 include 方向和目录结构。
|
||
|
||
### 退出条件
|
||
|
||
- 本计划第 3 节的硬约束全部满足。
|
||
|
||
## 9.1 文件级实施矩阵
|
||
|
||
这一节是执行时的真正落地清单。阶段说明解决“做什么”,这一节解决“先动哪些文件、怎么桥接、删哪些旧件”。
|
||
|
||
### Phase 0 文件级动作
|
||
|
||
目标:冻结现状,不改行为。
|
||
|
||
重点读取与建档文件:
|
||
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindow.h/.cpp`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp`
|
||
- `new_editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.cpp`
|
||
- `new_editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h/.cpp`
|
||
- `new_editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h/.cpp`
|
||
|
||
必须沉淀的基线:
|
||
|
||
- 消息到渲染路径时序图
|
||
- steady frame 时序图
|
||
- immediate frame 时序图
|
||
- global tab drag 时序图
|
||
- detach panel -> 新窗口创建时序图
|
||
- utility window open / reuse 时序图
|
||
|
||
审核检查点:
|
||
|
||
- 文档是否覆盖所有 `WM_*` 关键路径
|
||
- 是否识别了所有 direct Win32 API 调用点
|
||
- 是否列出当前 transfer request 生产者和消费者
|
||
|
||
### Phase 1 文件级动作
|
||
|
||
目标:先落地新协议,不改主行为。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Windowing/Domain/WindowId.h`
|
||
- `new_editor/app/Windowing/Domain/WindowKind.h`
|
||
- `new_editor/app/Windowing/Domain/WindowState.h`
|
||
- `new_editor/app/Windowing/Domain/WindowTopologyState.h`
|
||
- `new_editor/app/Windowing/Domain/WindowEvent.h`
|
||
- `new_editor/app/Windowing/Domain/WindowIntent.h`
|
||
- `new_editor/app/Windowing/Domain/WindowCommand.h`
|
||
- `new_editor/app/Windowing/Domain/WindowEffect.h`
|
||
- `new_editor/app/Windowing/Domain/WindowContentInput.h`
|
||
- `new_editor/app/Windowing/Domain/WindowContentOutput.h`
|
||
- `new_editor/app/Windowing/Domain/FramePriority.h`
|
||
|
||
新增 reducer 建议:
|
||
|
||
- `new_editor/app/Windowing/Application/Reducers/WindowLifecycleReducer.*`
|
||
- `new_editor/app/Windowing/Application/Reducers/WindowInputReducer.*`
|
||
- `new_editor/app/Windowing/Application/Reducers/WindowChromeReducer.*`
|
||
|
||
桥接策略:
|
||
|
||
- 旧 `EditorWindow` 继续跑,但开始把内部可见状态映射到新的 `WindowState` 草案。
|
||
- 新 domain 类型只作为纯类型定义,不接管现网行为。
|
||
|
||
本阶段不能做的事:
|
||
|
||
- 不能让新 domain 类型依赖 `windows.h`
|
||
- 不能在新协议里出现 `HWND`
|
||
- 不能为了兼容旧代码把 Win32 类型偷偷包一层再塞回 domain
|
||
|
||
### Phase 2 文件级动作
|
||
|
||
目标:把单窗口真状态收回到 `WindowSession`。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Windowing/Application/WindowSession.h`
|
||
- `new_editor/app/Windowing/Application/WindowSession.cpp`
|
||
- `new_editor/app/Windowing/Application/WindowSessionStore.h`
|
||
- `new_editor/app/Windowing/Application/WindowSessionStore.cpp`
|
||
- `new_editor/app/Windowing/Application/WindowSessionResult.h`
|
||
|
||
优先迁移状态来源:
|
||
|
||
1. `EditorWindowState.h`
|
||
2. `EditorWindowInputController`
|
||
3. `EditorWindowChromeController`
|
||
4. `EditorWindow` 中与 resize / dpi / queued immediate frame 相关的状态
|
||
|
||
具体做法:
|
||
|
||
- 先把当前 scattered state 建成 `WindowState` 快照字段,不立即删旧控制器。
|
||
- 让 `EditorWindow` 只通过 `WindowSession` 读写窗口状态。
|
||
- 外部模块改成读取 `WindowSession::Snapshot()`,不再读 `EditorWindow` 私有成员。
|
||
- 旧 controller 如果暂时还存在,只允许退化为无状态 helper,不允许继续做独立状态持有者。
|
||
|
||
必须优先清掉的穿透点:
|
||
|
||
- `EditorWindow.h` 中的 `friend class` 链
|
||
- `EditorWindowMessageDispatcher` 对 `m_inputController` / `m_chromeController` / `m_runtime` 的直接访问
|
||
- `EditorWindowFrameDriver` 对 `window.m_chromeController` 的直接访问
|
||
|
||
阶段验收:
|
||
|
||
- 所有单窗口状态写入都只经由 `WindowSession` 或 reducer
|
||
- `EditorWindow` 不再是唯一可写状态袋
|
||
- 不存在旧 controller 与 `WindowSession` 双写
|
||
- `friend class` 不再承担状态穿透写入入口
|
||
|
||
### Phase 3 文件级动作
|
||
|
||
目标:把消息路径收口成纯适配层。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Platform/Win32/Adapter/Win32WindowProcAdapter.h`
|
||
- `new_editor/app/Platform/Win32/Adapter/Win32WindowProcAdapter.cpp`
|
||
- `new_editor/app/Platform/Win32/Adapter/Win32MessageTranslator.*`
|
||
|
||
旧文件处理:
|
||
|
||
- `EditorWindowMessageDispatcher.cpp` 第一阶段变薄,只保留:
|
||
- `HWND -> WindowId` 查找
|
||
- 调用 `Win32WindowProcAdapter`
|
||
- 把事件送给 `WindowSession`
|
||
- 后续删除 `EditorWindowMessageDispatcher.*`
|
||
|
||
需要迁出的逻辑:
|
||
|
||
- `WM_SIZE` 内直接 immediate frame
|
||
- `WM_DPICHANGED` 内直接 render
|
||
- `WM_PAINT` 内直接 `OnPaintMessage`
|
||
- `WM_CLOSE` 中对生命周期的复杂编排
|
||
- `WM_MOUSE*` 中直接 global tab drag / chrome action 决策
|
||
|
||
阶段验收:
|
||
|
||
- `WndProc` 中只剩翻译和转发
|
||
- 业务层收到的是 `WindowEvent`,不是 `WM_*`
|
||
|
||
### Phase 4 文件级动作
|
||
|
||
目标:建立统一帧调度器。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Windowing/Application/FrameScheduler.h`
|
||
- `new_editor/app/Windowing/Application/FrameScheduler.cpp`
|
||
- `new_editor/app/Windowing/Application/FrameRequestQueue.h`
|
||
- `new_editor/app/Windowing/Application/FrameRequestQueue.cpp`
|
||
|
||
旧文件收缩:
|
||
|
||
- `Runtime/EditorWindowFrameDriver.*` 先转成 scheduler 内部 helper,再删除
|
||
- `EditorWindowHostRuntime::RenderAllWindows(...)` 改为委托 `FrameScheduler`
|
||
|
||
关键迁移动作:
|
||
|
||
- 把 `DriveFrame` / `DriveImmediateFrame` 收束成统一请求模型
|
||
- 把 `RequestSkipNextSteadyStateFrame` 这种补丁语义转为显式调度规则
|
||
- 把 `HasQueuedCompletedImmediateFrame` / `ConsumeQueuedCompletedImmediateFrameTransferRequests`
|
||
转换为 scheduler 完成回调或拓扑事务队列
|
||
- 把 frame transaction 顺序固定到:
|
||
event drain -> request merge -> content update -> topology submit -> render -> present -> feedback -> paint validation
|
||
- 为 `WM_PAINT` / `WM_SIZE` / `WM_DPICHANGED` 建立明确的 `FrameReason` / `FramePriority` 映射
|
||
|
||
阶段验收:
|
||
|
||
- steady frame / immediate frame 共享同一套调度实体
|
||
- 新代码里不存在“为了某个消息直接 present 一帧”的入口
|
||
|
||
### Phase 5 文件级动作
|
||
|
||
目标:把内容层从 binding 探测链改成正式输出协议。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Windowing/Content/IWindowContentHost.h`
|
||
- `new_editor/app/Windowing/Content/WorkspaceContentHost.h`
|
||
- `new_editor/app/Windowing/Content/WorkspaceContentHost.cpp`
|
||
- `new_editor/app/Windowing/Content/UtilityContentHost.h`
|
||
- `new_editor/app/Windowing/Content/UtilityContentHost.cpp`
|
||
|
||
重点改造旧文件:
|
||
|
||
- `Platform/Win32/Content/EditorWindowContentController.h`
|
||
- `Platform/Win32/Content/EditorWorkspaceWindowContentController.*`
|
||
- `Platform/Win32/Content/EditorUtilityWindowContentController.*`
|
||
|
||
替换策略:
|
||
|
||
1. 先让旧 content controller 内部构造 `WindowContentOutput`
|
||
2. 再让应用层只消费 output,不再 `TryGet*Binding`
|
||
3. 等应用层不再 probe binding 后,删除 binding 接口
|
||
|
||
必须退出主干的接口:
|
||
|
||
- `TryGetWorkspaceBinding`
|
||
- `TryGetDockHostBinding`
|
||
- `TryGetInputFeedbackBinding`
|
||
- `TryGetTitleBarBinding`
|
||
- `EditorWindowFrameTransferRequests`
|
||
|
||
阶段验收:
|
||
|
||
- 内容层对外只暴露一个宿主接口
|
||
- cursor / capture / title / utility / detach 等信号都能通过 output 表达
|
||
|
||
### Phase 6 文件级动作
|
||
|
||
目标:建立显式拓扑状态源。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Windowing/Application/WindowTopologyService.h`
|
||
- `new_editor/app/Windowing/Application/WindowTopologyService.cpp`
|
||
- `new_editor/app/Windowing/Application/WindowTopologyResult.h`
|
||
- `new_editor/app/Windowing/Application/WindowTopologySnapshot.h`
|
||
|
||
优先迁移旧文件职责:
|
||
|
||
- `Windowing/EditorWindowWorkspaceCoordinator.*`
|
||
- `Windowing/EditorUtilityWindowCoordinator.*`
|
||
|
||
必须先抽离的事务:
|
||
|
||
- active / primary window 管理
|
||
- global tab drag 会话
|
||
- panel detach -> 新窗口创建
|
||
- cross-window drop
|
||
- utility window open / reuse / focus
|
||
|
||
核心要求:
|
||
|
||
- `BuildLiveWindowWorkspaceSet(...)` 只能作为短期兼容代码,最终删除
|
||
- topology state 必须显式保存,而不是运行时临时拼装
|
||
- topology state 必须持有按 `windowId` 建立的权威 workspace projection / session snapshot
|
||
|
||
阶段验收:
|
||
|
||
- 任何跨窗口行为都能从 `WindowTopologyState` 解释
|
||
- 不再需要从 live windows 反推出唯一真相
|
||
|
||
### Phase 7 文件级动作
|
||
|
||
目标:把平台执行层和业务决策彻底分开。
|
||
|
||
新增文件建议:
|
||
|
||
- `new_editor/app/Windowing/Application/WindowApplicationRuntime.h`
|
||
- `new_editor/app/Windowing/Application/WindowApplicationRuntime.cpp`
|
||
- `new_editor/app/Platform/Win32/Backend/Win32WindowBackend.h`
|
||
- `new_editor/app/Platform/Win32/Backend/Win32WindowBackend.cpp`
|
||
- `new_editor/app/Platform/Win32/Projection/Win32WindowProjector.h`
|
||
- `new_editor/app/Platform/Win32/Projection/Win32WindowProjector.cpp`
|
||
- `new_editor/app/Platform/Win32/Rendering/D3D12WindowRenderHost.h`
|
||
- `new_editor/app/Platform/Win32/Rendering/D3D12WindowRenderHost.cpp`
|
||
|
||
迁移来源:
|
||
|
||
- `EditorWindowHostRuntime.cpp` 中的 Win32 建窗、ShowWindow、UpdateWindow
|
||
- `EditorWindowWorkspaceCoordinator.cpp` 中的 `SetWindowTextW`
|
||
- `EditorWindowChromeController.cpp` 中的窗口移动/缩放/最大化相关 Win32 调用
|
||
- `EditorWindowRuntimeController.*` 中的渲染宿主职责
|
||
|
||
投影规则:
|
||
|
||
- `WindowApplicationRuntime` 只做依赖组装和主循环调度
|
||
- `WindowSession` / `WindowTopologyService` 只产生命令和 effect
|
||
- `Win32WindowProjector` 把状态变化映射成 effect 列表
|
||
- `Win32WindowBackend` 执行 effect
|
||
- `D3D12WindowRenderHost` 只执行渲染宿主职责
|
||
|
||
阶段验收:
|
||
|
||
- 应用层不直接包含 Win32 行为代码
|
||
- render host 不直接承担内容决策
|
||
|
||
### Phase 8 文件级动作
|
||
|
||
目标:删掉旧骨架,只保留新主干。
|
||
|
||
计划删除的旧文件范围:
|
||
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowManager.*`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.*`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.*`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.*`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.*`
|
||
- `new_editor/app/Platform/Win32/Runtime/EditorWindowFrameDriver.*`
|
||
- `new_editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h`
|
||
|
||
高风险清理项:
|
||
|
||
- `EditorContext` 中所有 per-window capability
|
||
- 旧 chrome controller 中残留的状态机
|
||
- 所有 `TryGet*Binding` 调用点
|
||
- 所有 `friend class` 穿透
|
||
|
||
最终收口标准:
|
||
|
||
- 旧桥只剩零或极薄 facade
|
||
- include 方向符合第 8.1 节规则
|
||
- 代码库中查不到 transfer request 和 binding probe 主干调用
|
||
|
||
---
|
||
|
||
## 10. `EditorContext` 重构要求
|
||
|
||
当前 `EditorContext` 必须被收缩,只保留全局状态和全局服务。
|
||
|
||
### 可以继续保留在全局中的内容
|
||
|
||
- shell asset
|
||
- selection service
|
||
- command focus service
|
||
- project runtime
|
||
- scene runtime
|
||
- system interaction host
|
||
- session store
|
||
|
||
### 必须移出的内容
|
||
|
||
- per-window text measurer
|
||
- per-window render host
|
||
- per-window capture / presentation capability
|
||
- 任何依赖“当前窗口实例”的局部能力
|
||
|
||
建议拆分为:
|
||
|
||
- `EditorAppContext`
|
||
- `EditorSessionStore`
|
||
- per-window capability context
|
||
|
||
---
|
||
|
||
## 11. 测试策略
|
||
|
||
## 11.1 测试 target 与目录规划
|
||
|
||
必须落成以下 target 规划:
|
||
|
||
- `window_session_tests`
|
||
- `window_topology_tests`
|
||
- `frame_scheduler_tests`
|
||
- `win32_message_translator_tests`
|
||
- `new_editor_windowing_tests` 作为聚合 target
|
||
|
||
建议目录:
|
||
|
||
- `new_editor/tests/windowing/window_session/`
|
||
- `new_editor/tests/windowing/window_topology/`
|
||
- `new_editor/tests/windowing/frame_scheduler/`
|
||
- `new_editor/tests/platform/win32_message_translator/`
|
||
|
||
## 11.2 fake 组件要求
|
||
|
||
为保证这些测试不是空壳,必须同时定义:
|
||
|
||
- `FakeWindowBackend`
|
||
- 记录 effect 执行顺序,不实际调用 Win32
|
||
- `FakeRenderHost`
|
||
- 记录 resize / render / present / feedback 顺序
|
||
- `FakeContentHost`
|
||
- 可配置输出 `WindowContentOutput`
|
||
- `FakeTopologyObserver` 或 `FakeTopologySink`
|
||
- 校验 topology transaction 提交顺序
|
||
- `FakeFrameClock`
|
||
- 控制 steady tick / priority frame 时序
|
||
|
||
## 11.3 阶段门禁命令
|
||
|
||
计划执行时必须收口到明确命令,不能只写“补测试”。最低门禁如下:
|
||
|
||
- 编译门禁:
|
||
- `cmake --build build --config Debug --target XCUIEditorApp`
|
||
- 单元测试门禁:
|
||
- `cmake --build build --config Debug --target new_editor_windowing_tests`
|
||
- `ctest --test-dir build --config Debug --output-on-failure -R "window_session|window_topology|frame_scheduler|win32_message_translator"`
|
||
- 启动级冒烟:
|
||
- 启动 `build/new_editor/Debug/XCUIEditor.exe`,确认能稳定启动并进入主循环
|
||
|
||
阶段要求:
|
||
|
||
- Phase 0 结束前必须把这些命令和 target 写进计划。
|
||
- Phase 1 结束前必须把 target scaffold 和 fake 组件接口纳入 CMake 与目录结构。
|
||
- Phase 2 及之后每个阶段结束时,未通过本阶段门禁不得进入下一阶段。
|
||
|
||
### 单元测试
|
||
|
||
必须补:
|
||
|
||
- `WindowSession` 生命周期测试
|
||
- 输入 reducer 测试
|
||
- chrome reducer 测试
|
||
- topology service 拖拽事务测试
|
||
- frame scheduler 优先级测试
|
||
|
||
### 平台适配测试
|
||
|
||
- `Win32WindowProcAdapter`
|
||
- `Win32WindowProjector`
|
||
- `Win32WindowBackend` effect 执行桩
|
||
|
||
### 集成测试
|
||
|
||
至少覆盖:
|
||
|
||
1. 建窗 / 关窗
|
||
2. 主窗口关闭触发全局回收
|
||
3. resize / dpi change 后的帧调度
|
||
4. panel detach -> 新窗口创建
|
||
5. global tab drag -> cross-window drop
|
||
6. utility window open / reuse / focus
|
||
|
||
### 高频回归项
|
||
|
||
每阶段后都要回归:
|
||
|
||
- capture 是否正确
|
||
- chrome hover / press / resize 是否正常
|
||
- resize 时是否抖动或错帧
|
||
- utility window 是否重复创建
|
||
- 跨窗口拖拽是否残留 preview
|
||
- DPI 切换后 cursor / title / size 是否一致
|
||
|
||
---
|
||
|
||
## 12. 风险与控制
|
||
|
||
### 风险 A:新旧状态双写
|
||
|
||
控制:
|
||
|
||
- 每阶段明确唯一可写状态源。
|
||
- 审核员重点检查是否存在双写。
|
||
|
||
### 风险 B:移除即时帧后交互变钝
|
||
|
||
控制:
|
||
|
||
- `FrameScheduler` 必须支持优先帧。
|
||
- resize / paint 恢复链必须做延迟对比。
|
||
|
||
### 风险 C:拓扑迁移时语义漂移
|
||
|
||
控制:
|
||
|
||
- 先冻结旧行为。
|
||
- 每个事务都要有明确测试和日志。
|
||
|
||
### 风险 D:`EditorContext` 收缩影响外围模块
|
||
|
||
控制:
|
||
|
||
- 先加新接口,再迁移调用点。
|
||
- 兼容桥必须有明确删除阶段。
|
||
|
||
---
|
||
|
||
## 13. 完成定义
|
||
|
||
当且仅当以下条件同时满足时,重构完成:
|
||
|
||
1. 新窗口体系不存在 `friend` 穿透状态。
|
||
2. domain / application 中没有 Win32 类型泄漏。
|
||
3. `WndProc` 路径中不存在直接 render / present。
|
||
4. `EditorWindowFrameTransferRequests` 已删除。
|
||
5. `TryGet*Binding` 探测链已删除。
|
||
6. `WindowTopologyService` 是唯一跨窗口状态源。
|
||
7. `EditorContext` 不再挂载窗口局部能力。
|
||
8. 旧 coordinator / dispatcher / driver 已退出主干。
|
||
9. blocker / major 级审核评论全部关闭。
|
||
|
||
---
|
||
|
||
## 14. 审核评论规则
|
||
|
||
这部分直接作为本计划的附录使用,规则保持简单。
|
||
|
||
### 规则 1:评论必须带阶段和严重度
|
||
|
||
严重度只允许:
|
||
|
||
- `BLOCKER`
|
||
- `MAJOR`
|
||
- `MINOR`
|
||
- `QUESTION`
|
||
|
||
### 规则 2:评论必须有证据
|
||
|
||
证据至少包含一项:
|
||
|
||
- 文件路径
|
||
- 类名 / 函数名
|
||
- 可复现行为
|
||
- 测试或日志
|
||
|
||
没有证据的评论不进入正式台账。
|
||
|
||
### 规则 3:一条评论只打一个核心问题
|
||
|
||
不要把多个无关问题塞进同一条评论。
|
||
|
||
### 规则 4:`BLOCKER` 直接卡阶段
|
||
|
||
有未关闭的 `BLOCKER` 时,不允许进入下一阶段。
|
||
|
||
### 规则 5:`MAJOR` 必须在当前阶段关闭
|
||
|
||
`MAJOR` 不一定立即停工,但当前阶段结束前必须处理完。
|
||
|
||
### 规则 6:执行者必须逐条回复
|
||
|
||
回复至少包含:
|
||
|
||
- 处理动作
|
||
- 证据
|
||
- 当前剩余风险
|
||
|
||
### 规则 7:评论只能追加,不删除历史
|
||
|
||
即使评论被驳回,也要保留记录。
|
||
|
||
### 规则 8:计划变更必须先记评论区
|
||
|
||
如果实施过程中要改阶段目标、改边界、改顺序,必须先在评论区登记“计划变更请求”。
|
||
|
||
---
|
||
|
||
## 15. 评论区
|
||
|
||
### 15.1 使用说明
|
||
|
||
以下评论区用于登记:
|
||
|
||
- 阶段开始
|
||
- 阶段结束
|
||
- 审核员评论
|
||
- 执行者回复
|
||
- 审核结论
|
||
- 计划变更请求
|
||
|
||
### 15.2 状态总览
|
||
|
||
| Phase | Status | Start Date | End Date | Executor Summary | Reviewer Summary |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Phase 0 | Not Started | | | | |
|
||
| Phase 1 | Not Started | | | | |
|
||
| Phase 2 | Not Started | | | | |
|
||
| Phase 3 | Not Started | | | | |
|
||
| Phase 4 | Not Started | | | | |
|
||
| Phase 5 | Not Started | | | | |
|
||
| Phase 6 | Not Started | | | | |
|
||
| Phase 7 | Not Started | | | | |
|
||
| Phase 8 | Not Started | | | | |
|
||
|
||
### 15.3 评论模板
|
||
|
||
```md
|
||
Comment ID:
|
||
Phase:
|
||
Severity:
|
||
Status: Open
|
||
|
||
Summary:
|
||
|
||
Evidence:
|
||
-
|
||
|
||
Impact:
|
||
|
||
Required Action:
|
||
|
||
Close Condition:
|
||
|
||
Reviewer:
|
||
Date:
|
||
```
|
||
|
||
### 15.4 回复模板
|
||
|
||
```md
|
||
Response To:
|
||
Status: Answered
|
||
Executor:
|
||
Date:
|
||
|
||
Action Taken:
|
||
-
|
||
|
||
Evidence:
|
||
-
|
||
|
||
Residual Risk:
|
||
|
||
Requested Status:
|
||
```
|
||
|
||
### 15.5 计划变更请求模板
|
||
|
||
```md
|
||
Plan Change Request ID:
|
||
Affected Phase:
|
||
Date:
|
||
Executor:
|
||
|
||
Reason:
|
||
|
||
Requested Change:
|
||
|
||
Impact:
|
||
-
|
||
|
||
Rollback Condition:
|
||
```
|
||
|
||
---
|
||
|
||
## 16. 阶段评论记录区
|
||
|
||
## Phase 0
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
_No open comments. 2026-04-23 的 REVIEW-P0-001 ~ REVIEW-P0-006 已吸收到计划正文。_
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 1
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 2
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 3
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 4
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 5
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 6
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 7
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|
||
|
||
---
|
||
|
||
## Phase 8
|
||
|
||
### Start
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Goal:
|
||
- Exit Criteria:
|
||
|
||
### Comments
|
||
|
||
_No comments yet._
|
||
|
||
### End
|
||
|
||
- Date:
|
||
- Executor:
|
||
- Requested Review Decision:
|
||
|
||
### Reviewer Decision
|
||
|
||
- Decision:
|
||
- Reviewer:
|
||
- Notes:
|