feat(datamodel): cloning the datamodel
This commit is contained in:
parent
cab5ec0514
commit
e9757ab306
5 changed files with 142 additions and 3 deletions
|
@ -17,6 +17,7 @@ typedef std::function<void(InstanceRef instance, std::string property, Data::Var
|
||||||
|
|
||||||
extern Camera camera;
|
extern Camera camera;
|
||||||
extern std::shared_ptr<DataModel> gDataModel;
|
extern std::shared_ptr<DataModel> gDataModel;
|
||||||
|
extern std::shared_ptr<DataModel> editModeDataModel;
|
||||||
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
|
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
|
||||||
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||||
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||||
|
|
|
@ -378,7 +378,7 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
|
||||||
} else {
|
} else {
|
||||||
// Otheriise, queue this property to be updated later, and keep its current value
|
// Otheriise, queue this property to be updated later, and keep its current value
|
||||||
auto& refs = state->refsAwaitingRemap[ref];
|
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;
|
state->refsAwaitingRemap[ref] = refs;
|
||||||
|
|
||||||
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
|
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "datamodel.h"
|
#include "datamodel.h"
|
||||||
#include "base/service.h"
|
#include "base/service.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
|
#include "objects/base/refstate.h"
|
||||||
#include "objects/base/service.h"
|
#include "objects/base/service.h"
|
||||||
#include "objects/meta.h"
|
#include "objects/meta.h"
|
||||||
#include "workspace.h"
|
#include "workspace.h"
|
||||||
|
@ -145,4 +146,130 @@ result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindSe
|
||||||
if (services.count(className) != 0)
|
if (services.count(className) != 0)
|
||||||
return std::make_optional(std::dynamic_pointer_cast<Service>(services[className]));
|
return std::make_optional(std::dynamic_pointer_cast<Service>(services[className]));
|
||||||
return (std::optional<std::shared_ptr<Service>>)std::nullopt;
|
return (std::optional<std::shared_ptr<Service>>)std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DataModel> DataModel::CloneModel() {
|
||||||
|
RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>();
|
||||||
|
std::shared_ptr<DataModel> 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<Instance> refWeak = GetPropertyValue(property).expect().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
|
||||||
|
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::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
|
||||||
|
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newModel)).expect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone services
|
||||||
|
for (std::shared_ptr<Instance> 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<Service>(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<DataModel> target, std::shared_ptr<Service> 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<Instance> 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<Instance> refWeak = service->GetPropertyValue(property).expect().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
|
||||||
|
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<Service>(newInstance);
|
||||||
|
|
||||||
|
// Remap queued properties
|
||||||
|
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[service->shared_from_this()]) {
|
||||||
|
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone children
|
||||||
|
// for (std::shared_ptr<Instance> child : service->GetChildren()) {
|
||||||
|
// std::optional<std::shared_ptr<Instance>> clonedChild = child->Clone(state);
|
||||||
|
// if (clonedChild)
|
||||||
|
// newInstance->AddChild(clonedChild.value());
|
||||||
|
// }
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
#include "error/result.h"
|
#include "error/result.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
|
#include "objects/base/refstate.h"
|
||||||
#include "objects/meta.h"
|
#include "objects/meta.h"
|
||||||
#include "panic.h"
|
#include "panic.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -18,6 +19,7 @@ class Service;
|
||||||
class DataModel : public Instance {
|
class DataModel : public Instance {
|
||||||
private:
|
private:
|
||||||
void DeserializeService(pugi::xml_node node);
|
void DeserializeService(pugi::xml_node node);
|
||||||
|
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefState<_RefStatePropertyCell>);
|
||||||
public:
|
public:
|
||||||
const static InstanceType TYPE;
|
const static InstanceType TYPE;
|
||||||
|
|
||||||
|
@ -68,4 +70,5 @@ public:
|
||||||
inline bool HasFile() { return this->currentFile.has_value(); }
|
inline bool HasFile() { return this->currentFile.has_value(); }
|
||||||
void SaveToFile(std::optional<std::string> path = std::nullopt);
|
void SaveToFile(std::optional<std::string> path = std::nullopt);
|
||||||
static std::shared_ptr<DataModel> LoadFromFile(std::string path);
|
static std::shared_ptr<DataModel> LoadFromFile(std::string path);
|
||||||
|
std::shared_ptr<DataModel> CloneModel();
|
||||||
};
|
};
|
|
@ -274,6 +274,11 @@ void MainWindow::connectActionHandlers() {
|
||||||
ui->actionRunSimulation->setEnabled(false);
|
ui->actionRunSimulation->setEnabled(false);
|
||||||
ui->actionPauseSimulation->setEnabled(true);
|
ui->actionPauseSimulation->setEnabled(true);
|
||||||
ui->actionStopSimulation->setEnabled(true);
|
ui->actionStopSimulation->setEnabled(true);
|
||||||
|
|
||||||
|
std::shared_ptr<DataModel> newModel = editModeDataModel->CloneModel();
|
||||||
|
gDataModel = newModel;
|
||||||
|
gDataModel->Init();
|
||||||
|
ui->explorerView->updateRoot(gDataModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->actionPauseSimulation, &QAction::triggered, this, [&]() {
|
connect(ui->actionPauseSimulation, &QAction::triggered, this, [&]() {
|
||||||
|
@ -282,7 +287,6 @@ void MainWindow::connectActionHandlers() {
|
||||||
runState = RUN_PAUSED;
|
runState = RUN_PAUSED;
|
||||||
ui->actionRunSimulation->setEnabled(true);
|
ui->actionRunSimulation->setEnabled(true);
|
||||||
ui->actionPauseSimulation->setEnabled(false);
|
ui->actionPauseSimulation->setEnabled(false);
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->actionStopSimulation, &QAction::triggered, this, [&]() {
|
connect(ui->actionStopSimulation, &QAction::triggered, this, [&]() {
|
||||||
|
@ -292,7 +296,11 @@ void MainWindow::connectActionHandlers() {
|
||||||
ui->actionRunSimulation->setEnabled(true);
|
ui->actionRunSimulation->setEnabled(true);
|
||||||
ui->actionPauseSimulation->setEnabled(false);
|
ui->actionPauseSimulation->setEnabled(false);
|
||||||
ui->actionStopSimulation->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);
|
ui->actionRunSimulation->setEnabled(true);
|
||||||
|
|
Loading…
Add table
Reference in a new issue