181 lines
No EOL
6.4 KiB
C++
181 lines
No EOL
6.4 KiB
C++
#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 "objects/script/serverscriptservice.h"
|
|
#include "datatypes/variant.h"
|
|
#include "workspace.h"
|
|
#include "logger.h"
|
|
#include "panic.h"
|
|
#include <pugixml.hpp>
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
DataModel::DataModel()
|
|
: Instance(&TYPE) {
|
|
this->name = "Place";
|
|
}
|
|
|
|
void DataModel::Init(bool runMode) {
|
|
// Create the workspace if it doesn't exist
|
|
if (this->services.count("Workspace") == 0) {
|
|
this->services["Workspace"] = std::make_shared<Workspace>();
|
|
AddChild(this->services["Workspace"]);
|
|
}
|
|
|
|
GetService<ServerScriptService>();
|
|
|
|
// Init all services
|
|
for (auto [_, service] : this->services) {
|
|
service->InitService();
|
|
if (runMode) service->OnRun();
|
|
}
|
|
}
|
|
|
|
void DataModel::SaveToFile(std::optional<std::string> path) {
|
|
if (!path.has_value() && !this->currentFile.has_value()) {
|
|
Logger::fatalError("Cannot save DataModel because no path was provided.");
|
|
panic();
|
|
}
|
|
|
|
std::string target = path.has_value() ? path.value() : this->currentFile.value();
|
|
|
|
std::ofstream outStream(target);
|
|
|
|
pugi::xml_document doc;
|
|
pugi::xml_node root = doc.append_child("openblocks");
|
|
|
|
for (std::shared_ptr<Instance> child : this->GetChildren()) {
|
|
child->Serialize(root);
|
|
}
|
|
|
|
doc.save(outStream);
|
|
currentFile = target;
|
|
name = target;
|
|
Logger::info("Place saved successfully");
|
|
}
|
|
|
|
std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
|
|
std::ifstream inStream(path);
|
|
pugi::xml_document doc;
|
|
doc.load(inStream);
|
|
|
|
pugi::xml_node rootNode = doc.child("openblocks");
|
|
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
|
|
RefStateDeserialize state = std::make_shared<__RefStateDeserialize>();
|
|
|
|
for (pugi::xml_node childNode : rootNode.children("Item")) {
|
|
// 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();
|
|
|
|
return newModel;
|
|
}
|
|
|
|
result<std::shared_ptr<Service>, NoSuchService> DataModel::GetService(std::string className) {
|
|
if (services.count(className) != 0)
|
|
return std::dynamic_pointer_cast<Service>(services[className]);
|
|
|
|
if (!INSTANCE_MAP[className] || ~(INSTANCE_MAP[className]->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) == 0) {
|
|
return NoSuchService(className);
|
|
}
|
|
|
|
services[className] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[className]->constructor());
|
|
AddChild(std::dynamic_pointer_cast<Instance>(services[className]));
|
|
services[className]->InitService();
|
|
|
|
return std::dynamic_pointer_cast<Service>(services[className]);
|
|
}
|
|
|
|
result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindService(std::string className) {
|
|
if (!INSTANCE_MAP[className] || (INSTANCE_MAP[className]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
|
return NoSuchService(className);
|
|
}
|
|
|
|
if (services.count(className) != 0)
|
|
return std::make_optional(std::dynamic_pointer_cast<Service>(services[className]));
|
|
return (std::optional<std::shared_ptr<Service>>)std::nullopt;
|
|
}
|
|
|
|
std::shared_ptr<DataModel> DataModel::CloneModel() {
|
|
RefStateClone state = std::make_shared<__RefStateClone>();
|
|
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 std::shared_ptr<Instance> properties using map above
|
|
if (meta.type.type == DATA_VALUE && meta.type.descriptor == &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
|
|
newModel->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(newModel, property));
|
|
state->refsAwaitingRemap[ref] = refs;
|
|
|
|
newModel->SetPropertyValue(property, InstanceRef(ref)).expect();
|
|
}
|
|
} else {
|
|
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, InstanceRef(newModel)).expect();
|
|
}
|
|
|
|
// Clone services
|
|
for (std::shared_ptr<Instance> child : GetChildren()) {
|
|
auto result = child->Clone(state);
|
|
if (!result)
|
|
continue;
|
|
|
|
newModel->AddChild(result.value());
|
|
|
|
// Special case: Ignore instances parented to DataModel which are not services
|
|
if (child->GetClass()->flags & INSTANCE_SERVICE) {
|
|
newModel->services[child->GetClass()->className] = std::dynamic_pointer_cast<Service>(result.value());
|
|
}
|
|
}
|
|
|
|
return newModel;
|
|
} |