diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index f539f278484f..487fad8b880b 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 59dd983ed25a..e3db9ff13823 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; - } + return Variant(); + } - GDScript *p = base.ptr(); - String path = p->get_script_path(); - Vector sname; + 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; + return RTR("Not a script with an instance"); + } - while (p->_owner) { - sname.push_back(p->local_name); - p = p->_owner; - } - sname.reverse(); + 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"); + } - 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(); + GDScript *p = base.ptr(); + String path = p->get_script_path(); + Vector sname; - *r_ret = RTR("Not based on a resource file"); + while (p->_owner) { + sname.push_back(p->local_name); + p = p->_owner; + } + sname.reverse(); - return; - } + 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); + NodePath cp(sname, Vector(), false); - Dictionary d; - d["@subpath"] = cp; - d["@path"] = path; + 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]; - } + 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); } - *r_ret = d; + d[E.key] = member; } } + + return d; } - 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); + 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_args[0]->get_type() != Variant::DICTIONARY) { + 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; - *r_ret = Variant(); - - return; + return Variant(); } - Dictionary d = *p_args[0]; + 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