252 lines
8.4 KiB
Markdown
252 lines
8.4 KiB
Markdown
# NewEditor Frame/Lifecycle Runtime Refactor Plan
|
|
|
|
Date: 2026-04-22
|
|
|
|
## 1. Objective
|
|
|
|
This plan targets the highest-priority remaining architecture debt in `new_editor`:
|
|
|
|
1. frame execution still has multiple live owners
|
|
2. window destruction still has multiple live owners
|
|
3. `EditorShellRuntime` is still a per-frame god-object
|
|
|
|
The goal is not to split files mechanically.
|
|
The goal is to collapse ownership so that:
|
|
|
|
1. only one module can drive a frame
|
|
2. only one module can finalize window death
|
|
3. `EditorShellRuntime` becomes a thin facade over explicit per-frame stages
|
|
|
|
## 2. Confirmed Root Issues
|
|
|
|
### 2.1 Frame scheduling is not single-owner
|
|
|
|
`RenderFrame()` is currently called from:
|
|
|
|
- main host render loop
|
|
- `WM_PAINT`
|
|
- `WM_SIZE`
|
|
- `WM_DPICHANGED`
|
|
- `WM_EXITSIZEMOVE`
|
|
- borderless chrome transition logic
|
|
|
|
This means rendering is still driven by both:
|
|
|
|
1. the normal frame loop
|
|
2. synchronous Win32 message-side side effects
|
|
|
|
As long as this remains true, frame timing, transfer request ordering, and present timing
|
|
will keep depending on which path happened to trigger first.
|
|
|
|
### 2.2 Window lifetime is not single-owner
|
|
|
|
`Shutdown()`, `DestroyWindow(...)`, `MarkDestroyed()`, and host erase currently happen from
|
|
multiple places in `WindowManager`, host runtime, and workspace coordination.
|
|
|
|
This means there is still no single authoritative answer to:
|
|
|
|
1. who requests close
|
|
2. who observes native window destruction
|
|
3. who shuts runtime down
|
|
4. who removes the managed window from the host container
|
|
|
|
This is the same root class of problem that previously caused sub-window close regressions.
|
|
|
|
### 2.3 EditorShellRuntime still owns too many per-frame responsibilities
|
|
|
|
`EditorShellRuntime::Update(...)` still performs, in one place:
|
|
|
|
1. shell definition build
|
|
2. workspace/session sync
|
|
3. dock host interaction update
|
|
4. viewport request/update
|
|
5. hosted panel input routing
|
|
6. hosted panel update
|
|
7. status/trace/command-focus synchronization
|
|
|
|
That makes the runtime difficult to reason about and causes unrelated per-frame concerns to
|
|
stay tightly coupled.
|
|
|
|
## 3. Target End State
|
|
|
|
After this refactor:
|
|
|
|
1. only one frame driver can execute `BeginFrame -> UpdateAndAppend -> Present`
|
|
2. Win32 message dispatch only records frame requests / dirty reasons, never renders directly
|
|
3. borderless chrome logic can request a new frame, but cannot render directly
|
|
4. only one lifecycle coordinator can finalize managed window destruction
|
|
5. `WorkspaceCoordinator` can request a window-set mutation, but cannot directly destroy windows
|
|
6. `EditorShellRuntime` becomes a thin composition facade over smaller stage modules
|
|
|
|
## 4. New Modules
|
|
|
|
### 4.1 Frame ownership
|
|
|
|
Add:
|
|
|
|
- `new_editor/app/Platform/Win32/EditorWindowFrameDriver.h`
|
|
- `new_editor/app/Platform/Win32/EditorWindowFrameDriver.cpp`
|
|
- `new_editor/app/Platform/Win32/EditorWindowFrameRequest.h`
|
|
|
|
Responsibilities:
|
|
|
|
- own the only live path that executes a frame
|
|
- consume frame request reasons / dirty state
|
|
- normalize when transfer requests are collected and applied
|
|
|
|
### 4.2 Lifecycle ownership
|
|
|
|
Add:
|
|
|
|
- `new_editor/app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h`
|
|
- `new_editor/app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.cpp`
|
|
|
|
Responsibilities:
|
|
|
|
- own close request state
|
|
- own runtime shutdown sequencing
|
|
- own native destroy finalization and managed erase
|
|
- own primary-window cascade behavior
|
|
|
|
### 4.3 Shell runtime stage split
|
|
|
|
Add:
|
|
|
|
- `new_editor/app/Composition/EditorShellSessionCoordinator.h`
|
|
- `new_editor/app/Composition/EditorShellSessionCoordinator.cpp`
|
|
- `new_editor/app/Composition/EditorShellInteractionEngine.h`
|
|
- `new_editor/app/Composition/EditorShellInteractionEngine.cpp`
|
|
- `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h`
|
|
- `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp`
|
|
- `new_editor/app/Composition/EditorShellDrawComposer.h`
|
|
- `new_editor/app/Composition/EditorShellDrawComposer.cpp`
|
|
|
|
Responsibilities:
|
|
|
|
- `SessionCoordinator`: context/workspace/session/command-focus/status synchronization
|
|
- `InteractionEngine`: shell definition, dock host interaction, shell frame/state update
|
|
- `HostedPanelCoordinator`: hosted panel input filtering, panel update, panel capture ownership
|
|
- `DrawComposer`: shell draw packets, panel draw packets, viewport frame application to draw data
|
|
|
|
## 5. Refactor Strategy
|
|
|
|
### Phase A. Collapse frame execution to one driver
|
|
|
|
Refactor current flow so that:
|
|
|
|
1. `EditorWindowHostRuntime` requests frame execution through `EditorWindowFrameDriver`
|
|
2. `EditorWindowMessageDispatcher` no longer calls `RenderFrame(...)` directly
|
|
3. `EditorWindowChromeController` no longer calls `RenderFrame(...)` directly
|
|
4. `EditorWindow` no longer exposes uncontrolled render entrypoints to unrelated callers
|
|
|
|
Message-side behavior must become:
|
|
|
|
- mark dirty
|
|
- enqueue reason
|
|
- invalidate host window when needed
|
|
|
|
But never:
|
|
|
|
- begin a frame
|
|
- build draw data
|
|
- present directly
|
|
|
|
### Phase B. Collapse lifecycle to one owner
|
|
|
|
Refactor current flow so that:
|
|
|
|
1. `WM_CLOSE` only means `RequestClose`
|
|
2. `WM_DESTROY` only reports native death
|
|
3. `EditorWindowLifecycleCoordinator` becomes the only place that can:
|
|
- call `Shutdown()`
|
|
- finalize managed destruction
|
|
- erase the managed window from host storage
|
|
- cascade close from the primary window
|
|
4. `WorkspaceCoordinator` stops duplicating host destroy logic
|
|
|
|
### Phase C. Split EditorShellRuntime by stage, not by helper dumping
|
|
|
|
Do not split by random utility extraction.
|
|
Split by stage ownership:
|
|
|
|
1. session sync stage
|
|
2. shell interaction stage
|
|
3. hosted panel stage
|
|
4. draw composition stage
|
|
|
|
`EditorShellRuntime` should remain as a thin orchestrating facade that wires these stages
|
|
together in a stable order.
|
|
|
|
### Phase D. Normalize panel capture ownership
|
|
|
|
As part of hosted panel coordination:
|
|
|
|
1. stop hardcoding panel capture checks directly in the runtime facade where possible
|
|
2. centralize panel capture ownership through one hosted-panel coordination boundary
|
|
3. make it explicit which hosted panels participate in pointer-stream ownership decisions
|
|
|
|
## 6. File-Level Impact
|
|
|
|
### 6.1 Existing files to shrink / re-scope
|
|
|
|
- `new_editor/app/Platform/Win32/EditorWindow.cpp`
|
|
- `new_editor/app/Platform/Win32/EditorWindow.h`
|
|
- `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp`
|
|
- `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp`
|
|
- `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h`
|
|
- `new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp`
|
|
- `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp`
|
|
- `new_editor/app/Composition/EditorShellRuntime.cpp`
|
|
- `new_editor/app/Composition/EditorShellRuntime.h`
|
|
|
|
### 6.2 Build graph
|
|
|
|
Update:
|
|
|
|
- `new_editor/CMakeLists.txt`
|
|
|
|
to register the new frame/lifecycle/runtime stage units.
|
|
|
|
## 7. Validation
|
|
|
|
### 7.1 Structural validation
|
|
|
|
After completion:
|
|
|
|
1. direct `RenderFrame(...)` call sites outside the frame driver must be gone
|
|
2. duplicate `Shutdown()/DestroyWindow()/MarkDestroyed()/erase` chains must be gone
|
|
3. `WorkspaceCoordinator` must no longer perform raw window destruction
|
|
4. `EditorShellRuntime` facade must delegate to explicit stage modules
|
|
|
|
### 7.2 Runtime validation
|
|
|
|
Must validate:
|
|
|
|
1. editor starts normally
|
|
2. main window continues rendering normally
|
|
3. resize / maximize / drag-restore no longer perform direct message-side rendering
|
|
4. detached panel windows and tool windows still open/close correctly
|
|
5. closing child windows does not close the main window
|
|
6. closing the primary window still cascades correctly
|
|
7. frame transfer requests still behave correctly during docking / detach workflows
|
|
|
|
## 8. Execution Order
|
|
|
|
1. introduce frame request model and frame driver
|
|
2. remove message-side and chrome-side direct rendering
|
|
3. introduce lifecycle coordinator
|
|
4. remove duplicate destroy/shutdown ownership from host runtime and workspace coordinator
|
|
5. split `EditorShellRuntime` into session/interaction/panel/draw stages
|
|
6. rebuild and smoke-test
|
|
7. perform structural grep validation
|
|
|
|
## 9. Success Criteria
|
|
|
|
This refactor is complete only if all of the following are true:
|
|
|
|
1. `new_editor` frame execution has a single live owner
|
|
2. `new_editor` managed window destruction has a single live owner
|
|
3. `EditorShellRuntime` is no longer a monolithic per-frame god-object
|
|
4. resize / dpi / chrome transitions no longer trigger direct rendering from message handlers
|
|
5. build and smoke validation both pass
|