Files
XCEngine/editor/app/Services/Engine/EngineEditorServices.cpp

481 lines
15 KiB
C++

#include "Engine/EngineEditorServices.h"
#include "Scene/EngineEditorSceneBackend.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEngine/Components/ComponentFactoryRegistry.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/TransformComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Rendering/Picking/RenderObjectIdRegistry.h>
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Scene/SceneManager.h>
#include <algorithm>
#include <filesystem>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::Components::Component;
using ::XCEngine::Components::ComponentFactoryRegistry;
using ::XCEngine::Components::GameObject;
using ::XCEngine::Components::Scene;
using ::XCEngine::Components::SceneManager;
using ::XCEngine::Components::TransformComponent;
using ::XCEngine::Rendering::RenderObjectId;
using ::XCEngine::Rendering::RenderObjectIdRegistry;
using ::XCEngine::Resources::ResourceManager;
using ::XCEngine::Resources::Shader;
void TraceSceneStartup(std::string message) {
::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message));
}
struct ClipboardNode {
std::string name = {};
std::string transformPayload = {};
std::vector<std::pair<std::string, std::string>> components = {};
std::vector<ClipboardNode> children = {};
};
Scene* ResolvePrimaryScene(SceneManager& sceneManager) {
if (Scene* activeScene = sceneManager.GetActiveScene();
activeScene != nullptr) {
return activeScene;
}
const std::vector<Scene*> scenes = sceneManager.GetAllScenes();
if (!scenes.empty() && scenes.front() != nullptr) {
sceneManager.SetActiveScene(scenes.front());
return scenes.front();
}
return nullptr;
}
std::pair<std::string, std::string> SerializeComponent(
const Component* component) {
std::ostringstream payload = {};
component->Serialize(payload);
return { component->GetName(), payload.str() };
}
ClipboardNode CopyGameObjectRecursive(const GameObject& gameObject) {
ClipboardNode node = {};
node.name = gameObject.GetName();
if (const TransformComponent* transform = gameObject.GetTransform();
transform != nullptr) {
std::ostringstream payload = {};
transform->Serialize(payload);
node.transformPayload = payload.str();
}
const auto components = gameObject.GetComponents<Component>();
for (const Component* component : components) {
if (component == nullptr || component == gameObject.GetTransform()) {
continue;
}
node.components.push_back(SerializeComponent(component));
}
for (GameObject* child : gameObject.GetChildren()) {
if (child == nullptr) {
continue;
}
node.children.push_back(CopyGameObjectRecursive(*child));
}
return node;
}
GameObject* PasteGameObjectRecursive(
Scene& scene,
const ClipboardNode& node,
GameObject* parent) {
GameObject* gameObject = scene.CreateGameObject(node.name, parent);
if (gameObject == nullptr) {
return nullptr;
}
if (!node.transformPayload.empty()) {
if (TransformComponent* transform = gameObject->GetTransform();
transform != nullptr) {
std::istringstream transformStream(node.transformPayload);
transform->Deserialize(transformStream);
}
}
for (const auto& componentData : node.components) {
Component* component =
ComponentFactoryRegistry::Get().CreateComponent(
gameObject,
componentData.first);
if (component == nullptr || componentData.second.empty()) {
continue;
}
std::istringstream payloadStream(componentData.second);
component->Deserialize(payloadStream);
}
for (const ClipboardNode& child : node.children) {
PasteGameObjectRecursive(scene, child, gameObject);
}
return gameObject;
}
bool WouldCreateCycle(
const GameObject& source,
const GameObject& targetParent) {
const GameObject* current = &targetParent;
while (current != nullptr) {
if (current == &source) {
return true;
}
current = current->GetParent();
}
return false;
}
std::vector<GameObject*> ResolveSiblingSequence(
Scene& scene,
GameObject* parent) {
return parent != nullptr
? parent->GetChildren()
: scene.GetRootGameObjects();
}
std::optional<std::size_t> FindSiblingIndex(
const std::vector<GameObject*>& siblings,
const GameObject* gameObject) {
if (gameObject == nullptr) {
return std::nullopt;
}
const auto it =
std::find(siblings.begin(), siblings.end(), gameObject);
if (it == siblings.end()) {
return std::nullopt;
}
return static_cast<std::size_t>(std::distance(siblings.begin(), it));
}
GameObject* FindGameObjectByItemId(
SceneManager& sceneManager,
std::string_view itemId) {
Scene* scene = ResolvePrimaryScene(sceneManager);
const std::optional<GameObject::ID> gameObjectId =
ParseEditorGameObjectItemId(itemId);
if (scene == nullptr || !gameObjectId.has_value()) {
return nullptr;
}
return scene->FindByID(gameObjectId.value());
}
bool MoveGameObjectRelativeToTarget(
SceneManager& sceneManager,
std::string_view itemId,
std::string_view targetItemId,
bool placeAfterTarget) {
Scene* scene = ResolvePrimaryScene(sceneManager);
GameObject* gameObject = FindGameObjectByItemId(sceneManager, itemId);
GameObject* target = FindGameObjectByItemId(sceneManager, targetItemId);
if (scene == nullptr ||
gameObject == nullptr ||
target == nullptr ||
gameObject == target) {
return false;
}
GameObject* targetParent = target->GetParent();
if (targetParent != nullptr &&
WouldCreateCycle(*gameObject, *targetParent)) {
return false;
}
const std::vector<GameObject*> siblings =
ResolveSiblingSequence(*scene, targetParent);
const std::optional<std::size_t> targetIndex =
FindSiblingIndex(siblings, target);
if (!targetIndex.has_value()) {
return false;
}
std::size_t finalIndex =
targetIndex.value() + (placeAfterTarget ? 1u : 0u);
if (gameObject->GetParent() == targetParent) {
const std::optional<std::size_t> sourceIndex =
FindSiblingIndex(siblings, gameObject);
if (sourceIndex.has_value()) {
if ((!placeAfterTarget && sourceIndex.value() < targetIndex.value()) ||
(placeAfterTarget && sourceIndex.value() <= targetIndex.value())) {
--finalIndex;
}
if (finalIndex == sourceIndex.value()) {
return false;
}
}
}
gameObject->SetParent(targetParent, finalIndex, true);
return true;
}
class EngineEditorSceneBackend final : public EditorSceneBackend {
public:
EngineEditorSceneBackend(
SceneManager& sceneManager,
ResourceManager& resourceManager)
: m_sceneManager(sceneManager)
, m_resourceManager(resourceManager) {}
EditorStartupSceneResult EnsureStartupScene(
const std::filesystem::path& projectRoot) override {
EditorStartupSceneResult result = {};
TraceSceneStartup("EnsureStartupScene begin projectRoot=" + projectRoot.string());
if (projectRoot.empty()) {
return result;
}
m_resourceManager.SetResourceRoot(projectRoot.string().c_str());
TraceSceneStartup("ResourceManager::SetResourceRoot complete");
if (Scene* activeScene = ResolvePrimaryScene(m_sceneManager);
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();
if (std::filesystem::exists(startupScenePath) &&
std::filesystem::is_regular_file(startupScenePath)) {
TraceSceneStartup(
"SceneManager::LoadScene begin path=" + startupScenePath.string());
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
m_resourceManager);
m_sceneManager.LoadScene(startupScenePath.string());
}
TraceSceneStartup("SceneManager::LoadScene end");
Scene* loadedScene =
m_sceneManager.GetScene(startupScenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene(m_sceneManager);
} else {
m_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 = m_sceneManager.CreateScene("Main");
scene != nullptr) {
m_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;
}
Scene* GetActiveScene() const override {
return ResolvePrimaryScene(m_sceneManager);
}
bool OpenSceneAsset(const std::filesystem::path& scenePath) override {
if (scenePath.empty()) {
return false;
}
std::error_code errorCode = {};
if (!std::filesystem::exists(scenePath, errorCode) ||
errorCode ||
!std::filesystem::is_regular_file(scenePath, errorCode)) {
return false;
}
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
m_resourceManager);
m_sceneManager.LoadScene(scenePath.string());
}
Scene* loadedScene = m_sceneManager.GetScene(scenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene(m_sceneManager);
} else {
m_sceneManager.SetActiveScene(loadedScene);
}
return loadedScene != nullptr;
}
GameObject* FindGameObject(std::string_view itemId) const override {
return FindGameObjectByItemId(m_sceneManager, 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;
}
bool DeleteGameObject(std::string_view itemId) override {
Scene* scene = ResolvePrimaryScene(m_sceneManager);
GameObject* gameObject = FindGameObject(itemId);
if (scene == nullptr || gameObject == nullptr) {
return false;
}
scene->DestroyGameObject(gameObject);
return true;
}
std::string DuplicateGameObject(std::string_view itemId) override {
Scene* scene = ResolvePrimaryScene(m_sceneManager);
GameObject* gameObject = FindGameObject(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 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;
}
bool MoveGameObjectBefore(
std::string_view itemId,
std::string_view targetItemId) override {
return MoveGameObjectRelativeToTarget(
m_sceneManager,
itemId,
targetItemId,
false);
}
bool MoveGameObjectAfter(
std::string_view itemId,
std::string_view targetItemId) override {
return MoveGameObjectRelativeToTarget(
m_sceneManager,
itemId,
targetItemId,
true);
}
bool MoveGameObjectToRoot(std::string_view itemId) override {
GameObject* gameObject = FindGameObject(itemId);
if (gameObject == nullptr || gameObject->GetParent() == nullptr) {
return false;
}
gameObject->SetParent(nullptr);
return true;
}
private:
SceneManager& m_sceneManager;
ResourceManager& m_resourceManager;
};
class EngineEditorServices final : public EditorEngineServices {
public:
void UpdateAsyncLoads() override {
ResourceManager::Get().UpdateAsyncLoads();
}
void Shutdown() override {
ResourceManager::Get().Shutdown();
}
std::unique_ptr<EditorSceneBackend> CreateSceneBackend() override {
return CreateEngineEditorSceneBackend(
SceneManager::Get(),
ResourceManager::Get());
}
::XCEngine::Resources::ResourceHandle<Shader> LoadShader(
const ::XCEngine::Containers::String& path) override {
return ResourceManager::Get().Load<Shader>(path);
}
bool TryResolveRenderObjectId(
RenderObjectId renderObjectId,
std::uint64_t& outRuntimeObjectId) const override {
return RenderObjectIdRegistry::Get().TryResolveRuntimeObjectId(
renderObjectId,
outRuntimeObjectId);
}
};
} // namespace
std::unique_ptr<EditorSceneBackend> CreateEngineEditorSceneBackend(
SceneManager& sceneManager,
ResourceManager& resourceManager) {
return std::make_unique<EngineEditorSceneBackend>(
sceneManager,
resourceManager);
}
std::unique_ptr<EditorEngineServices> CreateEngineEditorServices() {
return std::make_unique<EngineEditorServices>();
}
} // namespace XCEngine::UI::Editor::App