20 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:
XCEditorCorenow limits its public app include surface toeditor/app/Core,editor/app/Commands,editor/app/State, andeditor/app/Host/Interfaces, and app/core sources now include through explicit module roots instead of a privateeditor/appcompatibility root. Concrete host code has converged undereditor/app/Host/Win32andeditor/app/Host/D3D12.- Native editor resources now cross the app-core boundary through a neutral
EditorHostResourceServicecontract undereditor/app/Host/Interfaces.XCEditorCoreno longer consumesapp/Bootstrapor Win32 resource helper code to load built-in PNGs, title-bar branding, or executable-relative capture output paths. - Feature panels no longer use
Composition/EditorContext.hdirectly. The app-core and app feature/viewport test targets now exerciseXCEditorCoreoutside the executable host. - Windowing now consumes composition through neutral contracts under
editor/app/Core/Windowing:EditorFrameServicesfor per-frame composition/state access andEditorWorkspaceShellRuntimefor per-window shell runtime behavior.EditorContextandEditorShellRuntimeremain the concrete composition implementations, injected fromApplication.
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. - Win32 now hands render startup a neutral
Rendering::Host::EditorWindowRenderRuntimeSurfacevalue, and D3D12 no longer includesPlatform/Win32/**editor surface headers to obtainHWND. - Neutral host-facing contracts now live under
editor/app/Host/Interfaces, including editor window host interfaces, render-runtime contracts, texture/viewport host contracts, pointer-capture contracts, and system interaction service interfaces. - Concrete Win32 and D3D12 host implementations now live under
editor/app/Host/Win32andeditor/app/Host/D3D12. - Shared window screen geometry, chrome metrics, and frame transfer requests
now live under
editor/app/Core/Windowing, andXCEditorCoreno longer exports the wholeeditor/approot as a public include directory. app/Windowing/**no longer includes concreteComposition/EditorContext.horComposition/EditorShellRuntime.h. Instead it consumesEditorFrameServicesandEditorWorkspaceShellRuntimefromeditor/app/Core/Windowing.- The private
editor/appcompatibility include root is gone fromXCEditorCoreandXCEditor. App implementation files now include through explicit module roots such asapp/Composition,app/Features,app/Windowing,app/Rendering,app/Scene,app/Services,app/Support,app/Host/Interfaces,app/Host/Win32, andapp/Host/D3D12; only executable-host code consumesapp/Bootstrap. editor_app_core_testsnow linksXCEditorCoredirectly and uses explicit app module include roots. Its initial suite covers host command routing, project runtime, shell asset validation, project browser model, hierarchy scene binding, and inspector presentation.editor_app_feature_testsnow linksXCEditorCoredirectly and uses explicit app module include roots. It restores project panel, scene viewport, viewport render-plan, viewport object-id picker, and app input-routing tests without widening the include surface.XCEditorCoreis now controlled byXCENGINE_BUILD_XCUI_EDITOR_COREinstead of the executable-host switch.XCENGINE_BUILD_XCUI_EDITOR_APP=OFFcan still build the product core and the app-facing test targets when renderer editor support is enabled.- The old Win32 tab-drag-drop target test now covers the current reusable
XCEditor/Docking/UIEditorDockHostTransfer.hAPI througheditor_windowing_phase1_tests.
The root issue is not the existence of a single executable target by itself.
The root issue was that shared app contracts were stored inside concrete layer
directories, and CMake exposed the whole editor/app include root through
XCEditorCore usage requirements. The public include surface has now been
narrowed, and internal source compatibility with the private app root has now
been removed. App-core and app feature/viewport tests now exercise the
narrowed surface without using the whole editor/app include root.
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.- The current composition interfaces for windowing are
Core/Windowing/EditorFrameServices.handCore/Windowing/EditorWorkspaceShellRuntime.h. - 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. app/Windowing/**no longer accepts or callsEditorWorkspacePanelRuntimeSetFactory.ApplicationcomposesCreateEditorWorkspacePanelRuntimeSet()withCreateEditorWorkspaceShellRuntime(...), then injects a zero-argumentEditorWorkspaceShellRuntimeFactoryinto windowing.
Phase 2: Introduce XCEditorCore
Status: completed and build-graph enforced.
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/Host/Win32/**app/Host/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.
Completed target-graph cut:
XCENGINE_BUILD_XCUI_EDITOR_COREcontrolsXCEditorCoreand defaults toON.XCENGINE_BUILD_XCUI_EDITOR_APPcontrols only the concrete executable host targetXCEditor.XCEditorrequiresXCENGINE_BUILD_XCUI_EDITOR_CORE=ON.- When either the core library or executable host is enabled, the root build
defaults
XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ONbecause editor core owns viewport services that link renderer editor support. - The host-off validation shape is:
-DXCENGINE_BUILD_XCUI_EDITOR_APP=OFF -DXCENGINE_BUILD_XCUI_EDITOR_CORE=ON -DXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON.
Phase 3: Restore App-Core And App Feature Tests
Status: completed for the initial app-core and app feature/viewport suites.
Created:
editor_app_core_tests
editor_app_feature_tests
This target links:
XCEditorCore
GTest::gtest_main
The initial suite contains 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
The restored app feature/viewport suite contains:
test_editor_window_input_routing.cpptest_project_panel.cpptest_scene_viewport_render_plan.cpptest_scene_viewport_runtime.cpptest_viewport_object_id_picker.cpp
Completed stale-reference cleanup:
Ports/SystemInteractionPort.htest use was replaced withSystemInteractionService.h.Rendering/Viewport/ViewportRenderTargetInternal.htest use was replaced with currentViewportRenderTargetsandViewportRenderTargetUtilsheaders.app/Platform/Win32/WindowManager/TabDragDropTarget.hwas not restored. The test now coversResolveUIEditorDockHostTabDropTargetfrom the reusableXCEditor/Docking/UIEditorDockHostTransfer.hAPI througheditor_windowing_phase1_tests.
These test targets are part of the architecture. If app core and app feature/viewport behavior 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.
Completed cuts:
- The old
Win32EditorWindowRenderRuntimeSurfaceconcrete adapter was removed. EditorWindowRenderRuntimeSurfaceis now a value contract inHost/Interfaces/EditorWindowRenderRuntime.h.- Win32 fills that neutral contract during native surface capture; D3D12 reads the native handle from the contract instead of including a Win32 editor windowing header.
- Neutral host-facing contracts now live under
app/Host/Interfaces/. EditorWindowTransferRequests, window screen geometry, and title-bar chrome metrics now live underapp/Core/Windowing/.XCEditorCorenow exposes onlyapp/Core,app/Commands,app/State, andapp/Host/Interfacesthrough its public usage requirements, and bothXCEditorCoreandXCEditorenumerate explicit module roots instead of usingeditor/appas a compatibility include directory.- Concrete Win32 and D3D12 host implementations now live under
app/Host/Win32andapp/Host/D3D12;XCEditorconsumes those roots privately, whileXCEditorCoreonly seesapp/Host/Interfaces. - Native PNG resource lookup and executable-directory discovery now go through
Host/Interfaces/EditorHostResourceService.h. The concreteHost/Win32/Resources/Win32EditorResourceService.*ownsEditorResources.h, Win32 resource APIs, and the mapping from product resource IDs to built-in editor icon requests, soXCEditorCoreno longer needsapp/Bootstrapas a private include root for resource loading. - Windowing now depends on composition through
Core/Windowing/EditorFrameServices.handCore/Windowing/EditorWorkspaceShellRuntime.h.Applicationinjects the concreteEditorShellRuntimefactory, andEditorContextremains the concreteEditorFrameServicesimplementation. - The shell-runtime construction hook visible to
app/Windowing/**is now a zero-argumentEditorWorkspaceShellRuntimeFactory. Workspace panel runtime set construction stays inApplication, so windowing no longer seesEditorWorkspacePanelRuntimeSetFactory.
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
cmake --build build --config Debug --target editor_app_feature_tests
cmake --build build --config Debug --target editor_windowing_phase1_tests
To prove the product core boundary without the executable host, configure a separate build directory with:
cmake -S . -B build/editor_core_hostoff -DXCENGINE_BUILD_XCUI_EDITOR_APP=OFF -DXCENGINE_BUILD_XCUI_EDITOR_CORE=ON -DXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON
cmake --build build/editor_core_hostoff --config Debug --target XCEditorCore
cmake --build build/editor_core_hostoff --config Debug --target editor_app_core_tests
cmake --build build/editor_core_hostoff --config Debug --target editor_app_feature_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.Windowingno longer includes concrete composition runtime/state headers; it depends onCore/Windowingcomposition interfaces instead.Servicesno longer includeFeatures/**.- Win32 and D3D12 communicate through neutral host/render contracts.
- app-core and app feature/viewport tests are wired into CMake and build without running the executable.
editor/AGENTS.mddescribes the new target shape and directory rules.