feat: instance and property serialization

This commit is contained in:
maelstrom 2025-02-06 18:58:00 +01:00
parent 3c2eae2028
commit 37aafe48f4
19 changed files with 161 additions and 31 deletions

View file

@ -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" $<TARGET_OBJECTS:openblocks>)
# include_directories("src")

View file

@ -1,4 +1,4 @@
add_executable(client "src/main.cpp" $<TARGET_OBJECTS:openblocks>)
include_directories("../src")
target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D)
target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)

10
deps.txt Normal file
View file

@ -0,0 +1,10 @@
glm
opengl
assimp
sdl2
glfw
glut
glew
qt6
reactphysics3d
pugixml

View file

@ -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

View file

@ -11,7 +11,6 @@
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include "GLFW/glfw3.h"
#include "physics/util.h"
#include "qcursor.h"
#include "qevent.h"

View file

@ -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"
@ -62,6 +64,14 @@ MainWindow::MainWindow(QWidget *parent)
}
});
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);
simulationInit();

View file

@ -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 {

View file

@ -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");
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <pugixml.hpp>
#include <string>
@ -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)

View file

@ -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; };
@ -80,3 +80,20 @@ Data::CFrame Data::CFrame::operator +(Data::Vector3 vector) const {
Data::CFrame Data::CFrame::operator -(Data::Vector3 vector) const {
return *this + -vector;
}
// 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]));
}

View file

@ -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;

View file

@ -6,3 +6,9 @@ Data::String Data::Variant::ToString() const {
return it.ToString();
}, this->wrapped);
}
void Data::Variant::Serialize(pugi::xml_node* node) const {
std::visit([&](auto&& it) {
it.Serialize(node);
}, this->wrapped);
}

View file

@ -30,5 +30,6 @@ namespace Data {
template <typename T> Variant(T obj) : wrapped(obj) {}
template <typename T> T get() { return std::get<T>(wrapped); }
Data::String ToString() const;
void Serialize(pugi::xml_node* node) const;
};
}

View file

@ -42,3 +42,11 @@ Data::Vector3 Data::Vector3::Cross(Data::Vector3 other) const {
float Data::Vector3::Dot(Data::Vector3 other) const {
return glm::dot(this->vector, other.vector);
}
// 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()));
}

View file

@ -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;

View file

@ -141,3 +141,26 @@ std::vector<std::string> 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);
}
}

View file

@ -11,6 +11,7 @@
#include <map>
#include <vector>
#include <../include/expected.hpp>
#include <pugixml.hpp>
#include "member.h"
@ -68,6 +69,9 @@ public:
tl::expected<PropertyMeta, MemberNotFound> GetPropertyMeta(std::string name);
// Returning a list of property names feels kinda janky. Is this really the way to go?
std::vector<std::string> GetProperties();
// Serialization
void Serialize(pugi::xml_node* parent);
};
typedef std::shared_ptr<Instance> InstanceRef;

View file

@ -15,21 +15,27 @@ struct FieldCodec {
Data::Variant (*read)(void* source);
};
template <typename T, typename U>
void _writeCodec(Data::Variant source, void* destination) {
*(U*)destination = (U)source.get<T>();
}
template <typename T, typename U>
Data::Variant _readCodec(void* source) {
return T(*(U*)source);
}
template <typename T, typename U>
constexpr FieldCodec fieldCodecOf() {
return FieldCodec {
.write = &_writeCodec<T, U>,
.read = &_readCodec<T, U>,
.write = [](Data::Variant source, void* destination) {
*(U*)destination = (U)source.get<T>();
},
.read = [](void* source) -> Data::Variant {
return T(*(U*)source);
},
};
}
template <typename T>
constexpr FieldCodec fieldCodecOf() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
*(T*)destination = source.get<T>();
},
.read = [](void* source) -> Data::Variant {
return *(T*)source;
},
};
}
@ -38,11 +44,17 @@ std::function<void(std::string name)> 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<std::function<void(std::string name)>> updateCallback;
PropertyFlags flags;
};
typedef std::variant<PropertyMeta> MemberMeta;

View file

@ -63,9 +63,29 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
this->memberMap = std::make_unique<MemberMap>(MemberMap {
.super = std::move(this->memberMap),
.members = {
{ "Anchored", { .backingField = &anchored, .type = &Data::Bool::TYPE, .codec = fieldCodecOf<Data::Bool, bool>(), .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<Data::Bool, bool>(),
.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<Data::CFrame>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
} }
}
});
}