diff --git a/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp index 42c9da66..30af228b 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp @@ -21,6 +21,8 @@ //#==============================================================# #include +#include "Serialization/SHSerialization.h" + namespace SHADE { @@ -171,6 +173,10 @@ namespace SHADE editor->selectedEntities.clear(); editor->selectedEntities.push_back(eid); } + if(ImGui::Selectable("Copy")) + { + SHLOG_INFO(SHSerialization::SerializeEntitiesToString(editor->selectedEntities)) + } if(ImGui::Selectable(std::format("{} Delete", ICON_MD_DELETE).data())) { SHEntityManager::DestroyEntity(eid); diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp index 3cb6561d..c33f4fb6 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp @@ -17,6 +17,8 @@ #include #include +#include "Serialization/SHSerialization.h" + namespace SHADE { constexpr ImGuiWindowFlags editorMenuBarFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | @@ -73,7 +75,14 @@ namespace SHADE { if (ImGui::BeginMenu("File")) { - + if(ImGui::Selectable("Save")) + { + SHSerialization::SerializeSceneToFile("../../Assets/Scenes/Test.SHADE"); + } + if(ImGui::Selectable("Load")) + { + SHSerialization::DeserializeSceneFromFile("../../Assets/Scenes/Test.SHADE"); + } ImGui::EndMenu(); } if(ImGui::BeginMenu("Edit")) diff --git a/SHADE_Engine/src/Serialization/SHSerialization.cpp b/SHADE_Engine/src/Serialization/SHSerialization.cpp new file mode 100644 index 00000000..e259fbbc --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerialization.cpp @@ -0,0 +1,220 @@ +#include "SHpch.h" +#include "SHSerializationHelper.hpp" +#include "SHSerialization.h" + +#include + +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Scene/SHSceneManager.h" +#include "Tools/SHException.h" +#include "Assets/SHAssetManager.h" +#include + +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Physics/Components/SHRigidBodyComponent.h" + +namespace SHADE +{ + void SHSerialization::SerializeSceneToFile(std::filesystem::path const& path) + { + YAML::Emitter out; + SerializeSceneToEmitter(out); + std::ofstream file(path.c_str()); + if (file.good()) + { + file << out.c_str(); + file.close(); + } + } + + std::string SHSerialization::SerializeSceneToString() + { + YAML::Emitter out; + SerializeSceneToEmitter(out); + return std::basic_string(out.c_str()); + } + + void SHSerialization::SerializeSceneToEmitter(YAML::Emitter& out) + { + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + auto root = sceneGraph.GetRoot(); + + SHASSERT(root != nullptr, "Root is null. Failed to serialize scene to node."); + + auto const& children = root->GetChildren(); + out << YAML::BeginSeq; + + auto pred = [&out](SHSceneNode* node) {out << SerializeEntityToNode(node); }; + sceneGraph.Traverse(pred); + //out << SerializeEntityToNode(child); + + out << YAML::EndSeq; + } + + static void DeserializeEntity(YAML::iterator& it, YAML::Node const& node, std::vector& createdEntities, EntityID parentEID = MAX_EID) + { + if (!node[EIDNode]) + return; + EntityID eid = node[EIDNode].as(); + std::string name = "Default"; + if (node[EntityNameNode]) + name = node[EntityNameNode].as(); + //Compile component IDs + const auto componentIDList = SHSerialization::GetComponentIDList(node[ComponentsNode]); + eid = SHEntityManager::CreateEntity(componentIDList, eid, name, parentEID); + createdEntities.push_back(eid); + if (node[NumberOfChildrenNode]) + { + if(const int numOfChildren = node[NumberOfChildrenNode].as(); numOfChildren > 0) + { + ++it; + for (int i = 0; i < numOfChildren; ++i) + { + DeserializeEntity(it, (*it), createdEntities, eid); + if((i + 1) < numOfChildren) + ++it; + } + } + } + } + + void SHSerialization::DeserializeSceneFromFile(std::filesystem::path const& path) + { + //TODO:Shift to using XQ's FileIO + std::ifstream iFile; + iFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + std::string fileContent = ""; + + try + { + // Open file + // Read file's buffer contents into streams + iFile.open(path); + std::stringstream fileStream; + fileStream << iFile.rdbuf(); + + fileContent = fileStream.str(); + + // Close file handler + iFile.close(); + } + catch (std::ifstream::failure e) + { + SHLOG_ERROR("Could not read file"); + } + YAML::Node entities = YAML::Load(fileContent); + std::vector createdEntities{}; + + //Create Entities + for (auto it = entities.begin(); it != entities.end(); ++it) + { + DeserializeEntity(it, (*it), createdEntities); + } + + //Initialize Entity + auto entityVecIt = createdEntities.begin(); + for (auto it = entities.begin(); it != entities.end(); ++it) + { + InitializeEntity(*it, *entityVecIt++); + } + } + + void SHSerialization::EmitEntity(SHSceneNode* entityNode, YAML::Emitter& out) + { + out << SerializeEntityToNode(entityNode); + auto const& children = entityNode->GetChildren(); + for(auto const& child : children) + { + EmitEntity(child, out); + } + } + + std::string SHSerialization::SerializeEntitiesToString(std::vector const& entities) + { + YAML::Emitter out; + YAML::Node node; + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + for (auto const& eid : entities) + { + auto entityNode = sceneGraph.GetNode(eid); + EmitEntity(entityNode, out); + } + return std::basic_string(out.c_str()); + } + + void SHSerialization::SerializeEntityToFile(std::filesystem::path const& path) + { + } + + YAML::Node SHSerialization::SerializeEntityToNode(SHSceneNode* sceneNode) + { + YAML::Node node; + auto eid = sceneNode->GetEntityID(); + auto entity = SHEntityManager::GetEntityByID(eid); + if (!sceneNode || !entity) + { + node = YAML::Null; + return node; + } + node.SetStyle(YAML::EmitterStyle::Block); + node[EIDNode] = eid; + node[EntityNameNode] = entity->name; + node[IsActiveNode] = sceneNode->IsActive(); + auto const& children = sceneNode->GetChildren(); + node[NumberOfChildrenNode] = children.size(); + + YAML::Node components; + + if (const auto transform = SHComponentManager::GetComponent_s(eid)) + { + components[rttr::type::get().get_name().data()] = SHSerializationHelper::SerializeComponentToNode(transform); + } + if (const auto renderable = SHComponentManager::GetComponent_s(eid)) + { + components[rttr::type::get().get_name().data()] = SHSerializationHelper::SerializeComponentToNode(renderable); + } + if (const auto rigidbody = SHComponentManager::GetComponent_s(eid)) + { + components[rttr::type::get().get_name().data()] = SHSerializationHelper::SerializeComponentToNode(rigidbody); + } + node[ComponentsNode] = components; + return node; + } + + template, bool> = true> + std::optional GetComponentID(YAML::Node const& componentNode) + { + if (componentNode[rttr::type::get().get_name().data()]) + return { SHFamilyID::GetID() }; + else + return std::nullopt; + } + + std::vector SHSerialization::GetComponentIDList(YAML::Node const& componentsNode) + { + std::vector componentIDList; + + auto id = GetComponentID(componentsNode); + if (id.has_value()) + componentIDList.push_back(id.value()); + + id = GetComponentID(componentsNode); + if (id.has_value()) + componentIDList.push_back(id.value()); + + id = GetComponentID(componentsNode); + if (id.has_value()) + componentIDList.push_back(id.value()); + + return componentIDList; + } + + void SHSerialization::InitializeEntity(YAML::Node const& entityNode, EntityID const& eid) + { + auto const componentsNode = entityNode[ComponentsNode]; + if (!componentsNode) + return; + SHSerializationHelper::InitializeComponentFromNode(componentsNode, eid); + } +} diff --git a/SHADE_Engine/src/Serialization/SHSerialization.h b/SHADE_Engine/src/Serialization/SHSerialization.h new file mode 100644 index 00000000..d247de7a --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerialization.h @@ -0,0 +1,43 @@ +#pragma once + +#include "SH_API.h" +#include +#include + +#include + +namespace YAML +{ + class Emitter; + class Node; +} + +namespace SHADE +{ + class SHSceneNode; + + constexpr const char* ComponentsNode = "Components"; + constexpr const char* EntityNameNode = "Name"; + constexpr const char* EIDNode = "EID"; + constexpr const char* IsActiveNode = "IsActive"; + constexpr const char* NumberOfChildrenNode = "NumberOfChildren"; + + struct SH_API SHSerialization + { + //TODO: change paths to resource ID + static void SerializeSceneToFile(std::filesystem::path const& path); + static std::string SerializeSceneToString(); + static void SerializeSceneToEmitter(YAML::Emitter& out); + static void DeserializeSceneFromFile(std::filesystem::path const& path); + + + static void EmitEntity(SHSceneNode* entityNode, YAML::Emitter& out); + static std::string SerializeEntitiesToString(std::vector const& entities); + static void SerializeEntityToFile(std::filesystem::path const& path); + static YAML::Node SerializeEntityToNode(SHSceneNode* sceneNode); + + static std::vector GetComponentIDList(YAML::Node const& componentsNode); + private: + static void InitializeEntity(YAML::Node const& entityNode, EntityID const& eid); + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Serialization/SHSerializationHelper.hpp b/SHADE_Engine/src/Serialization/SHSerializationHelper.hpp new file mode 100644 index 00000000..da98c885 --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerializationHelper.hpp @@ -0,0 +1,204 @@ +#pragma once + +#include "ECS_Base/Components/SHComponent.h" +#include + +#include + +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" + +namespace SHADE +{ + struct SHSerializationHelper + { + template , bool> = true> + static std::string SerializeComponentToString(ComponentType* component) + { + return std::string(); + } + + template , bool> = true> + static void SerializeComponentToFile(ComponentType* component, std::filesystem::path const& path) + { + } + + static YAML::Node RTTRToNode(const rttr::variant& var) + { + YAML::Node node; + auto varType = var.get_type(); + if (varType == rttr::type::get()) + { + node.SetStyle(YAML::EmitterStyle::Flow); + node["X"] = var.convert().x; + node["Y"] = var.convert().y; + node["Z"] = var.convert().z; + node["W"] = var.convert().w; + } + else if (varType == rttr::type::get()) + { + node.SetStyle(YAML::EmitterStyle::Flow); + node["X"] = var.convert().x; + node["Y"] = var.convert().y; + node["Z"] = var.convert().z; + } + else if (varType == rttr::type::get()) + { + node.SetStyle(YAML::EmitterStyle::Flow); + node["X"] = var.convert().x; + node["Y"] = var.convert().y; + } + else if (varType.is_arithmetic()) + { + bool ok = false; + if (varType == rttr::type::get()) + node = var.to_bool(); + else if (varType == rttr::type::get()) + node = var.to_int8(&ok); + else if (varType == rttr::type::get()) + node = var.to_int16(&ok); + else if (varType == rttr::type::get()) + node = var.to_int32(&ok); + else if (varType == rttr::type::get()) + node = var.to_int64(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint8(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint16(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint32(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint64(&ok); + else if (varType == rttr::type::get()) + node = var.to_float(&ok); + else if (varType == rttr::type::get()) + node = var.to_double(&ok); + //else if (varType == rttr::type::get()) //same as uint8_t + // node = var.to_uint8(); + } + else if (varType.is_enumeration()) + { + bool ok = false; + auto result = var.to_string(&ok); + if (ok) + { + node = var.to_string(); + } + else + { + ok = false; + auto value = var.to_uint64(&ok); + if (ok) + node = value; + else + node = YAML::Null; + } + } + else + { + auto properties = var.get_type().get_properties(); + for (auto property : properties) + { + node[property.get_name().data()] = RTTRToNode(property.get_value(var)); + } + } + return node; + } + + template , bool> = true> + static YAML::Node SerializeComponentToNode(ComponentType* component) + { + YAML::Node node{}; + if (!component) + return node; + + auto componentType = rttr::type::get(); + node = RTTRToNode(*component); + + return node; + } + + template , bool> = true> + static void InitializeProperty(ComponentType* component, rttr::property const& prop, YAML::Node const& propertyNode) + { + auto propType = prop.get_type(); + if (propType == rttr::type::get()) + { + SHVec4 vec{ propertyNode["X"].as(), propertyNode["Y"].as(), propertyNode["Z"].as(), propertyNode["W"].as() }; + prop.set_value(component, vec); + } + else if (propType == rttr::type::get()) + { + SHVec3 vec{ propertyNode["X"].as(), propertyNode["Y"].as(), propertyNode["Z"].as() }; + prop.set_value(component, vec); + } + else if (propType == rttr::type::get()) + { + SHVec2 vec{ propertyNode["X"].as(), propertyNode["Y"].as() }; + prop.set_value(component, vec); + } + else if (propType.is_arithmetic()) + { + bool ok = false; + if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(component, propertyNode.as()); + } + else if (propType.is_enumeration()) + { + auto enumAlign = prop.get_enumeration(); + prop.set_value(component, enumAlign.name_to_value(propertyNode.as())); + } + else + { + auto properties = propType.get_properties(); + for (auto property : properties) + { + InitializeProperty(component, property, propertyNode[property.get_name().data()]); + } + } + } + + template , bool> = true> + static void InitializeComponentFromNode(YAML::Node const& componentsNode, EntityID const& eid) + { + auto component = SHComponentManager::GetComponent_s(eid); + if (componentsNode.IsNull() && !component) + return; + auto rttrType = rttr::type::get(); + auto componentNode = componentsNode[rttrType.get_name().data()]; + if (componentsNode.IsNull()) + return; + auto properties = rttrType.get_properties(); + for (auto const& prop : properties) + { + if (componentNode[prop.get_name().data()]) + { + InitializeProperty(component, prop, componentNode[prop.get_name().data()]); + } + } + } + + }; +}