12 KiB
Editor Core Refactor Plan
Goal
This refactor turns the current editor/app directory map from a documented
convention into a build-checked architecture.
The desired production shape is:
XCUIEditor
reusable editor UI framework:
widgets, docking, shell, workspace, menu, viewport slots, field controls
XCEditorCore
editor product core:
composition, commands, state, project and scene services, feature panels,
window/workspace core, and host-facing contracts
XCEditor
thin executable host:
main, Win32 process/window host, D3D12 runtime host, resources, startup,
final object wiring
XCEditor may keep the output name XCEngine.exe; the target name should
remain XCEditor.
Current Problem
The reusable layer is mostly healthy:
editor/include/XCEditor/**andeditor/src/**formXCUIEditor.XCUIEditoris testable and mostly free of app, Win32, and D3D12 concerns.
The app layer has a good directory vocabulary but enforcement is still incomplete:
XCEditorCoreexists, but its include surface still exposes the wholeeditor/approot to app code.- Feature panels no longer use
Composition/EditorContext.hdirectly, but the app include surface still needs to be narrowed before this boundary is build-enforced. - Win32 and D3D12 currently know about each other through concrete headers.
- App-side tests exist but are not consistently wired into CMake; some are stale and reference removed headers.
Completed boundary cuts:
- Project service ownership has moved to
editor/app/Services/Project.EditorProjectRuntimedepends on the service-ownedProjectBrowserModel, andFeatures/Project/ProjectPanelowns the widget-tree projection. - Panel IDs live under
editor/app/Core/Panels, and workspace panel runtime contracts live undereditor/app/Core/WorkspacePanels. - Shared window category, lifecycle, chrome-policy, and native-host-policy
contracts live under
editor/app/Core/Windowing. - Utility-window kinds, descriptors, and panel contracts live under
editor/app/Core/UtilityWindows; concrete Color Picker/Add Component panel creation lives undereditor/app/Features/EditorUtilityWindowRegistry.*and is injected fromApplication. - Panel-facing app services live under
editor/app/Core/Panels/EditorPanelServices.h. Workspace-panel and utility-window runtime contracts accept this service view instead ofEditorContext, so concrete feature panels no longer depend onComposition/EditorContext.h.
The root issue is not the existence of a single executable target by itself.
The root issue is that shared app contracts are stored inside concrete layer
directories, and CMake exposes the whole editor/app include root to all app
code. That makes the directory map advisory rather than enforceable.
Target Directory Shape
The refactor should converge on this shape:
editor/app/
Core/
Commands/
Panels/
State/
UtilityWindows/
Windowing/
WorkspacePanels/
Services/
Project/
Scene/
Features/
Console/
ColorPicker/
Hierarchy/
Inspector/
Project/
Scene/
Windowing/
Content/
Coordinator/
Frame/
Host/
Runtime/
Host/
Interfaces/
Win32/
D3D12/
Bootstrap/
Support/
This is a convergence target, not a requirement to move every file at once. The first cut should only move contracts that are already acting as global app interfaces.
Dependency Rules
The final direction should be:
XCEditor -> XCEditorCore -> XCUIEditor -> XCEngine
Within XCEditorCore:
Compositionmay depend onCore,Services,Windowingcontracts, andXCUIEditor.Compositionmust not include concrete feature panel headers.Featuresmay depend onCore,Services, andXCUIEditor.Servicesmust not depend onFeatures.Statemust not depend onComposition.Windowingmay depend onCore,Compositioninterfaces, andXCUIEditor, but must not include Win32 or D3D12 concrete host headers.- Host contracts may live in
CoreorHost/Interfaces. - Win32 and D3D12 concrete implementations live outside
XCEditorCoreunless they are purely abstract host contracts.
Phase 1: Fix Shared Contract Placement
Move the most obviously misplaced shared contracts first.
1. Panel IDs
Move:
editor/app/Composition/EditorPanelIds.h
to:
editor/app/Core/Panels/EditorPanelIds.h
Then update all includes to use Core/Panels/EditorPanelIds.h.
Reason: panel IDs are global app model constants, not composition-private implementation details.
2. Workspace Panel Runtime Contract
Split the existing workspace panel registry header:
Current:
editor/app/Features/EditorWorkspacePanelRegistry.h
Target:
editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h
editor/app/Features/EditorWorkspacePanelRegistry.h
The new core header owns:
EditorWorkspacePanelCursorKindEditorWorkspacePanelUpdatePhaseEditorWorkspacePanelFrameEventEditorWorkspacePanelInitializationContextEditorWorkspacePanelShutdownContextEditorWorkspacePanelUpdateContextEditorWorkspacePanelEditorWorkspacePanelRuntimeSet
The feature registry header owns only:
CreateEditorWorkspacePanelRuntimeSet()
Reason: composition should depend on the panel runtime interface, not on the
feature registry. Concrete feature construction belongs to Features.
3. Keep the First Cut Buildable
This phase should not change behavior.
Expected behavioral diff: none.
Expected dependency improvement:
Composition -> Core/WorkspacePanels
Features -> Core/WorkspacePanels
Features -> Core/Panels
This does not fully solve all cycles, but it removes the worst misplaced public contract and creates the landing zone for the next cut.
Completed follow-up:
EditorPanelServicesnow carries the panel-facing references to session, project runtime, scene runtime, command focus, color-picker state, system interaction, text measurement, and utility-window requests.EditorWorkspacePanelRuntimeSetupdates a single phase at a time; Composition owns the command-focus sync point between main panels and after-focus panels.- Concrete workspace and utility panels use
EditorPanelServicesand no longer includeComposition/EditorContext.h.
Phase 2: Introduce XCEditorCore
Create:
add_library(XCEditorCore STATIC ...)
Initial contents:
app/Core/**app/Commands/**app/Composition/**app/Features/**app/Project/**orapp/Services/Project/**app/Scene/**orapp/Services/Scene/**app/State/**app/Core/UtilityWindows/**app/Core/Windowing/**app/Windowing/**core files that do not require Win32 or D3D12- host-facing abstract interfaces
Keep in XCEditor executable:
app/main.cppapp/Bootstrap/Application.*app/Bootstrap/EditorApp.rcapp/Platform/Win32/**app/Rendering/D3D12/**- any concrete host glue that includes
windows.h
XCEditor links XCEditorCore, XCUIEditor, and concrete platform/rendering
libraries.
Important: do not hide Win32/D3D12 in XCEditorCore just to make the first
CMake edit easier. If a source file needs windows.h, it belongs in the host
side until a neutral interface exists.
Phase 3: Restore App-Core Tests
Create or restore:
editor_app_core_tests
This target should link:
XCEditorCore
GTest::gtest_main
Start with tests that should not need Win32/D3D12:
test_editor_host_command_bridge.cpptest_editor_project_runtime.cpptest_editor_shell_asset_validation.cpptest_project_browser_model.cpptest_hierarchy_scene_binding.cpptest_inspector_presentation.cpp
Then either fix or remove stale test references:
Ports/SystemInteractionPort.hRendering/Viewport/ViewportRenderTargetInternal.h
The test target is part of the architecture. If app core cannot be tested without starting the executable host, the boundary is not real.
Phase 4: Service and Feature Boundary Cleanup
After XCEditorCore exists, remove remaining service-to-feature dependencies.
Project
Completed:
Services/Project/EditorProjectRuntime.h -> Services/Project/ProjectBrowserModel.h
Features/Project/ProjectPanel.h -> Services/Project/EditorProjectRuntime.h
Current shape:
Services/Project/ProjectBrowserModel.h
Services/Project/EditorProjectRuntime.h
Features/Project/ProjectPanel.h
ProjectBrowserModel is now service-owned domain/project state. It exposes
folder, asset, and breadcrumb data, while ProjectPanel converts folders into
UIEditorTreeViewItem presentation data. Keep future project filesystem and
command-target behavior in Services/Project, not under Features/Project.
Scene
Keep scene runtime as a service layer. Scene viewport feature code may use scene services, but scene services should not depend on concrete feature UI.
Utility Windows
Completed:
UtilityWindows/EditorUtilityWindowKind.h
UtilityWindows/EditorUtilityWindowPanel.h
UtilityWindows/EditorUtilityWindowRegistry.*
-> Core/UtilityWindows/EditorUtilityWindowRuntime.h
-> Core/UtilityWindows/EditorUtilityWindowRegistry.*
-> Features/EditorUtilityWindowRegistry.*
Current shape:
Core/UtilityWindows/EditorUtilityWindowRuntime.h
Core/UtilityWindows/EditorUtilityWindowRegistry.*
Features/EditorUtilityWindowRegistry.*
Keep utility kinds, descriptors, host context, panel contract, and factory
type in Core/UtilityWindows. Keep concrete utility panel construction in
Features/EditorUtilityWindowRegistry.* and inject it from the application
composition root. Windowing may resolve descriptors from Core, but must not
include Color Picker, Add Component, or other concrete feature-panel headers.
Phase 5: Host Boundary Cleanup
Split neutral host contracts from concrete implementations.
Target:
app/Host/Interfaces/
EditorWindowHostInterfaces.h
EditorWindowRenderRuntime.h
UiTextureHost.h
ViewportRenderHost.h
SystemInteractionService.h
app/Host/Win32/
Win32SystemInteractionHost.*
EditorWindow.*
message dispatch, DPI, chrome, pointer capture
app/Host/D3D12/
D3D12EditorWindowRenderRuntime.*
D3D12 UI renderer, texture host, text system, swap chain presenter
Then replace concrete Win32/D3D12 cross-includes with a neutral surface or factory contract.
Phase 6: Documentation Update
Update editor/AGENTS.md after each completed boundary cut.
Required changes:
- production target shape becomes
XCUIEditor,XCEditorCore,XCEditor Coreowns shared app contractsFeatures/EditorWorkspacePanelRegistry.*owns concrete feature factories- app tests link
XCEditorCore - no new app code should add direct
Composition <-> Featurescycles
Validation
Run these after each phase where possible:
cmake --build build --config Debug --target XCUIEditor
cmake --build build --config Debug --target XCEditorCore
cmake --build build --config Debug --target XCEditor
cmake --build build --config Debug --target editor_ui_tests
cmake --build build --config Debug --target editor_app_core_tests
When app smoke is available:
ctest -C Debug -R xceditor_smoke --output-on-failure
If XCEditorCore does not exist yet, skip that target until Phase 2 lands.
Done Criteria
The refactor is complete when:
XCEditorCore.libexists and is linked byXCEditor.XCEditorexecutable source is limited to host startup and concrete platform/render backend wiring.Compositionno longer includes concrete feature panel headers.Servicesno longer includeFeatures/**.- Win32 and D3D12 communicate through neutral host/render contracts.
- app-core tests are wired into CMake and build without running the executable.
editor/AGENTS.mddescribes the new target shape and directory rules.