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

@@ -11,6 +11,7 @@
- 产品装配应以 `app/Core/Product/EditorProductManifest.*` 为单一事实源。正式 panel 集、action route、runtime owner、viewport renderer owner 先在 manifest 中声明,再派生 shell / menu / command / runtime / viewport 注册。
- UI widget / shell / workspace 代码优先保持 model/state/request/frame/result 风格。新增行为要能被 `tests/UI/Editor/unit` 以纯状态方式测试。
- scene/project 的用户操作应通过 runtime 或 command route 进入,不要在 draw/append 阶段直接改 scene 或文件系统。
- scene 事实所有权走 `app/Core/Scene/EditorSceneBackend.h` 合约。`EditorSceneRuntime` 只消费显式 backend只有 `app/Services/Scene/EngineEditorSceneBackend.*` 可以直接触碰 engine `SceneManager::Get()` / `ResourceManager::Get()` 这类全局 runtime。
- scene 渲染私有逻辑不要塞进 panel 或 shell。新增渲染能力时先判断它属于 engine `Rendering/RHI`、editor viewport pass bundle还是 UI overlay。
- 运行时路径以 `app/Core/Environment/EditorRuntimePaths.h` 为显式契约。workspace、executable、resource、project、capture 根路径由 bootstrap 一次性解析并向下传递;不要在下游重新从 repo root、当前工作目录或文档文件推导运行环境。
- 资源路径、图标、shader、截图输出都应走明确服务或 host 接口。不要硬编码从当前工作目录猜路径;手动验证截图不得写回 source tree。
@@ -30,7 +31,7 @@
- `EditorSelectionService` 是 hierarchy/project/inspector/scene viewport 之间的选择同步核心。不要在单个 panel 内维护另一套长期选择真相。
- `EditorProjectRuntime` 包装 `ProjectBrowserModel`,负责 project tree/grid、选择、文件操作、scene asset open request。文件系统改动后要刷新并 revalidate selection。
- `ProjectPanel` 只消费由 `EditorContext` / `EditorPanelServices` 提供的 `EditorProjectRuntime`,不再拥有或初始化自己的 project runtime。测试也应显式创建 `EditorProjectRuntime` 并通过 `SetProjectRuntime()` 注入,不要把 `projectRoot` 传给 panel。
- `EditorSceneRuntime` 负责 startup scene、editor scene camera、hierarchy selection、component list、transform edit history 和 scene tool state。
- `EditorSceneBackend` 是 scene document/backend 的显式边界;`EngineEditorSceneBackend` 是当前接入 engine 全局 scene/resource runtime 的唯一生产实现。`EditorSceneRuntime` 负责 startup scene 编排、editor scene camera、hierarchy selection、component list、transform edit history 和 scene tool state,但不直接访问 engine 单例
当前目录地图:
@@ -43,10 +44,12 @@
- `include/XCEditor/Windowing`:多窗口 workspace 状态、同步计划和 presentation policy。
- `src/**`:对应公共头的实现。保持偏纯函数/状态机风格。
- `app/Core`:产品级 contracts、session、command focus、selection、panel services、scene/project/viewport/windowing 接口。
- `app/Core/Scene`scene backend 合约和 editor scene runtime 可消费的公共 scene 标识工具。
- `app/Core/Environment`运行时路径契约。bootstrap 负责解析 `EditorRuntimePaths`core/composition/features/rendering 只消费显式路径。
- `app/Core/Product`:产品 manifest。这里定义正式 panel 集、route 归属、runtime owner 和 viewport renderer owner。
- `app/Composition`:装配编辑器 shell。`EditorContext` 拥有 session、project runtime、scene runtime、selection、command bridge`EditorShellRuntime` 驱动 shell interaction、hosted panels 和 viewport runtime。
- `app/Features`:产品面板与场景视图工具。
- `app/Services`:产品 runtime 的生产服务实现。scene 的 engine 接入集中在 `app/Services/Scene/EngineEditorSceneBackend.*`project runtime 集中在 `app/Services/Project`
- `app/Rendering`:编辑器 viewport、icon、object-id picking、grid/outline/helper pass 相关服务。渲染执行仍走 engine `Rendering + RHI`
- `app/Host`宿主接口实现。Win32/D3D12 细节只能待在这里或 rendering host 实现里。
- `app/Windowing`窗口实例、内容控制器、生命周期协调器、workspace 多窗口同步、截图和 frame orchestration。
@@ -77,7 +80,9 @@ wWinMain
- `src/Viewport``include/XCEditor/Viewport`viewport slot/shell/input bridge只处理 UI 容器和输入桥。
- `app/Features/Scene/SceneViewportController.*`:场景视图产品交互,处理 Q/W/E/R 工具切换、F 聚焦、鼠标导航、scene icon picking、transform gizmo。
- `app/Core/Scene/EditorSceneRuntime.*`scene selection、editor camera、transform undo/redo、component mutation 和 scene render request 的事实来源
- `app/Core/Scene/EditorSceneBackend.h`scene backend contract、startup scene result 和 game object item id helpers
- `app/Services/Scene/EditorSceneRuntime.*`scene selection、editor camera、transform undo/redo、component mutation 和 scene render request 的事实来源;它必须通过注入的 `EditorSceneBackend` 访问真实 scene。
- `app/Services/Scene/EngineEditorSceneBackend.*`:当前唯一允许直接接入 engine `SceneManager` / `ResourceManager` 的 scene backend 生产实现。
- `app/Rendering/Viewport/ViewportHostService.*`:离屏 viewport 资源管理和 renderer 注册。
- `app/Rendering/Viewport/SceneViewportRenderService.*`:调用 engine `SceneRenderer`,插入 grid、selection outline、selected helpers、object-id 等 editor pass。
- object-id picking 依赖有效 object-id surface 和 frame serial修改时要覆盖 `test_viewport_object_id_picker.cpp` 及相关 viewport render plan 测试。
@@ -120,9 +125,8 @@ ctest --test-dir build -C Debug -R "editor|xceditor" --output-on-failure
- 已新增 manifest validation 测试,确保产品 manifest 能声明 panel runtime owner 和 viewport renderer owner并覆盖 `game` placeholder 的预期状态。
- 已更新根 `AGENT.md` 和本文件,去掉旧 `--project` 说法,记录当前 XCUI editor、`XCEditorCore` 分层和 product manifest 规则。
- 已移除 `ProjectPanel` 自持 `EditorProjectRuntime` 的 fallback 路径project runtime 事实源现在只来自 `EditorContext`,面板测试改为显式 runtime 注入。
- 已把 scene 的 engine 全局访问收敛到 `EngineEditorSceneBackend`,让 `EditorSceneRuntime` 通过 `EditorSceneBackend` 合约初始化、打开 scene 和执行 hierarchy mutation新增 backend contract 单测覆盖无 backend 失败和 fake backend 注入。
- 本次改动验证过:
- `cmake --build build --config Debug --target XCEditor`
- `cmake --build build --config Debug --target editor_app_core_tests`
- `.\build\tests\UI\Editor\unit\Debug\editor_app_core_tests.exe --gtest_filter=EditorShellAssetValidationTest.*`
- `cmake --build build --config Debug --target editor_app_feature_tests`
- `.\build\tests\UI\Editor\unit\Debug\editor_app_feature_tests.exe`

View File

@@ -283,7 +283,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_CORE)
app/Services/Scene/EditorSceneRuntime.cpp
app/Services/Project/EditorProjectRuntime.cpp
app/Services/Project/ProjectBrowserModel.cpp
app/Services/Scene/EditorSceneBridge.cpp
app/Services/Scene/EngineEditorSceneBackend.cpp
)
set(XCUI_EDITOR_APP_CORE_SOURCES

View File

@@ -1,5 +1,6 @@
#include "EditorContext.h"
#include "EditorShellAssetBuilder.h"
#include "Scene/EngineEditorSceneBackend.h"
#include "Scene/EditorSceneRuntime.h"
#include "Panels/EditorPanelIds.h"
#include "WorkspaceEventSync.h"
@@ -75,7 +76,7 @@ bool EditorContext::Initialize(const EditorRuntimePaths& runtimePaths) {
m_projectRuntime.Initialize(runtimePaths.projectRoot);
AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize end");
m_projectRuntime.BindSelectionService(&m_selectionService);
m_sceneRuntime.Reset();
m_sceneRuntime.SetBackend(CreateEngineEditorSceneBackend());
AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize begin");
m_sceneRuntime.Initialize(m_session.projectRoot);
AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize end");

View File

@@ -0,0 +1,83 @@
#pragma once
#include <XCEngine/Components/GameObject.h>
#include <charconv>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
namespace XCEngine::Components {
class Scene;
class GameObject;
} // namespace XCEngine::Components
namespace XCEngine::UI::Editor::App {
struct EditorStartupSceneResult {
bool ready = false;
bool loadedFromDisk = false;
std::filesystem::path scenePath = {};
std::string sceneName = {};
};
class EditorSceneBackend {
public:
virtual ~EditorSceneBackend() = default;
virtual EditorStartupSceneResult EnsureStartupScene(
const std::filesystem::path& projectRoot) = 0;
virtual ::XCEngine::Components::Scene* GetActiveScene() const = 0;
virtual bool OpenSceneAsset(const std::filesystem::path& scenePath) = 0;
virtual ::XCEngine::Components::GameObject* FindGameObject(
std::string_view itemId) const = 0;
virtual bool RenameGameObject(
std::string_view itemId,
std::string_view newName) = 0;
virtual bool DeleteGameObject(std::string_view itemId) = 0;
virtual std::string DuplicateGameObject(std::string_view itemId) = 0;
virtual bool ReparentGameObject(
std::string_view itemId,
std::string_view parentItemId) = 0;
virtual bool MoveGameObjectBefore(
std::string_view itemId,
std::string_view targetItemId) = 0;
virtual bool MoveGameObjectAfter(
std::string_view itemId,
std::string_view targetItemId) = 0;
virtual bool MoveGameObjectToRoot(std::string_view itemId) = 0;
};
inline std::string MakeEditorGameObjectItemId(
::XCEngine::Components::GameObject::ID id) {
return id == ::XCEngine::Components::GameObject::INVALID_ID
? std::string()
: std::to_string(id);
}
inline std::optional<::XCEngine::Components::GameObject::ID>
ParseEditorGameObjectItemId(std::string_view itemId) {
if (itemId.empty()) {
return std::nullopt;
}
::XCEngine::Components::GameObject::ID parsedId =
::XCEngine::Components::GameObject::INVALID_ID;
const char* first = itemId.data();
const char* last = itemId.data() + itemId.size();
const std::from_chars_result result =
std::from_chars(first, last, parsedId);
if (result.ec != std::errc() ||
result.ptr != last ||
parsedId == ::XCEngine::Components::GameObject::INVALID_ID) {
return std::nullopt;
}
return parsedId;
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -1,6 +1,6 @@
#include "HierarchyModel.h"
#include "Scene/EditorSceneBridge.h"
#include "Scene/EditorSceneBackend.h"
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Scene/Scene.h>

View File

@@ -1,50 +0,0 @@
#pragma once
#include <XCEngine/Components/GameObject.h>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
namespace XCEngine::Components {
class Scene;
} // namespace XCEngine::Components
namespace XCEngine::UI::Editor::App {
struct EditorStartupSceneResult {
bool ready = false;
bool loadedFromDisk = false;
std::filesystem::path scenePath = {};
std::string sceneName = {};
};
EditorStartupSceneResult EnsureEditorStartupScene(
const std::filesystem::path& projectRoot);
::XCEngine::Components::Scene* GetActiveEditorScene();
bool OpenEditorSceneAsset(const std::filesystem::path& scenePath);
std::string MakeEditorGameObjectItemId(::XCEngine::Components::GameObject::ID id);
std::optional<::XCEngine::Components::GameObject::ID> ParseEditorGameObjectItemId(
std::string_view itemId);
::XCEngine::Components::GameObject* FindEditorGameObject(std::string_view itemId);
bool RenameEditorGameObject(
std::string_view itemId,
std::string_view newName);
bool DeleteEditorGameObject(std::string_view itemId);
std::string DuplicateEditorGameObject(std::string_view itemId);
bool ReparentEditorGameObject(
std::string_view itemId,
std::string_view parentItemId);
bool MoveEditorGameObjectBefore(
std::string_view itemId,
std::string_view targetItemId);
bool MoveEditorGameObjectAfter(
std::string_view itemId,
std::string_view targetItemId);
bool MoveEditorGameObjectToRoot(std::string_view itemId);
} // namespace XCEngine::UI::Editor::App

View File

@@ -182,10 +182,18 @@ void EditorSceneRuntime::Reset() {
m_inspectorRevision = 0u;
}
void EditorSceneRuntime::SetBackend(std::unique_ptr<EditorSceneBackend> backend) {
m_backend = std::move(backend);
Reset();
}
bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) {
Reset();
if (m_backend == nullptr) {
return false;
}
m_projectRoot = projectRoot;
m_startupSceneResult = EnsureEditorStartupScene(projectRoot);
m_startupSceneResult = m_backend->EnsureStartupScene(projectRoot);
EnsureSceneViewCamera();
RefreshScene();
return m_startupSceneResult.ready;
@@ -225,7 +233,7 @@ const EditorStartupSceneResult& EditorSceneRuntime::GetStartupResult() const {
}
Scene* EditorSceneRuntime::GetActiveScene() const {
return GetActiveEditorScene();
return m_backend != nullptr ? m_backend->GetActiveScene() : nullptr;
}
::XCEngine::Components::CameraComponent* EditorSceneRuntime::GetSceneViewCamera() {
@@ -389,7 +397,7 @@ void EditorSceneRuntime::ClearSelection() {
}
bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath) {
if (!OpenEditorSceneAsset(scenePath)) {
if (m_backend == nullptr || !m_backend->OpenSceneAsset(scenePath)) {
return false;
}
@@ -413,13 +421,15 @@ bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath)
}
GameObject* EditorSceneRuntime::FindGameObject(std::string_view itemId) const {
return FindEditorGameObject(itemId);
return m_backend != nullptr ? m_backend->FindGameObject(itemId) : nullptr;
}
bool EditorSceneRuntime::RenameGameObject(
std::string_view itemId,
std::string_view newName) {
const bool renamed = RenameEditorGameObject(itemId, newName);
const bool renamed =
m_backend != nullptr &&
m_backend->RenameGameObject(itemId, newName);
if (renamed) {
IncrementInspectorRevision();
}
@@ -429,7 +439,9 @@ bool EditorSceneRuntime::RenameGameObject(
bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
ResetTransformEditHistory();
const bool deleted = DeleteEditorGameObject(itemId);
const bool deleted =
m_backend != nullptr &&
m_backend->DeleteGameObject(itemId);
if (deleted) {
IncrementInspectorRevision();
}
@@ -440,7 +452,10 @@ bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) {
ResetTransformEditHistory();
const std::string duplicatedItemId = DuplicateEditorGameObject(itemId);
const std::string duplicatedItemId =
m_backend != nullptr
? m_backend->DuplicateGameObject(itemId)
: std::string();
if (!duplicatedItemId.empty()) {
IncrementInspectorRevision();
SetSelection(duplicatedItemId);
@@ -455,7 +470,8 @@ bool EditorSceneRuntime::ReparentGameObject(
std::string_view parentItemId) {
ResetTransformEditHistory();
const bool reparented =
ReparentEditorGameObject(itemId, parentItemId);
m_backend != nullptr &&
m_backend->ReparentGameObject(itemId, parentItemId);
if (reparented) {
IncrementInspectorRevision();
}
@@ -468,7 +484,8 @@ bool EditorSceneRuntime::MoveGameObjectBefore(
std::string_view targetItemId) {
ResetTransformEditHistory();
const bool moved =
MoveEditorGameObjectBefore(itemId, targetItemId);
m_backend != nullptr &&
m_backend->MoveGameObjectBefore(itemId, targetItemId);
if (moved) {
IncrementInspectorRevision();
}
@@ -481,7 +498,8 @@ bool EditorSceneRuntime::MoveGameObjectAfter(
std::string_view targetItemId) {
ResetTransformEditHistory();
const bool moved =
MoveEditorGameObjectAfter(itemId, targetItemId);
m_backend != nullptr &&
m_backend->MoveGameObjectAfter(itemId, targetItemId);
if (moved) {
IncrementInspectorRevision();
}
@@ -491,7 +509,9 @@ bool EditorSceneRuntime::MoveGameObjectAfter(
bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) {
ResetTransformEditHistory();
const bool moved = MoveEditorGameObjectToRoot(itemId);
const bool moved =
m_backend != nullptr &&
m_backend->MoveGameObjectToRoot(itemId);
if (moved) {
IncrementInspectorRevision();
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "Scene/EditorSceneBridge.h"
#include "Scene/EditorSceneBackend.h"
#include "Scene/SceneViewportCameraController.h"
#include "Scene/SceneToolState.h"
#include "State/EditorSelectionService.h"
@@ -55,6 +55,7 @@ public:
EditorSceneRuntime& operator=(EditorSceneRuntime&&) = delete;
void Reset();
void SetBackend(std::unique_ptr<EditorSceneBackend> backend);
bool Initialize(const std::filesystem::path& projectRoot);
void BindSelectionService(EditorSelectionService* selectionService);
@@ -175,6 +176,7 @@ private:
void IncrementInspectorRevision();
std::filesystem::path m_projectRoot = {};
std::unique_ptr<EditorSceneBackend> m_backend = {};
EditorStartupSceneResult m_startupSceneResult = {};
EditorSelectionService m_ownedSelectionService = {};
EditorSelectionService* m_selectionService = &m_ownedSelectionService;

View File

@@ -1,4 +1,4 @@
#include "Scene/EditorSceneBridge.h"
#include "Scene/EngineEditorSceneBackend.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEngine/Components/ComponentFactoryRegistry.h>
@@ -8,7 +8,7 @@
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Scene/SceneManager.h>
#include <charconv>
#include <algorithm>
#include <filesystem>
#include <optional>
#include <sstream>
@@ -169,13 +169,24 @@ std::optional<std::size_t> FindSiblingIndex(
return static_cast<std::size_t>(std::distance(siblings.begin(), it));
}
GameObject* FindGameObjectByItemId(std::string_view itemId) {
Scene* scene = ResolvePrimaryScene();
const std::optional<GameObject::ID> gameObjectId =
ParseEditorGameObjectItemId(itemId);
if (scene == nullptr || !gameObjectId.has_value()) {
return nullptr;
}
return scene->FindByID(gameObjectId.value());
}
bool MoveGameObjectRelativeToTarget(
std::string_view itemId,
std::string_view targetItemId,
bool placeAfterTarget) {
Scene* scene = ResolvePrimaryScene();
GameObject* gameObject = FindEditorGameObject(itemId);
GameObject* target = FindEditorGameObject(targetItemId);
GameObject* gameObject = FindGameObjectByItemId(itemId);
GameObject* target = FindGameObjectByItemId(targetItemId);
if (scene == nullptr ||
gameObject == nullptr ||
target == nullptr ||
@@ -218,211 +229,192 @@ bool MoveGameObjectRelativeToTarget(
return true;
}
} // namespace
class EngineEditorSceneBackend final : public EditorSceneBackend {
public:
EditorStartupSceneResult EnsureStartupScene(
const std::filesystem::path& projectRoot) override {
EditorStartupSceneResult result = {};
TraceSceneStartup("EnsureStartupScene begin projectRoot=" + projectRoot.string());
if (projectRoot.empty()) {
return result;
}
EditorStartupSceneResult EnsureEditorStartupScene(
const std::filesystem::path& projectRoot) {
EditorStartupSceneResult result = {};
TraceSceneStartup("EnsureEditorStartupScene begin projectRoot=" + projectRoot.string());
if (projectRoot.empty()) {
ResourceManager::Get().SetResourceRoot(projectRoot.string().c_str());
TraceSceneStartup("ResourceManager::SetResourceRoot complete");
if (Scene* activeScene = ResolvePrimaryScene();
activeScene != nullptr) {
result.ready = true;
result.sceneName = activeScene->GetName();
TraceSceneStartup("EnsureStartupScene reused active scene=" + result.sceneName);
return result;
}
const std::filesystem::path startupScenePath =
(projectRoot / "Assets" / "Scenes" / "Main.xc").lexically_normal();
SceneManager& sceneManager = SceneManager::Get();
if (std::filesystem::exists(startupScenePath) &&
std::filesystem::is_regular_file(startupScenePath)) {
TraceSceneStartup(
"SceneManager::LoadScene begin path=" + startupScenePath.string());
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
ResourceManager::Get());
sceneManager.LoadScene(startupScenePath.string());
}
TraceSceneStartup("SceneManager::LoadScene end");
Scene* loadedScene =
sceneManager.GetScene(startupScenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene();
} else {
sceneManager.SetActiveScene(loadedScene);
}
if (loadedScene != nullptr) {
result.ready = true;
result.loadedFromDisk = true;
result.scenePath = startupScenePath;
result.sceneName = loadedScene->GetName();
TraceSceneStartup(
"EnsureStartupScene loaded scene=" + result.sceneName);
return result;
}
}
if (Scene* scene = sceneManager.CreateScene("Main");
scene != nullptr) {
sceneManager.SetActiveScene(scene);
result.ready = true;
result.sceneName = scene->GetName();
TraceSceneStartup(
"EnsureStartupScene created scene=" + result.sceneName);
}
TraceSceneStartup(
std::string("EnsureStartupScene end ready=") +
(result.ready ? "1" : "0"));
return result;
}
ResourceManager::Get().SetResourceRoot(projectRoot.string().c_str());
TraceSceneStartup("ResourceManager::SetResourceRoot complete");
if (Scene* activeScene = ResolvePrimaryScene();
activeScene != nullptr) {
result.ready = true;
result.sceneName = activeScene->GetName();
TraceSceneStartup("EnsureEditorStartupScene reused active scene=" + result.sceneName);
return result;
Scene* GetActiveScene() const override {
return ResolvePrimaryScene();
}
const std::filesystem::path startupScenePath =
(projectRoot / "Assets" / "Scenes" / "Main.xc").lexically_normal();
SceneManager& sceneManager = SceneManager::Get();
bool OpenSceneAsset(const std::filesystem::path& scenePath) override {
if (scenePath.empty()) {
return false;
}
if (std::filesystem::exists(startupScenePath) &&
std::filesystem::is_regular_file(startupScenePath)) {
TraceSceneStartup("SceneManager::LoadScene begin path=" + startupScenePath.string());
std::error_code errorCode = {};
if (!std::filesystem::exists(scenePath, errorCode) ||
errorCode ||
!std::filesystem::is_regular_file(scenePath, errorCode)) {
return false;
}
SceneManager& sceneManager = SceneManager::Get();
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
ResourceManager::Get());
sceneManager.LoadScene(startupScenePath.string());
sceneManager.LoadScene(scenePath.string());
}
TraceSceneStartup("SceneManager::LoadScene end");
Scene* loadedScene = sceneManager.GetScene(startupScenePath.stem().string());
Scene* loadedScene = sceneManager.GetScene(scenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene();
} else {
sceneManager.SetActiveScene(loadedScene);
}
if (loadedScene != nullptr) {
result.ready = true;
result.loadedFromDisk = true;
result.scenePath = startupScenePath;
result.sceneName = loadedScene->GetName();
TraceSceneStartup("EnsureEditorStartupScene loaded scene=" + result.sceneName);
return result;
return loadedScene != nullptr;
}
GameObject* FindGameObject(std::string_view itemId) const override {
return FindGameObjectByItemId(itemId);
}
bool RenameGameObject(
std::string_view itemId,
std::string_view newName) override {
GameObject* gameObject = FindGameObject(itemId);
if (gameObject == nullptr) {
return false;
}
gameObject->SetName(std::string(newName));
return true;
}
if (Scene* scene = sceneManager.CreateScene("Main");
scene != nullptr) {
sceneManager.SetActiveScene(scene);
result.ready = true;
result.sceneName = scene->GetName();
TraceSceneStartup("EnsureEditorStartupScene created scene=" + result.sceneName);
bool DeleteGameObject(std::string_view itemId) override {
Scene* scene = ResolvePrimaryScene();
GameObject* gameObject = FindGameObject(itemId);
if (scene == nullptr || gameObject == nullptr) {
return false;
}
scene->DestroyGameObject(gameObject);
return true;
}
TraceSceneStartup(
std::string("EnsureEditorStartupScene end ready=") +
(result.ready ? "1" : "0"));
return result;
}
std::string DuplicateGameObject(std::string_view itemId) override {
Scene* scene = ResolvePrimaryScene();
GameObject* gameObject = FindGameObject(itemId);
if (scene == nullptr || gameObject == nullptr) {
return {};
}
Scene* GetActiveEditorScene() {
return ResolvePrimaryScene();
}
bool OpenEditorSceneAsset(const std::filesystem::path& scenePath) {
if (scenePath.empty()) {
return false;
const ClipboardNode clipboard = CopyGameObjectRecursive(*gameObject);
GameObject* duplicate =
PasteGameObjectRecursive(*scene, clipboard, gameObject->GetParent());
return duplicate != nullptr
? MakeEditorGameObjectItemId(duplicate->GetID())
: std::string();
}
std::error_code errorCode = {};
if (!std::filesystem::exists(scenePath, errorCode) ||
errorCode ||
!std::filesystem::is_regular_file(scenePath, errorCode)) {
return false;
bool ReparentGameObject(
std::string_view itemId,
std::string_view parentItemId) override {
GameObject* gameObject = FindGameObject(itemId);
GameObject* newParent = FindGameObject(parentItemId);
if (gameObject == nullptr || newParent == nullptr ||
gameObject == newParent ||
gameObject->GetParent() == newParent ||
WouldCreateCycle(*gameObject, *newParent)) {
return false;
}
gameObject->SetParent(newParent);
return true;
}
SceneManager& sceneManager = SceneManager::Get();
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
ResourceManager::Get());
sceneManager.LoadScene(scenePath.string());
}
Scene* loadedScene = sceneManager.GetScene(scenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene();
} else {
sceneManager.SetActiveScene(loadedScene);
bool MoveGameObjectBefore(
std::string_view itemId,
std::string_view targetItemId) override {
return MoveGameObjectRelativeToTarget(itemId, targetItemId, false);
}
return loadedScene != nullptr;
}
std::string MakeEditorGameObjectItemId(GameObject::ID id) {
return id == GameObject::INVALID_ID ? std::string() : std::to_string(id);
}
std::optional<GameObject::ID> ParseEditorGameObjectItemId(
std::string_view itemId) {
if (itemId.empty()) {
return std::nullopt;
bool MoveGameObjectAfter(
std::string_view itemId,
std::string_view targetItemId) override {
return MoveGameObjectRelativeToTarget(itemId, targetItemId, true);
}
GameObject::ID parsedId = GameObject::INVALID_ID;
const char* first = itemId.data();
const char* last = itemId.data() + itemId.size();
const std::from_chars_result result =
std::from_chars(first, last, parsedId);
if (result.ec != std::errc() || result.ptr != last ||
parsedId == GameObject::INVALID_ID) {
return std::nullopt;
bool MoveGameObjectToRoot(std::string_view itemId) override {
GameObject* gameObject = FindGameObject(itemId);
if (gameObject == nullptr || gameObject->GetParent() == nullptr) {
return false;
}
gameObject->SetParent(nullptr);
return true;
}
};
return parsedId;
}
} // namespace
GameObject* FindEditorGameObject(std::string_view itemId) {
Scene* scene = ResolvePrimaryScene();
const std::optional<GameObject::ID> gameObjectId =
ParseEditorGameObjectItemId(itemId);
if (scene == nullptr || !gameObjectId.has_value()) {
return nullptr;
}
return scene->FindByID(gameObjectId.value());
}
bool RenameEditorGameObject(
std::string_view itemId,
std::string_view newName) {
GameObject* gameObject = FindEditorGameObject(itemId);
if (gameObject == nullptr) {
return false;
}
gameObject->SetName(std::string(newName));
return true;
}
bool DeleteEditorGameObject(std::string_view itemId) {
Scene* scene = ResolvePrimaryScene();
GameObject* gameObject = FindEditorGameObject(itemId);
if (scene == nullptr || gameObject == nullptr) {
return false;
}
scene->DestroyGameObject(gameObject);
return true;
}
std::string DuplicateEditorGameObject(std::string_view itemId) {
Scene* scene = ResolvePrimaryScene();
GameObject* gameObject = FindEditorGameObject(itemId);
if (scene == nullptr || gameObject == nullptr) {
return {};
}
const ClipboardNode clipboard = CopyGameObjectRecursive(*gameObject);
GameObject* duplicate =
PasteGameObjectRecursive(*scene, clipboard, gameObject->GetParent());
return duplicate != nullptr
? MakeEditorGameObjectItemId(duplicate->GetID())
: std::string();
}
bool ReparentEditorGameObject(
std::string_view itemId,
std::string_view parentItemId) {
GameObject* gameObject = FindEditorGameObject(itemId);
GameObject* newParent = FindEditorGameObject(parentItemId);
if (gameObject == nullptr || newParent == nullptr ||
gameObject == newParent ||
gameObject->GetParent() == newParent ||
WouldCreateCycle(*gameObject, *newParent)) {
return false;
}
gameObject->SetParent(newParent);
return true;
}
bool MoveEditorGameObjectBefore(
std::string_view itemId,
std::string_view targetItemId) {
return MoveGameObjectRelativeToTarget(itemId, targetItemId, false);
}
bool MoveEditorGameObjectAfter(
std::string_view itemId,
std::string_view targetItemId) {
return MoveGameObjectRelativeToTarget(itemId, targetItemId, true);
}
bool MoveEditorGameObjectToRoot(std::string_view itemId) {
GameObject* gameObject = FindEditorGameObject(itemId);
if (gameObject == nullptr || gameObject->GetParent() == nullptr) {
return false;
}
gameObject->SetParent(nullptr);
return true;
std::unique_ptr<EditorSceneBackend> CreateEngineEditorSceneBackend() {
return std::make_unique<EngineEditorSceneBackend>();
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,11 @@
#pragma once
#include "Scene/EditorSceneBackend.h"
#include <memory>
namespace XCEngine::UI::Editor::App {
std::unique_ptr<EditorSceneBackend> CreateEngineEditorSceneBackend();
} // namespace XCEngine::UI::Editor::App

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);