8.4 KiB
NewEditor Frame/Lifecycle Runtime Refactor Plan
Date: 2026-04-22
1. Objective
This plan targets the highest-priority remaining architecture debt in new_editor:
- frame execution still has multiple live owners
- window destruction still has multiple live owners
EditorShellRuntimeis still a per-frame god-object
The goal is not to split files mechanically. The goal is to collapse ownership so that:
- only one module can drive a frame
- only one module can finalize window death
EditorShellRuntimebecomes 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_PAINTWM_SIZEWM_DPICHANGEDWM_EXITSIZEMOVE- borderless chrome transition logic
This means rendering is still driven by both:
- the normal frame loop
- 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:
- who requests close
- who observes native window destruction
- who shuts runtime down
- 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:
- shell definition build
- workspace/session sync
- dock host interaction update
- viewport request/update
- hosted panel input routing
- hosted panel update
- 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:
- only one frame driver can execute
BeginFrame -> UpdateAndAppend -> Present - Win32 message dispatch only records frame requests / dirty reasons, never renders directly
- borderless chrome logic can request a new frame, but cannot render directly
- only one lifecycle coordinator can finalize managed window destruction
WorkspaceCoordinatorcan request a window-set mutation, but cannot directly destroy windowsEditorShellRuntimebecomes a thin composition facade over smaller stage modules
4. New Modules
4.1 Frame ownership
Add:
new_editor/app/Platform/Win32/EditorWindowFrameDriver.hnew_editor/app/Platform/Win32/EditorWindowFrameDriver.cppnew_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.hnew_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.hnew_editor/app/Composition/EditorShellSessionCoordinator.cppnew_editor/app/Composition/EditorShellInteractionEngine.hnew_editor/app/Composition/EditorShellInteractionEngine.cppnew_editor/app/Composition/EditorShellHostedPanelCoordinator.hnew_editor/app/Composition/EditorShellHostedPanelCoordinator.cppnew_editor/app/Composition/EditorShellDrawComposer.hnew_editor/app/Composition/EditorShellDrawComposer.cpp
Responsibilities:
SessionCoordinator: context/workspace/session/command-focus/status synchronizationInteractionEngine: shell definition, dock host interaction, shell frame/state updateHostedPanelCoordinator: hosted panel input filtering, panel update, panel capture ownershipDrawComposer: 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:
EditorWindowHostRuntimerequests frame execution throughEditorWindowFrameDriverEditorWindowMessageDispatcherno longer callsRenderFrame(...)directlyEditorWindowChromeControllerno longer callsRenderFrame(...)directlyEditorWindowno 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:
WM_CLOSEonly meansRequestCloseWM_DESTROYonly reports native deathEditorWindowLifecycleCoordinatorbecomes the only place that can:- call
Shutdown() - finalize managed destruction
- erase the managed window from host storage
- cascade close from the primary window
- call
WorkspaceCoordinatorstops 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:
- session sync stage
- shell interaction stage
- hosted panel stage
- 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:
- stop hardcoding panel capture checks directly in the runtime facade where possible
- centralize panel capture ownership through one hosted-panel coordination boundary
- 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.cppnew_editor/app/Platform/Win32/EditorWindow.hnew_editor/app/Platform/Win32/EditorWindowChromeController.cppnew_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cppnew_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.hnew_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cppnew_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cppnew_editor/app/Composition/EditorShellRuntime.cppnew_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:
- direct
RenderFrame(...)call sites outside the frame driver must be gone - duplicate
Shutdown()/DestroyWindow()/MarkDestroyed()/erasechains must be gone WorkspaceCoordinatormust no longer perform raw window destructionEditorShellRuntimefacade must delegate to explicit stage modules
7.2 Runtime validation
Must validate:
- editor starts normally
- main window continues rendering normally
- resize / maximize / drag-restore no longer perform direct message-side rendering
- detached panel windows and tool windows still open/close correctly
- closing child windows does not close the main window
- closing the primary window still cascades correctly
- frame transfer requests still behave correctly during docking / detach workflows
8. Execution Order
- introduce frame request model and frame driver
- remove message-side and chrome-side direct rendering
- introduce lifecycle coordinator
- remove duplicate destroy/shutdown ownership from host runtime and workspace coordinator
- split
EditorShellRuntimeinto session/interaction/panel/draw stages - rebuild and smoke-test
- perform structural grep validation
9. Success Criteria
This refactor is complete only if all of the following are true:
new_editorframe execution has a single live ownernew_editormanaged window destruction has a single live ownerEditorShellRuntimeis no longer a monolithic per-frame god-object- resize / dpi / chrome transitions no longer trigger direct rendering from message handlers
- build and smoke validation both pass