Refactor editor scene document ownership into runtime coordinator
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EditorScenePlaySession> m_playSession = {};
|
||||
|
||||
@@ -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<EditorSceneBackend> 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<EditorScenePlaySession> EditorSceneRuntime::BeginPlaySession() {
|
||||
return m_backend != nullptr
|
||||
? m_backend->BeginPlaySession()
|
||||
|
||||
@@ -38,13 +38,12 @@ public:
|
||||
|
||||
void Reset();
|
||||
void SetBackend(std::unique_ptr<EditorSceneBackend> 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<EditorSceneViewportIconSnapshot> BuildSceneViewportIconSnapshots() const;
|
||||
std::optional<EditorSceneViewportSelectionSnapshot> 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<EditorScenePlaySession> BeginPlaySession();
|
||||
|
||||
bool RenameGameObject(
|
||||
@@ -139,7 +139,6 @@ private:
|
||||
|
||||
std::filesystem::path m_projectRoot = {};
|
||||
std::unique_ptr<EditorSceneBackend> m_backend = {};
|
||||
EditorStartupSceneResult m_startupSceneResult = {};
|
||||
EditorSelectionService m_ownedSelectionService = {};
|
||||
EditorSelectionService* m_selectionService = &m_ownedSelectionService;
|
||||
std::vector<TransformEditTransaction> m_transformUndoStack = {};
|
||||
|
||||
@@ -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<Scene>(openedSceneName);
|
||||
activeScene = editScene.get();
|
||||
return true;
|
||||
}
|
||||
@@ -142,11 +148,14 @@ public:
|
||||
std::unique_ptr<Scene> editScene = {};
|
||||
std::unique_ptr<Scene> 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<FakeEditorSceneBackend>();
|
||||
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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user