mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
492 lines
14 KiB
C++
492 lines
14 KiB
C++
/*
|
|
* GDevelop Core
|
|
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
|
* reserved. This project is released under the MIT License.
|
|
*/
|
|
|
|
#include "GDCore/Project/Variable.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include "GDCore/Serialization/SerializerElement.h"
|
|
#include "GDCore/String.h"
|
|
#include "GDCore/Tools/UUID/UUID.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace gd {
|
|
|
|
gd::Variable Variable::badVariable;
|
|
|
|
gd::String Variable::TypeAsString(Type t) {
|
|
switch (t) {
|
|
case Type::String:
|
|
return "string";
|
|
case Type::Number:
|
|
return "number";
|
|
case Type::Boolean:
|
|
return "boolean";
|
|
case Type::Structure:
|
|
return "structure";
|
|
case Type::Array:
|
|
return "array";
|
|
case Type::MixedTypes:
|
|
return "mixed";
|
|
default:
|
|
return "error-type";
|
|
}
|
|
};
|
|
|
|
Variable::Type Variable::StringAsType(const gd::String& str) {
|
|
if (str == "string")
|
|
return Type::String;
|
|
else if (str == "number")
|
|
return Type::Number;
|
|
else if (str == "boolean")
|
|
return Type::Boolean;
|
|
else if (str == "structure")
|
|
return Type::Structure;
|
|
else if (str == "array")
|
|
return Type::Array;
|
|
else if (str == "mixed")
|
|
return Type::MixedTypes;
|
|
|
|
// Default to number
|
|
return Type::Number;
|
|
}
|
|
|
|
bool Variable::IsPrimitive(const Type type) {
|
|
return type == Type::String || type == Type::Number || type == Type::Boolean;
|
|
}
|
|
|
|
void Variable::CastTo(const Type newType) {
|
|
hasMixedValues = false;
|
|
if (newType == Type::Number)
|
|
SetValue(GetValue());
|
|
else if (newType == Type::String)
|
|
SetString(GetString());
|
|
else if (newType == Type::Boolean)
|
|
SetBool(GetBool());
|
|
else if (newType == Type::Structure) {
|
|
children.clear();
|
|
|
|
// Conversion is only possible for non primitive types
|
|
if (type == Type::Array)
|
|
for (auto i = childrenArray.begin(); i != childrenArray.end(); ++i)
|
|
children.insert(
|
|
std::make_pair(gd::String::From(i - childrenArray.begin()), (*i)));
|
|
|
|
type = Type::Structure;
|
|
// Free now unused memory
|
|
childrenArray.clear();
|
|
} else if (newType == Type::Array) {
|
|
childrenArray.clear();
|
|
|
|
// Conversion is only possible for non primitive types
|
|
if (type == Type::Structure)
|
|
for (auto i = children.begin(); i != children.end(); ++i)
|
|
childrenArray.push_back((*i).second);
|
|
|
|
type = Type::Array;
|
|
// Free now unused memory
|
|
children.clear();
|
|
} else if (newType == Type::MixedTypes) {
|
|
type = Type::MixedTypes;
|
|
hasMixedValues = true;
|
|
}
|
|
}
|
|
|
|
double Variable::GetValue() const {
|
|
if (type == Type::Number) {
|
|
return value;
|
|
} else if (type == Type::String) {
|
|
double retVal = str.empty() ? 0.0 : str.To<double>();
|
|
if (std::isnan(retVal)) retVal = 0.0;
|
|
return retVal;
|
|
} else if (type == Type::Boolean) {
|
|
return boolVal ? 1.0 : 0.0;
|
|
}
|
|
|
|
// It isn't possible to convert a non-primitive type to a number
|
|
return 0.0;
|
|
}
|
|
|
|
const gd::String& Variable::GetString() const {
|
|
if (type == Type::Number)
|
|
str = gd::String::From(value);
|
|
else if (type == Type::Boolean)
|
|
str = boolVal ? "1" : "0";
|
|
else if (type != Type::String)
|
|
str.clear();
|
|
|
|
return str;
|
|
}
|
|
|
|
bool Variable::GetBool() const {
|
|
if (type == Type::Boolean) {
|
|
return boolVal;
|
|
} else if (type == Type::String) {
|
|
return !str.empty();
|
|
} else if (type == Type::Number) {
|
|
return value != 0;
|
|
}
|
|
|
|
// It isn't possible to convert a non-primitive type to a boolean
|
|
return false;
|
|
}
|
|
|
|
bool Variable::HasChild(const gd::String& name) const {
|
|
return type == Type::Structure && children.find(name) != children.end();
|
|
}
|
|
|
|
/**
|
|
* \brief Return the child with the specified name.
|
|
*
|
|
* If the variable is not a structure or has not
|
|
* the specified child, an empty variable is returned.
|
|
*/
|
|
Variable& Variable::GetChild(const gd::String& name) {
|
|
auto it = children.find(name);
|
|
if (it != children.end()) return *it->second;
|
|
|
|
type = Type::Structure;
|
|
hasMixedValues = false;
|
|
children[name] = std::make_shared<gd::Variable>();
|
|
return *children[name];
|
|
}
|
|
|
|
/**
|
|
* \brief Return the child with the specified name.
|
|
*
|
|
* If the variable is not a structure or has not
|
|
* the specified child, an empty variable is returned.
|
|
*/
|
|
const Variable& Variable::GetChild(const gd::String& name) const {
|
|
auto it = children.find(name);
|
|
if (it != children.end()) return *it->second;
|
|
|
|
type = Type::Structure;
|
|
children[name] = std::make_shared<gd::Variable>();
|
|
return *children[name];
|
|
}
|
|
|
|
void Variable::RemoveChild(const gd::String& name) {
|
|
if (type != Type::Structure) return;
|
|
children.erase(name);
|
|
}
|
|
|
|
bool Variable::RenameChild(const gd::String& oldName,
|
|
const gd::String& newName) {
|
|
if (type != Type::Structure || !HasChild(oldName) || HasChild(newName))
|
|
return false;
|
|
|
|
children[newName] = children[oldName];
|
|
children.erase(oldName);
|
|
|
|
return true;
|
|
}
|
|
|
|
Variable& Variable::GetAtIndex(const size_t index) {
|
|
type = Type::Array;
|
|
while (childrenArray.size() <= index)
|
|
childrenArray.push_back(std::make_shared<gd::Variable>());
|
|
return *childrenArray[index];
|
|
};
|
|
|
|
const Variable& Variable::GetAtIndex(const size_t index) const {
|
|
if (childrenArray.size() <= index) return badVariable;
|
|
return *childrenArray.at(index);
|
|
};
|
|
|
|
void Variable::MoveChildInArray(const size_t oldIndex, const size_t newIndex) {
|
|
if (oldIndex >= childrenArray.size() || newIndex >= childrenArray.size())
|
|
return;
|
|
|
|
std::shared_ptr<gd::Variable> object = std::move(childrenArray[oldIndex]);
|
|
childrenArray.erase(childrenArray.begin() + oldIndex);
|
|
childrenArray.insert(childrenArray.begin() + newIndex, std::move(object));
|
|
}
|
|
|
|
Variable& Variable::PushNew() {
|
|
const size_t count = GetChildrenCount();
|
|
auto& variable = GetAtIndex(count);
|
|
if (type == Type::Array && count > 0) {
|
|
hasMixedValues = false;
|
|
const auto childType = GetAtIndex(count - 1).type;
|
|
variable.type = childType;
|
|
if (childType == Type::Number) {
|
|
variable.SetValue(0);
|
|
}
|
|
else if (childType == Type::String) {
|
|
variable.SetString("");
|
|
}
|
|
else if (childType == Type::Boolean) {
|
|
variable.SetBool(false);
|
|
}
|
|
}
|
|
return variable;
|
|
};
|
|
|
|
void Variable::RemoveAtIndex(const size_t index) {
|
|
if (index >= childrenArray.size()) return;
|
|
childrenArray.erase(childrenArray.begin() + index);
|
|
};
|
|
|
|
bool Variable::InsertAtIndex(const gd::Variable& variable, const size_t index) {
|
|
if (type != Type::Array) return false;
|
|
hasMixedValues = false;
|
|
auto newVariable = std::make_shared<gd::Variable>(variable);
|
|
if (index < childrenArray.size()) {
|
|
childrenArray.insert(childrenArray.begin() + index, newVariable);
|
|
} else {
|
|
childrenArray.push_back(newVariable);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
bool Variable::InsertChild(const gd::String& name,
|
|
const gd::Variable& variable) {
|
|
if (type != Type::Structure || HasChild(name)) {
|
|
return false;
|
|
}
|
|
hasMixedValues = false;
|
|
children[name] = std::make_shared<gd::Variable>(variable);
|
|
return true;
|
|
};
|
|
|
|
void Variable::SerializeTo(SerializerElement& element) const {
|
|
element.SetStringAttribute("type", TypeAsString(GetType()));
|
|
if (IsFolded()) element.SetBoolAttribute("folded", true);
|
|
|
|
if (!persistentUuid.empty())
|
|
element.SetStringAttribute("persistentUuid", persistentUuid);
|
|
|
|
if (type == Type::String) {
|
|
element.SetStringAttribute("value", GetString());
|
|
} else if (type == Type::Number) {
|
|
element.SetDoubleAttribute("value", GetValue());
|
|
} else if (type == Type::Boolean) {
|
|
element.SetBoolAttribute("value", GetBool());
|
|
} else if (type == Type::Structure) {
|
|
SerializerElement& childrenElement = element.AddChild("children");
|
|
childrenElement.ConsiderAsArrayOf("variable");
|
|
for (auto i = children.begin(); i != children.end(); ++i) {
|
|
SerializerElement& variableElement = childrenElement.AddChild("variable");
|
|
variableElement.SetAttribute("name", i->first);
|
|
i->second->SerializeTo(variableElement);
|
|
}
|
|
} else if (type == Type::Array) {
|
|
SerializerElement& childrenElement = element.AddChild("children");
|
|
childrenElement.ConsiderAsArrayOf("variable");
|
|
for (auto child : childrenArray) {
|
|
child->SerializeTo(childrenElement.AddChild("variable"));
|
|
}
|
|
}
|
|
if (hasMixedValues) {
|
|
element.SetBoolAttribute("hasMixedValues", true);
|
|
}
|
|
}
|
|
|
|
void Variable::UnserializeFrom(const SerializerElement& element) {
|
|
type = StringAsType(element.GetStringAttribute("type", "string"));
|
|
|
|
persistentUuid = element.GetStringAttribute("persistentUuid");
|
|
|
|
// Compatibility with GD <= 5.0.0-beta102
|
|
// Before, everything was stored as strings.
|
|
// We can unserialize primitives as string as they can be converted from/to
|
|
// strings anyways, but structures cannot be converted from a string.
|
|
// If we detect children, but the type is primitive (meaning the default type
|
|
// is used as a type if missing), then it should be unserialized as a
|
|
// structure instead of a string.
|
|
if (element.HasChild("children", "Children") && IsPrimitive(type))
|
|
type = Type::Structure;
|
|
// end of compatibility code
|
|
SetFolded(element.GetBoolAttribute("folded", false));
|
|
|
|
if (IsPrimitive(type)) {
|
|
if (type == Type::String) {
|
|
SetString(element.GetStringAttribute("value", "0", "Value"));
|
|
} else if (type == Type::Number) {
|
|
SetValue(element.GetDoubleAttribute("value", 0.0, "Value"));
|
|
} else if (type == Type::Boolean) {
|
|
SetBool(element.GetBoolAttribute("value", false, "Value"));
|
|
}
|
|
} else {
|
|
const SerializerElement& childrenElement =
|
|
element.GetChild("children", 0, "Children");
|
|
childrenElement.ConsiderAsArrayOf("variable", "Variable");
|
|
if (childrenElement.GetChildrenCount() == 0) return;
|
|
|
|
for (int i = 0; i < childrenElement.GetChildrenCount(); ++i) {
|
|
const SerializerElement& childElement = childrenElement.GetChild(i);
|
|
if (type == Type::Structure) {
|
|
gd::String name = childElement.GetStringAttribute("name", "", "Name");
|
|
children[name] = std::make_shared<gd::Variable>();
|
|
children[name]->UnserializeFrom(childElement);
|
|
} else if (type == Type::Array)
|
|
PushNew().UnserializeFrom(childElement);
|
|
}
|
|
}
|
|
if (element.GetBoolAttribute("hasMixedValues", false)) {
|
|
MarkAsMixedValues();
|
|
}
|
|
}
|
|
|
|
Variable& Variable::ResetPersistentUuid() {
|
|
persistentUuid = UUID::MakeUuid4();
|
|
for (auto& it : children) {
|
|
it.second->ResetPersistentUuid();
|
|
}
|
|
for (auto& it : childrenArray) {
|
|
it->ResetPersistentUuid();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Variable& Variable::ClearPersistentUuid() {
|
|
persistentUuid = "";
|
|
for (auto& it : children) {
|
|
it.second->ClearPersistentUuid();
|
|
}
|
|
for (auto& it : childrenArray) {
|
|
it->ClearPersistentUuid();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
std::vector<gd::String> Variable::GetAllChildrenNames() const {
|
|
std::vector<gd::String> names;
|
|
for (auto& it : children) {
|
|
names.push_back(it.first);
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
bool Variable::Contains(const gd::Variable& variableToSearch,
|
|
bool recursive) const {
|
|
for (auto& it : children) {
|
|
if (it.second.get() == &variableToSearch) return true;
|
|
if (recursive && it.second->Contains(variableToSearch, true)) return true;
|
|
}
|
|
for (auto& it : childrenArray) {
|
|
if (it.get() == &variableToSearch) return true;
|
|
if (recursive && it->Contains(variableToSearch, true)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Variable::RemoveRecursively(const gd::Variable& variableToRemove) {
|
|
for (auto it = children.begin(); it != children.end();) {
|
|
if (it->second.get() == &variableToRemove)
|
|
it = children.erase(it);
|
|
else {
|
|
it->second->RemoveRecursively(variableToRemove);
|
|
it++;
|
|
}
|
|
}
|
|
for (auto it = childrenArray.begin(); it != childrenArray.end();)
|
|
if (it->get() == &variableToRemove)
|
|
childrenArray.erase(it);
|
|
else {
|
|
(*it)->RemoveRecursively(variableToRemove);
|
|
it++;
|
|
};
|
|
}
|
|
|
|
Variable::Variable(const Variable& other)
|
|
: value(other.value),
|
|
str(other.str),
|
|
folded(other.folded),
|
|
boolVal(other.boolVal),
|
|
type(other.type),
|
|
persistentUuid(other.persistentUuid),
|
|
hasMixedValues(other.hasMixedValues) {
|
|
CopyChildren(other);
|
|
}
|
|
|
|
Variable& Variable::operator=(const Variable& other) {
|
|
if (this != &other) {
|
|
value = other.value;
|
|
str = other.str;
|
|
folded = other.folded;
|
|
boolVal = other.boolVal;
|
|
type = other.type;
|
|
persistentUuid = other.persistentUuid;
|
|
hasMixedValues = other.hasMixedValues;
|
|
CopyChildren(other);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void Variable::CopyChildren(const gd::Variable& other) {
|
|
children.clear();
|
|
for (auto& it : other.children) {
|
|
children[it.first] = std::make_shared<gd::Variable>(*it.second);
|
|
}
|
|
for (auto child : other.childrenArray) {
|
|
childrenArray.push_back(std::make_shared<gd::Variable>(*child.get()));
|
|
}
|
|
}
|
|
|
|
bool Variable::operator==(const gd::Variable &variable) const {
|
|
if (type != variable.type || hasMixedValues || variable.hasMixedValues) {
|
|
return false;
|
|
}
|
|
if (type == Variable::Type::Number) {
|
|
return value == variable.value;
|
|
}
|
|
if (type == Variable::Type::String) {
|
|
return str == variable.str;
|
|
}
|
|
if (type == Variable::Type::Boolean) {
|
|
return boolVal == variable.boolVal;
|
|
}
|
|
if (type == Variable::Type::Structure) {
|
|
if (children.size() != variable.children.size()) {
|
|
return false;
|
|
}
|
|
for (auto &pair : children) {
|
|
const gd::String &name = pair.first;
|
|
const auto &child = pair.second;
|
|
|
|
auto it = variable.children.find(name);
|
|
if (it == variable.children.end()) {
|
|
return false;
|
|
}
|
|
auto &otherChild = it->second;
|
|
if (*child != *otherChild) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (type == Variable::Type::Array) {
|
|
if (childrenArray.size() != variable.childrenArray.size()) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < childrenArray.size(); ++i) {
|
|
if (*childrenArray[i] != *variable.childrenArray[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
// MixedTypes variables can't equal another variable.
|
|
return false;
|
|
}
|
|
|
|
bool Variable::operator!=(const gd::Variable &variable) const {
|
|
return !(*this == variable);
|
|
}
|
|
|
|
void Variable::MarkAsMixedValues() {
|
|
hasMixedValues = true;
|
|
ClearChildren();
|
|
}
|
|
|
|
} // namespace gd
|