Files
XCEngine/docs/plan/editor-core-refactor-plan.md

568 lines
20 KiB
Markdown
Raw Normal View History

2026-04-27 13:40:26 +08:00
# 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:
```text
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/**` and `editor/src/**` form `XCUIEditor`.
- `XCUIEditor` is testable and mostly free of app, Win32, and D3D12 concerns.
The app layer has a good directory vocabulary but enforcement is still
incomplete:
2026-04-27 13:40:26 +08:00
- `XCEditorCore` now limits its public app include surface to
`editor/app/Core`, `editor/app/Commands`, `editor/app/State`, and
2026-04-27 22:21:40 +08:00
`editor/app/Host/Interfaces`, and app/core sources now include through
explicit module roots instead of a private `editor/app` compatibility root.
Concrete host code has converged under `editor/app/Host/Win32` and
`editor/app/Host/D3D12`.
2026-04-27 23:18:04 +08:00
- Native editor resources now cross the app-core boundary through a neutral
`EditorHostResourceService` contract under `editor/app/Host/Interfaces`.
`XCEditorCore` no longer consumes `app/Bootstrap` or 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.h` directly. The
app-core and app feature/viewport test targets now exercise `XCEditorCore`
outside the executable host.
- Windowing now consumes composition through neutral contracts under
`editor/app/Core/Windowing`: `EditorFrameServices` for per-frame
composition/state access and `EditorWorkspaceShellRuntime` for per-window
shell runtime behavior. `EditorContext` and `EditorShellRuntime` remain the
concrete composition implementations, injected from `Application`.
2026-04-27 13:40:26 +08:00
Completed boundary cuts:
- Project service ownership has moved to `editor/app/Services/Project`.
`EditorProjectRuntime` depends on the service-owned `ProjectBrowserModel`,
and `Features/Project/ProjectPanel` owns the widget-tree projection.
- Panel IDs live under `editor/app/Core/Panels`, and workspace panel runtime
contracts live under `editor/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 under `editor/app/Features/EditorUtilityWindowRegistry.*` and
is injected from `Application`.
- Panel-facing app services live under
`editor/app/Core/Panels/EditorPanelServices.h`. Workspace-panel and
utility-window runtime contracts accept this service view instead of
`EditorContext`, so concrete feature panels no longer depend on
`Composition/EditorContext.h`.
- Win32 now hands render startup a neutral
`Rendering::Host::EditorWindowRenderRuntimeSurface` value, and D3D12 no
longer includes `Platform/Win32/**` editor surface headers to obtain `HWND`.
- 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.
2026-04-27 22:21:40 +08:00
- Concrete Win32 and D3D12 host implementations now live under
`editor/app/Host/Win32` and `editor/app/Host/D3D12`.
- Shared window screen geometry, chrome metrics, and frame transfer requests
now live under `editor/app/Core/Windowing`, and
`XCEditorCore` no longer exports the whole `editor/app` root as a public
include directory.
- `app/Windowing/**` no longer includes concrete
`Composition/EditorContext.h` or `Composition/EditorShellRuntime.h`.
Instead it consumes `EditorFrameServices` and
`EditorWorkspaceShellRuntime` from `editor/app/Core/Windowing`.
2026-04-27 19:16:08 +08:00
- The private `editor/app` compatibility include root is gone from
`XCEditorCore` and `XCEditor`. App implementation files now include through
explicit module roots such as `app/Composition`, `app/Features`,
`app/Windowing`, `app/Rendering`, `app/Scene`, `app/Services`,
2026-04-27 23:18:04 +08:00
`app/Support`, `app/Host/Interfaces`, `app/Host/Win32`, and
`app/Host/D3D12`; only executable-host code consumes `app/Bootstrap`.
2026-04-27 19:47:02 +08:00
- `editor_app_core_tests` now links `XCEditorCore` directly 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_tests` now links `XCEditorCore` directly 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.
- `XCEditorCore` is now controlled by `XCENGINE_BUILD_XCUI_EDITOR_CORE`
instead of the executable-host switch. `XCENGINE_BUILD_XCUI_EDITOR_APP=OFF`
can 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.h` API through
`editor_windowing_phase1_tests`.
2026-04-27 13:40:26 +08:00
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
2026-04-27 19:16:08 +08:00
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.
2026-04-27 13:40:26 +08:00
## Target Directory Shape
The refactor should converge on this shape:
```text
editor/app/
Core/
Commands/
Panels/
State/
UtilityWindows/
Windowing/
2026-04-27 13:40:26 +08:00
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:
```text
XCEditor -> XCEditorCore -> XCUIEditor -> XCEngine
```
Within `XCEditorCore`:
- `Composition` may depend on `Core`, `Services`, `Windowing` contracts, and
`XCUIEditor`.
- `Composition` must not include concrete feature panel headers.
- `Features` may depend on `Core`, `Services`, and `XCUIEditor`.
- `Services` must not depend on `Features`.
- `State` must not depend on `Composition`.
- `Windowing` may depend on `Core`, `Composition` interfaces, and
`XCUIEditor`, but must not include Win32 or D3D12 concrete host headers.
- The current composition interfaces for windowing are
`Core/Windowing/EditorFrameServices.h` and
`Core/Windowing/EditorWorkspaceShellRuntime.h`.
2026-04-27 13:40:26 +08:00
- Host contracts may live in `Core` or `Host/Interfaces`.
- Win32 and D3D12 concrete implementations live outside `XCEditorCore` unless
they are purely abstract host contracts.
## Phase 1: Fix Shared Contract Placement
Move the most obviously misplaced shared contracts first.
### 1. Panel IDs
Move:
```text
editor/app/Composition/EditorPanelIds.h
```
to:
```text
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:
```text
editor/app/Features/EditorWorkspacePanelRegistry.h
```
Target:
```text
editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h
editor/app/Features/EditorWorkspacePanelRegistry.h
```
The new core header owns:
- `EditorWorkspacePanelCursorKind`
- `EditorWorkspacePanelUpdatePhase`
- `EditorWorkspacePanelFrameEvent`
- `EditorWorkspacePanelInitializationContext`
- `EditorWorkspacePanelShutdownContext`
- `EditorWorkspacePanelUpdateContext`
- `EditorWorkspacePanel`
- `EditorWorkspacePanelRuntimeSet`
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:
```text
Composition -> Core/WorkspacePanels
Features -> Core/WorkspacePanels
Features -> Core/Panels
2026-04-27 13:40:26 +08:00
```
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:
- `EditorPanelServices` now carries the panel-facing references to session,
project runtime, scene runtime, command focus, color-picker state, system
interaction, text measurement, and utility-window requests.
- `EditorWorkspacePanelRuntimeSet` updates 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 `EditorPanelServices` and no longer
include `Composition/EditorContext.h`.
- `app/Windowing/**` no longer accepts or calls
`EditorWorkspacePanelRuntimeSetFactory`. `Application` composes
`CreateEditorWorkspacePanelRuntimeSet()` with
`CreateEditorWorkspaceShellRuntime(...)`, then injects a zero-argument
`EditorWorkspaceShellRuntimeFactory` into windowing.
2026-04-27 13:40:26 +08:00
## Phase 2: Introduce XCEditorCore
Status: completed and build-graph enforced.
2026-04-27 13:40:26 +08:00
Create:
```cmake
add_library(XCEditorCore STATIC ...)
```
Initial contents:
- `app/Core/**`
- `app/Commands/**`
- `app/Composition/**`
- `app/Features/**`
- `app/Project/**` or `app/Services/Project/**`
- `app/Scene/**` or `app/Services/Scene/**`
- `app/State/**`
- `app/Core/UtilityWindows/**`
- `app/Core/Windowing/**`
2026-04-27 13:40:26 +08:00
- `app/Windowing/**` core files that do not require Win32 or D3D12
- host-facing abstract interfaces
Keep in `XCEditor` executable:
- `app/main.cpp`
- `app/Bootstrap/Application.*`
- `app/Bootstrap/EditorApp.rc`
2026-04-27 22:21:40 +08:00
- `app/Host/Win32/**`
- `app/Host/D3D12/**`
2026-04-27 13:40:26 +08:00
- 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_CORE` controls `XCEditorCore` and defaults to
`ON`.
- `XCENGINE_BUILD_XCUI_EDITOR_APP` controls only the concrete executable host
target `XCEditor`.
- `XCEditor` requires `XCENGINE_BUILD_XCUI_EDITOR_CORE=ON`.
- When either the core library or executable host is enabled, the root build
defaults `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON` because 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
2026-04-27 13:40:26 +08:00
Status: completed for the initial app-core and app feature/viewport suites.
2026-04-27 19:47:02 +08:00
Created:
2026-04-27 13:40:26 +08:00
```cmake
editor_app_core_tests
editor_app_feature_tests
2026-04-27 13:40:26 +08:00
```
2026-04-27 19:47:02 +08:00
This target links:
2026-04-27 13:40:26 +08:00
```text
XCEditorCore
GTest::gtest_main
```
2026-04-27 19:47:02 +08:00
The initial suite contains tests that should not need Win32/D3D12:
2026-04-27 13:40:26 +08:00
- `test_editor_host_command_bridge.cpp`
- `test_editor_project_runtime.cpp`
- `test_editor_shell_asset_validation.cpp`
- `test_project_browser_model.cpp`
- `test_hierarchy_scene_binding.cpp`
- `test_inspector_presentation.cpp`
The restored app feature/viewport suite contains:
2026-04-27 13:40:26 +08:00
- `test_editor_window_input_routing.cpp`
- `test_project_panel.cpp`
- `test_scene_viewport_render_plan.cpp`
- `test_scene_viewport_runtime.cpp`
- `test_viewport_object_id_picker.cpp`
2026-04-27 13:40:26 +08:00
Completed stale-reference cleanup:
- `Ports/SystemInteractionPort.h` test use was replaced with
`SystemInteractionService.h`.
- `Rendering/Viewport/ViewportRenderTargetInternal.h` test use was replaced
with current `ViewportRenderTargets` and `ViewportRenderTargetUtils`
headers.
- `app/Platform/Win32/WindowManager/TabDragDropTarget.h` was not restored.
The test now covers `ResolveUIEditorDockHostTabDropTarget` from the
reusable `XCEditor/Docking/UIEditorDockHostTransfer.h` API through
`editor_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.
2026-04-27 13:40:26 +08:00
## Phase 4: Service and Feature Boundary Cleanup
After `XCEditorCore` exists, remove remaining service-to-feature dependencies.
### Project
Completed:
2026-04-27 13:40:26 +08:00
```text
Services/Project/EditorProjectRuntime.h -> Services/Project/ProjectBrowserModel.h
Features/Project/ProjectPanel.h -> Services/Project/EditorProjectRuntime.h
2026-04-27 13:40:26 +08:00
```
Current shape:
2026-04-27 13:40:26 +08:00
```text
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`.
2026-04-27 13:40:26 +08:00
### 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:
```text
UtilityWindows/EditorUtilityWindowKind.h
UtilityWindows/EditorUtilityWindowPanel.h
UtilityWindows/EditorUtilityWindowRegistry.*
-> Core/UtilityWindows/EditorUtilityWindowRuntime.h
-> Core/UtilityWindows/EditorUtilityWindowRegistry.*
-> Features/EditorUtilityWindowRegistry.*
```
Current shape:
```text
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.
2026-04-27 13:40:26 +08:00
## Phase 5: Host Boundary Cleanup
Split neutral host contracts from concrete implementations.
Target:
```text
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 `Win32EditorWindowRenderRuntimeSurface` concrete adapter was removed.
- `EditorWindowRenderRuntimeSurface` is now a value contract in
`Host/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 under `app/Core/Windowing/`.
2026-04-27 19:16:08 +08:00
- `XCEditorCore` now exposes only `app/Core`, `app/Commands`, `app/State`, and
2026-04-27 22:21:40 +08:00
`app/Host/Interfaces` through its public usage requirements, and both
`XCEditorCore` and `XCEditor` enumerate explicit module roots instead of
using `editor/app` as a compatibility include directory.
- Concrete Win32 and D3D12 host implementations now live under
`app/Host/Win32` and `app/Host/D3D12`; `XCEditor` consumes those roots
privately, while `XCEditorCore` only sees `app/Host/Interfaces`.
2026-04-27 23:18:04 +08:00
- Native PNG resource lookup and executable-directory discovery now go through
`Host/Interfaces/EditorHostResourceService.h`. The concrete
`Host/Win32/Resources/Win32EditorResourceService.*` owns `EditorResources.h`,
Win32 resource APIs, and the mapping from product resource IDs to built-in
editor icon requests, so `XCEditorCore` no longer needs `app/Bootstrap` as a
private include root for resource loading.
- Windowing now depends on composition through
`Core/Windowing/EditorFrameServices.h` and
`Core/Windowing/EditorWorkspaceShellRuntime.h`. `Application` injects the
concrete `EditorShellRuntime` factory, and `EditorContext` remains the
concrete `EditorFrameServices` implementation.
- The shell-runtime construction hook visible to `app/Windowing/**` is now a
zero-argument `EditorWorkspaceShellRuntimeFactory`. Workspace panel runtime
set construction stays in `Application`, so windowing no longer sees
`EditorWorkspacePanelRuntimeSetFactory`.
2026-04-27 13:40:26 +08:00
## Phase 6: Documentation Update
Update `editor/AGENTS.md` after each completed boundary cut.
Required changes:
- production target shape becomes `XCUIEditor`, `XCEditorCore`, `XCEditor`
- `Core` owns shared app contracts
- `Features/EditorWorkspacePanelRegistry.*` owns concrete feature factories
- app tests link `XCEditorCore`
- no new app code should add direct `Composition <-> Features` cycles
## Validation
Run these after each phase where possible:
```powershell
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
2026-04-27 13:40:26 +08:00
```
To prove the product core boundary without the executable host, configure a
separate build directory with:
```powershell
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
```
2026-04-27 13:40:26 +08:00
When app smoke is available:
```powershell
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.lib` exists and is linked by `XCEditor`.
- `XCEditor` executable source is limited to host startup and concrete
platform/render backend wiring.
- `Composition` no longer includes concrete feature panel headers.
- `Windowing` no longer includes concrete composition runtime/state headers;
it depends on `Core/Windowing` composition interfaces instead.
2026-04-27 13:40:26 +08:00
- `Services` no longer include `Features/**`.
- 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.
2026-04-27 13:40:26 +08:00
- `editor/AGENTS.md` describes the new target shape and directory rules.