refactor(editor): isolate scene backend boundary

This commit is contained in:
2026-04-28 16:32:25 +08:00
parent 6c4663ed21
commit 23aab98a09
15 changed files with 481 additions and 253 deletions

View File

@@ -67,6 +67,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
set(EDITOR_APP_CORE_TEST_SOURCES
test_editor_host_command_bridge.cpp
test_editor_project_runtime.cpp
test_editor_scene_runtime_backend.cpp
test_editor_shell_asset_validation.cpp
test_project_browser_model.cpp
test_hierarchy_scene_binding.cpp

View File

@@ -0,0 +1,124 @@
#include "Scene/EditorSceneBackend.h"
#include "Scene/EditorSceneRuntime.h"
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Scene/Scene.h>
#include <gtest/gtest.h>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::Components::GameObject;
using ::XCEngine::Components::Scene;
class FakeEditorSceneBackend final : public EditorSceneBackend {
public:
EditorStartupSceneResult EnsureStartupScene(
const std::filesystem::path& projectRoot) override {
++ensureStartupSceneCallCount;
lastProjectRoot = projectRoot;
return startupSceneResult;
}
Scene* GetActiveScene() const override {
return activeScene;
}
bool OpenSceneAsset(const std::filesystem::path& scenePath) override {
lastOpenedScenePath = scenePath;
return openSceneResult;
}
GameObject* FindGameObject(std::string_view itemId) const override {
lastFindItemId = std::string(itemId);
return foundGameObject;
}
bool RenameGameObject(
std::string_view,
std::string_view) override {
return false;
}
bool DeleteGameObject(std::string_view) override {
return false;
}
std::string DuplicateGameObject(std::string_view) override {
return {};
}
bool ReparentGameObject(
std::string_view,
std::string_view) override {
return false;
}
bool MoveGameObjectBefore(
std::string_view,
std::string_view) override {
return false;
}
bool MoveGameObjectAfter(
std::string_view,
std::string_view) override {
return false;
}
bool MoveGameObjectToRoot(std::string_view) override {
return false;
}
EditorStartupSceneResult startupSceneResult = {};
Scene* activeScene = nullptr;
GameObject* foundGameObject = nullptr;
bool openSceneResult = false;
int ensureStartupSceneCallCount = 0;
std::filesystem::path lastProjectRoot = {};
std::filesystem::path lastOpenedScenePath = {};
mutable std::string lastFindItemId = {};
};
TEST(EditorSceneRuntimeBackendTests, InitializeFailsWithoutBoundBackend) {
EditorSceneRuntime runtime = {};
EXPECT_FALSE(runtime.Initialize("D:/Xuanchi/Main/XCEngine/project"));
}
TEST(EditorSceneRuntimeBackendTests, InitializeUsesBoundBackend) {
auto backend = std::make_unique<FakeEditorSceneBackend>();
backend->startupSceneResult.ready = true;
backend->startupSceneResult.sceneName = "Main";
FakeEditorSceneBackend* const backendPtr = backend.get();
EditorSceneRuntime runtime = {};
runtime.SetBackend(std::move(backend));
EXPECT_TRUE(runtime.Initialize("D:/Xuanchi/Main/XCEngine/project"));
EXPECT_EQ(backendPtr->ensureStartupSceneCallCount, 1);
EXPECT_EQ(
backendPtr->lastProjectRoot,
std::filesystem::path("D:/Xuanchi/Main/XCEngine/project"));
EXPECT_EQ(runtime.GetStartupResult().sceneName, "Main");
}
TEST(EditorSceneRuntimeBackendTests, FindGameObjectUsesBoundBackend) {
auto backend = std::make_unique<FakeEditorSceneBackend>();
Scene scene("Main");
GameObject probe("Probe");
backend->startupSceneResult.ready = true;
backend->activeScene = &scene;
backend->foundGameObject = &probe;
FakeEditorSceneBackend* const backendPtr = backend.get();
EditorSceneRuntime runtime = {};
runtime.SetBackend(std::move(backend));
ASSERT_TRUE(runtime.Initialize("D:/Xuanchi/Main/XCEngine/project"));
EXPECT_EQ(runtime.FindGameObject("42"), &probe);
EXPECT_EQ(backendPtr->lastFindItemId, "42");
}
} // namespace
} // namespace XCEngine::UI::Editor::App

View File

@@ -1,5 +1,5 @@
#include "Hierarchy/HierarchyModel.h"
#include "Scene/EditorSceneBridge.h"
#include "Scene/EngineEditorSceneBackend.h"
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
@@ -10,6 +10,7 @@
#include <chrono>
#include <filesystem>
#include <memory>
namespace XCEngine::UI::Editor::App {
namespace {
@@ -61,6 +62,10 @@ private:
std::filesystem::path m_root = {};
};
std::unique_ptr<EditorSceneBackend> CreateTestSceneBackend() {
return CreateEngineEditorSceneBackend();
}
TEST(HierarchySceneBindingTests, BuildFromSceneUsesRealGameObjectIds) {
ScopedSceneManagerReset reset = {};
@@ -97,8 +102,9 @@ TEST(HierarchySceneBindingTests, DuplicateGameObjectClonesHierarchyIntoScene) {
GameObject* child = scene->CreateGameObject("Child", root);
ASSERT_NE(child, nullptr);
const std::unique_ptr<EditorSceneBackend> backend = CreateTestSceneBackend();
const std::string duplicateId =
DuplicateEditorGameObject(MakeEditorGameObjectItemId(root->GetID()));
backend->DuplicateGameObject(MakeEditorGameObjectItemId(root->GetID()));
ASSERT_FALSE(duplicateId.empty());
const HierarchyModel model = HierarchyModel::BuildFromScene(scene);
@@ -124,7 +130,8 @@ TEST(HierarchySceneBindingTests, RenameGameObjectUpdatesRealSceneAndProjection)
const std::string itemId =
MakeEditorGameObjectItemId(gameObject->GetID());
ASSERT_TRUE(RenameEditorGameObject(itemId, "PlayerCamera"));
const std::unique_ptr<EditorSceneBackend> backend = CreateTestSceneBackend();
ASSERT_TRUE(backend->RenameGameObject(itemId, "PlayerCamera"));
EXPECT_EQ(gameObject->GetName(), "PlayerCamera");
const HierarchyModel model = HierarchyModel::BuildFromScene(scene);
@@ -147,7 +154,8 @@ TEST(HierarchySceneBindingTests, ReparentAndMoveToRootOperateOnRealScene) {
GameObject* child = scene->CreateGameObject("Child", parentA);
ASSERT_NE(child, nullptr);
ASSERT_TRUE(ReparentEditorGameObject(
const std::unique_ptr<EditorSceneBackend> backend = CreateTestSceneBackend();
ASSERT_TRUE(backend->ReparentGameObject(
MakeEditorGameObjectItemId(child->GetID()),
MakeEditorGameObjectItemId(parentB->GetID())));
EXPECT_EQ(child->GetParent(), parentB);
@@ -159,7 +167,7 @@ TEST(HierarchySceneBindingTests, ReparentAndMoveToRootOperateOnRealScene) {
ASSERT_EQ(parentBNode->children.size(), 1u);
EXPECT_EQ(parentBNode->children.front().label, "Child");
ASSERT_TRUE(MoveEditorGameObjectToRoot(
ASSERT_TRUE(backend->MoveGameObjectToRoot(
MakeEditorGameObjectItemId(child->GetID())));
EXPECT_EQ(child->GetParent(), nullptr);
@@ -181,15 +189,16 @@ TEST(HierarchySceneBindingTests, EnsureStartupSceneLoadsMainSceneAndSetsActive)
scene.Save(scenePath.string());
}
const std::unique_ptr<EditorSceneBackend> backend = CreateTestSceneBackend();
const EditorStartupSceneResult result =
EnsureEditorStartupScene(projectRoot.Root());
backend->EnsureStartupScene(projectRoot.Root());
EXPECT_TRUE(result.ready);
EXPECT_TRUE(result.loadedFromDisk);
ASSERT_NE(GetActiveEditorScene(), nullptr);
EXPECT_EQ(GetActiveEditorScene()->GetName(), "Main");
ASSERT_NE(backend->GetActiveScene(), nullptr);
EXPECT_EQ(backend->GetActiveScene()->GetName(), "Main");
const HierarchyModel model =
HierarchyModel::BuildFromScene(GetActiveEditorScene());
HierarchyModel::BuildFromScene(backend->GetActiveScene());
EXPECT_FALSE(model.Empty());
SceneManager& sceneManager = SceneManager::Get();

View File

@@ -3,6 +3,7 @@
#include "Inspector/InspectorPresentationModel.h"
#include "Inspector/InspectorSubject.h"
#include "Scene/EditorSceneRuntime.h"
#include "Scene/EngineEditorSceneBackend.h"
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
@@ -87,6 +88,10 @@ void SaveMainScene(const TemporaryProjectRoot& projectRoot) {
scene.Save(scenePath.string());
}
void BindEngineSceneBackend(EditorSceneRuntime& runtime) {
runtime.SetBackend(CreateEngineEditorSceneBackend());
}
const UIEditorPropertyGridSection* FindSection(
const InspectorPresentationModel& model,
std::string_view title) {
@@ -126,6 +131,7 @@ const InspectorPresentationComponentBinding* FindBinding(
TEST(InspectorPresentationModelTests, EmptySubjectBuildsDefaultEmptyState) {
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
const InspectorPresentationModel model =
BuildInspectorPresentationModel(
{},
@@ -150,6 +156,7 @@ TEST(InspectorPresentationModelTests, ProjectAssetSubjectBuildsIdentityAndLocati
std::filesystem::path("D:/Xuanchi/Main/XCEngine/project/Assets/Materials/Test.mat");
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
const InspectorPresentationModel model =
BuildInspectorPresentationModel(
subject,
@@ -190,6 +197,7 @@ TEST(InspectorPresentationModelTests, SceneObjectSubjectBuildsRegisteredComponen
SaveMainScene(projectRoot);
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = runtime.GetActiveScene();
ASSERT_NE(scene, nullptr);
@@ -261,6 +269,7 @@ TEST(InspectorPresentationModelTests, CameraSkyboxMaterialBuildsAssetField) {
SaveMainScene(projectRoot);
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = runtime.GetActiveScene();
ASSERT_NE(scene, nullptr);

View File

@@ -1,4 +1,5 @@
#include "Scene/EditorSceneRuntime.h"
#include "Scene/EngineEditorSceneBackend.h"
#include "Scene/SceneViewportController.h"
#include "Inspector/InspectorSubject.h"
#include "Viewport/SceneViewportRenderService.h"
@@ -106,6 +107,10 @@ void SaveMainScene(const TemporaryProjectRoot& projectRoot, const Math::Vector3&
scene.Save(scenePath.string());
}
void BindEngineSceneBackend(EditorSceneRuntime& runtime) {
runtime.SetBackend(CreateEngineEditorSceneBackend());
}
UIInputEvent MakePointerEvent(
UIInputEventType type,
float x,
@@ -207,6 +212,7 @@ TEST(SceneViewportRuntimeTests, ApplySceneViewportCameraInputUpdatesCameraTransf
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
auto* camera = runtime.GetSceneViewCamera();
@@ -233,6 +239,7 @@ TEST(SceneViewportRuntimeTests, FocusSceneSelectionRepositionsCameraAroundSelect
SaveMainScene(projectRoot, Math::Vector3(12.0f, 3.0f, -8.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = runtime.GetActiveScene();
ASSERT_NE(scene, nullptr);
@@ -261,6 +268,7 @@ TEST(SceneViewportRuntimeTests, BuildSceneViewportRenderRequestIncludesSelectedO
SaveMainScene(projectRoot, Math::Vector3(4.0f, 5.0f, 6.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = runtime.GetActiveScene();
ASSERT_NE(scene, nullptr);
@@ -284,6 +292,7 @@ TEST(SceneViewportRuntimeTests, SelectedComponentsExposeTransformAndAttachedCame
SaveMainScene(projectRoot, Math::Vector3(4.0f, 5.0f, 6.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = runtime.GetActiveScene();
ASSERT_NE(scene, nullptr);
@@ -314,6 +323,7 @@ TEST(SceneViewportRuntimeTests, RemoveSelectedComponentDropsRemovableDescriptorB
SaveMainScene(projectRoot, Math::Vector3(4.0f, 5.0f, 6.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
Scene* scene = runtime.GetActiveScene();
ASSERT_NE(scene, nullptr);
@@ -340,6 +350,7 @@ TEST(SceneViewportRuntimeTests, TransformSetterApisWriteLocalValuesOnSelectedTra
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.EnsureSceneSelection();
@@ -380,6 +391,7 @@ TEST(SceneViewportRuntimeTests, SelectionStampAdvancesOnSceneSelectionChanges) {
SaveMainScene(projectRoot, Math::Vector3(4.0f, 5.0f, 6.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.EnsureSceneSelection();
@@ -409,6 +421,7 @@ TEST(SceneViewportRuntimeTests, InspectorSelectionResolverFollowsUnifiedSelectio
EditorSelectionService selectionService = {};
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.BindSelectionService(&selectionService);
runtime.EnsureSceneSelection();
@@ -476,6 +489,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
auto* camera = runtime.GetSceneViewCamera();
@@ -536,6 +550,7 @@ TEST(SceneViewportRuntimeTests, MoveRightInputMovesSceneCameraTowardPositiveCame
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
auto* camera = runtime.GetSceneViewCamera();
@@ -560,6 +575,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics)
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
auto* camera = runtime.GetSceneViewCamera();
@@ -618,6 +634,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.SetToolMode(SceneToolMode::View);
@@ -677,6 +694,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
const float beforeDistance =
runtime.BuildSceneViewportRenderRequest().orbitDistance;
@@ -723,6 +741,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.SetToolMode(SceneToolMode::View);
EXPECT_EQ(runtime.GetToolMode(), SceneToolMode::View);
@@ -781,6 +800,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayClickSwitchesModeOnPointerDown)
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.SetToolMode(SceneToolMode::Translate);
@@ -819,6 +839,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayIncludesTransformButtonAndSwitch
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.SetToolMode(SceneToolMode::Translate);
@@ -860,6 +881,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayHandlesCoalescedClickInSingleFra
SaveMainScene(projectRoot, Math::Vector3(0.0f, 0.0f, 0.0f));
EditorSceneRuntime runtime = {};
BindEngineSceneBackend(runtime);
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
runtime.SetToolMode(SceneToolMode::Translate);