mirror of
https://github.com/godotengine/godot.git
synced 2024-11-21 11:32:13 +00:00
Core: Add typed dictionary support for binary serialization
This commit is contained in:
parent
ec6a1c0e79
commit
9ba098b670
@ -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,9 +809,30 @@ 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;
|
||||
//bool shared = count & 0x80000000;
|
||||
count &= 0x7FFFFFFF;
|
||||
|
||||
buf += 4;
|
||||
@ -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,75 +869,27 @@ 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);
|
||||
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.
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
|
||||
|
||||
int32_t count = decode_uint32(buf);
|
||||
// bool shared = count&0x80000000;
|
||||
//bool shared = count & 0x80000000;
|
||||
count &= 0x7FFFFFFF;
|
||||
|
||||
buf += 4;
|
||||
@ -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);
|
||||
@ -906,7 +960,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
|
||||
Vector<int32_t> data;
|
||||
|
||||
if (count) {
|
||||
//const int*rbuf=(const int*)buf;
|
||||
//const int *rbuf = (const int *)buf;
|
||||
data.resize(count);
|
||||
int32_t *w = data.ptrw();
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
@ -930,7 +984,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
|
||||
Vector<int64_t> data;
|
||||
|
||||
if (count) {
|
||||
//const int*rbuf=(const int*)buf;
|
||||
//const int *rbuf = (const int *)buf;
|
||||
data.resize(count);
|
||||
int64_t *w = data.ptrw();
|
||||
for (int64_t i = 0; i < count; i++) {
|
||||
@ -954,7 +1008,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
|
||||
Vector<float> data;
|
||||
|
||||
if (count) {
|
||||
//const float*rbuf=(const float*)buf;
|
||||
//const float *rbuf = (const float *)buf;
|
||||
data.resize(count);
|
||||
float *w = data.ptrw();
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user