fix(new_editor): finalize synchronous resize presentation cleanup

This commit is contained in:
2026-04-22 20:34:31 +08:00
parent e251b77d0d
commit dcede7f975
4 changed files with 119 additions and 24 deletions

View File

@@ -0,0 +1,118 @@
# NewEditor Frame Resize Synchronous Presentation Root Fix Plan
Date: 2026-04-22
Status: Completed Archive
## 1. Objective
Restore the original `new_editor` resize-time anti-deformation contract without adding fallback branches, duplicated resize paths, or new ownership confusion.
The fix must re-establish one clean rule:
1. when a host-side transition needs a new window size before the native window visibly adopts it, the window content must be rendered synchronously for that target size
2. when Win32 enters paint / size / DPI lifecycle messages, the host must be able to drive a synchronous frame before returning control
3. normal steady-state rendering must remain on the centralized frame driver path
## 2. Confirmed Root Cause
The regression was introduced by commit `b44f5ca9` (`refactor(new_editor): centralize win32 frame execution`).
That refactor removed the synchronous render step from the critical resize path:
1. `EditorWindowChromeController::HandleResizePointerMove(...)`
2. `EditorWindowChromeController::ApplyPredictedWindowRectTransition(...)`
3. `EditorWindowMessageDispatcher` handling of `WM_SIZE`, `WM_DPICHANGED`, `WM_EXITSIZEMOVE`, and `WM_PAINT`
and replaced it with deferred `RequestFrame(...)` bookkeeping.
That bookkeeping does not preserve the old contract because:
1. `RequestFrame(...)` only records a bitmask
2. rendering now happens later from the app main loop
3. the window can already be resized before the new frame is presented
4. the old presented surface is therefore stretched by the platform / swap chain path
This means the actual regression is not the custom title bar itself. The regression is the loss of synchronous presentation ownership in the Win32 host lifecycle.
## 3. Red Lines
The implementation must not:
1. add a second resize algorithm beside the current borderless resize path
2. add ad hoc `if (interactiveResize)` branches throughout unrelated rendering code
3. keep a dead frame-request state machine that no longer owns rendering policy
4. duplicate `RenderFrame(...)` orchestration in multiple modules
5. touch workspace transfer / docking semantics unrelated to resize-time presentation
## 4. Target End State
After the fix:
1. `EditorWindowFrameDriver` remains the single general-purpose frame execution entry point
2. synchronous Win32 lifecycle rendering uses that same driver wherever possible
3. `WM_PAINT` remains a special case only because it requires `BeginPaint` / `EndPaint`
4. borderless predicted transitions render the target-size frame synchronously before `SetWindowPos(...)`
5. dead `RequestFrame(...)` / `EditorWindowFrameRequestReason` plumbing is removed if it no longer owns any real policy
## 5. Implementation Plan
### Phase A. Remove the dead deferred frame-request layer
Completed on 2026-04-22.
1. removed `EditorWindowFrameRequestReason`
2. removed `EditorWindow::RequestFrame(...)`
3. removed `EditorWindow::ConsumePendingFrameRequestReasons()`
4. removed all call sites that only existed to defer rendering into the main loop
### Phase B. Rebuild one synchronous frame-execution helper path
Completed on 2026-04-22.
1. kept `EditorWindowFrameDriver::DriveFrame(...)` as the shared frame executor
2. added a dispatcher-local helper that drives a frame immediately and forwards transfer requests to `EditorWindowWorkspaceCoordinator`
3. restored that helper to `WM_SIZE`, `WM_DPICHANGED`, and `WM_EXITSIZEMOVE`
### Phase C. Restore paint-time synchronous rendering
Completed on 2026-04-22.
1. restored `WM_PAINT` to render synchronously inside the paint handling path
2. kept the paint special case local to `BeginPaint` / `EndPaint`
### Phase D. Restore predicted-transition pre-presentation
Completed on 2026-04-22.
1. `HandleResizePointerMove(...)` once again renders the target-size frame synchronously before `SetWindowPos(...)`
2. `ApplyPredictedWindowRectTransition(...)` does the same for maximize / restore style transitions
3. this was restored on the original transition path without adding a second resize subsystem
### Phase E. Verify no unrelated ownership was widened
Completed on 2026-04-22.
1. workspace transfer / docking semantics were left untouched
2. `EditorWindowHostRuntime::RenderAllWindows(...)` still owns steady-state frame pumping
3. only Win32 host lifecycle and predicted transition code were changed
## 6. Validation Requirements
Validation completed on 2026-04-22:
1. `XCUIEditorApp` Debug build passed after the fix
2. user verified the resize deformation regression was gone in actual runtime
Pending after this archive:
1. follow-up investigation for remaining resize jitter
2. any later refinement should stay on the same host lifecycle boundary and must not reintroduce a deferred fake scheduling layer
## 7. Completion Criteria
This work was completed with the following outcomes:
1. the regression source introduced by `b44f5ca9` was structurally removed
2. one clear synchronous presentation path was restored for resize-time lifecycle transitions
3. the dead deferred request layer was removed
4. the resulting code path remained a mainline host-lifecycle design instead of a post hoc fallback patch

View File

@@ -14,8 +14,6 @@ EditorWindowFrameTransferRequests EditorWindowFrameDriver::DriveFrame(
return {};
}
(void)window.ConsumePendingFrameRequestReasons();
EditorWindowFrameTransferRequests transferRequests =
window.RenderFrame(editorContext, globalTabDragActive);
if (const HWND hwnd = window.GetHwnd();

View File

@@ -1,22 +0,0 @@
#pragma once
#include <cstdint>
namespace XCEngine::UI::Editor::App {
enum class EditorWindowFrameRequestReason : std::uint32_t {
None = 0u,
Initial = 1u << 0u,
PaintMessage = 1u << 1u,
Resize = 1u << 2u,
DpiChanged = 1u << 3u,
ExitSizeMove = 1u << 4u,
BorderlessTransition = 1u << 5u,
ManualScreenshot = 1u << 6u
};
constexpr std::uint32_t ToFrameRequestMask(EditorWindowFrameRequestReason reason) {
return static_cast<std::uint32_t>(reason);
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -32,6 +32,7 @@ public:
private:
struct DispatchContext;
static void RenderAndHandleWindowFrame(const DispatchContext& context);
static bool EnsureTrackingMouseLeave(const DispatchContext& context);
static bool TryHandleChromeHoverConsumption(
const DispatchContext& context,