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
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) {
return Data::InstanceRef();
// Handled by Instance
panic();
}
static int inst_gc(lua_State*);

View file

@ -212,7 +212,7 @@ result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::stri
if (name == "Name") {
return PropertyMeta { &Data::String::TYPE };
} else if (name == "Parent") {
return PropertyMeta { &Data::InstanceRef::TYPE, };
return PropertyMeta { &Data::InstanceRef::TYPE, PROP_NOSAVE };
} else if (name == "ClassName") {
return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY };
}
@ -259,7 +259,7 @@ std::vector<std::string> Instance::GetProperties() {
// 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");
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);
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
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();
if (INSTANCE_MAP.count(className) == 0) {
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());
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
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()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
@ -362,7 +425,7 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
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();
// 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()]) {
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Clone children
for (std::shared_ptr<Instance> child : GetChildren()) {

View file

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

View file

@ -2,16 +2,26 @@
// Helper struct used for remapping reference when cloning/serializing
#include "datatypes/base.h"
#include <map>
#include <memory>
#include <vector>
class Instance;
template <typename T>
template <typename T, typename U, typename K>
struct __RefState {
std::map<std::shared_ptr<Instance>, std::shared_ptr<Instance>> remappedInstances;
std::map<std::shared_ptr<Instance>, std::vector<T>> refsAwaitingRemap;
std::map<K, U> remappedInstances;
std::map<K, std::vector<T>> refsAwaitingRemap;
int count = 0;
};
template <typename T>
using RefState = std::shared_ptr<__RefState<T>>;
template <typename T, typename U, typename K>
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");
}
void DataModel::DeserializeService(pugi::xml_node node) {
std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
return;
}
// void DataModel::DeserializeService(pugi::xml_node node, RefStateDeserialize state) {
// std::string className = node.attribute("class").value();
// if (INSTANCE_MAP.count(className) == 0) {
// Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
// return;
// }
if (services.count(className) != 0) {
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
return;
}
// if (services.count(className) != 0) {
// Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
// return;
// }
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = INSTANCE_MAP[className]->constructor();
AddChild(object);
// // This will error if an abstract instance is used in the file. Oh well, not my prob rn.
// InstanceRef object = INSTANCE_MAP[className]->constructor();
// AddChild(object);
// 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_) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value).expect();
}
// // 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_) {
// Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
// continue;
// }
// Data::Variant value = Data::Variant::Deserialize(propertyNode, state);
// object->SetPropertyValue(propertyName, value).expect();
// }
// Add children
for (pugi::xml_node childNode : node.children("Item")) {
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
}
// // Add children
// for (pugi::xml_node childNode : node.children("Item")) {
// result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode, state);
// if (child.isError()) {
// std::get<NoSuchInstance>(child.error().value()).logMessage();
// continue;
// }
// object->AddChild(child.expect());
// }
// 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);
}
// // 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);
@ -110,9 +110,28 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
pugi::xml_node rootNode = doc.child("openblocks");
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
RefStateDeserialize state = std::make_shared<__RefStateDeserialize>();
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();
@ -146,7 +165,7 @@ result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindSe
}
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();
// Copy properties

View file

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