feat(serialization): serialize instance references

This commit is contained in:
maelstrom 2025-05-29 22:07:00 +02:00
parent 3a3b2d12c9
commit 8b7fef624f
6 changed files with 156 additions and 63 deletions

View file

@ -33,11 +33,13 @@ Data::InstanceRef::operator std::weak_ptr<Instance>() {
// Serialization // Serialization
void Data::InstanceRef::Serialize(pugi::xml_node node) const { void Data::InstanceRef::Serialize(pugi::xml_node node) const {
// node.text().set(this->ToHex()); // Handled by Instance
panic();
} }
Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) { Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) {
return Data::InstanceRef(); // Handled by Instance
panic();
} }
static int inst_gc(lua_State*); static int inst_gc(lua_State*);

View file

@ -212,7 +212,7 @@ result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::stri
if (name == "Name") { if (name == "Name") {
return PropertyMeta { &Data::String::TYPE }; return PropertyMeta { &Data::String::TYPE };
} else if (name == "Parent") { } else if (name == "Parent") {
return PropertyMeta { &Data::InstanceRef::TYPE, }; return PropertyMeta { &Data::InstanceRef::TYPE, PROP_NOSAVE };
} else if (name == "ClassName") { } else if (name == "ClassName") {
return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY }; return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY };
} }
@ -259,7 +259,7 @@ std::vector<std::string> Instance::GetProperties() {
// Serialization // Serialization
void Instance::Serialize(pugi::xml_node parent) { void Instance::Serialize(pugi::xml_node parent, RefStateSerialize state) {
pugi::xml_node node = parent.append_child("Item"); pugi::xml_node node = parent.append_child("Item");
node.append_attribute("class").set_value(this->GetClass()->className); node.append_attribute("class").set_value(this->GetClass()->className);
@ -271,16 +271,47 @@ void Instance::Serialize(pugi::xml_node parent) {
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name); pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
propertyNode.append_attribute("name").set_value(name); propertyNode.append_attribute("name").set_value(name);
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
// Update InstanceRef properties using map above
if (meta.type == &Data::InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(name).expect("Declared property is missing").get<Data::InstanceRef>();
if (refWeak.expired()) continue;
auto ref = refWeak.lock();
auto remappedRef = state->remappedInstances[ref]; // TODO: I think this is okay? Maybe?? Add null check?
if (remappedRef != "") {
// If the instance has already been remapped, set the new value
propertyNode.text().set(remappedRef);
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(propertyNode);
state->refsAwaitingRemap[ref] = refs;
}
} else {
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
}
} }
// Remap self
std::string remappedId = "OB" + std::to_string(state->count++);
state->remappedInstances[shared_from_this()] = remappedId;
node.append_attribute("referent").set_value(remappedId);
// Remap queued properties
for (pugi::xml_node ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.text().set(remappedId);
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Add children // Add children
for (InstanceRef child : this->children) { for (InstanceRef child : this->children) {
child->Serialize(node); child->Serialize(node, state);
} }
} }
result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) { result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node, RefStateDeserialize state) {
std::string className = node.attribute("class").value(); std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) { if (INSTANCE_MAP.count(className) == 0) {
return NoSuchInstance(className); return NoSuchInstance(className);
@ -300,13 +331,45 @@ result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str()); Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue; continue;
} }
Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value).expect("Declared property was missing"); // Update InstanceRef properties using map above
if (meta_.expect().type == &Data::InstanceRef::TYPE) {
if (propertyNode.text().empty())
continue;
std::string refId = propertyNode.text().as_string();
auto remappedRef = state->remappedInstances[refId]; // TODO: I think this is okay? Maybe?? Add null check?
if (remappedRef) {
// If the instance has already been remapped, set the new value
object->SetPropertyValue(propertyName, Data::InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[refId];
refs.push_back(std::make_pair(object, propertyName));
state->refsAwaitingRemap[refId] = refs;
object->SetPropertyValue(propertyName, Data::InstanceRef()).expect();
}
} else {
Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
}
} }
// Remap self
std::string remappedId = node.attribute("referent").value();
state->remappedInstances[remappedId] = object;
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[remappedId]) {
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(object)).expect();
}
state->refsAwaitingRemap[remappedId].clear();
// Read children // Read children
for (pugi::xml_node childNode : node.children("Item")) { for (pugi::xml_node childNode : node.children("Item")) {
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode); result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode, state);
if (child.isError()) { if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage(); std::get<NoSuchInstance>(child.error().value()).logMessage();
continue; continue;
@ -362,7 +425,7 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
return *this; return *this;
} }
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePropertyCell> state) { std::optional<std::shared_ptr<Instance>> Instance::Clone(RefStateClone state) {
std::shared_ptr<Instance> newInstance = GetClass()->constructor(); std::shared_ptr<Instance> newInstance = GetClass()->constructor();
// Copy properties // Copy properties
@ -403,6 +466,7 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) { for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect(); ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
} }
state->refsAwaitingRemap[shared_from_this()].clear();
// Clone children // Clone children
for (std::shared_ptr<Instance> child : GetChildren()) { for (std::shared_ptr<Instance> child : GetChildren()) {

View file

@ -42,8 +42,6 @@ struct InstanceType {
InstanceFlags flags; InstanceFlags flags;
}; };
typedef std::pair<std::shared_ptr<Instance>, std::string> _RefStatePropertyCell;
class DescendantsIterator; class DescendantsIterator;
class JointInstance; class JointInstance;
@ -135,9 +133,9 @@ public:
} }
// Serialization // Serialization
void Serialize(pugi::xml_node parent); void Serialize(pugi::xml_node parent, RefStateSerialize state = std::make_shared<__RefStateSerialize>());
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node); static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node, RefStateDeserialize state = std::make_shared<__RefStateDeserialize>());
std::optional<std::shared_ptr<Instance>> Clone(RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>()); std::optional<std::shared_ptr<Instance>> Clone(RefStateClone state = std::make_shared<__RefStateClone>());
}; };
typedef std::shared_ptr<Instance> InstanceRef; typedef std::shared_ptr<Instance> InstanceRef;

View file

@ -2,16 +2,26 @@
// Helper struct used for remapping reference when cloning/serializing // Helper struct used for remapping reference when cloning/serializing
#include "datatypes/base.h"
#include <map> #include <map>
#include <memory> #include <memory>
#include <vector> #include <vector>
class Instance; class Instance;
template <typename T> template <typename T, typename U, typename K>
struct __RefState { struct __RefState {
std::map<std::shared_ptr<Instance>, std::shared_ptr<Instance>> remappedInstances; std::map<K, U> remappedInstances;
std::map<std::shared_ptr<Instance>, std::vector<T>> refsAwaitingRemap; std::map<K, std::vector<T>> refsAwaitingRemap;
int count = 0;
}; };
template <typename T> template <typename T, typename U, typename K>
using RefState = std::shared_ptr<__RefState<T>>; using RefState = std::shared_ptr<__RefState<T, U, K>>;
typedef __RefState<std::pair<std::shared_ptr<Instance>, std::string>, std::shared_ptr<Instance>, std::shared_ptr<Instance>> __RefStateClone;
typedef __RefState<pugi::xml_node, std::string, std::shared_ptr<Instance>> __RefStateSerialize;
typedef __RefState<std::pair<std::shared_ptr<Instance>, std::string>, std::shared_ptr<Instance>, std::string> __RefStateDeserialize;
typedef std::shared_ptr<__RefStateClone> RefStateClone;
typedef std::shared_ptr<__RefStateSerialize> RefStateSerialize;
typedef std::shared_ptr<__RefStateDeserialize> RefStateDeserialize;

View file

@ -59,49 +59,49 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
Logger::info("Place saved successfully"); Logger::info("Place saved successfully");
} }
void DataModel::DeserializeService(pugi::xml_node node) { // void DataModel::DeserializeService(pugi::xml_node node, RefStateDeserialize state) {
std::string className = node.attribute("class").value(); // std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) { // if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown service: '%s'", className.c_str()); // Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
return; // return;
} // }
if (services.count(className) != 0) { // if (services.count(className) != 0) {
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str()); // Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
return; // return;
} // }
// This will error if an abstract instance is used in the file. Oh well, not my prob rn. // // This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = INSTANCE_MAP[className]->constructor(); // InstanceRef object = INSTANCE_MAP[className]->constructor();
AddChild(object); // AddChild(object);
// Read properties // // Read properties
pugi::xml_node propertiesNode = node.child("Properties"); // pugi::xml_node propertiesNode = node.child("Properties");
for (pugi::xml_node propertyNode : propertiesNode) { // for (pugi::xml_node propertyNode : propertiesNode) {
std::string propertyName = propertyNode.attribute("name").value(); // std::string propertyName = propertyNode.attribute("name").value();
auto meta_ = object->GetPropertyMeta(propertyName); // auto meta_ = object->GetPropertyMeta(propertyName);
if (!meta_) { // if (!meta_) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str()); // Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue; // continue;
} // }
Data::Variant value = Data::Variant::Deserialize(propertyNode); // Data::Variant value = Data::Variant::Deserialize(propertyNode, state);
object->SetPropertyValue(propertyName, value).expect(); // object->SetPropertyValue(propertyName, value).expect();
} // }
// Add children // // Add children
for (pugi::xml_node childNode : node.children("Item")) { // for (pugi::xml_node childNode : node.children("Item")) {
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode); // result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode, state);
if (child.isError()) { // if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage(); // std::get<NoSuchInstance>(child.error().value()).logMessage();
continue; // continue;
} // }
object->AddChild(child.expect()); // object->AddChild(child.expect());
} // }
// We add the service to the list // // We add the service to the list
// All services get init'd at once in InitServices // // All services get init'd at once in InitServices
this->services[className] = std::dynamic_pointer_cast<Service>(object); // this->services[className] = std::dynamic_pointer_cast<Service>(object);
} // }
std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) { std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
std::ifstream inStream(path); std::ifstream inStream(path);
@ -110,9 +110,28 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
pugi::xml_node rootNode = doc.child("openblocks"); pugi::xml_node rootNode = doc.child("openblocks");
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>(); std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
RefStateDeserialize state = std::make_shared<__RefStateDeserialize>();
for (pugi::xml_node childNode : rootNode.children("Item")) { for (pugi::xml_node childNode : rootNode.children("Item")) {
newModel->DeserializeService(childNode); // Make sure the class hasn't already been deserialized
std::string className = childNode.attribute("class").value();
// TODO: Make this push its children into the first service, or however it is actually done in the thing
// for parity
if (newModel->services.count(className) != 0) {
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
continue;
}
auto result = Instance::Deserialize(childNode, state);
if (result.isError()) {
Logger::errorf("Failed to deserialize service: %s", result.errorMessage()->c_str());
continue;
}
auto service = result.expect();
newModel->AddChild(service);
newModel->services[className] = std::dynamic_pointer_cast<Service>(service);
} }
newModel->Init(); newModel->Init();
@ -146,7 +165,7 @@ result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindSe
} }
std::shared_ptr<DataModel> DataModel::CloneModel() { std::shared_ptr<DataModel> DataModel::CloneModel() {
RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>(); RefStateClone state = std::make_shared<__RefStateClone>();
std::shared_ptr<DataModel> newModel = DataModel::New(); std::shared_ptr<DataModel> newModel = DataModel::New();
// Copy properties // Copy properties

View file

@ -16,8 +16,8 @@ class Service;
class DEF_INST_(abstract) DataModel : public Instance { class DEF_INST_(abstract) DataModel : public Instance {
AUTOGEN_PREAMBLE AUTOGEN_PREAMBLE
private: private:
void DeserializeService(pugi::xml_node node); // void DeserializeService(pugi::xml_node node, RefStateDeserialize);
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefState<_RefStatePropertyCell>); static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefStateClone);
public: public:
std::map<std::string, std::shared_ptr<Service>> services; std::map<std::string, std::shared_ptr<Service>> services;