refactor(editor): isolate scene backend boundary
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
83
editor/app/Core/Scene/EditorSceneBackend.h
Normal file
83
editor/app/Core/Scene/EditorSceneBackend.h
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
11
editor/app/Services/Scene/EngineEditorSceneBackend.h
Normal file
11
editor/app/Services/Scene/EngineEditorSceneBackend.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
124
tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp
Normal file
124
tests/UI/Editor/unit/test_editor_scene_runtime_backend.cpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user