diff --git a/editor/AGENTS.md b/editor/AGENTS.md index 5bccad38..a380fb43 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -15,6 +15,8 @@ The primary product line is still runtime/product loop closure: - keep play mode transactional: `EditorRuntimeCoordinator` must enter play through `EditorSceneRuntime::BeginPlaySession`, and `RuntimeLoop` must run the play-session runtime scene rather than the editable document scene +- drive `EditorRuntimeCoordinator` from the app frame pump exactly once per + outer frame; do not tick runtime from per-window shell/content update paths - bind a real script assembly builder behind `scripts.*`; until then the coordinator must keep the command honestly disabled - keep `Game`, `Scene`, `Inspector`, `Selection`, and `Console` coherent across @@ -62,14 +64,18 @@ Rules: - `EditorHostCommandBridge` delegates `file.*`, `run.*`, and `scripts.*` to a bound runtime owner. If no owner is bound, it must continue exposing honest disabled messages. -- `EditorSceneRuntime` owns raw scene editing behavior and document-adjacent - scene mutations. Scene viewport private state now lives in +- `EditorSceneRuntime` owns raw scene editing behavior and scene mutations. It + may return transient startup/open/new results, but it must not own the live + scene document state. Scene viewport private state now lives in `SceneViewportSession`, owned by the scene viewport feature/panel instance, not by `EditorContext`, `EditorFrameServices`, or the shared scene runtime. `EditorRuntimeCoordinator` owns scene document state: current path/name, dirty flag, new/open/save routing, and runtime-mode transitions. +- `EditorRuntimeCoordinator` time advancement is app-owned. It must tick once + per outer application frame before window rendering, not once per workspace + window or once per shell update. - `ProjectPanel` may identify an openable scene asset, but scene document loading - must go through `EditorFrameServices::RequestOpenSceneAsset` and the + must go through the bound typed scene-open request callback and the coordinator. Do not reintroduce `ProjectRuntime` pending-open queues. - `scripts.rebuild` has a bound coordinator owner, but no in-process script assembly builder is currently wired. Keep it disabled and explicit until that @@ -81,7 +87,8 @@ Rules: stay on the window/content and shell-orchestration seam only. It must not be passed through the workspace-panel seam or the utility-window panel seam as a generic dependency source; workspace panels and utility panels should receive - explicit panel-local bindings or typed callbacks instead + explicit panel-local bindings or typed callbacks instead. Runtime time-step + ownership does not belong on this interface. ## Working Rules @@ -118,6 +125,10 @@ Rules: dependency bundle - no `EditorFrameServices` tunneling into workspace-panel update/prepare paths - no `EditorFrameServices` tunneling into utility-window panel update paths +- no per-window or per-shell runtime ticking path; runtime must not advance once + per workspace window +- no new live scene document metadata on `EditorSceneRuntime`; coordinator + remains the single owner for current path/name/dirty state - no new path where Scene viewport render requests or camera/tool state are recomputed from `EditorContext` instead of the owning `SceneViewportSession` - no scene-open side channel in `EditorProjectRuntime`; project UI must call the diff --git a/editor/app/Composition/EditorContext.cpp b/editor/app/Composition/EditorContext.cpp index 399edf4e..b412ddde 100644 --- a/editor/app/Composition/EditorContext.cpp +++ b/editor/app/Composition/EditorContext.cpp @@ -74,7 +74,9 @@ bool EditorContext::Initialize( m_projectRuntime.BindSelectionService(&m_selectionService); m_sceneRuntime.SetBackend(sceneBackendFactory.CreateSceneBackend()); AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize begin"); - if (!m_sceneRuntime.Initialize(m_session.projectRoot)) { + const EditorStartupSceneResult startupScene = + m_sceneRuntime.Initialize(m_session.projectRoot); + if (!startupScene.ready) { m_validationMessage = "Editor scene runtime failed to initialize."; AppendUIEditorRuntimeTrace("startup", m_validationMessage); return false; @@ -85,7 +87,8 @@ bool EditorContext::Initialize( m_session, m_sceneRuntime, m_projectRuntime, - runtimePaths); + runtimePaths, + startupScene); ResetEditorColorPickerToolState(m_colorPickerToolState); ResetEditorUtilityWindowRequestState(m_utilityWindowRequestState); SyncSessionFromSelectionService(); diff --git a/editor/app/Core/Scene/EditorSceneBackend.h b/editor/app/Core/Scene/EditorSceneBackend.h index 9c7cc04f..7167b3c0 100644 --- a/editor/app/Core/Scene/EditorSceneBackend.h +++ b/editor/app/Core/Scene/EditorSceneBackend.h @@ -26,6 +26,10 @@ struct EditorStartupSceneResult { bool loadedFromDisk = false; std::filesystem::path scenePath = {}; std::string sceneName = {}; + + operator bool() const { + return ready; + } }; using EditorSceneObjectId = std::uint64_t; diff --git a/editor/app/Services/Runtime/EditorRuntimeCoordinator.cpp b/editor/app/Services/Runtime/EditorRuntimeCoordinator.cpp index 32cf1772..ca575d68 100644 --- a/editor/app/Services/Runtime/EditorRuntimeCoordinator.cpp +++ b/editor/app/Services/Runtime/EditorRuntimeCoordinator.cpp @@ -50,6 +50,14 @@ std::string ResolveSceneDisplayName(const std::filesystem::path& scenePath) { : scenePath.stem().string(); } +std::string ResolveDocumentSceneName( + std::string_view sceneName, + const std::filesystem::path& scenePath) { + return !sceneName.empty() + ? std::string(sceneName) + : ResolveSceneDisplayName(scenePath); +} + } // namespace EditorRuntimeCoordinator::~EditorRuntimeCoordinator() { @@ -60,21 +68,15 @@ void EditorRuntimeCoordinator::Initialize( EditorSession& session, EditorSceneRuntime& sceneRuntime, EditorProjectRuntime& projectRuntime, - const EditorRuntimePaths& runtimePaths) { + const EditorRuntimePaths& runtimePaths, + const EditorStartupSceneResult& startupScene) { Shutdown(); m_session = &session; m_sceneRuntime = &sceneRuntime; m_projectRuntime = &projectRuntime; m_runtimePaths = runtimePaths; - - const EditorStartupSceneResult& startupScene = - m_sceneRuntime->GetStartupResult(); - m_session->currentScenePath = startupScene.scenePath; - m_session->currentSceneName = startupScene.sceneName.empty() - ? ResolveSceneDisplayName(startupScene.scenePath) - : startupScene.sceneName; - m_session->sceneDocumentDirty = false; - m_session->runtimeMode = EditorRuntimeMode::Edit; + ApplyStartupSceneDocument(startupScene); + SetRuntimeMode(EditorRuntimeMode::Edit); CaptureCleanSceneRevision(); } @@ -82,12 +84,15 @@ void EditorRuntimeCoordinator::Shutdown() { m_runtimeLoop.Stop(); m_playSession.reset(); if (m_session != nullptr) { - m_session->runtimeMode = EditorRuntimeMode::Edit; + m_sceneDocument = {}; + SyncSessionDocumentProjection(); + SetRuntimeMode(EditorRuntimeMode::Edit); } m_session = nullptr; m_sceneRuntime = nullptr; m_projectRuntime = nullptr; m_runtimePaths = EditorRuntimePaths{}; + m_sceneDocument = {}; m_lastFrameTickTime = {}; m_lastCleanSceneContentRevision = 0u; m_lastObservedSceneContentRevision = 0u; @@ -116,16 +121,16 @@ void EditorRuntimeCoordinator::Tick(float deltaSeconds) { SyncSceneDocumentDirtyFromRevision(); if (!m_runtimeLoop.IsRunning()) { if (m_session->runtimeMode != EditorRuntimeMode::Edit) { - m_session->runtimeMode = EditorRuntimeMode::Edit; + SetRuntimeMode(EditorRuntimeMode::Edit); } return; } m_runtimeLoop.Tick(deltaSeconds); if (m_runtimeLoop.IsPaused()) { - m_session->runtimeMode = EditorRuntimeMode::Paused; + SetRuntimeMode(EditorRuntimeMode::Paused); } else { - m_session->runtimeMode = EditorRuntimeMode::Play; + SetRuntimeMode(EditorRuntimeMode::Play); } } @@ -147,10 +152,13 @@ bool EditorRuntimeCoordinator::RequestOpenSceneAsset( return false; } - m_session->currentScenePath = scenePath; - m_session->currentSceneName = ResolveSceneDisplayName(scenePath); - m_session->sceneDocumentDirty = false; + m_sceneDocument.ready = true; + m_sceneDocument.loadedFromDisk = true; + m_sceneDocument.currentPath = scenePath; + m_sceneDocument.currentName = ResolveActiveSceneDisplayName(scenePath); + m_sceneDocument.dirty = false; CaptureCleanSceneRevision(); + SyncSessionDocumentProjection(); SetLastMessage("Opened scene: " + scenePath.string()); return true; } @@ -182,7 +190,7 @@ EditorRuntimeCoordinator::EvaluateFileCommand( if (!HasActiveScene()) { return BuildDisabledResult("No active scene to save."); } - if (m_session->currentScenePath.empty()) { + if (m_sceneDocument.currentPath.empty()) { return BuildDisabledResult( "Save Scene As is required before saving this scene."); } @@ -215,28 +223,30 @@ EditorRuntimeCoordinator::DispatchFileCommand( return BuildRejectedDispatch(m_lastMessage); } - const EditorStartupSceneResult& startupScene = - m_sceneRuntime->GetStartupResult(); - m_session->currentScenePath = std::filesystem::path(); - m_session->currentSceneName = startupScene.sceneName.empty() - ? std::string("Untitled") - : startupScene.sceneName; - m_session->sceneDocumentDirty = true; + m_sceneDocument.ready = true; + m_sceneDocument.loadedFromDisk = false; + m_sceneDocument.currentPath.clear(); + m_sceneDocument.currentName = ResolveActiveSceneDisplayName(); + m_sceneDocument.dirty = true; CaptureCleanSceneRevision(); + SyncSessionDocumentProjection(); SetLastMessage("New scene created."); return BuildExecutedDispatch(m_lastMessage); } if (commandId == "file.save_scene") { - const std::filesystem::path scenePath = m_session->currentScenePath; + const std::filesystem::path scenePath = m_sceneDocument.currentPath; if (!m_sceneRuntime->SaveScene(scenePath)) { SetLastMessage("Failed to save scene: " + scenePath.string()); return BuildRejectedDispatch(m_lastMessage); } - m_session->sceneDocumentDirty = false; - m_session->currentSceneName = ResolveSceneDisplayName(scenePath); + m_sceneDocument.ready = true; + m_sceneDocument.loadedFromDisk = !scenePath.empty(); + m_sceneDocument.currentName = ResolveActiveSceneDisplayName(scenePath); + m_sceneDocument.dirty = false; CaptureCleanSceneRevision(); + SyncSessionDocumentProjection(); SetLastMessage("Scene saved: " + scenePath.string()); return BuildExecutedDispatch(m_lastMessage); } @@ -408,9 +418,8 @@ bool EditorRuntimeCoordinator::StartPlayMode() { if (!EnsurePlaySession()) { return false; } - m_runtimeLoop.Start(m_playSession->GetRuntimeScene()); - m_session->runtimeMode = EditorRuntimeMode::Play; + SetRuntimeMode(EditorRuntimeMode::Play); m_lastFrameTickTime = std::chrono::steady_clock::now(); return m_runtimeLoop.IsRunning(); } @@ -423,7 +432,7 @@ bool EditorRuntimeCoordinator::StopPlayMode() { m_runtimeLoop.Stop(); m_playSession.reset(); m_sceneRuntime->RefreshScene(); - m_session->runtimeMode = EditorRuntimeMode::Edit; + SetRuntimeMode(EditorRuntimeMode::Edit); return true; } @@ -433,7 +442,7 @@ bool EditorRuntimeCoordinator::PausePlayMode() { } m_runtimeLoop.Pause(); - m_session->runtimeMode = EditorRuntimeMode::Paused; + SetRuntimeMode(EditorRuntimeMode::Paused); return true; } @@ -443,7 +452,7 @@ bool EditorRuntimeCoordinator::ResumePlayMode() { } m_runtimeLoop.Resume(); - m_session->runtimeMode = EditorRuntimeMode::Play; + SetRuntimeMode(EditorRuntimeMode::Play); m_lastFrameTickTime = std::chrono::steady_clock::now(); return true; } @@ -457,17 +466,52 @@ bool EditorRuntimeCoordinator::StepPlayMode() { if (!EnsurePlaySession()) { return false; } - m_runtimeLoop.Start(m_playSession->GetRuntimeScene()); } m_runtimeLoop.StepFrame(); m_runtimeLoop.Tick(m_runtimeLoop.GetSettings().fixedDeltaTime); - m_session->runtimeMode = EditorRuntimeMode::Paused; + SetRuntimeMode(EditorRuntimeMode::Paused); m_lastFrameTickTime = std::chrono::steady_clock::now(); return true; } +void EditorRuntimeCoordinator::ApplyStartupSceneDocument( + const EditorStartupSceneResult& startupScene) { + m_sceneDocument.ready = startupScene.ready; + m_sceneDocument.loadedFromDisk = startupScene.loadedFromDisk; + m_sceneDocument.currentPath = startupScene.scenePath; + m_sceneDocument.currentName = + ResolveDocumentSceneName(startupScene.sceneName, startupScene.scenePath); + m_sceneDocument.dirty = false; + SyncSessionDocumentProjection(); +} + +std::string EditorRuntimeCoordinator::ResolveActiveSceneDisplayName( + const std::filesystem::path& fallbackPath) const { + const std::filesystem::path& scenePath = + fallbackPath.empty() ? m_sceneDocument.currentPath : fallbackPath; + const std::string sceneName = + IsReady() ? m_sceneRuntime->GetActiveSceneName() : std::string(); + return ResolveDocumentSceneName(sceneName, scenePath); +} + +void EditorRuntimeCoordinator::SyncSessionDocumentProjection() { + if (m_session == nullptr) { + return; + } + + m_session->currentScenePath = m_sceneDocument.currentPath; + m_session->currentSceneName = m_sceneDocument.currentName; + m_session->sceneDocumentDirty = m_sceneDocument.dirty; +} + +void EditorRuntimeCoordinator::SetRuntimeMode(EditorRuntimeMode mode) { + if (m_session != nullptr) { + m_session->runtimeMode = mode; + } +} + void EditorRuntimeCoordinator::SyncSceneDocumentDirtyFromRevision() { if (!IsReady()) { return; @@ -478,7 +522,8 @@ void EditorRuntimeCoordinator::SyncSceneDocumentDirtyFromRevision() { m_lastObservedSceneContentRevision = revision; } if (revision != m_lastCleanSceneContentRevision) { - m_session->sceneDocumentDirty = true; + m_sceneDocument.dirty = true; + SyncSessionDocumentProjection(); } } diff --git a/editor/app/Services/Runtime/EditorRuntimeCoordinator.h b/editor/app/Services/Runtime/EditorRuntimeCoordinator.h index 6b5a0ef8..00a0b209 100644 --- a/editor/app/Services/Runtime/EditorRuntimeCoordinator.h +++ b/editor/app/Services/Runtime/EditorRuntimeCoordinator.h @@ -17,6 +17,7 @@ namespace XCEngine::UI::Editor::App { class EditorProjectRuntime; class EditorScenePlaySession; class EditorSceneRuntime; +struct EditorStartupSceneResult; struct EditorSession; class EditorRuntimeCoordinator final @@ -31,7 +32,8 @@ public: EditorSession& session, EditorSceneRuntime& sceneRuntime, EditorProjectRuntime& projectRuntime, - const EditorRuntimePaths& runtimePaths); + const EditorRuntimePaths& runtimePaths, + const EditorStartupSceneResult& startupScene); void Shutdown(); void TickFrame(); @@ -54,6 +56,14 @@ public: std::string_view commandId) override; private: + struct SceneDocumentState { + bool ready = false; + bool loadedFromDisk = false; + std::filesystem::path currentPath = {}; + std::string currentName = {}; + bool dirty = false; + }; + bool IsReady() const; bool HasActiveScene() const; bool IsPlayModeActive() const; @@ -63,6 +73,11 @@ private: bool PausePlayMode(); bool ResumePlayMode(); bool StepPlayMode(); + void ApplyStartupSceneDocument(const EditorStartupSceneResult& startupScene); + std::string ResolveActiveSceneDisplayName( + const std::filesystem::path& fallbackPath = {}) const; + void SyncSessionDocumentProjection(); + void SetRuntimeMode(EditorRuntimeMode mode); void SyncSceneDocumentDirtyFromRevision(); void CaptureCleanSceneRevision(); void SetLastMessage(std::string message); @@ -71,6 +86,7 @@ private: EditorSceneRuntime* m_sceneRuntime = nullptr; EditorProjectRuntime* m_projectRuntime = nullptr; EditorRuntimePaths m_runtimePaths = {}; + SceneDocumentState m_sceneDocument = {}; ::XCEngine::Components::RuntimeLoop m_runtimeLoop{ ::XCEngine::Components::RuntimeLoop::Settings{} }; std::unique_ptr m_playSession = {}; diff --git a/editor/app/Services/Scene/EditorSceneRuntime.cpp b/editor/app/Services/Scene/EditorSceneRuntime.cpp index 5ff85aa6..42c5b8b5 100644 --- a/editor/app/Services/Scene/EditorSceneRuntime.cpp +++ b/editor/app/Services/Scene/EditorSceneRuntime.cpp @@ -103,7 +103,6 @@ std::string_view GetSceneToolInteractionLockName(SceneToolInteractionLock lock) void EditorSceneRuntime::Reset() { m_projectRoot.clear(); - m_startupSceneResult = {}; m_ownedSelectionService.ClearSelection(); m_selectionService = &m_ownedSelectionService; ResetTransformEditHistory(); @@ -116,15 +115,17 @@ void EditorSceneRuntime::SetBackend(std::unique_ptr backend) Reset(); } -bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) { +EditorStartupSceneResult EditorSceneRuntime::Initialize( + const std::filesystem::path& projectRoot) { Reset(); if (m_backend == nullptr) { - return false; + return {}; } m_projectRoot = projectRoot; - m_startupSceneResult = m_backend->EnsureStartupScene(projectRoot); + const EditorStartupSceneResult startupScene = + m_backend->EnsureStartupScene(projectRoot); RefreshScene(); - return m_startupSceneResult.ready; + return startupScene; } void EditorSceneRuntime::BindSelectionService( @@ -152,10 +153,6 @@ void EditorSceneRuntime::EnsureSceneSelection() { SelectFirstAvailableGameObject(); } -const EditorStartupSceneResult& EditorSceneRuntime::GetStartupResult() const { - return m_startupSceneResult; -} - EditorSceneHierarchySnapshot EditorSceneRuntime::BuildHierarchySnapshot() const { return m_backend != nullptr ? m_backend->BuildHierarchySnapshot() @@ -284,18 +281,6 @@ bool EditorSceneRuntime::NewScene(std::string_view sceneName) { return false; } - m_startupSceneResult.ready = true; - m_startupSceneResult.loadedFromDisk = false; - m_startupSceneResult.scenePath = std::filesystem::path(); - if (::XCEngine::Components::Scene* activeScene = m_backend->GetActiveScene(); - activeScene != nullptr) { - m_startupSceneResult.sceneName = activeScene->GetName(); - } else { - m_startupSceneResult.sceneName = sceneName.empty() - ? std::string("Untitled") - : std::string(sceneName); - } - ResetTransformEditHistory(); SelectionService().ClearSelection(); IncrementInspectorRevision(); @@ -310,11 +295,6 @@ bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath) return false; } - m_startupSceneResult.ready = true; - m_startupSceneResult.loadedFromDisk = true; - m_startupSceneResult.scenePath = scenePath; - m_startupSceneResult.sceneName = scenePath.stem().string(); - ResetTransformEditHistory(); SelectionService().ClearSelection(); IncrementInspectorRevision(); @@ -332,6 +312,15 @@ bool EditorSceneRuntime::SaveScene(const std::filesystem::path& scenePath) { return m_backend != nullptr ? m_backend->GetActiveScene() : nullptr; } +std::string EditorSceneRuntime::GetActiveSceneName() const { + if (const ::XCEngine::Components::Scene* activeScene = GetActiveScene(); + activeScene != nullptr) { + return activeScene->GetName(); + } + + return {}; +} + std::unique_ptr EditorSceneRuntime::BeginPlaySession() { return m_backend != nullptr ? m_backend->BeginPlaySession() diff --git a/editor/app/Services/Scene/EditorSceneRuntime.h b/editor/app/Services/Scene/EditorSceneRuntime.h index 85dde1b1..c5ce11e2 100644 --- a/editor/app/Services/Scene/EditorSceneRuntime.h +++ b/editor/app/Services/Scene/EditorSceneRuntime.h @@ -38,13 +38,12 @@ public: void Reset(); void SetBackend(std::unique_ptr backend); - bool Initialize(const std::filesystem::path& projectRoot); + EditorStartupSceneResult Initialize(const std::filesystem::path& projectRoot); void BindSelectionService(EditorSelectionService* selectionService); void RefreshScene(); void EnsureSceneSelection(); - const EditorStartupSceneResult& GetStartupResult() const; EditorSceneHierarchySnapshot BuildHierarchySnapshot() const; std::vector BuildSceneViewportIconSnapshots() const; std::optional BuildSceneViewportSelectionSnapshot() const; @@ -67,6 +66,7 @@ public: bool OpenSceneAsset(const std::filesystem::path& scenePath); bool SaveScene(const std::filesystem::path& scenePath); ::XCEngine::Components::Scene* GetActiveScene() const; + std::string GetActiveSceneName() const; std::unique_ptr BeginPlaySession(); bool RenameGameObject( @@ -139,7 +139,6 @@ private: std::filesystem::path m_projectRoot = {}; std::unique_ptr m_backend = {}; - EditorStartupSceneResult m_startupSceneResult = {}; EditorSelectionService m_ownedSelectionService = {}; EditorSelectionService* m_selectionService = &m_ownedSelectionService; std::vector m_transformUndoStack = {}; diff --git a/tests/UI/Editor/unit/test_editor_runtime_coordinator.cpp b/tests/UI/Editor/unit/test_editor_runtime_coordinator.cpp index 5b907172..4cbfb6f4 100644 --- a/tests/UI/Editor/unit/test_editor_runtime_coordinator.cpp +++ b/tests/UI/Editor/unit/test_editor_runtime_coordinator.cpp @@ -62,7 +62,13 @@ public: return true; } - bool OpenSceneAsset(const std::filesystem::path&) override { + bool OpenSceneAsset(const std::filesystem::path& scenePath) override { + lastOpenedScenePath = scenePath; + if (!openSceneResult) { + return false; + } + + editScene = std::make_unique(openedSceneName); activeScene = editScene.get(); return true; } @@ -142,11 +148,14 @@ public: std::unique_ptr editScene = {}; std::unique_ptr runtimeScene = {}; Scene* activeScene = nullptr; + std::filesystem::path lastOpenedScenePath = {}; int beginPlaySessionCallCount = 0; int endPlaySessionCallCount = 0; int saveActiveSceneCallCount = 0; bool savedActiveSceneWasEditScene = false; bool failBeginPlaySession = false; + bool openSceneResult = true; + std::string openedSceneName = "Opened"; }; FakeEditorScenePlaySession::~FakeEditorScenePlaySession() { @@ -162,12 +171,15 @@ struct RuntimeCoordinatorHarness { auto backend = std::make_unique(); backendPtr = backend.get(); sceneRuntime.SetBackend(std::move(backend)); - EXPECT_TRUE(sceneRuntime.Initialize("D:/Project")); + const EditorStartupSceneResult startupScene = + sceneRuntime.Initialize("D:/Project"); + EXPECT_TRUE(startupScene.ready); coordinator.Initialize( session, sceneRuntime, projectRuntime, - EditorRuntimePaths{}); + EditorRuntimePaths{}, + startupScene); } EditorSession session = {}; @@ -177,6 +189,42 @@ struct RuntimeCoordinatorHarness { EditorRuntimeCoordinator coordinator = {}; }; +TEST(EditorRuntimeCoordinatorTests, InitializeProjectsStartupSceneDocumentStateToSession) { + RuntimeCoordinatorHarness harness = {}; + + EXPECT_EQ( + harness.session.currentScenePath, + std::filesystem::path("D:/Project/Assets/Scenes/Main.xc")); + EXPECT_EQ(harness.session.currentSceneName, "Edit"); + EXPECT_FALSE(harness.session.sceneDocumentDirty); + EXPECT_EQ(harness.session.runtimeMode, EditorRuntimeMode::Edit); +} + +TEST(EditorRuntimeCoordinatorTests, NewSceneProjectsUnsavedDirtyDocumentStateToSession) { + RuntimeCoordinatorHarness harness = {}; + + const UIEditorHostCommandDispatchResult newSceneResult = + harness.coordinator.DispatchFileCommand("file.new_scene"); + EXPECT_TRUE(newSceneResult.commandExecuted); + EXPECT_TRUE(harness.session.currentScenePath.empty()); + EXPECT_EQ(harness.session.currentSceneName, "Untitled"); + EXPECT_TRUE(harness.session.sceneDocumentDirty); +} + +TEST(EditorRuntimeCoordinatorTests, OpenSceneProjectsCoordinatorOwnedDocumentStateToSession) { + RuntimeCoordinatorHarness harness = {}; + ASSERT_NE(harness.backendPtr, nullptr); + harness.backendPtr->openedSceneName = "Opened Scene"; + + const std::filesystem::path scenePath = + "D:/Project/Assets/Scenes/Secondary.xc"; + ASSERT_TRUE(harness.coordinator.RequestOpenSceneAsset(scenePath)); + EXPECT_EQ(harness.backendPtr->lastOpenedScenePath, scenePath); + EXPECT_EQ(harness.session.currentScenePath, scenePath); + EXPECT_EQ(harness.session.currentSceneName, "Opened Scene"); + EXPECT_FALSE(harness.session.sceneDocumentDirty); +} + TEST(EditorRuntimeCoordinatorTests, PlayModeRunsRuntimeSceneAndRestoresEditSceneOnStop) { RuntimeCoordinatorHarness harness = {}; ASSERT_NE(harness.backendPtr, nullptr); diff --git a/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp b/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp index 7521d353..48f3f75f 100644 --- a/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp +++ b/tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp @@ -112,7 +112,9 @@ public: TEST(EditorSceneRuntimeBackendTests, InitializeFailsWithoutBoundBackend) { EditorSceneRuntime runtime = {}; - EXPECT_FALSE(runtime.Initialize("D:/Xuanchi/Main/XCEngine/project")); + const EditorStartupSceneResult result = + runtime.Initialize("D:/Xuanchi/Main/XCEngine/project"); + EXPECT_FALSE(result.ready); } TEST(EditorSceneRuntimeBackendTests, InitializeUsesBoundBackend) { @@ -124,12 +126,14 @@ TEST(EditorSceneRuntimeBackendTests, InitializeUsesBoundBackend) { EditorSceneRuntime runtime = {}; runtime.SetBackend(std::move(backend)); - EXPECT_TRUE(runtime.Initialize("D:/Xuanchi/Main/XCEngine/project")); + const EditorStartupSceneResult result = + runtime.Initialize("D:/Xuanchi/Main/XCEngine/project"); + EXPECT_TRUE(result.ready); EXPECT_EQ(backendPtr->ensureStartupSceneCallCount, 1); EXPECT_EQ( backendPtr->lastProjectRoot, std::filesystem::path("D:/Xuanchi/Main/XCEngine/project")); - EXPECT_EQ(runtime.GetStartupResult().sceneName, "Main"); + EXPECT_EQ(result.sceneName, "Main"); } TEST(EditorSceneRuntimeBackendTests, SetSelectionUsesBoundBackendObjectSnapshotLookup) {