#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 #include #include #include #include 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(); AddChild(this->services["Workspace"]); } GetService(); // Init all services for (auto [_, service] : this->services) { service->InitService(); if (runMode) service->OnRun(); } } void DataModel::SaveToFile(std::optional 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 child : this->GetChildren()) { child->Serialize(root); } doc.save(outStream); currentFile = target; name = target; Logger::info("Place saved successfully"); } std::shared_ptr 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 newModel = std::make_shared(); 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); } newModel->currentFile = path; newModel->Init(); return newModel; } result, NoSuchService> DataModel::GetService(std::string className) { if (services.count(className) != 0) return std::dynamic_pointer_cast(services[className]); if (!INSTANCE_MAP[className] || ~(INSTANCE_MAP[className]->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) == 0) { return NoSuchService(className); } services[className] = std::dynamic_pointer_cast(INSTANCE_MAP[className]->constructor()); AddChild(std::dynamic_pointer_cast(services[className])); services[className]->InitService(); return std::dynamic_pointer_cast(services[className]); } result>, 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(services[className])); return (std::optional>)std::nullopt; } std::shared_ptr DataModel::CloneModel() { RefStateClone state = std::make_shared<__RefStateClone>(); 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 std::shared_ptr properties using map above if (meta.type.descriptor == &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, 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::string> ref : state->refsAwaitingRemap[shared_from_this()]) { ref.first->SetPropertyValue(ref.second, InstanceRef(newModel)).expect(); } // Clone services for (std::shared_ptr 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(result.value()); } } return newModel; }