feat(serialization): serialize instance references
This commit is contained in:
parent
3a3b2d12c9
commit
8b7fef624f
6 changed files with 156 additions and 63 deletions
|
@ -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*);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue