306 lines
11 KiB
C++
306 lines
11 KiB
C++
#include "Scripting/ScriptField.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
|
|
namespace XCEngine {
|
|
namespace Scripting {
|
|
|
|
namespace {
|
|
|
|
bool IsSafeScriptChar(unsigned char ch) {
|
|
return std::isalnum(ch) != 0 || ch == '_' || ch == '-' || ch == '.' || ch == '/' || ch == ' ';
|
|
}
|
|
|
|
int HexToInt(char ch) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
return ch - '0';
|
|
}
|
|
if (ch >= 'A' && ch <= 'F') {
|
|
return 10 + (ch - 'A');
|
|
}
|
|
if (ch >= 'a' && ch <= 'f') {
|
|
return 10 + (ch - 'a');
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
template<typename T>
|
|
std::string SerializeScalar(T value) {
|
|
std::ostringstream os;
|
|
os << std::setprecision(std::numeric_limits<T>::max_digits10) << value;
|
|
return os.str();
|
|
}
|
|
|
|
template<typename TVector>
|
|
bool TryDeserializeVector(const std::string& value, TVector& outVector, int componentCount) {
|
|
std::string normalized = value;
|
|
std::replace(normalized.begin(), normalized.end(), ',', ' ');
|
|
|
|
std::istringstream stream(normalized);
|
|
float components[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
for (int i = 0; i < componentCount; ++i) {
|
|
if (!(stream >> components[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if constexpr (std::is_same_v<TVector, Math::Vector2>) {
|
|
outVector = Math::Vector2(components[0], components[1]);
|
|
} else if constexpr (std::is_same_v<TVector, Math::Vector3>) {
|
|
outVector = Math::Vector3(components[0], components[1], components[2]);
|
|
} else {
|
|
outVector = Math::Vector4(components[0], components[1], components[2], components[3]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string ScriptFieldTypeToString(ScriptFieldType type) {
|
|
switch (type) {
|
|
case ScriptFieldType::None: return "None";
|
|
case ScriptFieldType::Float: return "Float";
|
|
case ScriptFieldType::Double: return "Double";
|
|
case ScriptFieldType::Bool: return "Bool";
|
|
case ScriptFieldType::Int32: return "Int32";
|
|
case ScriptFieldType::UInt64: return "UInt64";
|
|
case ScriptFieldType::String: return "String";
|
|
case ScriptFieldType::Vector2: return "Vector2";
|
|
case ScriptFieldType::Vector3: return "Vector3";
|
|
case ScriptFieldType::Vector4: return "Vector4";
|
|
case ScriptFieldType::GameObject: return "GameObject";
|
|
case ScriptFieldType::Component: return "Component";
|
|
}
|
|
|
|
return "None";
|
|
}
|
|
|
|
bool TryParseScriptFieldType(const std::string& value, ScriptFieldType& outType) {
|
|
if (value == "None") {
|
|
outType = ScriptFieldType::None;
|
|
} else if (value == "Float") {
|
|
outType = ScriptFieldType::Float;
|
|
} else if (value == "Double") {
|
|
outType = ScriptFieldType::Double;
|
|
} else if (value == "Bool") {
|
|
outType = ScriptFieldType::Bool;
|
|
} else if (value == "Int32") {
|
|
outType = ScriptFieldType::Int32;
|
|
} else if (value == "UInt64") {
|
|
outType = ScriptFieldType::UInt64;
|
|
} else if (value == "String") {
|
|
outType = ScriptFieldType::String;
|
|
} else if (value == "Vector2") {
|
|
outType = ScriptFieldType::Vector2;
|
|
} else if (value == "Vector3") {
|
|
outType = ScriptFieldType::Vector3;
|
|
} else if (value == "Vector4") {
|
|
outType = ScriptFieldType::Vector4;
|
|
} else if (value == "GameObject") {
|
|
outType = ScriptFieldType::GameObject;
|
|
} else if (value == "Component") {
|
|
outType = ScriptFieldType::Component;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsScriptFieldValueCompatible(ScriptFieldType type, const ScriptFieldValue& value) {
|
|
switch (type) {
|
|
case ScriptFieldType::None: return std::holds_alternative<std::monostate>(value);
|
|
case ScriptFieldType::Float: return std::holds_alternative<float>(value);
|
|
case ScriptFieldType::Double: return std::holds_alternative<double>(value);
|
|
case ScriptFieldType::Bool: return std::holds_alternative<bool>(value);
|
|
case ScriptFieldType::Int32: return std::holds_alternative<int32_t>(value);
|
|
case ScriptFieldType::UInt64: return std::holds_alternative<uint64_t>(value);
|
|
case ScriptFieldType::String: return std::holds_alternative<std::string>(value);
|
|
case ScriptFieldType::Vector2: return std::holds_alternative<Math::Vector2>(value);
|
|
case ScriptFieldType::Vector3: return std::holds_alternative<Math::Vector3>(value);
|
|
case ScriptFieldType::Vector4: return std::holds_alternative<Math::Vector4>(value);
|
|
case ScriptFieldType::GameObject: return std::holds_alternative<GameObjectReference>(value);
|
|
case ScriptFieldType::Component: return std::holds_alternative<ComponentReference>(value);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ScriptFieldValue CreateDefaultScriptFieldValue(ScriptFieldType type) {
|
|
switch (type) {
|
|
case ScriptFieldType::None: return std::monostate{};
|
|
case ScriptFieldType::Float: return 0.0f;
|
|
case ScriptFieldType::Double: return 0.0;
|
|
case ScriptFieldType::Bool: return false;
|
|
case ScriptFieldType::Int32: return int32_t(0);
|
|
case ScriptFieldType::UInt64: return uint64_t(0);
|
|
case ScriptFieldType::String: return std::string();
|
|
case ScriptFieldType::Vector2: return Math::Vector2::Zero();
|
|
case ScriptFieldType::Vector3: return Math::Vector3::Zero();
|
|
case ScriptFieldType::Vector4: return Math::Vector4::Zero();
|
|
case ScriptFieldType::GameObject: return GameObjectReference{};
|
|
case ScriptFieldType::Component: return ComponentReference{};
|
|
}
|
|
|
|
return std::monostate{};
|
|
}
|
|
|
|
std::string SerializeScriptFieldValue(ScriptFieldType type, const ScriptFieldValue& value) {
|
|
if (!IsScriptFieldValueCompatible(type, value)) {
|
|
return std::string();
|
|
}
|
|
|
|
switch (type) {
|
|
case ScriptFieldType::None:
|
|
return std::string();
|
|
case ScriptFieldType::Float:
|
|
return SerializeScalar(std::get<float>(value));
|
|
case ScriptFieldType::Double:
|
|
return SerializeScalar(std::get<double>(value));
|
|
case ScriptFieldType::Bool:
|
|
return std::get<bool>(value) ? "1" : "0";
|
|
case ScriptFieldType::Int32:
|
|
return std::to_string(std::get<int32_t>(value));
|
|
case ScriptFieldType::UInt64:
|
|
return std::to_string(std::get<uint64_t>(value));
|
|
case ScriptFieldType::String:
|
|
return EscapeScriptString(std::get<std::string>(value));
|
|
case ScriptFieldType::Vector2: {
|
|
const Math::Vector2& vector = std::get<Math::Vector2>(value);
|
|
return SerializeScalar(vector.x) + "," + SerializeScalar(vector.y);
|
|
}
|
|
case ScriptFieldType::Vector3: {
|
|
const Math::Vector3& vector = std::get<Math::Vector3>(value);
|
|
return SerializeScalar(vector.x) + "," + SerializeScalar(vector.y) + "," + SerializeScalar(vector.z);
|
|
}
|
|
case ScriptFieldType::Vector4: {
|
|
const Math::Vector4& vector = std::get<Math::Vector4>(value);
|
|
return SerializeScalar(vector.x) + "," + SerializeScalar(vector.y) + "," + SerializeScalar(vector.z) + "," + SerializeScalar(vector.w);
|
|
}
|
|
case ScriptFieldType::GameObject:
|
|
return std::to_string(std::get<GameObjectReference>(value).gameObjectUUID);
|
|
case ScriptFieldType::Component: {
|
|
const ComponentReference reference = std::get<ComponentReference>(value);
|
|
return std::to_string(reference.gameObjectUUID) + "," + std::to_string(reference.scriptComponentUUID);
|
|
}
|
|
}
|
|
|
|
return std::string();
|
|
}
|
|
|
|
bool TryDeserializeScriptFieldValue(ScriptFieldType type, const std::string& value, ScriptFieldValue& outValue) {
|
|
try {
|
|
switch (type) {
|
|
case ScriptFieldType::None:
|
|
outValue = std::monostate{};
|
|
return true;
|
|
case ScriptFieldType::Float:
|
|
outValue = std::stof(value);
|
|
return true;
|
|
case ScriptFieldType::Double:
|
|
outValue = std::stod(value);
|
|
return true;
|
|
case ScriptFieldType::Bool:
|
|
outValue = (value == "1" || value == "true" || value == "True");
|
|
return true;
|
|
case ScriptFieldType::Int32:
|
|
outValue = static_cast<int32_t>(std::stoi(value));
|
|
return true;
|
|
case ScriptFieldType::UInt64:
|
|
outValue = static_cast<uint64_t>(std::stoull(value));
|
|
return true;
|
|
case ScriptFieldType::String:
|
|
outValue = UnescapeScriptString(value);
|
|
return true;
|
|
case ScriptFieldType::Vector2: {
|
|
Math::Vector2 parsedValue;
|
|
if (!TryDeserializeVector(value, parsedValue, 2)) {
|
|
return false;
|
|
}
|
|
outValue = parsedValue;
|
|
return true;
|
|
}
|
|
case ScriptFieldType::Vector3: {
|
|
Math::Vector3 parsedValue;
|
|
if (!TryDeserializeVector(value, parsedValue, 3)) {
|
|
return false;
|
|
}
|
|
outValue = parsedValue;
|
|
return true;
|
|
}
|
|
case ScriptFieldType::Vector4: {
|
|
Math::Vector4 parsedValue;
|
|
if (!TryDeserializeVector(value, parsedValue, 4)) {
|
|
return false;
|
|
}
|
|
outValue = parsedValue;
|
|
return true;
|
|
}
|
|
case ScriptFieldType::GameObject:
|
|
outValue = GameObjectReference{static_cast<uint64_t>(std::stoull(value))};
|
|
return true;
|
|
case ScriptFieldType::Component: {
|
|
const size_t separator = value.find(',');
|
|
if (separator == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
outValue = ComponentReference{
|
|
static_cast<uint64_t>(std::stoull(value.substr(0, separator))),
|
|
static_cast<uint64_t>(std::stoull(value.substr(separator + 1)))
|
|
};
|
|
return true;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string EscapeScriptString(const std::string& value) {
|
|
std::ostringstream os;
|
|
os << std::uppercase << std::hex;
|
|
|
|
for (unsigned char ch : value) {
|
|
if (IsSafeScriptChar(ch)) {
|
|
os << static_cast<char>(ch);
|
|
} else {
|
|
os << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(ch);
|
|
}
|
|
}
|
|
|
|
return os.str();
|
|
}
|
|
|
|
std::string UnescapeScriptString(const std::string& value) {
|
|
std::string result;
|
|
result.reserve(value.size());
|
|
|
|
for (size_t i = 0; i < value.size(); ++i) {
|
|
if (value[i] == '%' && i + 2 < value.size()) {
|
|
const int high = HexToInt(value[i + 1]);
|
|
const int low = HexToInt(value[i + 2]);
|
|
if (high >= 0 && low >= 0) {
|
|
result.push_back(static_cast<char>((high << 4) | low));
|
|
i += 2;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
result.push_back(value[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace Scripting
|
|
} // namespace XCEngine
|