From f1f96e9023fcb2c94c5cca0f6f979684055db253 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 | 240 ++++++++++-------- .../scripts/utility_functions/bar.notest.gd | 9 + .../tests/scripts/utility_functions/bar.tres | 9 + .../scripts/utility_functions/foo.notest.gd | 9 + .../tests/scripts/utility_functions/foo.tres | 9 + .../scripts/utility_functions/inst_to_dict.gd | 27 ++ .../utility_functions/inst_to_dict.out | 4 + .../scripts/utility_functions/qux.notest.gd | 7 + .../tests/scripts/utility_functions/qux.tres | 7 + 10 files changed, 224 insertions(+), 101 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/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 5fe47d69df6..8f531acfe53 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 59dd983ed25..e3db9ff1382 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -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; \ @@ -74,11 +88,22 @@ return; \ } +#define VALIDATE_ARG_BOOL(m_arg) \ + if (p_args[m_arg]->get_type() != Variant::BOOL) { \ + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ + r_error.argument = m_arg; \ + r_error.expected = Variant::BOOL; \ + *r_ret = Variant(); \ + return; \ + } + #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) +#define VALIDATE_ARG_BOOL(m_arg) #endif @@ -242,96 +267,108 @@ 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); + static inline Variant _inst_to_dict(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"); + return Variant(); + } - if (p_args[0]->get_type() == Variant::NIL) { - *r_ret = Variant(); - } else if (p_args[0]->get_type() != Variant::OBJECT) { + if (p_var->get_type() == Variant::NIL) { + return Variant(); + } else if (p_var->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; - *r_ret = Variant(); - } else { - Object *obj = *p_args[0]; - if (!obj) { - *r_ret = Variant(); - - } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - *r_ret = RTR("Not a script with an instance"); - return; - } else { - GDScriptInstance *ins = static_cast(obj->get_script_instance()); - Ref base = ins->get_script(); - if (base.is_null()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - *r_ret = RTR("Not based on a script"); - return; - } - - GDScript *p = base.ptr(); - String path = p->get_script_path(); - Vector sname; - - while (p->_owner) { - sname.push_back(p->local_name); - p = p->_owner; - } - sname.reverse(); - - if (!path.is_resource_file()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - *r_ret = Variant(); - - *r_ret = RTR("Not based on a resource file"); - - return; - } - - NodePath cp(sname, Vector(), false); - - Dictionary d; - d["@subpath"] = cp; - d["@path"] = path; - - for (const KeyValue &E : base->member_indices) { - if (!d.has(E.key)) { - d[E.key] = ins->members[E.value.index]; - } - } - *r_ret = d; - } + return Variant(); } - } - 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); - - if (p_args[0]->get_type() != Variant::DICTIONARY) { + Object *obj = *p_var; + if (!obj) { + return Variant(); + } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; - *r_ret = Variant(); - - return; + return RTR("Not a script with an instance"); } - Dictionary d = *p_args[0]; + GDScriptInstance *ins = static_cast(obj->get_script_instance()); + Ref base = ins->get_script(); + if (base.is_null()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + return RTR("Not based on a script"); + } + + GDScript *p = base.ptr(); + String path = p->get_script_path(); + Vector sname; + + while (p->_owner) { + sname.push_back(p->local_name); + p = p->_owner; + } + sname.reverse(); + + if (!path.is_resource_file()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + return RTR("Not based on a resource file"); + } + + NodePath cp(sname, Vector(), false); + + Dictionary d; + d["@subpath"] = cp; + d["@path"] = path; + + for (const KeyValue &E : base->member_indices) { + if (!d.has(E.key)) { + Variant member = ins->members[E.value.index]; + if (p_deep && member.get_type() == Variant::OBJECT) { + member = _inst_to_dict(&member, p_deep, ++p_recursion_count, r_error); + } + d[E.key] = member; + } + } + + return d; + } + + static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_MIN_MAX_ARG_COUNT(1, 2); + + bool deep = false; + if (p_arg_count > 1) { + VALIDATE_ARG_BOOL(1); + deep = *p_args[1]; + } + + *r_ret = _inst_to_dict(p_args[0], deep, 1, r_error); + } + + static inline Variant _dict_to_inst(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"); + return Variant(); + } + + if (p_var->get_type() != Variant::DICTIONARY) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + return Variant(); + } + + Dictionary d = *p_var; if (!d.has("@path")) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; - *r_ret = RTR("Invalid instance dictionary format (missing @path)"); - - return; + return RTR("Invalid instance dictionary format (missing @path)"); } Ref