#include "Engine/EngineEditorServices.h" #include "Scene/EngineEditorSceneBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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::Math::Quaternion; using ::XCEngine::Math::Vector3; using ::XCEngine::Rendering::RenderObjectId; using ::XCEngine::Rendering::RenderObjectIdRegistry; using ::XCEngine::Resources::ResourceManager; using ::XCEngine::Resources::Shader; constexpr char kComponentIdSeparator = '#'; void TraceSceneStartup(std::string message) { ::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message)); } struct ClipboardNode { std::string name = {}; std::string transformPayload = {}; std::vector> components = {}; std::vector children = {}; }; Scene* ResolvePrimaryScene(SceneManager& sceneManager) { if (Scene* activeScene = sceneManager.GetActiveScene(); activeScene != nullptr) { return activeScene; } const std::vector scenes = sceneManager.GetAllScenes(); if (!scenes.empty() && scenes.front() != nullptr) { sceneManager.SetActiveScene(scenes.front()); return scenes.front(); } return nullptr; } std::pair 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(); 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 ResolveSiblingSequence( Scene& scene, GameObject* parent) { return parent != nullptr ? parent->GetChildren() : scene.GetRootGameObjects(); } std::optional FindSiblingIndex( const std::vector& 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::distance(siblings.begin(), it)); } GameObject* FindGameObjectByItemId( SceneManager& sceneManager, std::string_view itemId) { Scene* scene = ResolvePrimaryScene(sceneManager); const std::optional gameObjectId = ParseEditorGameObjectItemId(itemId); if (scene == nullptr || !gameObjectId.has_value()) { return nullptr; } return scene->FindByID(gameObjectId.value()); } EditorSceneHierarchyNode BuildHierarchySnapshotNodeRecursive( const GameObject& gameObject) { EditorSceneHierarchyNode node = {}; node.itemId = MakeEditorGameObjectItemId(gameObject.GetID()); node.displayName = gameObject.GetName().empty() ? std::string("GameObject") : gameObject.GetName(); node.children.reserve(gameObject.GetChildCount()); for (std::size_t childIndex = 0u; childIndex < gameObject.GetChildCount(); ++childIndex) { const GameObject* child = gameObject.GetChild(childIndex); if (child == nullptr) { continue; } node.children.push_back(BuildHierarchySnapshotNodeRecursive(*child)); } return node; } std::string BuildEditorComponentId( std::string_view typeName, std::size_t ordinal) { std::string componentId(typeName); componentId.push_back(kComponentIdSeparator); componentId += std::to_string(ordinal); return componentId; } bool ParseEditorComponentId( std::string_view componentId, std::string& outTypeName, std::size_t& outOrdinal) { const std::size_t separatorIndex = componentId.find(kComponentIdSeparator); if (separatorIndex == std::string_view::npos || separatorIndex == 0u || separatorIndex + 1u >= componentId.size()) { return false; } outTypeName = std::string(componentId.substr(0u, separatorIndex)); std::size_t ordinal = 0u; const std::string_view ordinalText = componentId.substr(separatorIndex + 1u); const char* first = ordinalText.data(); const char* last = ordinalText.data() + ordinalText.size(); const std::from_chars_result result = std::from_chars(first, last, ordinal); if (result.ec != std::errc() || result.ptr != last) { outTypeName.clear(); return false; } outOrdinal = ordinal; return true; } EditorSceneComponentDescriptor BuildComponentDescriptor( const Component& component, std::size_t ordinal) { EditorSceneComponentDescriptor descriptor = {}; descriptor.typeName = component.GetName(); descriptor.componentId = BuildEditorComponentId(descriptor.typeName, ordinal); descriptor.component = &component; descriptor.removable = component.GetGameObject() != nullptr && component.GetGameObject()->GetTransform() != &component; return descriptor; } EditorSceneComponentDescriptor ResolveComponentDescriptor( const GameObject& gameObject, std::string_view componentId) { std::string typeName = {}; std::size_t ordinal = 0u; if (!ParseEditorComponentId(componentId, typeName, ordinal)) { return {}; } std::size_t currentOrdinal = 0u; for (const Component* component : gameObject.GetComponents()) { if (component == nullptr || component->GetName() != typeName) { continue; } if (currentOrdinal == ordinal) { return BuildComponentDescriptor(*component, currentOrdinal); } ++currentOrdinal; } return {}; } 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 siblings = ResolveSiblingSequence(*scene, targetParent); const std::optional 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 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); } EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override { EditorSceneHierarchySnapshot snapshot = {}; Scene* scene = ResolvePrimaryScene(m_sceneManager); if (scene == nullptr) { return snapshot; } const std::vector roots = scene->GetRootGameObjects(); snapshot.roots.reserve(roots.size()); for (const GameObject* root : roots) { if (root == nullptr) { continue; } snapshot.roots.push_back(BuildHierarchySnapshotNodeRecursive(*root)); } return snapshot; } 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 AddComponent( std::string_view itemId, std::string_view componentTypeName) override { if (componentTypeName.empty()) { return false; } GameObject* gameObject = FindGameObject(itemId); if (gameObject == nullptr) { return false; } Component* addedComponent = ComponentFactoryRegistry::Get().CreateComponent( gameObject, std::string(componentTypeName)); return addedComponent != nullptr; } std::vector GetComponents( std::string_view itemId) const override { std::vector descriptors = {}; const GameObject* gameObject = FindGameObject(itemId); if (gameObject == nullptr) { return descriptors; } const std::vector components = gameObject->GetComponents(); descriptors.reserve(components.size()); std::unordered_map ordinalsByType = {}; for (const Component* component : components) { if (component == nullptr) { continue; } const std::string typeName = component->GetName(); const std::size_t ordinal = ordinalsByType[typeName]; descriptors.push_back(BuildComponentDescriptor(*component, ordinal)); ordinalsByType[typeName] = ordinal + 1u; } return descriptors; } bool RemoveComponent( std::string_view itemId, std::string_view componentId) override { GameObject* gameObject = FindGameObject(itemId); if (gameObject == nullptr) { return false; } const EditorSceneComponentDescriptor descriptor = ResolveComponentDescriptor(*gameObject, componentId); Component* component = const_cast(descriptor.component); if (!descriptor.IsValid() || !descriptor.removable || component == nullptr) { return false; } return gameObject->RemoveComponent(component); } bool SetTransformLocalPosition( std::string_view itemId, std::string_view componentId, const Vector3& position) override { TransformComponent* transform = ResolveTransformComponent(itemId, componentId); if (transform == nullptr) { return false; } transform->SetLocalPosition(position); return true; } bool SetTransformLocalEulerAngles( std::string_view itemId, std::string_view componentId, const Vector3& eulerAngles) override { TransformComponent* transform = ResolveTransformComponent(itemId, componentId); if (transform == nullptr) { return false; } transform->SetLocalEulerAngles(eulerAngles); return true; } bool SetTransformLocalScale( std::string_view itemId, std::string_view componentId, const Vector3& scale) override { TransformComponent* transform = ResolveTransformComponent(itemId, componentId); if (transform == nullptr) { return false; } transform->SetLocalScale(scale); return true; } bool MutateComponent( std::string_view itemId, std::string_view componentId, const std::function& mutation) override { if (!mutation) { return false; } GameObject* gameObject = FindGameObject(itemId); if (gameObject == nullptr) { return false; } const EditorSceneComponentDescriptor descriptor = ResolveComponentDescriptor(*gameObject, componentId); Component* component = const_cast(descriptor.component); return descriptor.IsValid() && component != nullptr && mutation(*component); } bool QueryWorldTransform( EditorSceneObjectId objectId, Vector3& outPosition, Quaternion& outRotation, Vector3& outScale) const override { Scene* scene = ResolvePrimaryScene(m_sceneManager); GameObject* gameObject = scene != nullptr ? scene->FindByID(objectId) : nullptr; const TransformComponent* transform = gameObject != nullptr ? gameObject->GetTransform() : nullptr; if (transform == nullptr) { return false; } outPosition = transform->GetPosition(); outRotation = transform->GetRotation(); outScale = transform->GetScale(); return true; } bool SetWorldTransform( EditorSceneObjectId objectId, const Vector3& position, const Quaternion& rotation, const Vector3& scale) override { TransformComponent* transform = ResolveTransformComponent(objectId); if (transform == nullptr) { return false; } transform->SetPosition(position); transform->SetRotation(rotation); transform->SetScale(scale); return true; } bool SetWorldPositionRotation( EditorSceneObjectId objectId, const Vector3& position, const Quaternion& rotation) override { TransformComponent* transform = ResolveTransformComponent(objectId); if (transform == nullptr) { return false; } transform->SetPosition(position); transform->SetRotation(rotation); return true; } bool SetObjectLocalScale( EditorSceneObjectId objectId, const Vector3& localScale) override { TransformComponent* transform = ResolveTransformComponent(objectId); if (transform == nullptr) { return false; } transform->SetLocalScale(localScale); return true; } 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: TransformComponent* ResolveTransformComponent(EditorSceneObjectId objectId) const { Scene* scene = ResolvePrimaryScene(m_sceneManager); GameObject* gameObject = scene != nullptr ? scene->FindByID(objectId) : nullptr; return gameObject != nullptr ? gameObject->GetTransform() : nullptr; } TransformComponent* ResolveTransformComponent( std::string_view itemId, std::string_view componentId) const { GameObject* gameObject = FindGameObject(itemId); if (gameObject == nullptr) { return nullptr; } const EditorSceneComponentDescriptor descriptor = ResolveComponentDescriptor(*gameObject, componentId); return const_cast( dynamic_cast(descriptor.component)); } 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 CreateSceneBackend() override { return CreateEngineEditorSceneBackend( SceneManager::Get(), ResourceManager::Get()); } ::XCEngine::Resources::ResourceHandle LoadShader( const ::XCEngine::Containers::String& path) override { return ResourceManager::Get().Load(path); } bool TryResolveRenderObjectId( RenderObjectId renderObjectId, std::uint64_t& outRuntimeObjectId) const override { return RenderObjectIdRegistry::Get().TryResolveRuntimeObjectId( renderObjectId, outRuntimeObjectId); } }; } // namespace std::unique_ptr CreateEngineEditorSceneBackend( SceneManager& sceneManager, ResourceManager& resourceManager) { return std::make_unique( sceneManager, resourceManager); } std::unique_ptr CreateEngineEditorServices() { return std::make_unique(); } } // namespace XCEngine::UI::Editor::App