Merge pull request #98120 from dalexeev/core-typed-dicts-bin-serialization

Core: Add typed dictionary support for binary serialization
This commit is contained in:
Thaddeus Crews 2024-11-12 09:28:00 -06:00
commit 8fd672cd87
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
2 changed files with 352 additions and 152 deletions

View File

@ -33,8 +33,6 @@
#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"
#include <limits.h>
#include <stdio.h>
@ -69,10 +67,31 @@ ObjectID EncodedObjectAsID::get_object_id() const {
// 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)
#define HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT 16
// For `Variant::DICTIONARY`.
// Occupies bits 16 and 17.
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_MASK (0b11 << 16)
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT 16
// Occupies bits 18 and 19.
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_MASK (0b11 << 18)
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT 18
enum ContainerTypeKind {
CONTAINER_TYPE_KIND_NONE = 0b00,
CONTAINER_TYPE_KIND_BUILTIN = 0b01,
CONTAINER_TYPE_KIND_CLASS_NAME = 0b10,
CONTAINER_TYPE_KIND_SCRIPT = 0b11,
};
struct ContainerType {
Variant::Type builtin_type = Variant::NIL;
StringName class_name;
Ref<Script> script;
};
#define GET_CONTAINER_TYPE_KIND(m_header, m_field) \
((ContainerTypeKind)(((m_header) & HEADER_DATA_FIELD_##m_field##_MASK) >> HEADER_DATA_FIELD_##m_field##_SHIFT))
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);
@ -80,7 +99,7 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
int32_t strlen = decode_uint32(buf);
int32_t pad = 0;
// Handle padding
// Handle padding.
if (strlen % 4) {
pad = 4 - strlen % 4;
}
@ -88,7 +107,7 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
buf += 4;
len -= 4;
// Ensure buffer is big enough
// Ensure buffer is big enough.
ERR_FAIL_ADD_OF(strlen, pad, ERR_FILE_EOF);
ERR_FAIL_COND_V(strlen < 0 || strlen + pad > len, ERR_FILE_EOF);
@ -96,10 +115,10 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
ERR_FAIL_COND_V(str.parse_utf8((const char *)buf, strlen) != OK, ERR_INVALID_DATA);
r_string = str;
// Add padding
// Add padding.
strlen += pad;
// Update buffer pos, left data count, and return size
// Update buffer pos, left data count, and return size.
buf += strlen;
len -= strlen;
if (r_len) {
@ -109,6 +128,65 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
return OK;
}
static Error _decode_container_type(const uint8_t *&buf, int &len, int *r_len, bool p_allow_objects, ContainerTypeKind p_type_kind, ContainerType &r_type) {
switch (p_type_kind) {
case CONTAINER_TYPE_KIND_NONE: {
return OK;
} break;
case CONTAINER_TYPE_KIND_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);
r_type.builtin_type = (Variant::Type)bt;
if (!p_allow_objects && r_type.builtin_type == Variant::OBJECT) {
r_type.class_name = EncodedObjectAsID::get_class_static();
}
return OK;
} break;
case CONTAINER_TYPE_KIND_CLASS_NAME: {
String str;
Error err = _decode_string(buf, len, r_len, str);
if (err) {
return err;
}
r_type.builtin_type = Variant::OBJECT;
if (p_allow_objects) {
r_type.class_name = str;
} else {
r_type.class_name = EncodedObjectAsID::get_class_static();
}
return OK;
} break;
case CONTAINER_TYPE_KIND_SCRIPT: {
String path;
Error err = _decode_string(buf, len, r_len, path);
if (err) {
return err;
}
r_type.builtin_type = Variant::OBJECT;
if (p_allow_objects) {
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path \"%s\".", path));
r_type.script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(r_type.script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path \"%s\".", path));
r_type.class_name = r_type.script->get_instance_base_type();
} else {
r_type.class_name = EncodedObjectAsID::get_class_static();
}
return OK;
} break;
}
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid container type kind."); // Future proofing.
}
Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_objects, int p_depth) {
ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Variant is too deep. Bailing.");
const uint8_t *buf = p_buffer;
@ -126,7 +204,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
*r_len = 4;
}
// Note: We cannot use sizeof(real_t) for decoding, in case a different size is encoded.
// 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 (header & HEADER_TYPE_MASK) {
@ -188,7 +266,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
// math types
// Math types.
case Variant::VECTOR2: {
Vector2 val;
if (header & HEADER_DATA_FLAG_64) {
@ -539,7 +617,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = val;
} break;
// misc types
// Misc types.
case Variant::COLOR: {
ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA);
Color val;
@ -568,7 +647,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
int32_t strlen = decode_uint32(buf);
if (strlen & 0x80000000) {
//new format
// New format.
ERR_FAIL_COND_V(len < 12, ERR_INVALID_DATA);
Vector<StringName> names;
Vector<StringName> subnames;
@ -607,8 +686,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = NodePath(names, subnames, np_flags & 1);
} else {
//old format, just a string
// Old format, just a string.
ERR_FAIL_V(ERR_INVALID_DATA);
}
@ -698,9 +776,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
if (str == "script" && value.get_type() != Variant::NIL) {
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, vformat("Invalid script path: '%s'.", path));
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path \"%s\".", path));
Ref<Script> script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path: '%s'.", path));
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path \"%s\".", path));
obj->set_script(script);
} else {
obj->set(str, value);
@ -731,7 +809,28 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = Signal(id, StringName(name));
} break;
case Variant::DICTIONARY: {
ContainerType key_type;
{
ContainerTypeKind key_type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_DICTIONARY_KEY);
Error err = _decode_container_type(buf, len, r_len, p_allow_objects, key_type_kind, key_type);
if (err) {
return err;
}
}
ContainerType value_type;
{
ContainerTypeKind value_type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_DICTIONARY_VALUE);
Error err = _decode_container_type(buf, len, r_len, p_allow_objects, value_type_kind, value_type);
if (err) {
return err;
}
}
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
//bool shared = count & 0x80000000;
count &= 0x7FFFFFFF;
@ -743,7 +842,10 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += 4; // Size of count number.
}
Dictionary d;
Dictionary dict;
if (key_type.builtin_type != Variant::NIL || value_type.builtin_type != Variant::NIL) {
dict.set_typed(key_type.builtin_type, key_type.class_name, key_type.script, value_type.builtin_type, value_type.class_name, value_type.script);
}
for (int i = 0; i < count; i++) {
Variant key, value;
@ -767,69 +869,21 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += used;
}
d[key] = value;
dict[key] = value;
}
r_variant = d;
r_variant = dict;
} break;
case Variant::ARRAY: {
Variant::Type builtin_type = Variant::VARIANT_MAX;
StringName class_name;
Ref<Script> script;
ContainerType type;
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;
if (!p_allow_objects && builtin_type == Variant::OBJECT) {
class_name = EncodedObjectAsID::get_class_static();
}
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME: {
String str;
Error err = _decode_string(buf, len, r_len, str);
{
ContainerTypeKind type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_ARRAY);
Error err = _decode_container_type(buf, len, r_len, p_allow_objects, type_kind, type);
if (err) {
return err;
}
builtin_type = Variant::OBJECT;
if (p_allow_objects) {
class_name = str;
} else {
class_name = EncodedObjectAsID::get_class_static();
}
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT: {
String path;
Error err = _decode_string(buf, len, r_len, path);
if (err) {
return err;
}
builtin_type = Variant::OBJECT;
if (p_allow_objects) {
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path: '%s'.", path));
script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path: '%s'.", path));
class_name = script->get_instance_base_type();
} else {
class_name = EncodedObjectAsID::get_class_static();
}
} break;
default:
ERR_FAIL_V(ERR_INVALID_DATA); // Future proofing.
}
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
@ -845,29 +899,29 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += 4; // Size of count number.
}
Array varr;
if (builtin_type != Variant::VARIANT_MAX) {
varr.set_typed(builtin_type, class_name, script);
Array array;
if (type.builtin_type != Variant::NIL) {
array.set_typed(type.builtin_type, type.class_name, type.script);
}
for (int i = 0; i < count; i++) {
int used = 0;
Variant v;
Error err = decode_variant(v, buf, len, &used, p_allow_objects, p_depth + 1);
Variant elem;
Error err = decode_variant(elem, buf, len, &used, p_allow_objects, p_depth + 1);
ERR_FAIL_COND_V_MSG(err != OK, err, "Error when trying to decode Variant.");
buf += used;
len -= used;
varr.push_back(v);
array.push_back(elem);
if (r_len) {
(*r_len) += used;
}
}
r_variant = varr;
r_variant = array;
} break;
// arrays
// Packed arrays.
case Variant::PACKED_BYTE_ARRAY: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
@ -1265,13 +1319,50 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) {
r_len += 4 + utf8.length();
while (r_len % 4) {
r_len++; //pad
r_len++; // Pad.
if (buf) {
*(buf++) = 0;
}
}
}
static void _encode_container_type_header(const ContainerType &p_type, uint32_t &header, uint32_t p_shift, bool p_full_objects) {
if (p_type.builtin_type != Variant::NIL) {
if (p_type.script.is_valid()) {
header |= (p_full_objects ? CONTAINER_TYPE_KIND_SCRIPT : CONTAINER_TYPE_KIND_CLASS_NAME) << p_shift;
} else if (p_type.class_name != StringName()) {
header |= CONTAINER_TYPE_KIND_CLASS_NAME << p_shift;
} else {
// No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`.
header |= CONTAINER_TYPE_KIND_BUILTIN << p_shift;
}
}
}
static Error _encode_container_type(const ContainerType &p_type, uint8_t *&buf, int &r_len, bool p_full_objects) {
if (p_type.builtin_type != Variant::NIL) {
if (p_type.script.is_valid()) {
if (p_full_objects) {
String path = p_type.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 a container type.");
_encode_string(path, buf, r_len);
} else {
_encode_string(EncodedObjectAsID::get_class_static(), buf, r_len);
}
} else if (p_type.class_name != StringName()) {
_encode_string(p_full_objects ? p_type.class_name.operator String() : EncodedObjectAsID::get_class_static(), buf, r_len);
} else {
// No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`.
if (buf) {
encode_uint32(p_type.builtin_type, buf);
buf += 4;
}
r_len += 4;
}
}
return OK;
}
Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects, int p_depth) {
ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Potential infinite recursion detected. Bailing.");
uint8_t *buf = r_buffer;
@ -1310,20 +1401,32 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
header |= HEADER_DATA_FLAG_OBJECT_AS_ID;
}
} break;
case Variant::DICTIONARY: {
Dictionary dict = p_variant;
ContainerType key_type;
key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
key_type.class_name = dict.get_typed_key_class_name();
key_type.script = dict.get_typed_key_script();
_encode_container_type_header(key_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);
ContainerType value_type;
value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
value_type.class_name = dict.get_typed_value_class_name();
value_type.script = dict.get_typed_value_script();
_encode_container_type_header(value_type, header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
} break;
case Variant::ARRAY: {
Array array = p_variant;
if (array.is_typed()) {
Ref<Script> script = array.get_typed_script();
if (script.is_valid()) {
header |= p_full_objects ? HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT : HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
} else if (array.get_typed_class_name() != StringName()) {
header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
} else {
// No need to check `p_full_objects` since for `Variant::OBJECT`
// `array.get_typed_class_name()` should be non-empty.
header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN;
}
}
ContainerType type;
type.builtin_type = (Variant::Type)array.get_typed_builtin();
type.class_name = array.get_typed_class_name();
type.script = array.get_typed_script();
_encode_container_type_header(type, header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
} break;
#ifdef REAL_T_IS_DOUBLE
case Variant::VECTOR2:
@ -1344,7 +1447,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
#endif // REAL_T_IS_DOUBLE
default: {
} // nothing to do at this stage
// Nothing to do at this stage.
} break;
}
if (buf) {
@ -1355,7 +1459,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
switch (p_variant.get_type()) {
case Variant::NIL: {
//nothing to do
// Nothing to do.
} break;
case Variant::BOOL: {
if (buf) {
@ -1367,7 +1471,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
case Variant::INT: {
if (header & HEADER_DATA_FLAG_64) {
//64 bits
// 64 bits.
if (buf) {
encode_uint64(p_variant.operator int64_t(), buf);
}
@ -1401,7 +1505,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::NODE_PATH: {
NodePath np = p_variant;
if (buf) {
encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); //for compatibility with the old format
encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); // For compatibility with the old format.
encode_uint32(np.get_subname_count(), buf + 4);
uint32_t np_flags = 0;
if (np.is_absolute()) {
@ -1451,7 +1555,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
// math types
// Math types.
case Variant::VECTOR2: {
if (buf) {
Vector2 v2 = p_variant;
@ -1635,7 +1739,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
// misc types
// Misc types.
case Variant::COLOR: {
if (buf) {
Color c = p_variant;
@ -1746,29 +1850,53 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 8;
} break;
case Variant::DICTIONARY: {
Dictionary d = p_variant;
Dictionary dict = p_variant;
{
ContainerType key_type;
key_type.builtin_type = (Variant::Type)dict.get_typed_key_builtin();
key_type.class_name = dict.get_typed_key_class_name();
key_type.script = dict.get_typed_key_script();
Error err = _encode_container_type(key_type, buf, r_len, p_full_objects);
if (err) {
return err;
}
}
{
ContainerType value_type;
value_type.builtin_type = (Variant::Type)dict.get_typed_value_builtin();
value_type.class_name = dict.get_typed_value_class_name();
value_type.script = dict.get_typed_value_script();
Error err = _encode_container_type(value_type, buf, r_len, p_full_objects);
if (err) {
return err;
}
}
if (buf) {
encode_uint32(uint32_t(d.size()), buf);
encode_uint32(uint32_t(dict.size()), buf);
buf += 4;
}
r_len += 4;
List<Variant> keys;
d.get_key_list(&keys);
dict.get_key_list(&keys);
for (const Variant &E : keys) {
for (const Variant &key : keys) {
int len;
Error err = encode_variant(E, buf, len, p_full_objects, p_depth + 1);
Error err = encode_variant(key, 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;
}
Variant *v = d.getptr(E);
ERR_FAIL_NULL_V(v, ERR_BUG);
err = encode_variant(*v, buf, len, p_full_objects, p_depth + 1);
Variant *value = dict.getptr(key);
ERR_FAIL_NULL_V(value, ERR_BUG);
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;
@ -1781,27 +1909,15 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::ARRAY: {
Array array = p_variant;
if (array.is_typed()) {
Variant variant = array.get_typed_script();
Ref<Script> script = variant;
if (script.is_valid()) {
if (p_full_objects) {
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 {
_encode_string(EncodedObjectAsID::get_class_static(), buf, r_len);
}
} else if (array.get_typed_class_name() != StringName()) {
_encode_string(p_full_objects ? array.get_typed_class_name().operator String() : EncodedObjectAsID::get_class_static(), buf, r_len);
} else {
// No need to check `p_full_objects` since for `Variant::OBJECT`
// `array.get_typed_class_name()` should be non-empty.
if (buf) {
encode_uint32(array.get_typed_builtin(), buf);
buf += 4;
}
r_len += 4;
{
ContainerType type;
type.builtin_type = (Variant::Type)array.get_typed_builtin();
type.class_name = array.get_typed_class_name();
type.script = array.get_typed_script();
Error err = _encode_container_type(type, buf, r_len, p_full_objects);
if (err) {
return err;
}
}
@ -1811,9 +1927,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
r_len += 4;
for (const Variant &var : array) {
for (const Variant &elem : array) {
int len;
Error err = encode_variant(var, buf, len, p_full_objects, p_depth + 1);
Error err = encode_variant(elem, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
if (buf) {
@ -1823,7 +1939,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
} break;
// arrays
// Packed arrays.
case Variant::PACKED_BYTE_ARRAY: {
Vector<uint8_t> data = p_variant;
int datalen = data.size();
@ -1939,7 +2056,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 4 + utf8.length() + 1;
while (r_len % 4) {
r_len++; //pad
r_len++; // Pad.
if (buf) {
*(buf++) = 0;
}
@ -2057,9 +2174,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
Vector<float> vector3_to_float32_array(const Vector3 *vecs, size_t count) {
// We always allocate a new array, and we don't memcpy.
// We also don't consider returning a pointer to the passed vectors when sizeof(real_t) == 4.
// One reason is that we could decide to put a 4th component in Vector3 for SIMD/mobile performance,
// We always allocate a new array, and we don't `memcpy()`.
// We also don't consider returning a pointer to the passed vectors when `sizeof(real_t) == 4`.
// One reason is that we could decide to put a 4th component in `Vector3` for SIMD/mobile performance,
// which would cause trouble with these optimizations.
Vector<float> floats;
if (count == 0) {

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 header");
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 header + 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,7 +192,7 @@ 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 header + 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, "HEADER_DATA_FLAG_64");
@ -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 header + 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,7 +232,7 @@ 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 header + 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, "HEADER_DATA_FLAG_64");
@ -335,10 +335,10 @@ TEST_CASE("[Marshalls] Typed array encoding") {
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(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_MESSAGE(buffer[2] == 0x01, "CONTAINER_TYPE_KIND_BUILTIN");
CHECK(buffer[3] == 0x00);
// Check array type.
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
@ -370,7 +370,7 @@ 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
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, CONTAINER_TYPE_KIND_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).
@ -386,6 +386,89 @@ TEST_CASE("[Marshalls] Typed array decoding") {
CHECK(array[0] == Variant(uint64_t(0x0f123456789abcdef)));
}
TEST_CASE("[Marshalls] Typed dicttionary encoding") {
int r_len;
Dictionary dictionary;
dictionary.set_typed(Variant::INT, StringName(), Ref<Script>(), Variant::INT, StringName(), Ref<Script>());
dictionary[Variant(uint64_t(0x0f123456789abcdef))] = Variant(uint64_t(0x0f123456789abcdef));
uint8_t buffer[40];
CHECK(encode_variant(dictionary, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 40, "Length == 4 bytes for header + 8 bytes for dictionary type + 4 bytes for dictionary size + 24 bytes for key-value pair.");
CHECK_MESSAGE(buffer[0] == 0x1b, "Variant::DICTIONARY");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x05, "key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN");
CHECK(buffer[3] == 0x00);
// Check dictionary key type.
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
CHECK(buffer[5] == 0x00);
CHECK(buffer[6] == 0x00);
CHECK(buffer[7] == 0x00);
// Check dictionary value type.
CHECK_MESSAGE(buffer[8] == 0x02, "Variant::INT");
CHECK(buffer[9] == 0x00);
CHECK(buffer[10] == 0x00);
CHECK(buffer[11] == 0x00);
// Check dictionary size.
CHECK(buffer[12] == 0x01);
CHECK(buffer[13] == 0x00);
CHECK(buffer[14] == 0x00);
CHECK(buffer[15] == 0x00);
// Check key type.
CHECK_MESSAGE(buffer[16] == 0x02, "Variant::INT");
CHECK(buffer[17] == 0x00);
CHECK_MESSAGE(buffer[18] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[19] == 0x00);
// Check key value.
CHECK(buffer[20] == 0xef);
CHECK(buffer[21] == 0xcd);
CHECK(buffer[22] == 0xab);
CHECK(buffer[23] == 0x89);
CHECK(buffer[24] == 0x67);
CHECK(buffer[25] == 0x45);
CHECK(buffer[26] == 0x23);
CHECK(buffer[27] == 0xf1);
// Check value type.
CHECK_MESSAGE(buffer[28] == 0x02, "Variant::INT");
CHECK(buffer[29] == 0x00);
CHECK_MESSAGE(buffer[30] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[31] == 0x00);
// Check value value.
CHECK(buffer[32] == 0xef);
CHECK(buffer[33] == 0xcd);
CHECK(buffer[34] == 0xab);
CHECK(buffer[35] == 0x89);
CHECK(buffer[36] == 0x67);
CHECK(buffer[37] == 0x45);
CHECK(buffer[38] == 0x23);
CHECK(buffer[39] == 0xf1);
}
TEST_CASE("[Marshalls] Typed dictionary decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x1b, 0x00, 0x05, 0x00, // Variant::DICTIONARY, key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN
0x02, 0x00, 0x00, 0x00, // Dictionary key type (Variant::INT).
0x02, 0x00, 0x00, 0x00, // Dictionary value type (Variant::INT).
0x01, 0x00, 0x00, 0x00, // Dictionary size.
0x02, 0x00, 0x01, 0x00, // Key type (Variant::INT, HEADER_DATA_FLAG_64).
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Key value.
0x02, 0x00, 0x01, 0x00, // Value type (Variant::INT, HEADER_DATA_FLAG_64).
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Value value.
};
CHECK(decode_variant(variant, buffer, 40, &r_len) == OK);
CHECK(r_len == 40);
CHECK(variant.get_type() == Variant::DICTIONARY);
Dictionary dictionary = variant;
CHECK(dictionary.get_typed_key_builtin() == Variant::INT);
CHECK(dictionary.get_typed_value_builtin() == Variant::INT);
CHECK(dictionary.size() == 1);
CHECK(dictionary.has(Variant(uint64_t(0x0f123456789abcdef))));
CHECK(dictionary[Variant(uint64_t(0x0f123456789abcdef))] == Variant(uint64_t(0x0f123456789abcdef)));
}
} // namespace TestMarshalls
#endif // TEST_MARSHALLS_H