Skip to content

Commit

Permalink
Making inst_to_dict and dict_to_inst recursive
Browse files Browse the repository at this point in the history
Fixes #6533

Making GDScript inst_to_dict/dict_to_inst utility functions recursive.
Adding also a new macro to validate the number of the required arguments.
  • Loading branch information
pafuent committed Sep 20, 2024
1 parent 0a4aedb commit f816784
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 18 deletions.
8 changes: 8 additions & 0 deletions modules/gdscript/doc_classes/@GDScript.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@
<method name="dict_to_inst">
<return type="Object" />
<param index="0" name="dictionary" type="Dictionary" />
<param index="1" name="depth" type="int" default="0" />
<description>
Converts a [param dictionary] (created with [method inst_to_dict]) back to an Object instance. Can be useful for deserializing.
When [param depth] is [code]0[/code] only one level of values of [param dictionary] will be converted to an Object instance
When [param depth] is [code]x > 0[/code] only [code]x[/code] levels of values of [param dictionary] will be converted to nested Object instances
When [param depth] is [code]-1[/code] all nested values of [param dictionary] will be converted to nested Object instances
</description>
</method>
<method name="get_stack">
Expand Down Expand Up @@ -106,8 +110,12 @@
<method name="inst_to_dict">
<return type="Dictionary" />
<param index="0" name="instance" type="Object" />
<param index="1" name="depth" type="int" default="0" />
<description>
Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing.
When [param depth] is [code]0[/code] only one level of properties of [param instance] will be converted to Dictionary values
When [param depth] is [code]x > 0[/code] only [code]x[/code] levels of properties of [param instance] will be converted to nested dictionaries
When [param depth] is [code]-1[/code] all nested objects of [param instance] will be converted to nested dictionaries
[b]Note:[/b] Cannot be used to serialize objects with built-in scripts attached or objects allocated within built-in scripts.
[codeblock]
var foo = "bar"
Expand Down
68 changes: 50 additions & 18 deletions modules/gdscript/gdscript_utility_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@
return; \
}

#define VALIDATE_MIN_MAX_ARG_COUNT(m_min_count, m_max_count) \
if (p_arg_count < m_min_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.expected = m_min_count; \
*r_ret = Variant(); \
return; \
} \
if (p_arg_count > m_max_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
r_error.expected = m_max_count; \
*r_ret = Variant(); \
return; \
}

#define VALIDATE_ARG_INT(m_arg) \
if (p_args[m_arg]->get_type() != Variant::INT) { \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
Expand All @@ -77,6 +91,7 @@
#else

#define VALIDATE_ARG_COUNT(m_count)
#define VALIDATE_MIN_MAX_ARG_COUNT(m_min_count, m_max_count)
#define VALIDATE_ARG_INT(m_arg)
#define VALIDATE_ARG_NUM(m_arg)

Expand Down Expand Up @@ -243,7 +258,13 @@ struct GDScriptUtilityFunctionsDefinitions {
}

static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
VALIDATE_MIN_MAX_ARG_COUNT(1, 2);

int64_t depth = 0;
if (p_arg_count > 1) {
VALIDATE_ARG_INT(1);
depth = *p_args[1];
}

if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
Expand Down Expand Up @@ -303,7 +324,15 @@ struct GDScriptUtilityFunctionsDefinitions {

for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
if (!d.has(E.key)) {
d[E.key] = ins->members[E.value.index];
Variant member = ins->members[E.value.index];
if ((depth > 0 || depth == -1) && member.get_type() == Variant::OBJECT) {
Variant nested;
Variant new_depth = depth == -1 ? depth : depth - 1;
const Variant *args[2] = { &member, &new_depth };
inst_to_dict(&nested, args, p_arg_count, r_error);
member = nested;
}
d[E.key] = member;
}
}
*r_ret = d;
Expand All @@ -312,7 +341,13 @@ struct GDScriptUtilityFunctionsDefinitions {
}

static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
VALIDATE_MIN_MAX_ARG_COUNT(1, 2);

int64_t depth = 0;
if (p_arg_count > 1) {
VALIDATE_ARG_INT(1);
depth = *p_args[1];
}

if (p_args[0]->get_type() != Variant::DICTIONARY) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
Expand Down Expand Up @@ -382,24 +417,21 @@ struct GDScriptUtilityFunctionsDefinitions {

for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
ins->members.write[E.value.index] = d[E.key];
Variant member = d[E.key];
if ((depth > 0 || depth == -1) && member.get_type() == Variant::DICTIONARY) {
Variant nested;
Variant new_depth = depth == -1 ? depth : depth - 1;
const Variant *args[2] = { &member, &new_depth };
dict_to_inst(&nested, args, p_arg_count, r_error);
member = nested;
}
ins->members.write[E.value.index] = member;
}
}
}

static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
if (p_arg_count < 3) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.expected = 3;
*r_ret = Variant();
return;
}
if (p_arg_count > 4) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.expected = 4;
*r_ret = Variant();
return;
}
VALIDATE_MIN_MAX_ARG_COUNT(3, 4);

VALIDATE_ARG_INT(0);
VALIDATE_ARG_INT(1);
Expand Down Expand Up @@ -717,8 +749,8 @@ void GDScriptUtilityFunctions::register_functions() {
REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
REGISTER_FUNC_DEF(inst_to_dict, false, 0, Variant::DICTIONARY, ARG("instance", Variant::OBJECT), ARG("depth", Variant::INT));
REGISTER_FUNC_DEF(dict_to_inst, false, 0, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY), ARG("depth", Variant::INT));
REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extends Resource

@export var text: String
@export var qux: Resource


func _init(p_text = "", p_qux = null):
text = p_text
qux = p_qux
9 changes: 9 additions & 0 deletions modules/gdscript/tests/scripts/utility_functions/bar.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://cqd4xsl30rr7b"]

[ext_resource type="Script" path="./bar.notest.gd" id="1_bar"]
[ext_resource type="Script" path="./qux.tres" id="1_qux"]

[resource]
script = ExtResource("1_bar")
text = "lorem ipsum"
qux = ExtResource("1_qux")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extends Resource

@export var number: int
@export var bar: Resource


func _init(p_number = 0, p_bar = null):
number = p_number
bar = p_bar
9 changes: 9 additions & 0 deletions modules/gdscript/tests/scripts/utility_functions/foo.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://cqd4xsl30rr7a"]

[ext_resource type="Script" path="./foo.notest.gd" id="1_foo"]
[ext_resource type="Script" path="./bar.tres" id="1_bar"]

[resource]
script = ExtResource("1_foo")
number = 42
bar = ExtResource("1_bar")
56 changes: 56 additions & 0 deletions modules/gdscript/tests/scripts/utility_functions/inst_to_dict.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
extends Resource

var foo = preload("./foo.tres")


func test():
var obj:Object = foo
var dict:Dictionary = inst_to_dict(obj)
var dict_ok = true
dict_ok = dict_ok && dict.get("number") == 42
dict_ok = dict_ok && dict.get("bar") is Resource
if not dict_ok:
printerr("Can't convert instance to dictionary properly")

dict = inst_to_dict(obj, 1)
dict_ok = true
dict_ok = dict_ok && dict.get("number") == 42
var bar_dict:Dictionary = dict.get("bar")
dict_ok = dict_ok && bar_dict.text == "lorem ipsum"
dict_ok = dict_ok && bar_dict.get("qux") is Resource
if not dict_ok:
printerr("Can't convert instance to dictionary properly")

var inst = dict_to_inst(dict, 1)
var equals = true
equals = equals && foo.number == inst.number
equals = equals && foo.bar.text == inst.bar.text
equals = equals && inst.bar.qux is Resource
if not equals:
printerr("Can't revert from instance to dictionary properly")

dict = inst_to_dict(obj, 2)
print(dict.keys())
print(dict.values())

inst = dict_to_inst(dict, 2)
equals = true
equals = equals && foo.number == inst.number
equals = equals && foo.bar.text == inst.bar.text
equals = equals && foo.bar.qux.decimal == inst.bar.qux.decimal
if not equals:
printerr("Can't revert from instance to dictionary properly")

dict = inst_to_dict(obj, -1)
print(dict.keys())
print(dict.values())

inst = dict_to_inst(dict, -1)
equals = true
equals = equals && foo.number == inst.number
equals = equals && foo.bar.text == inst.bar.text
equals = equals && foo.bar.qux.decimal == inst.bar.qux.decimal
if not equals:
printerr("Can't revert from instance to dictionary properly")

print('ok')
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
GDTEST_OK
["@subpath", "@path", &"number", &"bar"]
[^"", "res://utility_functions/foo.notest.gd", 42, { "@subpath": ^"", "@path": "res://utility_functions/bar.notest.gd", &"text": "lorem ipsum", &"qux": { "@subpath": ^"", "@path": "res://utility_functions/qux.notest.gd", &"decimal": 0.5 } }]
["@subpath", "@path", &"number", &"bar"]
[^"", "res://utility_functions/foo.notest.gd", 42, { "@subpath": ^"", "@path": "res://utility_functions/bar.notest.gd", &"text": "lorem ipsum", &"qux": { "@subpath": ^"", "@path": "res://utility_functions/qux.notest.gd", &"decimal": 0.5 } }]
ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
extends Resource

@export var decimal: float


func _init(p_decimal = ""):
decimal = p_decimal
7 changes: 7 additions & 0 deletions modules/gdscript/tests/scripts/utility_functions/qux.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://cqd4xsl30rr7c"]

[ext_resource type="Script" path="./qux.notest.gd" id="1_qux"]

[resource]
script = ExtResource("1_qux")
decimal = 0.5

0 comments on commit f816784

Please sign in to comment.