diff --git a/core/src/common.h b/core/src/common.h index b592786..2faae1b 100644 --- a/core/src/common.h +++ b/core/src/common.h @@ -17,6 +17,7 @@ typedef std::function gDataModel; +extern std::shared_ptr editModeDataModel; inline std::shared_ptr gWorkspace() { return std::dynamic_pointer_cast(gDataModel->services["Workspace"]); } extern std::optional hierarchyPreUpdateHandler; extern std::optional hierarchyPostUpdateHandler; diff --git a/core/src/objects/base/instance.cpp b/core/src/objects/base/instance.cpp index 66ab54f..f2f64c5 100644 --- a/core/src/objects/base/instance.cpp +++ b/core/src/objects/base/instance.cpp @@ -378,7 +378,7 @@ std::optional> Instance::Clone(RefState<_RefStatePrope } 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)); + refs.push_back(std::make_pair(newInstance, property)); state->refsAwaitingRemap[ref] = refs; newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect(); diff --git a/core/src/objects/datamodel.cpp b/core/src/objects/datamodel.cpp index 321c1bc..b245d40 100644 --- a/core/src/objects/datamodel.cpp +++ b/core/src/objects/datamodel.cpp @@ -1,6 +1,7 @@ #include "datamodel.h" #include "base/service.h" #include "objects/base/instance.h" +#include "objects/base/refstate.h" #include "objects/base/service.h" #include "objects/meta.h" #include "workspace.h" @@ -145,4 +146,130 @@ result>, NoSuchService> DataModel::FindSe if (services.count(className) != 0) return std::make_optional(std::dynamic_pointer_cast(services[className])); return (std::optional>)std::nullopt; +} + +std::shared_ptr DataModel::CloneModel() { + RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>(); + std::shared_ptr newModel = DataModel::New(); + + // 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 + newModel->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(newModel, property)); + state->refsAwaitingRemap[ref] = refs; + + newModel->SetPropertyValue(property, Data::InstanceRef(ref)).expect(); + } + } else { + Data::Variant value = GetPropertyValue(property).expect(); + newModel->SetPropertyValue(property, value).expect(); + } + } + + // Remap self + state->remappedInstances[shared_from_this()] = newModel; + + // Remap queued properties + for (std::pair, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) { + ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newModel)).expect(); + } + + // Clone services + for (std::shared_ptr child : GetChildren()) { + // Special case: Instances parented to DataModel which are not services + if (!(child->GetClass()->flags & INSTANCE_SERVICE)) { + auto result = child->Clone(state); + if (result) + newModel->AddChild(result.value()); + continue; + } + DataModel::cloneService(newModel, std::dynamic_pointer_cast(child), state); + } + + // Clone children of services + for (const auto& [className, service] : services) { + for (auto child : service->GetChildren()) { + auto result = child->Clone(state); + if (result) + service->AddChild(result.value()); + } + } + + return newModel; +} + +void DataModel::cloneService(std::shared_ptr target, std::shared_ptr service, RefState<_RefStatePropertyCell> state) { + // !!!!! THIS MAY CAUSE PROBLEMS !!!!! + // The way we are updating references means that their property onUpdate handler + // gets called immediately. They may expect certain invariants to be held such as workspace existing, while this + // is not the case. Currently, since this invariant is only used in the case of referring directly to workspace etc., + // this is fine, since GetService will avoid duplication problems. But here be dragons. + std::shared_ptr newInstance = target->GetService(service->GetClass()->className).expect(); + + // Copy properties + for (std::string property : service->GetProperties()) { + PropertyMeta meta = service->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 = service->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(newInstance, property)); + state->refsAwaitingRemap[ref] = refs; + + newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect(); + } + } else { + Data::Variant value = service->GetPropertyValue(property).expect(); + newInstance->SetPropertyValue(property, value).expect(); + } + } + + // Remap self + state->remappedInstances[service->shared_from_this()] = newInstance; + + // Add service prior to adding children, as children may expect service to already be parented to DataModel + target->AddChild(newInstance); + target->services[service->GetClass()->className] = std::dynamic_pointer_cast(newInstance); + + // Remap queued properties + for (std::pair, std::string> ref : state->refsAwaitingRemap[service->shared_from_this()]) { + ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect(); + } + + // Clone children + // for (std::shared_ptr child : service->GetChildren()) { + // std::optional> clonedChild = child->Clone(state); + // if (clonedChild) + // newInstance->AddChild(clonedChild.value()); + // } } \ No newline at end of file diff --git a/core/src/objects/datamodel.h b/core/src/objects/datamodel.h index 0fd1894..1511900 100644 --- a/core/src/objects/datamodel.h +++ b/core/src/objects/datamodel.h @@ -4,6 +4,7 @@ #include "error/result.h" #include "logger.h" #include "objects/base/instance.h" +#include "objects/base/refstate.h" #include "objects/meta.h" #include "panic.h" #include @@ -18,6 +19,7 @@ class Service; class DataModel : public Instance { private: void DeserializeService(pugi::xml_node node); + static void cloneService(std::shared_ptr target, std::shared_ptr, RefState<_RefStatePropertyCell>); public: const static InstanceType TYPE; @@ -68,4 +70,5 @@ public: inline bool HasFile() { return this->currentFile.has_value(); } void SaveToFile(std::optional path = std::nullopt); static std::shared_ptr LoadFromFile(std::string path); + std::shared_ptr CloneModel(); }; \ No newline at end of file diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp index dd77a09..8c23efd 100644 --- a/editor/mainwindow.cpp +++ b/editor/mainwindow.cpp @@ -274,6 +274,11 @@ void MainWindow::connectActionHandlers() { ui->actionRunSimulation->setEnabled(false); ui->actionPauseSimulation->setEnabled(true); ui->actionStopSimulation->setEnabled(true); + + std::shared_ptr newModel = editModeDataModel->CloneModel(); + gDataModel = newModel; + gDataModel->Init(); + ui->explorerView->updateRoot(gDataModel); }); connect(ui->actionPauseSimulation, &QAction::triggered, this, [&]() { @@ -282,7 +287,6 @@ void MainWindow::connectActionHandlers() { runState = RUN_PAUSED; ui->actionRunSimulation->setEnabled(true); ui->actionPauseSimulation->setEnabled(false); - return; }); connect(ui->actionStopSimulation, &QAction::triggered, this, [&]() { @@ -292,7 +296,11 @@ void MainWindow::connectActionHandlers() { ui->actionRunSimulation->setEnabled(true); ui->actionPauseSimulation->setEnabled(false); ui->actionStopSimulation->setEnabled(false); - return; + + // GC: Check to make sure gDataModel gets properly garbage collected prior to this + gDataModel = editModeDataModel; + gDataModel->Init(); + ui->explorerView->updateRoot(gDataModel); }); ui->actionRunSimulation->setEnabled(true);