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

465 lines
14 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 19:16:08 +08:00
`editor/app/Host`, and app/core sources now include through explicit module
roots instead of a private `editor/app` compatibility root. The app has not
yet converged on the final directory names.
- Feature panels no longer use `Composition/EditorContext.h` directly, but
app-core tests still need to be restored before the boundary is exercised
outside the executable host.
2026-04-27 13:40:26 +08:00
- 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`.
`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.
- 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.
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`,
`app/Support`, `app/Bootstrap`, and `app/Platform/Win32`.
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. Remaining work is to finish wiring app-core tests against the
narrowed surface and continue converging host code on the target directory
shape.
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.
- 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`.
2026-04-27 13:40:26 +08:00
## Phase 2: Introduce XCEditorCore
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`
- `app/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:
```cmake
editor_app_core_tests
```
This target should link:
```text
XCEditorCore
GTest::gtest_main
```
Start with tests that should not need Win32/D3D12:
- `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`
Then either fix or remove stale test references:
- `Ports/SystemInteractionPort.h`
- `Rendering/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:
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
`app/Host` through its public usage requirements, and both `XCEditorCore`
and `XCEditor` enumerate explicit private module roots instead of using
`editor/app` as a compatibility include directory.
- Remaining work in this phase is to continue converging concrete host code on
the `app/Host/Win32` and `app/Host/D3D12` directory shape.
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
```
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.
- `Services` no longer include `Features/**`.
- Win32 and D3D12 communicate through neutral host/render contracts.
- app-core tests are wired into CMake and build without running the executable.
- `editor/AGENTS.md` describes the new target shape and directory rules.