From 153b5266de221d4affc283b0c44d0ede80212029 Mon Sep 17 00:00:00 2001 From: Pablo Andres Fuente Date: Mon, 16 Sep 2024 11:48:04 -0300 Subject: [PATCH] Making `inst_to_dict` and `dict_to_inst` recursive 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 and another to validate that an argument is boolean. --- modules/gdscript/doc_classes/@GDScript.xml | 4 + .../gdscript/gdscript_utility_functions.cpp | 93 ++++++++++++++++--- .../scripts/utility_functions/bar.notest.gd | 9 ++ .../tests/scripts/utility_functions/bar.tres | 9 ++ .../errors/inst_to_dict_not_resource.gd | 10 ++ .../errors/inst_to_dict_not_resource.out | 6 ++ .../scripts/utility_functions/foo.notest.gd | 9 ++ .../tests/scripts/utility_functions/foo.tres | 9 ++ .../scripts/utility_functions/inst_to_dict.gd | 54 +++++++++++ .../utility_functions/inst_to_dict.out | 6 ++ .../scripts/utility_functions/qux.notest.gd | 7 ++ .../tests/scripts/utility_functions/qux.tres | 7 ++ 12 files changed, 208 insertions(+), 15 deletions(-) create mode 100644 modules/gdscript/tests/scripts/utility_functions/bar.notest.gd create mode 100644 modules/gdscript/tests/scripts/utility_functions/bar.tres create mode 100644 modules/gdscript/tests/scripts/utility_functions/errors/inst_to_dict_not_resource.gd create mode 100644 modules/gdscript/tests/scripts/utility_functions/errors/inst_to_dict_not_resource.out create mode 100644 modules/gdscript/tests/scripts/utility_functions/foo.notest.gd create mode 100644 modules/gdscript/tests/scripts/utility_functions/foo.tres create mode 100644 modules/gdscript/tests/scripts/utility_functions/inst_to_dict.gd create mode 100644 modules/gdscript/tests/scripts/utility_functions/inst_to_dict.out create mode 100644 modules/gdscript/tests/scripts/utility_functions/qux.notest.gd create mode 100644 modules/gdscript/tests/scripts/utility_functions/qux.tres diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index ede4ce6617aa..17dc5d6b4fce 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -77,8 +77,10 @@ + Converts a [param dictionary] (created with [method inst_to_dict]) back to an Object instance. Can be useful for deserializing. + If [param deep] is [code]true[/code], the method converts any inner instances recursively. Use this option only if you're sure that instances have no circular references to each other as it can lead to an endless conversion loop. @@ -106,8 +108,10 @@ + Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing. + If [param deep] is [code]true[/code], the method converts any inner instances recursively. Use this option only if you're sure that instances have no circular references to each other as it can lead to an endless conversion loop. [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" diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 82460696964d..bcbd80301bce 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -224,16 +224,21 @@ struct GDScriptUtilityFunctionsDefinitions { *r_ret = ResourceLoader::load(*p_args[0]); } - static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { - DEBUG_VALIDATE_ARG_COUNT(1, 1); - DEBUG_VALIDATE_ARG_TYPE(0, Variant::OBJECT); + static inline void _inst_to_dict(Variant *r_ret, const Variant *p_var, bool p_deep, int p_recursion_count, Callable::CallError &r_error) { + if (p_var->get_type() == Variant::NIL) { + *r_ret = Variant(); + return; + } - if (p_args[0]->get_type() == Variant::NIL) { + if (unlikely(!Variant::can_convert_strict(p_var->get_type(), Variant::OBJECT))) { *r_ret = Variant(); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; return; } - Object *obj = *p_args[0]; + Object *obj = *p_var; if (!obj) { *r_ret = Variant(); return; @@ -268,19 +273,52 @@ struct GDScriptUtilityFunctionsDefinitions { for (const KeyValue &E : base->member_indices) { if (!d.has(E.key)) { - d[E.key] = inst->members[E.value.index]; + Variant member = inst->members[E.value.index]; + if (p_deep && member.get_type() == Variant::OBJECT) { + Variant inner_dict; + _inst_to_dict(&inner_dict, &member, p_deep, ++p_recursion_count, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + return; + } else { + member = inner_dict; + } + } + d[E.key] = member; } } *r_ret = d; + return; } - static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { - DEBUG_VALIDATE_ARG_COUNT(1, 1); - DEBUG_VALIDATE_ARG_TYPE(0, Variant::DICTIONARY); + static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + DEBUG_VALIDATE_ARG_COUNT(1, 2); + + bool deep = false; + if (p_arg_count > 1) { + DEBUG_VALIDATE_ARG_TYPE(1, Variant::BOOL); + deep = *p_args[1]; + } - Dictionary d = *p_args[0]; + _inst_to_dict(r_ret, p_args[0], deep, 1, r_error); + } + static inline void _dict_to_inst(Variant *r_ret, const Variant *p_var, bool p_deep, int p_recursion_count, Callable::CallError &r_error) { + if (p_recursion_count > MAX_RECURSION) { + ERR_PRINT("Max recursion reached"); + *r_ret = Variant(); + return; + } + + if (unlikely(!Variant::can_convert_strict(p_var->get_type(), Variant::DICTIONARY))) { + *r_ret = Variant(); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + return; + } + + Dictionary d = *p_var; VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !d.has("@path"), RTR("Invalid instance dictionary format (missing @path).")); Ref