Browse Source

Core: Add typed dictionary support for binary serialization

Danil Alexeev 9 months ago
parent
commit
9ba098b670
2 changed files with 352 additions and 152 deletions
  1. 261 144
      core/io/marshalls.cpp
  2. 91 8
      tests/core/io/test_marshalls.h

+ 261 - 144
core/io/marshalls.cpp

@@ -33,8 +33,6 @@
 #include "core/io/resource_loader.h"
 #include "core/io/resource_loader.h"
 #include "core/object/ref_counted.h"
 #include "core/object/ref_counted.h"
 #include "core/object/script_language.h"
 #include "core/object/script_language.h"
-#include "core/os/keyboard.h"
-#include "core/string/print_string.h"
 
 
 #include <limits.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdio.h>
@@ -69,10 +67,31 @@ ObjectID EncodedObjectAsID::get_object_id() const {
 // For `Variant::ARRAY`.
 // For `Variant::ARRAY`.
 // Occupies bits 16 and 17.
 // Occupies bits 16 and 17.
 #define HEADER_DATA_FIELD_TYPED_ARRAY_MASK (0b11 << 16)
 #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) {
 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);
 	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 strlen = decode_uint32(buf);
 	int32_t pad = 0;
 	int32_t pad = 0;
 
 
-	// Handle padding
+	// Handle padding.
 	if (strlen % 4) {
 	if (strlen % 4) {
 		pad = 4 - 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;
 	buf += 4;
 	len -= 4;
 	len -= 4;
 
 
-	// Ensure buffer is big enough
+	// Ensure buffer is big enough.
 	ERR_FAIL_ADD_OF(strlen, pad, ERR_FILE_EOF);
 	ERR_FAIL_ADD_OF(strlen, pad, ERR_FILE_EOF);
 	ERR_FAIL_COND_V(strlen < 0 || strlen + pad > len, 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);
 	ERR_FAIL_COND_V(str.parse_utf8((const char *)buf, strlen) != OK, ERR_INVALID_DATA);
 	r_string = str;
 	r_string = str;
 
 
-	// Add padding
+	// Add padding.
 	strlen += pad;
 	strlen += pad;
 
 
-	// Update buffer pos, left data count, and return size
+	// Update buffer pos, left data count, and return size.
 	buf += strlen;
 	buf += strlen;
 	len -= strlen;
 	len -= strlen;
 	if (r_len) {
 	if (r_len) {
@@ -109,6 +128,65 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
 	return OK;
 	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) {
 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.");
 	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;
 	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;
 		*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.
 	// 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.
 	// This does lead to some code duplication for decoding, but compatibility is the priority.
 	switch (header & HEADER_TYPE_MASK) {
 	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;
 		} break;
 
 
-		// math types
+		// Math types.
 		case Variant::VECTOR2: {
 		case Variant::VECTOR2: {
 			Vector2 val;
 			Vector2 val;
 			if (header & HEADER_DATA_FLAG_64) {
 			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;
 			r_variant = val;
 
 
 		} break;
 		} break;
-		// misc types
+
+		// Misc types.
 		case Variant::COLOR: {
 		case Variant::COLOR: {
 			ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA);
 			ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA);
 			Color val;
 			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);
 			int32_t strlen = decode_uint32(buf);
 
 
 			if (strlen & 0x80000000) {
 			if (strlen & 0x80000000) {
-				//new format
+				// New format.
 				ERR_FAIL_COND_V(len < 12, ERR_INVALID_DATA);
 				ERR_FAIL_COND_V(len < 12, ERR_INVALID_DATA);
 				Vector<StringName> names;
 				Vector<StringName> names;
 				Vector<StringName> subnames;
 				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);
 				r_variant = NodePath(names, subnames, np_flags & 1);
 
 
 			} else {
 			} else {
-				//old format, just a string
-
+				// Old format, just a string.
 				ERR_FAIL_V(ERR_INVALID_DATA);
 				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) {
 						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.");
 							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;
 							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");
 							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);
 							obj->set_script(script);
 						} else {
 						} else {
 							obj->set(str, value);
 							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));
 			r_variant = Signal(id, StringName(name));
 		} break;
 		} break;
 		case Variant::DICTIONARY: {
 		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);
 			ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
+
 			int32_t count = decode_uint32(buf);
 			int32_t count = decode_uint32(buf);
-			//  bool shared = count&0x80000000;
+			//bool shared = count & 0x80000000;
 			count &= 0x7FFFFFFF;
 			count &= 0x7FFFFFFF;
 
 
 			buf += 4;
 			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.
 				(*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++) {
 			for (int i = 0; i < count; i++) {
 				Variant key, value;
 				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;
 					(*r_len) += used;
 				}
 				}
 
 
-				d[key] = value;
+				dict[key] = value;
 			}
 			}
 
 
-			r_variant = d;
+			r_variant = dict;
 
 
 		} break;
 		} break;
 		case Variant::ARRAY: {
 		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;
-					}
+			ContainerType type;
 
 
-					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);
 			ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
 
 
 			int32_t count = decode_uint32(buf);
 			int32_t count = decode_uint32(buf);
-			//  bool shared = count&0x80000000;
+			//bool shared = count & 0x80000000;
 			count &= 0x7FFFFFFF;
 			count &= 0x7FFFFFFF;
 
 
 			buf += 4;
 			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.
 				(*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++) {
 			for (int i = 0; i < count; i++) {
 				int used = 0;
 				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.");
 				ERR_FAIL_COND_V_MSG(err != OK, err, "Error when trying to decode Variant.");
 				buf += used;
 				buf += used;
 				len -= used;
 				len -= used;
-				varr.push_back(v);
+				array.push_back(elem);
 				if (r_len) {
 				if (r_len) {
 					(*r_len) += used;
 					(*r_len) += used;
 				}
 				}
 			}
 			}
 
 
-			r_variant = varr;
+			r_variant = array;
 
 
 		} break;
 		} break;
 
 
-		// arrays
+		// Packed arrays.
 		case Variant::PACKED_BYTE_ARRAY: {
 		case Variant::PACKED_BYTE_ARRAY: {
 			ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
 			ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
 			int32_t count = decode_uint32(buf);
 			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;
 			Vector<int32_t> data;
 
 
 			if (count) {
 			if (count) {
-				//const int*rbuf=(const int*)buf;
+				//const int *rbuf = (const int *)buf;
 				data.resize(count);
 				data.resize(count);
 				int32_t *w = data.ptrw();
 				int32_t *w = data.ptrw();
 				for (int32_t i = 0; i < count; i++) {
 				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;
 			Vector<int64_t> data;
 
 
 			if (count) {
 			if (count) {
-				//const int*rbuf=(const int*)buf;
+				//const int *rbuf = (const int *)buf;
 				data.resize(count);
 				data.resize(count);
 				int64_t *w = data.ptrw();
 				int64_t *w = data.ptrw();
 				for (int64_t i = 0; i < count; i++) {
 				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;
 			Vector<float> data;
 
 
 			if (count) {
 			if (count) {
-				//const float*rbuf=(const float*)buf;
+				//const float *rbuf = (const float *)buf;
 				data.resize(count);
 				data.resize(count);
 				float *w = data.ptrw();
 				float *w = data.ptrw();
 				for (int32_t i = 0; i < count; i++) {
 				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();
 	r_len += 4 + utf8.length();
 	while (r_len % 4) {
 	while (r_len % 4) {
-		r_len++; //pad
+		r_len++; // Pad.
 		if (buf) {
 		if (buf) {
 			*(buf++) = 0;
 			*(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) {
 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.");
 	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;
 	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;
 				header |= HEADER_DATA_FLAG_OBJECT_AS_ID;
 			}
 			}
 		} break;
 		} 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: {
 		case Variant::ARRAY: {
 			Array array = p_variant;
 			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;
 		} break;
 #ifdef REAL_T_IS_DOUBLE
 #ifdef REAL_T_IS_DOUBLE
 		case Variant::VECTOR2:
 		case Variant::VECTOR2:
@@ -1344,7 +1447,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 		} break;
 		} break;
 #endif // REAL_T_IS_DOUBLE
 #endif // REAL_T_IS_DOUBLE
 		default: {
 		default: {
-		} // nothing to do at this stage
+			// Nothing to do at this stage.
+		} break;
 	}
 	}
 
 
 	if (buf) {
 	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()) {
 	switch (p_variant.get_type()) {
 		case Variant::NIL: {
 		case Variant::NIL: {
-			//nothing to do
+			// Nothing to do.
 		} break;
 		} break;
 		case Variant::BOOL: {
 		case Variant::BOOL: {
 			if (buf) {
 			if (buf) {
@@ -1367,7 +1471,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 		} break;
 		} break;
 		case Variant::INT: {
 		case Variant::INT: {
 			if (header & HEADER_DATA_FLAG_64) {
 			if (header & HEADER_DATA_FLAG_64) {
-				//64 bits
+				// 64 bits.
 				if (buf) {
 				if (buf) {
 					encode_uint64(p_variant.operator int64_t(), 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: {
 		case Variant::NODE_PATH: {
 			NodePath np = p_variant;
 			NodePath np = p_variant;
 			if (buf) {
 			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);
 				encode_uint32(np.get_subname_count(), buf + 4);
 				uint32_t np_flags = 0;
 				uint32_t np_flags = 0;
 				if (np.is_absolute()) {
 				if (np.is_absolute()) {
@@ -1451,7 +1555,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 
 
 		} break;
 		} break;
 
 
-		// math types
+		// Math types.
 		case Variant::VECTOR2: {
 		case Variant::VECTOR2: {
 			if (buf) {
 			if (buf) {
 				Vector2 v2 = p_variant;
 				Vector2 v2 = p_variant;
@@ -1635,7 +1739,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 
 
 		} break;
 		} break;
 
 
-		// misc types
+		// Misc types.
 		case Variant::COLOR: {
 		case Variant::COLOR: {
 			if (buf) {
 			if (buf) {
 				Color c = p_variant;
 				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;
 			r_len += 8;
 		} break;
 		} break;
 		case Variant::DICTIONARY: {
 		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) {
 			if (buf) {
-				encode_uint32(uint32_t(d.size()), buf);
+				encode_uint32(uint32_t(dict.size()), buf);
 				buf += 4;
 				buf += 4;
 			}
 			}
 			r_len += 4;
 			r_len += 4;
 
 
 			List<Variant> keys;
 			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;
 				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(err, err);
 				ERR_FAIL_COND_V(len % 4, ERR_BUG);
 				ERR_FAIL_COND_V(len % 4, ERR_BUG);
 				r_len += len;
 				r_len += len;
 				if (buf) {
 				if (buf) {
 					buf += len;
 					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(err, err);
 				ERR_FAIL_COND_V(len % 4, ERR_BUG);
 				ERR_FAIL_COND_V(len % 4, ERR_BUG);
 				r_len += len;
 				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: {
 		case Variant::ARRAY: {
 			Array array = p_variant;
 			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;
 			r_len += 4;
 
 
-			for (const Variant &var : array) {
+			for (const Variant &elem : array) {
 				int len;
 				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(err, err);
 				ERR_FAIL_COND_V(len % 4, ERR_BUG);
 				ERR_FAIL_COND_V(len % 4, ERR_BUG);
 				if (buf) {
 				if (buf) {
@@ -1823,7 +1939,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 			}
 			}
 
 
 		} break;
 		} break;
-		// arrays
+
+		// Packed arrays.
 		case Variant::PACKED_BYTE_ARRAY: {
 		case Variant::PACKED_BYTE_ARRAY: {
 			Vector<uint8_t> data = p_variant;
 			Vector<uint8_t> data = p_variant;
 			int datalen = data.size();
 			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;
 				r_len += 4 + utf8.length() + 1;
 				while (r_len % 4) {
 				while (r_len % 4) {
-					r_len++; //pad
+					r_len++; // Pad.
 					if (buf) {
 					if (buf) {
 						*(buf++) = 0;
 						*(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) {
 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.
 	// which would cause trouble with these optimizations.
 	Vector<float> floats;
 	Vector<float> floats;
 	if (count == 0) {
 	if (count == 0) {

+ 91 - 8
tests/core/io/test_marshalls.h

@@ -160,7 +160,7 @@ TEST_CASE("[Marshalls] NIL Variant encoding") {
 	uint8_t buffer[4];
 	uint8_t buffer[4];
 
 
 	CHECK(encode_variant(variant, buffer, r_len) == OK);
 	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_MESSAGE(buffer[0] == 0x00, "Variant::NIL");
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[2] == 0x00);
 	CHECK(buffer[2] == 0x00);
@@ -174,7 +174,7 @@ TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
 	uint8_t buffer[8];
 	uint8_t buffer[8];
 
 
 	CHECK(encode_variant(variant, buffer, r_len) == OK);
 	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_MESSAGE(buffer[0] == 0x02, "Variant::INT");
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[2] == 0x00);
 	CHECK(buffer[2] == 0x00);
@@ -192,7 +192,7 @@ TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
 	uint8_t buffer[12];
 	uint8_t buffer[12];
 
 
 	CHECK(encode_variant(variant, buffer, r_len) == OK);
 	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_MESSAGE(buffer[0] == 0x02, "Variant::INT");
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[1] == 0x00);
 	CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
 	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];
 	uint8_t buffer[8];
 
 
 	CHECK(encode_variant(variant, buffer, r_len) == OK);
 	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_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[2] == 0x00);
 	CHECK(buffer[2] == 0x00);
@@ -232,7 +232,7 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
 	uint8_t buffer[12];
 	uint8_t buffer[12];
 
 
 	CHECK(encode_variant(variant, buffer, r_len) == OK);
 	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_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
 	CHECK(buffer[1] == 0x00);
 	CHECK(buffer[1] == 0x00);
 	CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
 	CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
@@ -335,10 +335,10 @@ TEST_CASE("[Marshalls] Typed array encoding") {
 	uint8_t buffer[24];
 	uint8_t buffer[24];
 
 
 	CHECK(encode_variant(array, buffer, r_len) == OK);
 	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_MESSAGE(buffer[0] == 0x1c, "Variant::ARRAY");
 	CHECK(buffer[1] == 0x00);
 	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(buffer[3] == 0x00);
 	// Check array type.
 	// Check array type.
 	CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
 	CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
@@ -370,7 +370,7 @@ TEST_CASE("[Marshalls] Typed array decoding") {
 	Variant variant;
 	Variant variant;
 	int r_len;
 	int r_len;
 	uint8_t buffer[] = {
 	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).
 		0x02, 0x00, 0x00, 0x00, // Array type (Variant::INT).
 		0x01, 0x00, 0x00, 0x00, // Array size.
 		0x01, 0x00, 0x00, 0x00, // Array size.
 		0x02, 0x00, 0x01, 0x00, // Element type (Variant::INT, HEADER_DATA_FLAG_64).
 		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)));
 	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
 } // namespace TestMarshalls
 
 
 #endif // TEST_MARSHALLS_H
 #endif // TEST_MARSHALLS_H