#include "Scripting/ScriptField.h" #include #include #include #include #include #include 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 std::string SerializeScalar(T value) { std::ostringstream os; os << std::setprecision(std::numeric_limits::max_digits10) << value; return os.str(); } template 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) { outVector = Math::Vector2(components[0], components[1]); } else if constexpr (std::is_same_v) { 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(value); case ScriptFieldType::Float: return std::holds_alternative(value); case ScriptFieldType::Double: return std::holds_alternative(value); case ScriptFieldType::Bool: return std::holds_alternative(value); case ScriptFieldType::Int32: return std::holds_alternative(value); case ScriptFieldType::UInt64: return std::holds_alternative(value); case ScriptFieldType::String: return std::holds_alternative(value); case ScriptFieldType::Vector2: return std::holds_alternative(value); case ScriptFieldType::Vector3: return std::holds_alternative(value); case ScriptFieldType::Vector4: return std::holds_alternative(value); case ScriptFieldType::GameObject: return std::holds_alternative(value); case ScriptFieldType::Component: return std::holds_alternative(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(value)); case ScriptFieldType::Double: return SerializeScalar(std::get(value)); case ScriptFieldType::Bool: return std::get(value) ? "1" : "0"; case ScriptFieldType::Int32: return std::to_string(std::get(value)); case ScriptFieldType::UInt64: return std::to_string(std::get(value)); case ScriptFieldType::String: return EscapeScriptString(std::get(value)); case ScriptFieldType::Vector2: { const Math::Vector2& vector = std::get(value); return SerializeScalar(vector.x) + "," + SerializeScalar(vector.y); } case ScriptFieldType::Vector3: { const Math::Vector3& vector = std::get(value); return SerializeScalar(vector.x) + "," + SerializeScalar(vector.y) + "," + SerializeScalar(vector.z); } case ScriptFieldType::Vector4: { const Math::Vector4& vector = std::get(value); return SerializeScalar(vector.x) + "," + SerializeScalar(vector.y) + "," + SerializeScalar(vector.z) + "," + SerializeScalar(vector.w); } case ScriptFieldType::GameObject: return std::to_string(std::get(value).gameObjectUUID); case ScriptFieldType::Component: { const ComponentReference reference = std::get(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(std::stoi(value)); return true; case ScriptFieldType::UInt64: outValue = static_cast(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(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(std::stoull(value.substr(0, separator))), static_cast(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(ch); } else { os << '%' << std::setw(2) << std::setfill('0') << static_cast(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((high << 4) | low)); i += 2; continue; } } result.push_back(value[i]); } return result; } } // namespace Scripting } // namespace XCEngine