From cab5ec0514536f1619a5f569e06fa9fde9ea22ee Mon Sep 17 00:00:00 2001 From: maelstrom Date: Sun, 20 Apr 2025 01:04:59 +0200 Subject: [PATCH] feat(instance): added clone method --- core/src/objects/base/instance.cpp | 74 ++++++++++++++++++++++++++++++ core/src/objects/base/instance.h | 5 ++ core/src/objects/base/refstate.h | 17 +++++++ editor/mainglwidget.cpp | 3 ++ 4 files changed, 99 insertions(+) create mode 100644 core/src/objects/base/refstate.h diff --git a/core/src/objects/base/instance.cpp b/core/src/objects/base/instance.cpp index 58a34a8..66ab54f 100644 --- a/core/src/objects/base/instance.cpp +++ b/core/src/objects/base/instance.cpp @@ -2,8 +2,10 @@ #include "common.h" #include "datatypes/meta.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/meta.h" #include "logger.h" #include "panic.h" @@ -12,6 +14,9 @@ #include #include #include +#include +#include +#include // Static so that this variable name is "local" to this source file const InstanceType Instance::TYPE = { @@ -348,4 +353,73 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) { current = current->GetParent().value()->GetChildren()[siblingIndex.back()]; return *this; +} + +std::optional> Instance::Clone(RefState<_RefStatePropertyCell> state) { + std::shared_ptr newInstance = GetClass()->constructor(); + + // Copy properties + for (std::string property : GetProperties()) { + PropertyMeta meta = GetPropertyMeta(property).expect(); + + if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue; + + // Update InstanceRef properties using map above + if (meta.type == &Data::InstanceRef::TYPE) { + std::weak_ptr refWeak = GetPropertyValue(property).expect().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 + newInstance->SetPropertyValue(property, Data::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(ref, property)); + state->refsAwaitingRemap[ref] = refs; + + newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect(); + } + } else { + Data::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::string> ref : state->refsAwaitingRemap[shared_from_this()]) { + ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect(); + } + + // Clone children + for (std::shared_ptr child : GetChildren()) { + std::optional> clonedChild = child->Clone(state); + if (clonedChild) + newInstance->AddChild(clonedChild.value()); + } + + return newInstance; +} + +std::vector>> Instance::GetReferenceProperties() { + std::vector>> referenceProperties; + + auto propertyNames = GetProperties(); + + for (std::string property : propertyNames) { + PropertyMeta meta = GetPropertyMeta(property).expect(); + if (meta.type != &Data::InstanceRef::TYPE) continue; + + std::weak_ptr ref = GetPropertyValue(property).expect().get(); + if (ref.expired()) continue; + referenceProperties.push_back(std::make_pair(property, ref.lock())); + } + + return referenceProperties; } \ No newline at end of file diff --git a/core/src/objects/base/instance.h b/core/src/objects/base/instance.h index 5b04065..34ea23a 100644 --- a/core/src/objects/base/instance.h +++ b/core/src/objects/base/instance.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -14,6 +15,7 @@ #include "error/instance.h" #include "error/result.h" #include "member.h" +#include "objects/base/refstate.h" class Instance; typedef std::shared_ptr(*InstanceConstructor)(); @@ -36,6 +38,7 @@ struct InstanceType { InstanceFlags flags; }; +typedef std::pair, std::string> _RefStatePropertyCell; class DescendantsIterator; @@ -99,6 +102,7 @@ public: void UpdateProperty(std::string name); // Returning a list of property names feels kinda janky. Is this really the way to go? std::vector GetProperties(); + std::vector>> GetReferenceProperties(); template result, InstanceCastError> CastTo() { @@ -112,6 +116,7 @@ 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>>()); }; typedef std::shared_ptr InstanceRef; diff --git a/core/src/objects/base/refstate.h b/core/src/objects/base/refstate.h new file mode 100644 index 0000000..bdef1d9 --- /dev/null +++ b/core/src/objects/base/refstate.h @@ -0,0 +1,17 @@ +#pragma once + +// Helper struct used for remapping reference when cloning/serializing + +#include +#include +#include +class Instance; + +template +struct __RefState { + std::map, std::shared_ptr> remappedInstances; + std::map, std::vector> refsAwaitingRemap; +}; + +template +using RefState = std::shared_ptr<__RefState>; \ No newline at end of file diff --git a/editor/mainglwidget.cpp b/editor/mainglwidget.cpp index 509d610..b8e138e 100644 --- a/editor/mainglwidget.cpp +++ b/editor/mainglwidget.cpp @@ -462,6 +462,9 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) { Logger::warning("warning message"); if (evt->key() == Qt::Key_O) Logger::error("error message"); + + if (evt->key() == Qt::Key_C && getSelection().size() > 0 && !getSelection()[0].expired()) + getSelection()[0].lock()->Clone().value()->SetParent(gWorkspace()); } void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {