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

9.7 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/** 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 weak enforcement:

  • editor/app/Composition directly includes concrete feature-panel contracts from editor/app/Features.
  • editor/app/Features also includes Composition/EditorContext.h and Composition/EditorPanelIds.h, creating a composition/feature cycle.
  • 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. EditorProjectRuntime depends on the service-owned ProjectBrowserModel, and Features/Project/ProjectPanel owns the widget-tree projection.

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/
    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:

  • 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:

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:

  • 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:

Composition -> Core/WorkspacePanels
Features    -> Core/WorkspacePanels
Features    -> Composition only where concrete panels still need EditorContext

This does not fully solve all cycles, but it removes the worst misplaced public contract and creates the landing zone for the next cut.

Phase 2: Introduce XCEditorCore

Create:

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/UtilityWindows/**
  • 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:

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.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:

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

Move utility descriptors and kinds to Core/UtilityWindows if they are shared by windowing and features. Concrete utility panel creation remains in Features.

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
  • 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:

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.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.