From 37aafe48f4a4ad2578b98bd6536abdaa895520c0 Mon Sep 17 00:00:00 2001 From: maelstrom Date: Thu, 6 Feb 2025 18:58:00 +0100 Subject: [PATCH] feat: instance and property serialization --- CMakeLists.txt | 11 +++++----- client/CMakeLists.txt | 2 +- deps.txt | 10 +++++++++ editor/CMakeLists.txt | 2 +- editor/mainglwidget.cpp | 1 - editor/mainwindow.cpp | 10 +++++++++ editor/panes/propertiesmodel.cpp | 12 +++++++++-- src/datatypes/base.cpp | 9 ++++++-- src/datatypes/base.h | 6 +++++- src/datatypes/cframe.cpp | 21 +++++++++++++++++-- src/datatypes/cframe.h | 1 + src/datatypes/meta.cpp | 6 ++++++ src/datatypes/meta.h | 1 + src/datatypes/vector.cpp | 10 ++++++++- src/datatypes/vector.h | 1 + src/objects/base/instance.cpp | 23 ++++++++++++++++++++ src/objects/base/instance.h | 4 ++++ src/objects/base/member.h | 36 +++++++++++++++++++++----------- src/objects/part.cpp | 26 ++++++++++++++++++++--- 19 files changed, 161 insertions(+), 31 deletions(-) create mode 100644 deps.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fef269..7d5aae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,14 +23,15 @@ find_package(GLUT REQUIRED) include_directories(${GLUT_INCLUDE_DIRS}) find_package(glfw3 REQUIRED) - find_package(OpenGL) - find_package(glm CONFIG REQUIRED) - find_package(assimp REQUIRED) - find_package(ReactPhysics3D REQUIRED) +find_package(pugixml REQUIRED) + +# PkgConfig packages +# find_package(PkgConfig REQUIRED) +# pkg_check_modules(PUGIXML REQUIRED pugixml) file(MAKE_DIRECTORY bin) @@ -39,7 +40,7 @@ include_directories("include") file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h") add_library(openblocks ${SOURCES}) set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks") -target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D) +target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} ${PUGIXML_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml) # add_executable(client "client/src/main.cpp" $) # include_directories("src") diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c386b80..b3eb8ac 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(client "src/main.cpp" $) include_directories("../src") -target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D) \ No newline at end of file +target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml) \ No newline at end of file diff --git a/deps.txt b/deps.txt new file mode 100644 index 0000000..7d9107e --- /dev/null +++ b/deps.txt @@ -0,0 +1,10 @@ +glm +opengl +assimp +sdl2 +glfw +glut +glew +qt6 +reactphysics3d +pugixml \ No newline at end of file diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index fd0a0d7..5d790d4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -66,7 +66,7 @@ else() qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) endif() -target_link_libraries(editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D) +target_link_libraries(editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an diff --git a/editor/mainglwidget.cpp b/editor/mainglwidget.cpp index 1ad678d..4d3908f 100644 --- a/editor/mainglwidget.cpp +++ b/editor/mainglwidget.cpp @@ -11,7 +11,6 @@ #include #include -#include "GLFW/glfw3.h" #include "physics/util.h" #include "qcursor.h" #include "qevent.h" diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp index ba5967a..f811be6 100644 --- a/editor/mainwindow.cpp +++ b/editor/mainwindow.cpp @@ -14,6 +14,8 @@ #include "common.h" #include "editorcommon.h" +#include "objects/base/instance.h" +#include "objects/datamodel.h" #include "physics/simulation.h" #include "objects/part.h" #include "qitemselectionmodel.h" @@ -61,6 +63,14 @@ MainWindow::MainWindow(QWidget *parent) ui->actionToggleSimulation->setIcon(QIcon::fromTheme("media-playback-start")); } }); + + connect(ui->actionSave, &QAction::triggered, this, [&]() { + pugi::xml_document doc; + pugi::xml_node node = doc.append_child("openblocks"); + workspace()->Serialize(&node); + + doc.print(std::cout); + }); // ui->explorerView->Init(ui); diff --git a/editor/panes/propertiesmodel.cpp b/editor/panes/propertiesmodel.cpp index d3223bc..74db11f 100644 --- a/editor/panes/propertiesmodel.cpp +++ b/editor/panes/propertiesmodel.cpp @@ -1,12 +1,20 @@ #include "propertiesmodel.h" #include "datatypes/base.h" +#include "datatypes/cframe.h" #include "objects/base/member.h" #include "qnamespace.h" PropertiesModel::PropertiesModel(InstanceRef selectedItem, QWidget *parent) : QAbstractItemModel(parent) , selectedItem(selectedItem) { - this->propertiesList = selectedItem->GetProperties(); + this->propertiesList.reserve(selectedItem->GetProperties().size()); + for (std::string name : selectedItem->GetProperties()) { + PropertyMeta meta = selectedItem->GetPropertyMeta(name).value(); + // Don't show CFrames in properties + if (meta.type == &Data::CFrame::TYPE) continue; + + this->propertiesList.push_back(name); + } } PropertiesModel::~PropertiesModel() = default; @@ -102,7 +110,7 @@ QModelIndex PropertiesModel::parent(const QModelIndex &index) const { } int PropertiesModel::rowCount(const QModelIndex &parent) const { - return !parent.isValid() ? selectedItem->GetProperties().size() : 0; + return !parent.isValid() ? this->propertiesList.size() : 0; } int PropertiesModel::columnCount(const QModelIndex &parent) const { diff --git a/src/datatypes/base.cpp b/src/datatypes/base.cpp index e28e759..3594fef 100644 --- a/src/datatypes/base.cpp +++ b/src/datatypes/base.cpp @@ -2,9 +2,10 @@ #define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \ Data::CLASS_NAME::~CLASS_NAME() = default; \ -Data::CLASS_NAME::operator WRAPPED_TYPE() { return value; } \ +Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \ const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, }; \ -const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; +const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \ +void Data::CLASS_NAME::Serialize(pugi::xml_node* node) const { node->text().set(std::string(this->ToString())); } Data::Base::~Base() {}; @@ -24,6 +25,10 @@ const Data::String Data::Null::ToString() const { return Data::String("null"); } +void Data::Null::Serialize(pugi::xml_node* node) const { + node->text().set("null"); +} + const Data::String Data::Bool::ToString() const { return Data::String(value ? "true" : "false"); } diff --git a/src/datatypes/base.h b/src/datatypes/base.h index 5f5afe7..781417f 100644 --- a/src/datatypes/base.h +++ b/src/datatypes/base.h @@ -1,5 +1,6 @@ #pragma once +#include #include @@ -8,11 +9,12 @@ public: \ CLASS_NAME(WRAPPED_TYPE); \ ~CLASS_NAME(); \ - operator WRAPPED_TYPE(); \ + operator const WRAPPED_TYPE() const; \ virtual const TypeInfo& GetType() const override; \ static const TypeInfo TYPE; \ \ virtual const Data::String ToString() const override; \ + virtual void Serialize(pugi::xml_node* node) const override; \ }; namespace Data { @@ -27,6 +29,7 @@ namespace Data { virtual ~Base(); virtual const TypeInfo& GetType() const = 0; virtual const Data::String ToString() const = 0; + virtual void Serialize(pugi::xml_node* node) const = 0; }; class Null : Base { @@ -37,6 +40,7 @@ namespace Data { static const TypeInfo TYPE; virtual const Data::String ToString() const override; + virtual void Serialize(pugi::xml_node* node) const override; }; DEF_WRAPPER_CLASS(Bool, bool) diff --git a/src/datatypes/cframe.cpp b/src/datatypes/cframe.cpp index f148b73..40b28f9 100644 --- a/src/datatypes/cframe.cpp +++ b/src/datatypes/cframe.cpp @@ -40,7 +40,7 @@ Data::CFrame::CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 Data::CFrame::~CFrame() = default; const Data::TypeInfo Data::CFrame::TYPE = { - .name = "CFrame", + .name = "CoordinateFrame", }; const Data::TypeInfo& Data::CFrame::GetType() const { return Data::Vector3::TYPE; }; @@ -79,4 +79,21 @@ Data::CFrame Data::CFrame::operator +(Data::Vector3 vector) const { Data::CFrame Data::CFrame::operator -(Data::Vector3 vector) const { return *this + -vector; -} \ No newline at end of file +} + +// Serialization + +void Data::CFrame::Serialize(pugi::xml_node* node) const { + node->append_child("X").text().set(std::to_string(this->X())); + node->append_child("Y").text().set(std::to_string(this->Y())); + node->append_child("Z").text().set(std::to_string(this->Z())); + node->append_child("R00").text().set(std::to_string(this->rotation[0][0])); + node->append_child("R01").text().set(std::to_string(this->rotation[1][0])); + node->append_child("R02").text().set(std::to_string(this->rotation[2][0])); + node->append_child("R10").text().set(std::to_string(this->rotation[0][1])); + node->append_child("R11").text().set(std::to_string(this->rotation[1][1])); + node->append_child("R12").text().set(std::to_string(this->rotation[2][1])); + node->append_child("R20").text().set(std::to_string(this->rotation[0][2])); + node->append_child("R21").text().set(std::to_string(this->rotation[1][2])); + node->append_child("R22").text().set(std::to_string(this->rotation[2][2])); +} diff --git a/src/datatypes/cframe.h b/src/datatypes/cframe.h index c5f761e..766e7cb 100644 --- a/src/datatypes/cframe.h +++ b/src/datatypes/cframe.h @@ -31,6 +31,7 @@ namespace Data { static const TypeInfo TYPE; virtual const Data::String ToString() const override; + virtual void Serialize(pugi::xml_node* parent) const override; operator glm::mat4() const; operator rp::Transform() const; diff --git a/src/datatypes/meta.cpp b/src/datatypes/meta.cpp index 06b1223..839750b 100644 --- a/src/datatypes/meta.cpp +++ b/src/datatypes/meta.cpp @@ -5,4 +5,10 @@ Data::String Data::Variant::ToString() const { return std::visit([](auto&& it) { return it.ToString(); }, this->wrapped); +} + +void Data::Variant::Serialize(pugi::xml_node* node) const { + std::visit([&](auto&& it) { + it.Serialize(node); + }, this->wrapped); } \ No newline at end of file diff --git a/src/datatypes/meta.h b/src/datatypes/meta.h index c14d0d4..3c14bba 100644 --- a/src/datatypes/meta.h +++ b/src/datatypes/meta.h @@ -30,5 +30,6 @@ namespace Data { template Variant(T obj) : wrapped(obj) {} template T get() { return std::get(wrapped); } Data::String ToString() const; + void Serialize(pugi::xml_node* node) const; }; } \ No newline at end of file diff --git a/src/datatypes/vector.cpp b/src/datatypes/vector.cpp index 69c9fd4..881de02 100644 --- a/src/datatypes/vector.cpp +++ b/src/datatypes/vector.cpp @@ -41,4 +41,12 @@ Data::Vector3 Data::Vector3::Cross(Data::Vector3 other) const { float Data::Vector3::Dot(Data::Vector3 other) const { return glm::dot(this->vector, other.vector); -} \ No newline at end of file +} + +// Serialization + +void Data::Vector3::Serialize(pugi::xml_node* node) const { + node->append_child("X").text().set(std::to_string(this->X())); + node->append_child("Y").text().set(std::to_string(this->Y())); + node->append_child("Z").text().set(std::to_string(this->Z())); +} diff --git a/src/datatypes/vector.h b/src/datatypes/vector.h index 9b04574..96d8349 100644 --- a/src/datatypes/vector.h +++ b/src/datatypes/vector.h @@ -24,6 +24,7 @@ namespace Data { static Data::Vector3 ONE; virtual const Data::String ToString() const override; + virtual void Serialize(pugi::xml_node* node) const override; operator glm::vec3() const; operator rp::Vector3() const; diff --git a/src/objects/base/instance.cpp b/src/objects/base/instance.cpp index bd6456e..3a2951a 100644 --- a/src/objects/base/instance.cpp +++ b/src/objects/base/instance.cpp @@ -140,4 +140,27 @@ std::vector Instance::GetProperties() { cachedMemberList = memberList; return memberList; +} + +// Serialization + +void Instance::Serialize(pugi::xml_node* parent) { + pugi::xml_node node = parent->append_child("Item"); + node.append_attribute("class").set_value(this->GetClass()->className); + + // Add properties + pugi::xml_node propertiesNode = node.append_child("Properties"); + for (std::string name : GetProperties()) { + PropertyMeta meta = GetPropertyMeta(name).value(); + if (meta.flags & PropertyFlags::PROP_NOSAVE) continue; // This property should not be serialized. Skip... + + pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name); + propertyNode.append_attribute("name").set_value(name); + GetPropertyValue(name)->Serialize(&propertyNode); + } + + // Add children + for (InstanceRef child : this->children) { + child->Serialize(&node); + } } \ No newline at end of file diff --git a/src/objects/base/instance.h b/src/objects/base/instance.h index 293e164..46fd6c6 100644 --- a/src/objects/base/instance.h +++ b/src/objects/base/instance.h @@ -11,6 +11,7 @@ #include #include #include <../include/expected.hpp> +#include #include "member.h" @@ -68,6 +69,9 @@ public: tl::expected GetPropertyMeta(std::string name); // Returning a list of property names feels kinda janky. Is this really the way to go? std::vector GetProperties(); + + // Serialization + void Serialize(pugi::xml_node* parent); }; typedef std::shared_ptr InstanceRef; diff --git a/src/objects/base/member.h b/src/objects/base/member.h index 991c502..1c93316 100644 --- a/src/objects/base/member.h +++ b/src/objects/base/member.h @@ -15,21 +15,27 @@ struct FieldCodec { Data::Variant (*read)(void* source); }; -template -void _writeCodec(Data::Variant source, void* destination) { - *(U*)destination = (U)source.get(); -} - -template -Data::Variant _readCodec(void* source) { - return T(*(U*)source); -} - template constexpr FieldCodec fieldCodecOf() { return FieldCodec { - .write = &_writeCodec, - .read = &_readCodec, + .write = [](Data::Variant source, void* destination) { + *(U*)destination = (U)source.get(); + }, + .read = [](void* source) -> Data::Variant { + return T(*(U*)source); + }, + }; +} + +template +constexpr FieldCodec fieldCodecOf() { + return FieldCodec { + .write = [](Data::Variant source, void* destination) { + *(T*)destination = source.get(); + }, + .read = [](void* source) -> Data::Variant { + return *(T*)source; + }, }; } @@ -38,11 +44,17 @@ std::function memberFunctionOf(void(T::*func)(std::strin return std::bind(func, obj, std::placeholders::_1); } +enum PropertyFlags { + PROP_HIDDEN = 1 << 0, // Hidden from the editor + PROP_NOSAVE = 1 << 1, // Do not serialize +}; + struct PropertyMeta { void* backingField; const Data::TypeInfo* type; FieldCodec codec; std::optional> updateCallback; + PropertyFlags flags; }; typedef std::variant MemberMeta; diff --git a/src/objects/part.cpp b/src/objects/part.cpp index 1303b9d..aa36d62 100644 --- a/src/objects/part.cpp +++ b/src/objects/part.cpp @@ -63,9 +63,29 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par this->memberMap = std::make_unique(MemberMap { .super = std::move(this->memberMap), .members = { - { "Anchored", { .backingField = &anchored, .type = &Data::Bool::TYPE, .codec = fieldCodecOf(), .updateCallback = memberFunctionOf(&Part::onUpdated, this) } }, - { "Position", { .backingField = &cframe, .type = &Data::Vector3::TYPE, .codec = cframePositionCodec(), .updateCallback = memberFunctionOf(&Part::onUpdated, this) } }, - { "Rotation", { .backingField = &cframe, .type = &Data::Vector3::TYPE, .codec = cframeRotationCodec(), .updateCallback = memberFunctionOf(&Part::onUpdated, this) } } + { "Anchored", { + .backingField = &anchored, + .type = &Data::Bool::TYPE, + .codec = fieldCodecOf(), + .updateCallback = memberFunctionOf(&Part::onUpdated, this) + } }, { "Position", { + .backingField = &cframe, + .type = &Data::Vector3::TYPE, + .codec = cframePositionCodec(), + .updateCallback = memberFunctionOf(&Part::onUpdated, this), + .flags = PropertyFlags::PROP_NOSAVE + } }, { "Rotation", { + .backingField = &cframe, + .type = &Data::Vector3::TYPE, + .codec = cframeRotationCodec(), + .updateCallback = memberFunctionOf(&Part::onUpdated, this), + .flags = PropertyFlags::PROP_NOSAVE + } }, { "CFrame", { + .backingField = &cframe, + .type = &Data::CFrame::TYPE, + .codec = fieldCodecOf(), + .updateCallback = memberFunctionOf(&Part::onUpdated, this), + } } } }); }