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
|
// 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*);
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue