Merge pull request #78219 from dalexeev/core-typed-arrays-bin-serialization

Core: Add typed array support for binary serialization
This commit is contained in:
Rémi Verschelde 2024-04-04 14:30:22 +02:00
commit ec5cae37d6
No known key found for this signature in database
GPG Key ID: C3336907360768E1
2 changed files with 239 additions and 53 deletions

View File

@ -30,7 +30,10 @@
#include "marshalls.h"
#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/string/print_string.h"
@ -55,9 +58,22 @@ ObjectID EncodedObjectAsID::get_object_id() const {
#define ERR_FAIL_ADD_OF(a, b, err) ERR_FAIL_COND_V(((int32_t)(b)) < 0 || ((int32_t)(a)) < 0 || ((int32_t)(a)) > INT_MAX - ((int32_t)(b)), err)
#define ERR_FAIL_MUL_OF(a, b, err) ERR_FAIL_COND_V(((int32_t)(a)) < 0 || ((int32_t)(b)) <= 0 || ((int32_t)(a)) > INT_MAX / ((int32_t)(b)), err)
#define ENCODE_MASK 0xFF
#define ENCODE_FLAG_64 1 << 16
#define ENCODE_FLAG_OBJECT_AS_ID 1 << 16
// Byte 0: `Variant::Type`, byte 1: unused, bytes 2 and 3: additional data.
#define HEADER_TYPE_MASK 0xFF
// For `Variant::INT`, `Variant::FLOAT` and other math types.
#define HEADER_DATA_FLAG_64 (1 << 16)
// For `Variant::OBJECT`.
#define HEADER_DATA_FLAG_OBJECT_AS_ID (1 << 16)
// For `Variant::ARRAY`.
// Occupies bits 16 and 17.
#define HEADER_DATA_FIELD_TYPED_ARRAY_MASK (0b11 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_NONE (0b00 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN (0b01 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME (0b10 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT (0b11 << 16)
static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r_string) {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
@ -101,9 +117,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
uint32_t type = decode_uint32(buf);
uint32_t header = decode_uint32(buf);
ERR_FAIL_COND_V((type & ENCODE_MASK) >= Variant::VARIANT_MAX, ERR_INVALID_DATA);
ERR_FAIL_COND_V((header & HEADER_TYPE_MASK) >= Variant::VARIANT_MAX, ERR_INVALID_DATA);
buf += 4;
len -= 4;
@ -114,7 +130,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
// Note: We cannot use sizeof(real_t) for decoding, in case a different size is encoded.
// Decoding math types always checks for the encoded size, while encoding always uses compilation setting.
// This does lead to some code duplication for decoding, but compatibility is the priority.
switch (type & ENCODE_MASK) {
switch (header & HEADER_TYPE_MASK) {
case Variant::NIL: {
r_variant = Variant();
} break;
@ -127,7 +143,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
}
} break;
case Variant::INT: {
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
int64_t val = decode_uint64(buf);
r_variant = val;
@ -146,7 +162,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::FLOAT: {
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double), ERR_INVALID_DATA);
double val = decode_double(buf);
r_variant = val;
@ -176,7 +192,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
// math types
case Variant::VECTOR2: {
Vector2 val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 2, ERR_INVALID_DATA);
val.x = decode_double(&buf[0]);
val.y = decode_double(&buf[sizeof(double)]);
@ -210,7 +226,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::RECT2: {
Rect2 val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA);
val.position.x = decode_double(&buf[0]);
val.position.y = decode_double(&buf[sizeof(double)]);
@ -250,7 +266,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::VECTOR3: {
Vector3 val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 3, ERR_INVALID_DATA);
val.x = decode_double(&buf[0]);
val.y = decode_double(&buf[sizeof(double)]);
@ -287,7 +303,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::VECTOR4: {
Vector4 val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA);
val.x = decode_double(&buf[0]);
val.y = decode_double(&buf[sizeof(double)]);
@ -327,7 +343,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::TRANSFORM2D: {
Transform2D val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 6, ERR_INVALID_DATA);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
@ -355,7 +371,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::PLANE: {
Plane val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA);
val.normal.x = decode_double(&buf[0]);
val.normal.y = decode_double(&buf[sizeof(double)]);
@ -381,7 +397,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::QUATERNION: {
Quaternion val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA);
val.x = decode_double(&buf[0]);
val.y = decode_double(&buf[sizeof(double)]);
@ -407,7 +423,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::AABB: {
AABB val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 6, ERR_INVALID_DATA);
val.position.x = decode_double(&buf[0]);
val.position.y = decode_double(&buf[sizeof(double)]);
@ -437,7 +453,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::BASIS: {
Basis val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 9, ERR_INVALID_DATA);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
@ -465,7 +481,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::TRANSFORM3D: {
Transform3D val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 12, ERR_INVALID_DATA);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
@ -499,7 +515,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::PROJECTION: {
Projection val;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V((size_t)len < sizeof(double) * 16, ERR_INVALID_DATA);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
@ -560,12 +576,12 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
uint32_t namecount = strlen &= 0x7FFFFFFF;
uint32_t subnamecount = decode_uint32(buf + 4);
uint32_t flags = decode_uint32(buf + 8);
uint32_t np_flags = decode_uint32(buf + 8);
len -= 12;
buf += 12;
if (flags & 2) { // Obsolete format with property separate from subpath
if (np_flags & 2) { // Obsolete format with property separate from subpath.
subnamecount++;
}
@ -589,7 +605,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
}
}
r_variant = NodePath(names, subnames, flags & 1);
r_variant = NodePath(names, subnames, np_flags & 1);
} else {
//old format, just a string
@ -608,8 +624,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = RID::from_uint64(id);
} break;
case Variant::OBJECT: {
if (type & ENCODE_FLAG_OBJECT_AS_ID) {
//this _is_ allowed
if (header & HEADER_DATA_FLAG_OBJECT_AS_ID) {
// This _is_ allowed.
ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
ObjectID val = ObjectID(decode_uint64(buf));
if (r_len) {
@ -625,7 +641,6 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = obj_as_id;
}
} else {
ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED);
@ -672,7 +687,16 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += used;
}
obj->set(str, value);
if (str == "script") {
ERR_FAIL_COND_V_MSG(value.get_type() != Variant::STRING, ERR_INVALID_DATA, "Invalid value for \"script\" property, expected script path as String.");
String path = value;
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
Ref<Script> script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
obj->set_script(script);
} else {
obj->set(str, value);
}
}
if (Object::cast_to<RefCounted>(obj)) {
@ -747,7 +771,60 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::ARRAY: {
Variant::Type builtin_type = Variant::VARIANT_MAX;
StringName class_name;
Ref<Script> script;
switch (header & HEADER_DATA_FIELD_TYPED_ARRAY_MASK) {
case HEADER_DATA_FIELD_TYPED_ARRAY_NONE:
break; // Untyped array.
case HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t bt = decode_uint32(buf);
buf += 4;
len -= 4;
if (r_len) {
(*r_len) += 4;
}
ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA);
builtin_type = (Variant::Type)bt;
ERR_FAIL_COND_V(!p_allow_objects && builtin_type == Variant::OBJECT, ERR_UNAUTHORIZED);
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME: {
ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED);
String str;
Error err = _decode_string(buf, len, r_len, str);
if (err) {
return err;
}
builtin_type = Variant::OBJECT;
class_name = str;
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT: {
ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED);
String path;
Error err = _decode_string(buf, len, r_len, path);
if (err) {
return err;
}
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
builtin_type = Variant::OBJECT;
class_name = script->get_instance_base_type();
} break;
default:
ERR_FAIL_V(ERR_INVALID_DATA); // Future proofing.
}
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
// bool shared = count&0x80000000;
count &= 0x7FFFFFFF;
@ -760,6 +837,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
}
Array varr;
if (builtin_type != Variant::VARIANT_MAX) {
varr.set_typed(builtin_type, class_name, script);
}
for (int i = 0; i < count; i++) {
int used = 0;
@ -936,7 +1016,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<Vector2> varray;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_MUL_OF(count, sizeof(double) * 2, ERR_INVALID_DATA);
ERR_FAIL_COND_V(count < 0 || count * sizeof(double) * 2 > (size_t)len, ERR_INVALID_DATA);
@ -996,7 +1076,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<Vector3> varray;
if (type & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_MUL_OF(count, sizeof(double) * 3, ERR_INVALID_DATA);
ERR_FAIL_COND_V(count < 0 || count * sizeof(double) * 3 > (size_t)len, ERR_INVALID_DATA);
@ -1122,20 +1202,20 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len = 0;
uint32_t flags = 0;
uint32_t header = p_variant.get_type();
switch (p_variant.get_type()) {
case Variant::INT: {
int64_t val = p_variant;
if (val > (int64_t)INT_MAX || val < (int64_t)INT_MIN) {
flags |= ENCODE_FLAG_64;
header |= HEADER_DATA_FLAG_64;
}
} break;
case Variant::FLOAT: {
double d = p_variant;
float f = d;
if (double(f) != d) {
flags |= ENCODE_FLAG_64;
header |= HEADER_DATA_FLAG_64;
}
} break;
case Variant::OBJECT: {
@ -1151,7 +1231,23 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
if (!p_full_objects) {
flags |= ENCODE_FLAG_OBJECT_AS_ID;
header |= HEADER_DATA_FLAG_OBJECT_AS_ID;
}
} break;
case Variant::ARRAY: {
Array array = p_variant;
if (array.is_typed()) {
Ref<Script> script = array.get_typed_script();
if (script.is_valid()) {
ERR_FAIL_COND_V(!p_full_objects, ERR_UNAVAILABLE);
header |= HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT;
} else if (array.get_typed_class_name() != StringName()) {
ERR_FAIL_COND_V(!p_full_objects, ERR_UNAVAILABLE);
header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
} else {
ERR_FAIL_COND_V(!p_full_objects && array.get_typed_builtin() == Variant::OBJECT, ERR_UNAVAILABLE);
header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN;
}
}
} break;
#ifdef REAL_T_IS_DOUBLE
@ -1168,7 +1264,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::BASIS:
case Variant::RECT2:
case Variant::AABB: {
flags |= ENCODE_FLAG_64;
header |= HEADER_DATA_FLAG_64;
} break;
#endif // REAL_T_IS_DOUBLE
default: {
@ -1176,7 +1272,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
if (buf) {
encode_uint32(p_variant.get_type() | flags, buf);
encode_uint32(header, buf);
buf += 4;
}
r_len += 4;
@ -1194,7 +1290,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
case Variant::INT: {
if (flags & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
//64 bits
if (buf) {
encode_uint64(p_variant.operator int64_t(), buf);
@ -1210,7 +1306,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
} break;
case Variant::FLOAT: {
if (flags & ENCODE_FLAG_64) {
if (header & HEADER_DATA_FLAG_64) {
if (buf) {
encode_double(p_variant.operator double(), buf);
}
@ -1523,8 +1619,21 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
_encode_string(E.name, buf, r_len);
Variant value;
if (E.name == CoreStringNames::get_singleton()->_script) {
Ref<Script> script = obj->get_script();
if (script.is_valid()) {
String path = script->get_path();
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script.");
value = path;
}
} else {
value = obj->get(E.name);
}
int len;
Error err = encode_variant(obj->get(E.name), buf, len, p_full_objects, p_depth + 1);
Error err = encode_variant(value, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
r_len += len;
@ -1594,24 +1703,41 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
case Variant::ARRAY: {
Array v = p_variant;
Array array = p_variant;
if (buf) {
encode_uint32(uint32_t(v.size()), buf);
buf += 4;
if (array.is_typed()) {
Variant variant = array.get_typed_script();
Ref<Script> script = variant;
if (script.is_valid()) {
String path = script->get_path();
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type.");
_encode_string(path, buf, r_len);
} else if (array.get_typed_class_name() != StringName()) {
_encode_string(array.get_typed_class_name(), buf, r_len);
} else {
if (buf) {
encode_uint32(array.get_typed_builtin(), buf);
buf += 4;
}
r_len += 4;
}
}
if (buf) {
encode_uint32(uint32_t(array.size()), buf);
buf += 4;
}
r_len += 4;
for (int i = 0; i < v.size(); i++) {
for (int i = 0; i < array.size(); i++) {
int len;
Error err = encode_variant(v.get(i), buf, len, p_full_objects, p_depth + 1);
Error err = encode_variant(array.get(i), buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
r_len += len;
if (buf) {
buf += len;
}
r_len += len;
}
} break;

View File

@ -160,7 +160,7 @@ TEST_CASE("[Marshalls] NIL Variant encoding") {
uint8_t buffer[4];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for Variant::Type");
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for header");
CHECK_MESSAGE(buffer[0] == 0x00, "Variant::NIL");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
@ -174,7 +174,7 @@ TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
uint8_t buffer[8];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for Variant::Type + 4 bytes for int32_t");
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for int32_t");
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
@ -192,10 +192,10 @@ TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
uint8_t buffer[12];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for Variant::Type + 8 bytes for int64_t");
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for int64_t");
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "ENCODE_FLAG_64");
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0xef);
@ -214,7 +214,7 @@ TEST_CASE("[Marshalls] FLOAT single precision Variant encoding") {
uint8_t buffer[8];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for Variant::Type + 4 bytes for float");
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for float");
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
@ -232,10 +232,10 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
uint8_t buffer[12];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for Variant::Type + 8 bytes for double");
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for double");
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "ENCODE_FLAG_64");
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[3] == 0x00);
// Check value
CHECK(buffer[4] == 0x55);
@ -292,7 +292,7 @@ TEST_CASE("[Marshalls] INT 64 bit Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x02, 0x00, 0x01, 0x00, // Variant::INT & ENCODE_FLAG_64
0x02, 0x00, 0x01, 0x00, // Variant::INT, HEADER_DATA_FLAG_64
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1 // value
};
@ -318,7 +318,7 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x03, 0x00, 0x01, 0x00, // Variant::FLOAT & ENCODE_FLAG_64
0x03, 0x00, 0x01, 0x00, // Variant::FLOAT, HEADER_DATA_FLAG_64
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f // value
};
@ -326,6 +326,66 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant decoding") {
CHECK(r_len == 12);
CHECK(variant == Variant(0.33333333333333333));
}
TEST_CASE("[Marshalls] Typed array encoding") {
int r_len;
Array array;
array.set_typed(Variant::INT, StringName(), Ref<Script>());
array.push_back(Variant(uint64_t(0x0f123456789abcdef)));
uint8_t buffer[24];
CHECK(encode_variant(array, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 24, "Length == 4 bytes for header + 4 bytes for array type + 4 bytes for array size + 12 bytes for element");
CHECK_MESSAGE(buffer[0] == 0x1c, "Variant::ARRAY");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN");
CHECK(buffer[3] == 0x00);
// Check array type.
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
CHECK(buffer[5] == 0x00);
CHECK(buffer[6] == 0x00);
CHECK(buffer[7] == 0x00);
// Check array size.
CHECK(buffer[8] == 0x01);
CHECK(buffer[9] == 0x00);
CHECK(buffer[10] == 0x00);
CHECK(buffer[11] == 0x00);
// Check element type.
CHECK_MESSAGE(buffer[12] == 0x02, "Variant::INT");
CHECK(buffer[13] == 0x00);
CHECK_MESSAGE(buffer[14] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[15] == 0x00);
// Check element value.
CHECK(buffer[16] == 0xef);
CHECK(buffer[17] == 0xcd);
CHECK(buffer[18] == 0xab);
CHECK(buffer[19] == 0x89);
CHECK(buffer[20] == 0x67);
CHECK(buffer[21] == 0x45);
CHECK(buffer[22] == 0x23);
CHECK(buffer[23] == 0xf1);
}
TEST_CASE("[Marshalls] Typed array decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN
0x02, 0x00, 0x00, 0x00, // Array type (Variant::INT).
0x01, 0x00, 0x00, 0x00, // Array size.
0x02, 0x00, 0x01, 0x00, // Element type (Variant::INT, HEADER_DATA_FLAG_64).
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Element value.
};
CHECK(decode_variant(variant, buffer, 24, &r_len) == OK);
CHECK(r_len == 24);
CHECK(variant.get_type() == Variant::ARRAY);
Array array = variant;
CHECK(array.get_typed_builtin() == Variant::INT);
CHECK(array.size() == 1);
CHECK(array[0] == Variant(uint64_t(0x0f123456789abcdef)));
}
} // namespace TestMarshalls
#endif // TEST_MARSHALLS_H