From 8b7fef624f083e5c04e2f67eea99d56725dc3705 Mon Sep 17 00:00:00 2001 From: maelstrom Date: Thu, 29 May 2025 22:07:00 +0200 Subject: [PATCH] feat(serialization): serialize instance references --- core/src/datatypes/ref.cpp | 6 +- core/src/objects/base/instance.cpp | 82 ++++++++++++++++++++++--- core/src/objects/base/instance.h | 8 +-- core/src/objects/base/refstate.h | 20 ++++-- core/src/objects/datamodel.cpp | 99 ++++++++++++++++++------------ core/src/objects/datamodel.h | 4 +- 6 files changed, 156 insertions(+), 63 deletions(-) diff --git a/core/src/datatypes/ref.cpp b/core/src/datatypes/ref.cpp index 3aa35a1..9fcb054 100644 --- a/core/src/datatypes/ref.cpp +++ b/core/src/datatypes/ref.cpp @@ -33,11 +33,13 @@ Data::InstanceRef::operator std::weak_ptr() { // 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*); diff --git a/core/src/objects/base/instance.cpp b/core/src/objects/base/instance.cpp index 5f995d0..44d15e0 100644 --- a/core/src/objects/base/instance.cpp +++ b/core/src/objects/base/instance.cpp @@ -212,7 +212,7 @@ result 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 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 refWeak = GetPropertyValue(name).expect("Declared property is missing").get(); + 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 Instance::Deserialize(pugi::xml_node node) { +result 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 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::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 child = Instance::Deserialize(childNode); + result child = Instance::Deserialize(childNode, state); if (child.isError()) { std::get(child.error().value()).logMessage(); continue; @@ -362,7 +425,7 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) { return *this; } -std::optional> Instance::Clone(RefState<_RefStatePropertyCell> state) { +std::optional> Instance::Clone(RefStateClone state) { std::shared_ptr newInstance = GetClass()->constructor(); // Copy properties @@ -403,6 +466,7 @@ std::optional> Instance::Clone(RefState<_RefStatePrope for (std::pair, 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 child : GetChildren()) { diff --git a/core/src/objects/base/instance.h b/core/src/objects/base/instance.h index b1f968a..744e2fb 100644 --- a/core/src/objects/base/instance.h +++ b/core/src/objects/base/instance.h @@ -42,8 +42,6 @@ struct InstanceType { InstanceFlags flags; }; -typedef std::pair, std::string> _RefStatePropertyCell; - class DescendantsIterator; class JointInstance; @@ -135,9 +133,9 @@ public: } // Serialization - void Serialize(pugi::xml_node parent); - static result, NoSuchInstance> Deserialize(pugi::xml_node node); - std::optional> Clone(RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>()); + void Serialize(pugi::xml_node parent, RefStateSerialize state = std::make_shared<__RefStateSerialize>()); + static result, NoSuchInstance> Deserialize(pugi::xml_node node, RefStateDeserialize state = std::make_shared<__RefStateDeserialize>()); + std::optional> Clone(RefStateClone state = std::make_shared<__RefStateClone>()); }; typedef std::shared_ptr InstanceRef; diff --git a/core/src/objects/base/refstate.h b/core/src/objects/base/refstate.h index bdef1d9..bc0ae4b 100644 --- a/core/src/objects/base/refstate.h +++ b/core/src/objects/base/refstate.h @@ -2,16 +2,26 @@ // Helper struct used for remapping reference when cloning/serializing +#include "datatypes/base.h" #include #include #include class Instance; -template +template struct __RefState { - std::map, std::shared_ptr> remappedInstances; - std::map, std::vector> refsAwaitingRemap; + std::map remappedInstances; + std::map> refsAwaitingRemap; + int count = 0; }; -template -using RefState = std::shared_ptr<__RefState>; \ No newline at end of file +template +using RefState = std::shared_ptr<__RefState>; + +typedef __RefState, std::string>, std::shared_ptr, std::shared_ptr> __RefStateClone; +typedef __RefState> __RefStateSerialize; +typedef __RefState, std::string>, std::shared_ptr, std::string> __RefStateDeserialize; + +typedef std::shared_ptr<__RefStateClone> RefStateClone; +typedef std::shared_ptr<__RefStateSerialize> RefStateSerialize; +typedef std::shared_ptr<__RefStateDeserialize> RefStateDeserialize; \ No newline at end of file diff --git a/core/src/objects/datamodel.cpp b/core/src/objects/datamodel.cpp index 70473e3..22eff4c 100644 --- a/core/src/objects/datamodel.cpp +++ b/core/src/objects/datamodel.cpp @@ -59,49 +59,49 @@ void DataModel::SaveToFile(std::optional 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 child = Instance::Deserialize(childNode); - if (child.isError()) { - std::get(child.error().value()).logMessage(); - continue; - } - object->AddChild(child.expect()); - } +// // Add children +// for (pugi::xml_node childNode : node.children("Item")) { +// result child = Instance::Deserialize(childNode, state); +// if (child.isError()) { +// std::get(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(object); -} +// // We add the service to the list +// // All services get init'd at once in InitServices +// this->services[className] = std::dynamic_pointer_cast(object); +// } std::shared_ptr DataModel::LoadFromFile(std::string path) { std::ifstream inStream(path); @@ -110,9 +110,28 @@ std::shared_ptr DataModel::LoadFromFile(std::string path) { pugi::xml_node rootNode = doc.child("openblocks"); std::shared_ptr newModel = std::make_shared(); + 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); } newModel->Init(); @@ -146,7 +165,7 @@ result>, NoSuchService> DataModel::FindSe } std::shared_ptr DataModel::CloneModel() { - RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>(); + RefStateClone state = std::make_shared<__RefStateClone>(); std::shared_ptr newModel = DataModel::New(); // Copy properties diff --git a/core/src/objects/datamodel.h b/core/src/objects/datamodel.h index 13b68ea..236680e 100644 --- a/core/src/objects/datamodel.h +++ b/core/src/objects/datamodel.h @@ -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 target, std::shared_ptr, RefState<_RefStatePropertyCell>); + // void DeserializeService(pugi::xml_node node, RefStateDeserialize); + static void cloneService(std::shared_ptr target, std::shared_ptr, RefStateClone); public: std::map> services;