Files
XCEngine/editor/AGENTS.md

342 lines
18 KiB
Markdown

# XCUI Editor Agent Guide
This file is a working map for agents changing `editor/`. It is not a
substitute for reading the code. If this guide conflicts with the current
implementation, trust the implementation and update this guide in the same
change.
## Build Shape
- The root `CMakeLists.txt` always adds `editor/` before `tests/`.
- `editor/CMakeLists.txt` always builds `XCUIEditor`, a static library made
from `editor/include/XCEditor/**` plus `editor/src/**`.
- `XCUIEditor` outputs `XCUIEditor.lib` and is the reusable, platform-neutral
editor UI layer. It links
against `XCEngine` and is covered mainly by `tests/UI/Editor/unit`.
- `XCEditor` is built when `XCENGINE_BUILD_XCUI_EDITOR_APP=ON`; that mode
requires `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON`. The executable target
is named `XCEditor`, but its output name is `XCEngine`.
- The app target owns Win32 hosting, D3D12 window rendering, project/scene
runtime wiring, real editor panels, utility windows, and screenshots.
- The app is intentionally packaged as one executable target. Do not restore
internal app split targets such as `XCUIEditorAppLib`, `XCUIEditorAppCore`,
or `XCUIEditorHost` unless the product architecture is explicitly changed.
- Keep the CMake target named `XCEditor` so it does not collide with the engine
library target named `XCEngine`; keep the built executable output name as
`XCEngine`.
- Shared code uses C++20 and MSVC `/utf-8`; keep new files ASCII unless the
surrounding file already uses another encoding for a real reason.
## Directory Map
- `include/XCEditor/` is the public editor UI API: collections, docking,
fields, foundation, menus, panels, shell, viewport, widgets, windowing, and
workspace.
- `src/` implements that public API and may include private `src/**/Internal.h`
helpers. It should stay independent of app, Win32, D3D12, and project
runtime concerns.
- `app/Bootstrap/` contains process startup and shutdown through
`Application`.
- `app/Composition/` builds the editor shell asset, coordinates shell update
phases, and connects app state to hosted panel runtimes.
- `app/Windowing/` owns window instances, content controllers, frame transfer
requests, lifecycle, workspace synchronization, and utility-window creation.
- `app/Platform/Win32/` owns `HWND`, message dispatch, native pointer capture,
borderless chrome, DPI, placement, and Win32 system integration.
- `app/Rendering/` owns editor-window rendering hosts, D3D12 UI rendering,
built-in icons, viewport render targets, object picking, and scene viewport
passes.
- `app/Features/` contains user-facing panels and editor tools: Hierarchy,
Scene viewport, Inspector, Project, Console, Color Picker, and component UI.
- `app/Features/EditorWorkspacePanelRegistry.*` is the single composition
entry point for workspace panel runtimes. It owns the concrete workspace
panel adapters and keeps `app/Composition/**` from depending on individual
feature-panel classes.
- `app/Project/`, `app/Scene/`, and `app/State/` hold application services that
panels should use instead of owning global state themselves.
## Layering
- Keep `XCUIEditor` below the app. Public headers under `include/XCEditor`
must not include `app/**`, Win32, D3D12, or feature-panel headers.
- Keep platform specifics in `app/Platform/Win32`; keep GPU-window specifics in
`app/Rendering/D3D12`.
- Keep shell composition and widget logic data-driven. The reusable layer should
emit frames, layouts, draw data, command results, and transfer requests; the
app decides how those requests affect native windows and engine services.
- Use existing controller types for mutation:
`UIEditorWorkspaceController`, `UIEditorWindowWorkspaceController`, and
`EditorWindowSystem`.
- Use the existing service objects for app state: `EditorContext`,
`EditorProjectRuntime`, `EditorSceneRuntime`, `EditorSelectionService`,
`EditorCommandFocusService`, and utility-window request state.
## Startup Flow
1. `app/main.cpp` enters `RunXCEditor`, which creates `Application`.
2. `Application::Initialize` resolves the repo root, enables DPI awareness,
initializes runtime tracing under the executable `logs/` directory, and
installs the unhandled-exception crash trace hook.
3. `EditorContext::Initialize` builds and validates the shell asset, initializes
project and scene runtimes, resets selection/focus/tool state, builds
command and shortcut services, and marks the shell ready.
4. `Application` registers the Win32 window class, creates the Win32 system
interaction host, bootstraps `EditorWindowSystem` with the primary
workspace state, creates `EditorWindowHostRuntime`, creates the D3D12 render
runtime factory, and constructs `EditorWindowManager`.
5. `EditorWindowManager::CreateWorkspaceWindow` creates an
`EditorWindowInstance`, attaches an `EditorWorkspaceWindowContentController`,
asks the host runtime to create the native window, and initializes the
per-window render/content runtime.
6. The main loop drains up to 64 Win32 messages per tick, updates async
resource loads, reaps destroyed windows, renders all running windows, and
honors smoke-test auto-exit settings.
7. `Application::WndProc` forwards registered windows to
`EditorWindowMessageDispatcher`; unknown messages fall back to
`DefWindowProcW`.
## Frame Runtime Ownership
- `EditorContext` owns the app-level shell asset, editor session, command
routing, selection/focus services, project runtime, scene runtime, color
picker state, and pending utility-window requests.
- `EditorWindowSystem` owns the authoritative
`UIEditorWindowWorkspaceSet`. Workspace windows should derive their local
projection from this state rather than storing independent layout truth.
- `EditorWorkspaceWindowContentController` asks `EditorWindowSystem` for a live
`UIEditorWorkspaceController` every frame. A live controller writes mutations
through to the authoritative window workspace state; a copied controller is
only a preview.
- `EditorShellRuntime` owns per-window interactive runtime state: viewport host
service, built-in icons, the workspace panel runtime set, shell interaction
state/frame, splitter correction state, trace entries, draw composer,
interaction engine, hosted panel coordinator, and session coordinator.
- `EditorWindowRuntimeController` owns the content controller, render runtime,
screenshot controller, title-bar logo texture, text measurer access, DPI
scale, and frame-rate display.
- `EditorWindowInstance` is the managed host-window object. It owns lifecycle
state and bridges its native peer to the runtime controller.
- `EditorWindow` in `app/Platform/Win32/Windowing` owns the native peer
behavior: `HWND`, queued input, pointer capture, chrome interaction, resize
snapshots, cursor feedback, invalidation, and native destruction.
## Window Authority Model
- The authoritative window/workspace state is the
`UIEditorWindowWorkspaceSet` stored by `EditorWindowSystem`.
- Presentation data is derived from state through
`BuildEditorWorkspaceWindowProjection`; do not hand-maintain detached window
title, tab-strip title, primary flag, or minimum size in separate places.
- Workspace mutations that can affect multiple windows should start with
`EditorWindowSystem::BuildWorkspaceMutationController`, validate through
`ValidateWindowSet`, build a synchronization plan, apply host-window actions,
then commit the plan back to `EditorWindowSystem`.
- `EditorWindowSynchronizationPlanner` compares target workspace state against
host snapshots and emits create/update/close actions. Extend this planner and
its tests when changing workspace-window synchronization rules.
- Destroying the primary workspace window intentionally closes remaining
workspace windows. Destroying a detached workspace window reconciles the
authoritative window set and refreshes the remaining windows.
- `EditorWindowWorkspaceCoordinator::RefreshWindowPresentation` is the normal
path for refreshing projections and titles after a frame.
## Frame Transfer Requests
Frame transfer requests are the app boundary for work that cannot be committed
inside pure shell/widget code.
- `EditorWindowFrameOrchestrator` converts shell results into
`EditorWindowFrameTransferRequests`.
- Workspace requests currently include `beginGlobalTabDrag` and `detachPanel`.
These are consumed by `EditorWindowWorkspaceCoordinator`.
- Utility requests currently include `openUtilityWindow`. These are produced
from `EditorContext::RequestOpenUtilityWindow` and consumed by
`EditorUtilityWindowCoordinator`.
- Panels and shell code should request window-level work through transfer
requests or context request state, not by creating/destroying native windows
directly.
- Global tab drag uses screen coordinates, a captured owner window, an external
dock-host drop preview on the target window, and
`EditorWindowPointerCaptureOwner::GlobalTabDrag`.
## Window Categories
- Workspace windows use `EditorWorkspaceWindowContentController`. They expose
workspace, dock-host, input-feedback, title-bar, and viewport-rendering
capabilities.
- Detached workspace windows are still workspace windows. Their projection can
enable the detached title-bar tab strip and derive the window title from the
single visible panel.
- Utility windows use `EditorUtilityWindowContentController`. They host an
`EditorUtilityWindowPanel`, do not participate in workspace synchronization,
and currently do not expose dock-host or viewport capabilities.
- Utility descriptors live in `EditorUtilityWindowRegistry.cpp`. Current
utility windows are Color Picker and Add Component; both are single-instance
topmost tool windows.
## Shell, Panels, And Commands
- `EditorShellAssetBuilder.cpp` defines the product shell: panel registry,
default workspace layout, command registry, menus, toolbar buttons, shortcuts,
capture root, and status-bar definitions.
- Panel IDs are centralized in `EditorPanelIds.h`; prefer these constants over
raw string literals in app code.
- Workspace panels are adapted behind `EditorWorkspacePanelRuntimeSet`.
Composition code should ask that runtime set to initialize, prepare command
routes, update, draw, expose cursor/capture state, and collect frame events
instead of directly naming `HierarchyPanel`, `ProjectPanel`, `InspectorPanel`,
`ConsolePanel`, or `SceneViewportFeature`.
- Hosted panels receive mounted bounds and filtered input through
`UIEditorHostedPanelDispatchFrame`; concrete workspace-panel adapters resolve
their own dispatch entries from that frame.
- `EditorShellHostedPanelCoordinator` filters hosted-panel input when shell
capture owns the pointer stream, then updates `EditorWorkspacePanelRuntimeSet`.
Concrete adapters wire app services into their panels and the runtime set
syncs command focus before after-focus panels such as Console read session
state.
- `EditorHostCommandBridge` routes host commands to focused edit routes,
project asset routes, scene routes, inspector routes, workspace commands, or
the app exit handler.
- `WorkspaceEventSync` consumes generic `EditorWorkspacePanelFrameEvent`
entries from the workspace panel runtime set. Concrete hierarchy/project
event formatting lives behind the feature registry, while this sync layer
owns selection-backed session state, scene-open requests, status updates, and
runtime trace entries.
## Viewport Rendering
- Scene and Game are `ViewportShell` presentations. The Scene panel is the
active editor viewport implementation; Game is registered as a viewport panel
but is not equivalent to the runtime game view yet.
- `SceneViewportFeature` owns `SceneViewportController` and
`SceneViewportRenderService`.
- `ViewportHostService` is the per-window manager for viewport requests,
render-target allocation, retirement, fallback clearing, and dispatch to the
attached `ViewportRenderHost`.
- `SceneViewportRenderService` renders scene content through the engine
renderer and owns object-id picking state. Keep editor picking/render support
behind `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT`.
- D3D12 window rendering is behind the abstract
`Rendering::Host::EditorWindowRenderRuntime`; use the host interfaces when
adding app-level features.
## Modification Rules
- Add reusable widgets, docking/workspace behavior, shell interaction, or field
logic to `include/XCEditor/**` and `src/**`, with unit coverage in
`tests/UI/Editor/unit`.
- Add product editor behavior to `app/Features/**`. Workspace panels must
enter composition through `EditorWorkspacePanelRegistry.*` and the
`EditorWorkspacePanel` adapter interface; do not include concrete feature
panel headers from `EditorShellRuntime`, `EditorShellHostedPanelCoordinator`,
`EditorShellDrawComposer`, or `EditorShellSessionCoordinator`.
- Add new workspace panels by updating the panel ID constants, panel registry,
default workspace model, shell presentation, workspace panel runtime adapter,
and command/menu entries together.
- Add new utility windows through `EditorUtilityWindowKind`,
`EditorUtilityWindowRegistry`, an `EditorUtilityWindowPanel`, and the
context/request path. Do not model utility windows as workspace panels unless
they really dock inside the workspace.
- Add new commands in the command registry first, then menu/shortcut bindings,
host command bridge routing, and focused route tests.
- Keep workspace/window mutations transactional. Save the previous state,
validate after mutation, and roll back on invalid output, matching existing
controller patterns.
- Keep per-frame transient panel events separate from persistent app services.
Panels may emit frame events; `EditorContext` and runtime services own
persistent selection, command focus, project, and scene state.
- Release UI textures and GPU resources in `Shutdown` paths. D3D12 shutdown
should wait for GPU idle before releasing window resources.
- Do not write new manual-validation screenshots under the source tree.
Shared manual-validation hosts should write captures under the active build
directory.
## Current Architecture Debt
- `XCEditor` is the intentional single executable app target. App-layer
boundaries are maintained through directory ownership, namespaces, app/service
interfaces, focused reusable-layer tests, manual-validation hosts, and smoke
diagnostics, not through internal app static-library targets.
- Legacy app-split CMake targets have been removed. Do not reference
`XCUIEditorAppLib`, `XCUIEditorAppCore`, or `XCUIEditorHost` in new build
logic or tests.
- Existing source-tree capture history is present under some manual-validation
scenarios, but new captures should go to the build output directory.
- Runtime trace files are the main app smoke diagnostic. Avoid replacing
trace-backed diagnostics with UI-only feedback that tests cannot observe.
## Validation
- Build shared editor UI after reusable-layer changes:
`cmake --build <build-dir> --config Debug --target XCUIEditor`.
- Build the app after startup, windowing, rendering, feature-panel, or resource
changes:
`cmake --build <build-dir> --config Debug --target XCEditor`.
- Build reusable editor unit tests:
`cmake --build <build-dir> --config Debug --target editor_ui_tests`.
- Build window synchronization tests:
`cmake --build <build-dir> --config Debug --target editor_windowing_phase1_tests`.
- Build aggregate editor unit targets:
`cmake --build <build-dir> --config Debug --target editor_ui_unit_tests`.
- Build app smoke support:
`cmake --build <build-dir> --config Debug --target editor_ui_smoke_targets`.
- Run app smoke through CTest when `XCEditor` is enabled:
`ctest -C Debug -R xceditor_smoke --output-on-failure`.
- Useful environment flags:
`XCUIEDITOR_VERBOSE_TRACE=1`, `XCUI_AUTO_CAPTURE_ON_STARTUP=1`,
`XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_DURATION_SECONDS=<n>`,
and `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=<n>`.
## Recommended Reading
- `editor/CMakeLists.txt`
- `editor/app/Bootstrap/Application.cpp`
- `editor/app/Composition/EditorContext.cpp`
- `editor/app/Composition/EditorShellRuntime.cpp`
- `editor/app/Composition/EditorShellAssetBuilder.cpp`
- `editor/app/Windowing/EditorWindowManager.cpp`
- `editor/app/Windowing/EditorWindowInstance.cpp`
- `editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp`
- `editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp`
- `editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp`
- `editor/include/XCEditor/Windowing/System/EditorWindowSystem.h`
- `editor/src/Windowing/System/EditorWindowSystem.cpp`
- `editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h`
- `editor/src/Workspace/UIEditorWorkspaceController.cpp`
- `editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceController.h`
- `editor/src/Workspace/UIEditorWindowWorkspaceController.cpp`
- `editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp`
- `editor/app/Rendering/Host/EditorWindowRenderRuntime.h`
- `editor/app/Rendering/Viewport/ViewportHostService.h`
- `tests/UI/Editor/unit/CMakeLists.txt`
- `tests/UI/Editor/smoke/CMakeLists.txt`
- `tests/UI/Editor/manual_validation/README.md`
## Recent Cuts
This section is intentionally important. Keep it as the running ledger of
architecture cuts that have already been made, so future agents do not
re-litigate or accidentally undo them. Add concrete entries here whenever a
change closes a boundary, removes an obsolete path, or establishes a new
ownership rule.
- Primary workspace state is bootstrapped into `EditorWindowSystem` before
native window creation.
- Workspace content now pulls a live authoritative workspace controller every
frame instead of trusting a stale local copy.
- Panel detach and cross-window tab drag are frame transfer requests handled by
`EditorWindowWorkspaceCoordinator`, then synchronized through
`EditorWindowSynchronizationPlanner`.
- Utility windows are app content controllers backed by
`EditorUtilityWindowPanel`; they are not workspace windows.
- Viewport rendering is routed through `ViewportHostService` and the per-window
render runtime rather than directly from shell composition.
- The old app-split target residue was removed from CMake and tests. The app
remains a single `XCEditor` target with output name `XCEngine`, and
manual-validation hosts use the shared Core UI validation host instead of an
editor app host target.
- Workspace panel runtimes are now behind `EditorWorkspacePanelRuntimeSet` and
`EditorWorkspacePanelRegistry.*`. `app/Composition/**` no longer directly
owns or dispatches concrete Console, Hierarchy, Inspector, Project, or Scene
panel classes.