openblocks/core/src/objects/base/instance.cpp

510 lines
No EOL
19 KiB
C++

#include "instance.h"
#include "common.h"
#include "datatypes/primitives.h"
#include "datatypes/variant.h"
#include "datatypes/base.h"
#include "datatypes/ref.h"
#include "error/instance.h"
#include "objects/base/member.h"
#include "objects/base/refstate.h"
#include "objects/datamodel.h"
#include "objects/meta.h"
#include "logger.h"
#include "panic.h"
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <pugixml.hpp>
#include "ptr_helpers.h"
// Static so that this variable name is "local" to this source file
const InstanceType Instance::TYPE = {
.super = NULL,
.className = "Instance",
.constructor = NULL, // Instance is abstract and therefore not creatable
.explorerIcon = "instance",
};
// Instance is abstract, so it should not implement GetClass directly
// InstanceType* Instance::GetClass() {
// return &TYPE_;
// }
Instance::Instance(const InstanceType* type) {
this->name = type->className;
}
Instance::~Instance () {
}
template <typename T>
std::weak_ptr<T> optional_to_weak(std::optional<std::shared_ptr<T>> a) {
return a ? a.value() : std::weak_ptr<T>();
}
// TODO: Test this
bool Instance::ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent) {
for (std::optional<std::shared_ptr<Instance>> currentParent = newParent; currentParent.has_value(); currentParent = currentParent.value()->GetParent()) {
if (currentParent.value() == this->shared_from_this())
return false;
}
return true;
}
bool Instance::SetParent(std::optional<std::shared_ptr<Instance>> newParent) {
if (this->parentLocked || !ancestryContinuityCheck(newParent))
return false;
auto lastParent = GetParent();
if (hierarchyPreUpdateHandler.has_value()) hierarchyPreUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
// If we currently have a parent, remove ourselves from it before adding ourselves to the new one
if (!this->parent.expired()) {
auto oldParent = this->parent.lock();
oldParent->children.erase(std::find(oldParent->children.begin(), oldParent->children.end(), this->shared_from_this()));
}
// Add ourselves to the new parent
if (newParent.has_value()) {
newParent.value()->children.push_back(this->shared_from_this());
}
this->parent = optional_to_weak(newParent);
// TODO: Add code for sending signals for parent updates
// TODO: Yeahhh maybe this isn't the best way of doing this?
if (hierarchyPostUpdateHandler.has_value()) hierarchyPostUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
this->OnParentUpdated(lastParent, newParent);
updateAncestry(this->shared<Instance>(), newParent);
return true;
}
void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedChild, std::optional<std::shared_ptr<Instance>> newParent) {
auto oldDataModel = _dataModel;
auto oldWorkspace = _workspace;
// Update parent data model and workspace, if applicable
if (GetParent()) {
this->_dataModel = GetParent().value()->GetClass() == &DataModel::TYPE ? std::dynamic_pointer_cast<DataModel>(GetParent().value()) : GetParent().value()->_dataModel;
this->_workspace = GetParent().value()->GetClass() == &Workspace::TYPE ? std::dynamic_pointer_cast<Workspace>(GetParent().value()) : GetParent().value()->_workspace;
} else {
this->_dataModel = {};
this->_workspace = {};
}
OnAncestryChanged(updatedChild, newParent);
AncestryChanged->Fire({updatedChild.has_value() ? InstanceRef(updatedChild.value()) : InstanceRef(), newParent.has_value() ? InstanceRef(newParent.value()) : InstanceRef()});
// Old workspace used to exist, and workspaces differ
if (!oldWorkspace.expired() && oldWorkspace != _workspace) {
OnWorkspaceRemoved(oldWorkspace.lock());
}
// New workspace exists, and workspaces differ
if (!_workspace.expired() && (_workspace != oldWorkspace)) {
OnWorkspaceAdded(!oldWorkspace.expired() ? std::make_optional(oldWorkspace.lock()) : std::nullopt, _workspace.lock());
}
// Update ancestry in descendants
for (std::shared_ptr<Instance> child : children) {
child->updateAncestry(updatedChild, newParent);
}
}
std::optional<std::shared_ptr<DataModel>> Instance::dataModel() {
return (_dataModel.expired()) ? std::nullopt : std::make_optional(_dataModel.lock());
}
std::optional<std::shared_ptr<Workspace>> Instance::workspace() {
return (_workspace.expired()) ? std::nullopt : std::make_optional(_workspace.lock());
}
std::optional<std::shared_ptr<Instance>> Instance::GetParent() {
if (parent.expired()) return std::nullopt;
return parent.lock();
}
void Instance::Destroy() {
if (parentLocked) return;
// TODO: Implement proper distruction stuff
SetParent(std::nullopt);
parentLocked = true;
}
bool Instance::IsA(std::string className) {
const InstanceType* cur = GetClass();
while (cur && cur->className != className) { cur = cur->super; }
return cur != nullptr;
}
std::optional<std::shared_ptr<Instance>> Instance::FindFirstChild(std::string name) {
for (auto child : children) {
if (child->name == name)
return child;
}
return std::nullopt;
}
static std::shared_ptr<Instance> DUMMY_INSTANCE;
DescendantsIterator Instance::GetDescendantsStart() {
return DescendantsIterator(GetChildren().size() > 0 ? GetChildren()[0] : DUMMY_INSTANCE);
}
DescendantsIterator Instance::GetDescendantsEnd() {
return DescendantsIterator(DUMMY_INSTANCE);
}
bool Instance::IsParentLocked() {
return this->parentLocked;
}
void Instance::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
// Empty stub
}
void Instance::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
// Empty stub
}
void Instance::OnWorkspaceAdded(std::optional<std::shared_ptr<Workspace>> oldWorkspace, std::shared_ptr<Workspace> newWorkspace) {
// Empty stub
}
void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
// Empty stub
}
// Properties
result<Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
return InternalGetPropertyValue(name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Variant value, bool sendUpdateEvent) {
auto result = InternalSetPropertyValue(name, value);
if (result.isSuccess() && sendUpdateEvent) {
InternalUpdateProperty(name);
sendPropertyUpdatedSignal(shared_from_this(), name, value);
}
return result;
}
result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
return InternalGetPropertyMeta(name);
}
result<Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
if (name == "Name") {
return Variant(this->name);
} else if (name == "Parent") {
return Variant(InstanceRef(this->parent));
} else if (name == "ClassName") {
return Variant(GetClass()->className);
}
return MemberNotFound(GetClass()->className, name);
}
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) {
if (name == "Name") {
return PropertyMeta { &STRING_TYPE };
} else if (name == "Parent") {
return PropertyMeta { &InstanceRef::TYPE, PROP_NOSAVE };
} else if (name == "ClassName") {
return PropertyMeta { &STRING_TYPE, PROP_NOSAVE | PROP_READONLY };
}
return MemberNotFound(GetClass()->className, name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Variant value) {
if (name == "Name") {
this->name = (std::string)value.get<std::string>();
} else if (name == "Parent") {
std::weak_ptr<Instance> ref = value.get<InstanceRef>();
SetParent(ref.expired() ? std::nullopt : std::make_optional(ref.lock()));
} else if (name == "ClassName") {
return AssignToReadOnlyMember(GetClass()->className, name);
} else {
return MemberNotFound(GetClass()->className, name);
}
return {};
}
void Instance::InternalUpdateProperty(std::string name) {
}
std::vector<std::string> Instance::InternalGetProperties() {
std::vector<std::string> members;
members.push_back("Name");
members.push_back("Parent");
members.push_back("ClassName");
return members;
}
void Instance::UpdateProperty(std::string name) {
InternalUpdateProperty(name);
}
std::vector<std::string> Instance::GetProperties() {
if (cachedMemberList.has_value()) return cachedMemberList.value();
std::vector<std::string> memberList = InternalGetProperties();
cachedMemberList = memberList;
return memberList;
}
// Serialization
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);
// Add properties
pugi::xml_node propertiesNode = node.append_child("Properties");
for (std::string name : GetProperties()) {
PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing");
if (meta.flags & (PROP_NOSAVE | PROP_READONLY)) continue; // This property should not be serialized. Skip...
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
propertyNode.append_attribute("name").set_value(name);
// Update std::shared_ptr<Instance> properties using map above
if (meta.type == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(name).expect("Declared property is missing").get<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 (std::shared_ptr<Instance> child : this->children) {
child->Serialize(node, state);
}
}
result<std::shared_ptr<Instance>, 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);
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
std::shared_ptr<Instance> object = INSTANCE_MAP[className]->constructor();
object->GetChildren();
// const InstanceType* type = INSTANCE_MAP.at(className);
// 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;
}
// Update std::shared_ptr<Instance> properties using map above
if (meta_.expect().type == &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, 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, InstanceRef()).expect();
}
} else {
Variant value = 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, InstanceRef(object)).expect();
}
state->refsAwaitingRemap[remappedId].clear();
// Read children
for (pugi::xml_node childNode : node.children("Item")) {
result<std::shared_ptr<Instance>, NoSuchInstance> child = Instance::Deserialize(childNode, state);
if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
}
return object;
}
// DescendantsIterator
DescendantsIterator::DescendantsIterator(std::shared_ptr<Instance> current) : current(current), root(current == DUMMY_INSTANCE ? DUMMY_INSTANCE : current->GetParent()), siblingIndex { 0 } { }
DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
// If the current item is dummy, an error has occurred, this is not supposed to happen.
if (current == DUMMY_INSTANCE) {
Logger::fatalError("Attempt to increment a descendant iterator past its end\n");
panic();
}
// If the current item has children, enter it
if (current->GetChildren().size() > 0) {
siblingIndex.push_back(0);
current = current->GetChildren()[0];
return *this;
}
// Otherwise, we move to the next sibling, if applicable.
// But not if one up is null or the root element
if (!current->GetParent() || current == root) {
current = DUMMY_INSTANCE;
return *this;
}
// If we've hit the end of this item's children, move one up
while (current->GetParent() && current->GetParent().value()->GetChildren().size() <= (siblingIndex.back() + 1)) {
siblingIndex.pop_back();
current = current->GetParent().value();
// But not if one up is null or the root element
if (!current->GetParent() || current == root) {
current = DUMMY_INSTANCE;
return *this;
}
}
// Now move to the next sibling
siblingIndex.back()++;
current = current->GetParent().value()->GetChildren()[siblingIndex.back()];
return *this;
}
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefStateClone state) {
std::shared_ptr<Instance> newInstance = GetClass()->constructor();
// Copy properties
for (std::string property : GetProperties()) {
PropertyMeta meta = GetPropertyMeta(property).expect();
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
// Update std::shared_ptr<Instance> properties using map above
if (meta.type == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<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
newInstance->SetPropertyValue(property, InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(std::make_pair(newInstance, property));
state->refsAwaitingRemap[ref] = refs;
newInstance->SetPropertyValue(property, InstanceRef(ref)).expect();
}
} else {
Variant value = GetPropertyValue(property).expect();
newInstance->SetPropertyValue(property, value).expect();
}
}
// Remap self
state->remappedInstances[shared_from_this()] = newInstance;
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.first->SetPropertyValue(ref.second, InstanceRef(newInstance)).expect();
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Clone children
for (std::shared_ptr<Instance> child : GetChildren()) {
std::optional<std::shared_ptr<Instance>> clonedChild = child->Clone(state);
if (clonedChild)
newInstance->AddChild(clonedChild.value());
}
return newInstance;
}
std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetReferenceProperties() {
std::vector<std::pair<std::string, std::shared_ptr<Instance>>> referenceProperties;
auto propertyNames = GetProperties();
for (std::string property : propertyNames) {
PropertyMeta meta = GetPropertyMeta(property).expect();
if (meta.type != &InstanceRef::TYPE) continue;
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<InstanceRef>();
if (ref.expired()) continue;
referenceProperties.push_back(std::make_pair(property, ref.lock()));
}
return referenceProperties;
}
std::string Instance::GetFullName() {
std::string currentName = name;
std::optional<std::shared_ptr<Instance>> currentParent = GetParent();
while (currentParent.has_value() && !currentParent.value()->IsA("DataModel")) {
currentName = currentParent.value()->name + "." + currentName;
currentParent = currentParent.value()->GetParent();
}
return currentName;
}