feat: deserialization... among other things

This commit is contained in:
maelstrom 2025-02-07 01:11:09 +01:00
parent 37aafe48f4
commit a3d2026a35
21 changed files with 350 additions and 28 deletions

View file

@ -18,6 +18,7 @@
#include "objects/datamodel.h"
#include "physics/simulation.h"
#include "objects/part.h"
#include "qfiledialog.h"
#include "qitemselectionmodel.h"
#include "qobject.h"
#include "qsysinfo.h"
@ -36,14 +37,7 @@ MainWindow::MainWindow(QWidget *parent)
timer.start(33, this);
setMouseTracking(true);
connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
if (selected.count() == 0) return;
std::optional<InstanceRef> inst = selected.count() == 0 ? std::nullopt
: std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this());
ui->propertiesView->setSelected(inst);
});
ConnectSelectionChangeHandler();
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateSelectedTool(); });
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateSelectedTool(); });
@ -65,11 +59,23 @@ 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);
std::optional<std::string> path;
if (!dataModel->HasFile())
path = QFileDialog::getSaveFileName(this, QString::fromStdString("Save " + dataModel->name), "", "*.obl").toStdString();
if (path == "") return;
doc.print(std::cout);
dataModel->SaveToFile(path);
});
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
std::string path = QFileDialog::getOpenFileName(this, "Load file", "", "*.obl").toStdString();
if (path == "") return;
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
dataModel = newModel;
delete ui->explorerView->selectionModel();
ui->explorerView->reset();
ui->explorerView->setModel(new ExplorerModel(dataModel));
ConnectSelectionChangeHandler();
});
// ui->explorerView->Init(ui);
@ -104,6 +110,17 @@ MainWindow::MainWindow(QWidget *parent)
syncPartPhysics(ui->mainWidget->lastPart);
}
void MainWindow::ConnectSelectionChangeHandler() {
connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
if (selected.count() == 0) return;
std::optional<InstanceRef> inst = selected.count() == 0 ? std::nullopt
: std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this());
ui->propertiesView->setSelected(inst);
});
}
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
void MainWindow::timerEvent(QTimerEvent* evt) {
if (evt->timerId() != timer.timerId()) {

View file

@ -28,5 +28,6 @@ private:
void updateSelectedTool();
void timerEvent(QTimerEvent*) override;
void ConnectSelectionChangeHandler();
};
#endif // MAINWINDOW_H

View file

@ -15,7 +15,7 @@ typedef std::function<void(std::vector<InstanceRefWeak> oldSelection, std::vecto
extern Camera camera;
extern std::shared_ptr<DataModel> dataModel;
inline std::shared_ptr<Workspace> workspace() { return dataModel->workspace; }
inline std::shared_ptr<Workspace> workspace() { return std::dynamic_pointer_cast<Workspace>(dataModel->services["Workspace"]); }
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;

View file

@ -1,9 +1,10 @@
#include "base.h"
#include "meta.h"
#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 const WRAPPED_TYPE() const { return value; } \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, }; \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize }; \
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())); }
@ -13,14 +14,10 @@ Data::Null::Null() {};
Data::Null::~Null() = default;
const Data::TypeInfo Data::Null::TYPE = {
.name = "null",
.deserializer = &Data::Null::Deserialize,
};
const Data::TypeInfo& Data::Null::GetType() const { return Data::Null::TYPE; };
IMPL_WRAPPER_CLASS(Bool, bool, "bool")
IMPL_WRAPPER_CLASS(Int, int, "int")
IMPL_WRAPPER_CLASS(Float, float, "float")
IMPL_WRAPPER_CLASS(String, std::string, "string")
const Data::String Data::Null::ToString() const {
return Data::String("null");
}
@ -29,18 +26,45 @@ void Data::Null::Serialize(pugi::xml_node* node) const {
node->text().set("null");
}
Data::Variant Data::Null::Deserialize(pugi::xml_node* node) {
return Data::Null();
}
//
IMPL_WRAPPER_CLASS(Bool, bool, "bool")
IMPL_WRAPPER_CLASS(Int, int, "int")
IMPL_WRAPPER_CLASS(Float, float, "float")
IMPL_WRAPPER_CLASS(String, std::string, "string")
const Data::String Data::Bool::ToString() const {
return Data::String(value ? "true" : "false");
}
Data::Variant Data::Bool::Deserialize(pugi::xml_node* node) {
return Data::Bool(node->text().as_bool());
}
const Data::String Data::Int::ToString() const {
return Data::String(std::to_string(value));
}
Data::Variant Data::Int::Deserialize(pugi::xml_node* node) {
return Data::Int(node->text().as_int());
}
const Data::String Data::Float::ToString() const {
return Data::String(std::to_string(value));
}
Data::Variant Data::Float::Deserialize(pugi::xml_node* node) {
return Data::Float(node->text().as_float());
}
const Data::String Data::String::ToString() const {
return *this;
}
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
return Data::String(node->text().as_string());
}

View file

@ -1,8 +1,8 @@
#pragma once
#include <pugixml.hpp>
#include <string>
#include <functional>
#include <pugixml.hpp>
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
const WRAPPED_TYPE value; \
@ -15,11 +15,16 @@ public: \
\
virtual const Data::String ToString() const override; \
virtual void Serialize(pugi::xml_node* node) const override; \
static Data::Variant Deserialize(pugi::xml_node* node); \
};
namespace Data {
class Variant;
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
struct TypeInfo {
std::string name;
Deserializer deserializer;
TypeInfo(const TypeInfo&) = delete;
};
@ -41,6 +46,7 @@ namespace Data {
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node* node) const override;
static Data::Variant Deserialize(pugi::xml_node* node);
};
DEF_WRAPPER_CLASS(Bool, bool)

View file

@ -5,6 +5,19 @@
#include <reactphysics3d/mathematics/Transform.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
// #include "meta.h" // IWYU pragma: keep
Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
: translation(x, y, z)
, rotation({
// { R00, R01, R02 },
// { R10, R11, R12 },
// { R20, R21, R22 },
{ R00, R10, R20 },
{ R01, R11, R21 },
{ R02, R12, R22 },
}) {
}
Data::CFrame::CFrame(glm::vec3 translation, glm::mat3 rotation)
: translation(translation)
@ -41,6 +54,7 @@ Data::CFrame::CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3
Data::CFrame::~CFrame() = default;
const Data::TypeInfo Data::CFrame::TYPE = {
.name = "CoordinateFrame",
.deserializer = &Data::CFrame::Deserialize,
};
const Data::TypeInfo& Data::CFrame::GetType() const { return Data::Vector3::TYPE; };
@ -97,3 +111,21 @@ void Data::CFrame::Serialize(pugi::xml_node* node) const {
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]));
}
Data::Variant Data::CFrame::Deserialize(pugi::xml_node* node) {
return Data::CFrame(
node->child("X").text().as_float(),
node->child("Y").text().as_float(),
node->child("Z").text().as_float(),
node->child("R00").text().as_float(),
node->child("R01").text().as_float(),
node->child("R02").text().as_float(),
node->child("R10").text().as_float(),
node->child("R11").text().as_float(),
node->child("R12").text().as_float(),
node->child("R20").text().as_float(),
node->child("R21").text().as_float(),
node->child("R22").text().as_float()
);
}

View file

@ -22,6 +22,7 @@ namespace Data {
// CFrame(float x, float y, float z);
// CFrame(const glm::vec3&);
// CFrame(const rp::Vector3&);
CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22);
CFrame(const rp::Transform&);
CFrame(Data::Vector3 position, glm::quat quat);
CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up = Data::Vector3(0, 1, 0));
@ -32,6 +33,7 @@ namespace Data {
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node* parent) const override;
static Data::Variant Deserialize(pugi::xml_node* node);
operator glm::mat4() const;
operator rp::Transform() const;

View file

@ -1,4 +1,6 @@
#include "meta.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include <variant>
Data::String Data::Variant::ToString() const {
@ -11,4 +13,24 @@ void Data::Variant::Serialize(pugi::xml_node* node) const {
std::visit([&](auto&& it) {
it.Serialize(node);
}, this->wrapped);
}
}
Data::Variant Data::Variant::Deserialize(pugi::xml_node* node) {
if (Data::TYPE_MAP.count(node->name()) == 0) {
fprintf(stderr, "Unknown type for instance: '%s'\n", node->name());
abort();
}
const Data::TypeInfo* type = Data::TYPE_MAP[node->name()];
return type->deserializer(node);
}
std::map<std::string, const Data::TypeInfo*> Data::TYPE_MAP = {
{ "null", &Data::Null::TYPE },
{ "bool", &Data::Bool::TYPE },
{ "int", &Data::Int::TYPE },
{ "float", &Data::Float::TYPE },
{ "string", &Data::String::TYPE },
{ "Vector3", &Data::Vector3::TYPE },
{ "CoordinateFrame", &Data::CFrame::TYPE },
};

View file

@ -1,6 +1,7 @@
#pragma once
#include <variant>
#include <map>
#include "base.h"
#include "vector.h"
#include "cframe.h"
@ -30,6 +31,11 @@ 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;
static Data::Variant Deserialize(pugi::xml_node* node);
};
// Map of all data types to their type names
extern std::map<std::string, const TypeInfo*> TYPE_MAP;
}

View file

@ -1,5 +1,6 @@
#include "vector.h"
#include <glm/ext/quaternion_geometric.hpp>
#include "meta.h" // IWYU pragma: keep
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {};
Data::Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {};
@ -8,6 +9,7 @@ Data::Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y,
Data::Vector3::~Vector3() = default;
const Data::TypeInfo Data::Vector3::TYPE = {
.name = "Vector3",
.deserializer = &Data::Vector3::Deserialize,
};
const Data::TypeInfo& Data::Vector3::GetType() const { return Data::Vector3::TYPE; };
@ -50,3 +52,7 @@ void Data::Vector3::Serialize(pugi::xml_node* node) const {
node->append_child("Y").text().set(std::to_string(this->Y()));
node->append_child("Z").text().set(std::to_string(this->Z()));
}
Data::Variant Data::Vector3::Deserialize(pugi::xml_node* node) {
return Data::Vector3(node->child("X").text().as_float(), node->child("Y").text().as_float(), node->child("Z").text().as_float());
}

View file

@ -25,6 +25,7 @@ namespace Data {
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node* node) const override;
static Data::Variant Deserialize(pugi::xml_node* node);
operator glm::vec3() const;
operator rp::Vector3() const;

View file

@ -3,6 +3,7 @@
#include "../../datatypes/meta.h"
#include "datatypes/base.h"
#include "objects/base/member.h"
#include "objects/meta.h"
#include <algorithm>
#include <cstddef>
#include <cstdio>
@ -163,4 +164,39 @@ void Instance::Serialize(pugi::xml_node* parent) {
for (InstanceRef child : this->children) {
child->Serialize(&node);
}
}
InstanceRef Instance::Deserialize(pugi::xml_node* node) {
std::string className = node->attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
fprintf(stderr, "Unknown type for instance: '%s'\n", className.c_str());
abort();
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
// printf("What are you? A %s sandwich\n", className.c_str());
InstanceRef object = INSTANCE_MAP[className]->constructor();
object->GetChildren();
// const InstanceType* type = INSTANCE_MAP.at(className);
// Read properties
pugi::xml_node propertiesNode = node->child("Properties");
for (pugi::xml_node propertyNode : propertiesNode) {
std::string propertyName = propertyNode.attribute("name").value();
auto meta_ = object->GetPropertyMeta(propertyName);
if (!meta_.has_value()) {
fprintf(stderr, "Attempt to set unknown property '%s' of %s\n", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
Data::Variant value = Data::Variant::Deserialize(&propertyNode);
object->SetPropertyValue(propertyName, value);
}
// Read children
for (pugi::xml_node childNode : node->children("Item")) {
InstanceRef child = Instance::Deserialize(&childNode);
object->AddChild(child);
}
return object;
}

View file

@ -72,6 +72,7 @@ public:
// Serialization
void Serialize(pugi::xml_node* parent);
static std::shared_ptr<Instance> Deserialize(pugi::xml_node* node);
};
typedef std::shared_ptr<Instance> InstanceRef;

View file

@ -1,6 +1,18 @@
#include "datamodel.h"
#include "base/service.h"
#include "objects/base/instance.h"
#include "objects/base/service.h"
#include "workspace.h"
#include <cstdio>
#include <fstream>
#include <memory>
#include <functional>
// TODO: Move this to a different file
// ONLY add services here, all types are expected to inherit from Service, or errors WILL occur
std::map<std::string, std::function<std::shared_ptr<Service>(std::weak_ptr<DataModel>)>> SERVICE_CONSTRUCTORS = {
{ "Workspace", &Workspace::Create },
};
const InstanceType DataModel::TYPE = {
.super = &Instance::TYPE,
@ -14,9 +26,96 @@ const InstanceType* DataModel::GetClass() {
DataModel::DataModel()
: Instance(&TYPE) {
this->name = "Place";
}
void DataModel::Init() {
this->workspace = std::make_shared<Workspace>(shared<DataModel>());
this->workspace->InitService();
// Create the workspace if it doesn't exist
if (this->services.count("Workspace") == 0)
this->services["Workspace"] = std::make_shared<Workspace>(shared<DataModel>());
// Init all services
for (auto [_, service] : this->services) {
service->InitService();
}
}
void DataModel::SaveToFile(std::optional<std::string> path) {
if (!path.has_value() && !this->currentFile.has_value()) {
fprintf(stderr, "Cannot save DataModel because no path was provided.\n");
abort();
}
std::string target = path.has_value() ? path.value() : this->currentFile.value();
std::ofstream outStream(target);
pugi::xml_document doc;
pugi::xml_node root = doc.append_child("openblocks");
for (InstanceRef child : this->GetChildren()) {
child->Serialize(&root);
}
doc.save(outStream);
currentFile = target;
name = target;
printf("Place saved succesfully\n");
}
void DataModel::DeserializeService(pugi::xml_node* node) {
std::string className = node->attribute("class").value();
if (SERVICE_CONSTRUCTORS.count(className) == 0) {
fprintf(stderr, "Unknown service: '%s'\n", className.c_str());
abort();
}
printf("Adding a workspace. %d:%d:%d\n", services.count(className), services.size(), GetChildren().size());
if (services.count(className) != 0) {
fprintf(stderr, "Service %s defined multiple times in file\n", className.c_str());
return;
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = SERVICE_CONSTRUCTORS[className](shared<DataModel>());
// Read properties
pugi::xml_node propertiesNode = node->child("Properties");
for (pugi::xml_node propertyNode : propertiesNode) {
std::string propertyName = propertyNode.attribute("name").value();
auto meta_ = object->GetPropertyMeta(propertyName);
if (!meta_.has_value()) {
fprintf(stderr, "Attempt to set unknown property '%s' of %s\n", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
Data::Variant value = Data::Variant::Deserialize(&propertyNode);
object->SetPropertyValue(propertyName, value);
}
// Add children
for (pugi::xml_node childNode : node->children("Item")) {
InstanceRef child = Instance::Deserialize(&childNode);
object->AddChild(child);
}
// We add the service to the list
// All services get init'd at once in InitServices
this->services[className] = std::dynamic_pointer_cast<Service>(object);
}
std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
std::ifstream inStream(path);
pugi::xml_document doc;
doc.load(inStream);
pugi::xml_node rootNode = doc.child("openblocks");
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
for (pugi::xml_node childNode : rootNode.children("Item")) {
newModel->DeserializeService(&childNode);
}
newModel->Init();
return newModel;
}

View file

@ -5,17 +5,44 @@
class Workspace;
class DataModel;
class Service;
extern std::map<std::string, std::function<std::shared_ptr<Service>(std::weak_ptr<DataModel>)>> SERVICE_CONSTRUCTORS;
// The root instance to all objects in the hierarchy
class DataModel : public Instance {
//private:
private:
void DeserializeService(pugi::xml_node* node);
public:
const static InstanceType TYPE;
std::shared_ptr<Workspace> workspace;
std::map<std::string, std::shared_ptr<Service>> services;
std::optional<std::string> currentFile;
DataModel();
void Init();
static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); };
virtual const InstanceType* GetClass() override;
template <typename T>
std::shared_ptr<T> GetService(std::string name) {
if (services.count(name) != 0)
return services[name];
// TODO: Validate name
services[name] = SERVICE_CONSTRUCTORS[name](shared<DataModel>());
}
template <typename T>
std::optional<std::shared_ptr<T>> FindService() {
if (services.count(name) != 0)
return services[name];
return std::nullopt;
}
// Saving/loading
inline bool HasFile() { return this->currentFile.has_value(); }
void SaveToFile(std::optional<std::string> path = std::nullopt);
static std::shared_ptr<DataModel> LoadFromFile(std::string path);
};

10
src/objects/meta.cpp Normal file
View file

@ -0,0 +1,10 @@
#include "meta.h"
#include "objects/part.h"
#include "objects/workspace.h"
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "Instance", &Instance::TYPE },
{ "Part", &Part::TYPE },
{ "Workspace", &Workspace::TYPE },
{ "DataModel", &DataModel::TYPE },
};

8
src/objects/meta.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <map>
#include "objects/base/instance.h"
// Map of all instance types to their class names
extern std::map<std::string, const InstanceType*> INSTANCE_MAP;

View file

@ -85,6 +85,11 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
.type = &Data::CFrame::TYPE,
.codec = fieldCodecOf<Data::CFrame>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
} }, { "scale", {
.backingField = &scale,
.type = &Data::Vector3::TYPE,
.codec = fieldCodecOf<Data::Vector3, glm::vec3>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
} }
}
});

View file

@ -31,7 +31,11 @@ public:
// TODO: Switch these over to our dedicated datatypes
Data::CFrame cframe;
glm::vec3 scale;
Material material;
Material material {
.diffuse = glm::vec3(0.639216f, 0.635294f, 0.647059f),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
};
bool selected = false;
bool anchored = false;

View file

@ -12,6 +12,6 @@ public:
Workspace(std::weak_ptr<DataModel> dataModel);
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
// static inline InstanceRef Create() { return std::make_shared<Workspace>(); };
static inline std::shared_ptr<Service> Create(std::weak_ptr<DataModel> parent) { return std::make_shared<Workspace>(parent); };
virtual const InstanceType* GetClass() override;
};

View file

@ -110,6 +110,21 @@ void renderParts() {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
// if (inst->name == "Target") printf("(%f,%f,%f):(%f,%f,%f;%f,%f,%f;%f,%f,%f)\n",
// part->cframe.X(),
// part->cframe.Y(),
// part->cframe.Z(),
// part->cframe.RightVector().X(),
// part->cframe.UpVector().X(),
// part->cframe.LookVector().X(),
// part->cframe.RightVector().Y(),
// part->cframe.UpVector().Y(),
// part->cframe.LookVector().Y(),
// part->cframe.RightVector().Z(),
// part->cframe.UpVector().Z(),
// part->cframe.LookVector().Z()
// );
glm::mat4 model = part->cframe;
model = glm::scale(model, part->scale);
shader->set("model", model);