diff options
50 files changed, 821 insertions, 3 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp index a0df1b6240..6927db002b 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1434,6 +1434,10 @@ bool ClassDB::class_has_method(const StringName &p_class, const StringName &p_me return ::ClassDB::has_method(p_class, p_method, p_no_inheritance); } +int ClassDB::class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) const { + return ::ClassDB::get_method_argument_count(p_class, p_method, nullptr, p_no_inheritance); +} + TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class, bool p_no_inheritance) const { List<MethodInfo> methods; ::ClassDB::get_method_list(p_class, &methods, p_no_inheritance); @@ -1562,6 +1566,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::class_has_method, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_method_argument_count", "class", "method", "no_inheritance"), &ClassDB::class_get_method_argument_count, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false)); diff --git a/core/core_bind.h b/core/core_bind.h index 64ab4dd7e2..305f616cef 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -447,6 +447,8 @@ public: bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; + int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; + TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const; PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const; diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 67ec09d764..ca58d589bd 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -59,6 +59,8 @@ class CallableCustomExtension : public CallableCustom { GDExtensionCallableCustomToString to_string_func; + GDExtensionCallableCustomGetArgumentCount get_argument_count_func; + uint32_t _hash; static bool default_compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -143,6 +145,21 @@ public: return object; } + int get_argument_count(bool &r_is_valid) const override { + if (get_argument_count_func != nullptr) { + GDExtensionBool is_valid = false; + + GDExtensionInt ret = get_argument_count_func(userdata, &is_valid); + + if (is_valid) { + r_is_valid = true; + return ret; + } + } + r_is_valid = false; + return 0; + } + void *get_userdata(void *p_token) const { return (p_token == token) ? userdata : nullptr; } @@ -157,6 +174,7 @@ public: r_call_error.expected = error.expected; } +#ifndef DISABLE_DEPRECATED CallableCustomExtension(GDExtensionCallableCustomInfo *p_info) { userdata = p_info->callable_userdata; token = p_info->token; @@ -172,6 +190,35 @@ public: to_string_func = p_info->to_string_func; + get_argument_count_func = nullptr; + + // Pre-calculate the hash. + if (p_info->hash_func != nullptr) { + _hash = p_info->hash_func(userdata); + } else { + _hash = hash_murmur3_one_64((uint64_t)call_func); + _hash = hash_murmur3_one_64((uint64_t)userdata, _hash); + } + } +#endif + + CallableCustomExtension(GDExtensionCallableCustomInfo2 *p_info) { + userdata = p_info->callable_userdata; + token = p_info->token; + + object = p_info->object_id; + + call_func = p_info->call_func; + is_valid_func = p_info->is_valid_func; + free_func = p_info->free_func; + + equal_func = p_info->equal_func; + less_than_func = p_info->less_than_func; + + to_string_func = p_info->to_string_func; + + get_argument_count_func = p_info->get_argument_count_func; + // Pre-calculate the hash. if (p_info->hash_func != nullptr) { _hash = p_info->hash_func(userdata); @@ -1378,9 +1425,15 @@ static GDExtensionScriptInstancePtr gdextension_object_get_script_instance(GDExt return script_instance_extension->instance; } +#ifndef DISABLE_DEPRECATED static void gdextension_callable_custom_create(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo *p_custom_callable_info) { memnew_placement(r_callable, Callable(memnew(CallableCustomExtension(p_custom_callable_info)))); } +#endif + +static void gdextension_callable_custom_create2(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo2 *p_custom_callable_info) { + memnew_placement(r_callable, Callable(memnew(CallableCustomExtension(p_custom_callable_info)))); +} static void *gdextension_callable_custom_get_userdata(GDExtensionTypePtr p_callable, void *p_token) { const Callable &callable = *reinterpret_cast<const Callable *>(p_callable); @@ -1595,7 +1648,10 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(placeholder_script_instance_create); REGISTER_INTERFACE_FUNC(placeholder_script_instance_update); REGISTER_INTERFACE_FUNC(object_get_script_instance); +#ifndef DISABLE_DEPRECATED REGISTER_INTERFACE_FUNC(callable_custom_create); +#endif // DISABLE_DEPRECATED + REGISTER_INTERFACE_FUNC(callable_custom_create2); REGISTER_INTERFACE_FUNC(callable_custom_get_userdata); REGISTER_INTERFACE_FUNC(classdb_construct_object); REGISTER_INTERFACE_FUNC(classdb_get_method_bind); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index e7497a9d4c..c863507019 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -442,6 +442,8 @@ typedef GDExtensionBool (*GDExtensionCallableCustomLessThan)(void *callable_user typedef void (*GDExtensionCallableCustomToString)(void *callable_userdata, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out); +typedef GDExtensionInt (*GDExtensionCallableCustomGetArgumentCount)(void *callable_userdata, GDExtensionBool *r_is_valid); + typedef struct { /* Only `call_func` and `token` are strictly required, however, `object_id` should be passed if its not a static method. * @@ -471,7 +473,40 @@ typedef struct { GDExtensionCallableCustomLessThan less_than_func; GDExtensionCallableCustomToString to_string_func; -} GDExtensionCallableCustomInfo; +} GDExtensionCallableCustomInfo; // Deprecated. Use GDExtensionCallableCustomInfo2 instead. + +typedef struct { + /* Only `call_func` and `token` are strictly required, however, `object_id` should be passed if its not a static method. + * + * `token` should point to an address that uniquely identifies the GDExtension (for example, the + * `GDExtensionClassLibraryPtr` passed to the entry symbol function. + * + * `hash_func`, `equal_func`, and `less_than_func` are optional. If not provided both `call_func` and + * `callable_userdata` together are used as the identity of the callable for hashing and comparison purposes. + * + * The hash returned by `hash_func` is cached, `hash_func` will not be called more than once per callable. + * + * `is_valid_func` is necessary if the validity of the callable can change before destruction. + * + * `free_func` is necessary if `callable_userdata` needs to be cleaned up when the callable is freed. + */ + void *callable_userdata; + void *token; + + GDObjectInstanceID object_id; + + GDExtensionCallableCustomCall call_func; + GDExtensionCallableCustomIsValid is_valid_func; + GDExtensionCallableCustomFree free_func; + + GDExtensionCallableCustomHash hash_func; + GDExtensionCallableCustomEqual equal_func; + GDExtensionCallableCustomLessThan less_than_func; + + GDExtensionCallableCustomToString to_string_func; + + GDExtensionCallableCustomGetArgumentCount get_argument_count_func; +} GDExtensionCallableCustomInfo2; /* SCRIPT INSTANCE EXTENSION */ @@ -2510,6 +2545,7 @@ typedef GDExtensionScriptInstanceDataPtr (*GDExtensionInterfaceObjectGetScriptIn /** * @name callable_custom_create * @since 4.2 + * @deprecated in Godot 4.3. Use `callable_custom_create2` instead. * * Creates a custom Callable object from a function pointer. * @@ -2521,6 +2557,19 @@ typedef GDExtensionScriptInstanceDataPtr (*GDExtensionInterfaceObjectGetScriptIn typedef void (*GDExtensionInterfaceCallableCustomCreate)(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo *p_callable_custom_info); /** + * @name callable_custom_create2 + * @since 4.3 + * + * Creates a custom Callable object from a function pointer. + * + * Provided struct can be safely freed once the function returns. + * + * @param r_callable A pointer that will receive the new Callable. + * @param p_callable_custom_info The info required to construct a Callable. + */ +typedef void (*GDExtensionInterfaceCallableCustomCreate2)(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo2 *p_callable_custom_info); + +/** * @name callable_custom_get_userdata * @since 4.2 * diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h index f8e8c4d7e9..09fe9679f7 100644 --- a/core/object/callable_method_pointer.h +++ b/core/object/callable_method_pointer.h @@ -93,6 +93,11 @@ public: return data.instance->get_instance_id(); } + virtual int get_argument_count(bool &r_is_valid) const { + r_is_valid = true; + return sizeof...(P); + } + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error); @@ -140,6 +145,11 @@ public: return data.instance->get_instance_id(); } + virtual int get_argument_count(bool &r_is_valid) const { + r_is_valid = true; + return sizeof...(P); + } + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); @@ -187,6 +197,11 @@ public: return data.instance->get_instance_id(); } + virtual int get_argument_count(bool &r_is_valid) const override { + r_is_valid = true; + return sizeof...(P); + } + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override { ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); @@ -238,6 +253,11 @@ public: return ObjectID(); } + virtual int get_argument_count(bool &r_is_valid) const override { + r_is_valid = true; + return sizeof...(P); + } + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override { call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error); r_return_value = Variant(); @@ -280,6 +300,11 @@ public: return ObjectID(); } + virtual int get_argument_count(bool &r_is_valid) const override { + r_is_valid = true; + return sizeof...(P); + } + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override { call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error); } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 231a8e4d68..80a2703c2f 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -1666,6 +1666,31 @@ bool ClassDB::has_method(const StringName &p_class, const StringName &p_method, return false; } +int ClassDB::get_method_argument_count(const StringName &p_class, const StringName &p_method, bool *r_is_valid, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + MethodBind **method = type->method_map.getptr(p_method); + if (method && *method) { + if (r_is_valid) { + *r_is_valid = true; + } + return (*method)->get_argument_count(); + } + if (p_no_inheritance) { + break; + } + type = type->inherits_ptr; + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method) { _bind_method_custom(p_class, p_method, false); } diff --git a/core/object/class_db.h b/core/object/class_db.h index 7f117b4a9b..3b146dd06e 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -430,6 +430,7 @@ public: static void get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static void get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods_with_hash, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false); + static int get_method_argument_count(const StringName &p_class, const StringName &p_method, bool *r_is_valid = nullptr, bool p_no_inheritance = false); static MethodBind *get_method(const StringName &p_class, const StringName &p_name); static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr); static Vector<uint32_t> get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name); diff --git a/core/object/object.cpp b/core/object/object.cpp index 6a5a9efefa..e5d771844b 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -688,6 +688,59 @@ bool Object::has_method(const StringName &p_method) const { return false; } +int Object::_get_method_argument_count_bind(const StringName &p_method) const { + return get_method_argument_count(p_method); +} + +int Object::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + if (p_method == CoreStringNames::get_singleton()->_free) { + if (r_is_valid) { + *r_is_valid = true; + } + return 0; + } + + if (script_instance) { + bool valid = false; + int ret = script_instance->get_method_argument_count(p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + } + + { + bool valid = false; + int ret = ClassDB::get_method_argument_count(get_class_name(), p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + } + + const Script *scr = Object::cast_to<Script>(this); + while (scr != nullptr) { + bool valid = false; + int ret = scr->get_script_method_argument_count(p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + scr = scr->get_base_script().ptr(); + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + Variant Object::getvar(const Variant &p_key, bool *r_valid) const { if (r_valid) { *r_valid = false; @@ -1644,6 +1697,8 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("has_method", "method"), &Object::has_method); + ClassDB::bind_method(D_METHOD("get_method_argument_count", "method"), &Object::_get_method_argument_count_bind); + ClassDB::bind_method(D_METHOD("has_signal", "signal"), &Object::has_signal); ClassDB::bind_method(D_METHOD("get_signal_list"), &Object::_get_signal_list); ClassDB::bind_method(D_METHOD("get_signal_connection_list", "signal"), &Object::_get_signal_connection_list); diff --git a/core/object/object.h b/core/object/object.h index cb1495296d..2efcf70670 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -654,6 +654,7 @@ private: Variant _get_bind(const StringName &p_name) const; void _set_indexed_bind(const NodePath &p_name, const Variant &p_value); Variant _get_indexed_bind(const NodePath &p_name) const; + int _get_method_argument_count_bind(const StringName &p_name) const; _FORCE_INLINE_ void _construct_object(bool p_reference); @@ -865,6 +866,7 @@ public: Variant property_get_revert(const StringName &p_name) const; bool has_method(const StringName &p_method) const; + int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const; void get_method_list(List<MethodInfo> *p_list) const; Variant callv(const StringName &p_method, const Array &p_args); virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); diff --git a/core/object/script_instance.cpp b/core/object/script_instance.cpp index 303b127db1..65f44e8a6b 100644 --- a/core/object/script_instance.cpp +++ b/core/object/script_instance.cpp @@ -32,6 +32,28 @@ #include "core/object/script_language.h" +int ScriptInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + // Default implementation simply traverses hierarchy. + Ref<Script> script = get_script(); + while (script.is_valid()) { + bool valid = false; + int ret = script->get_script_method_argument_count(p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + + script = script->get_base_script(); + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + Variant ScriptInstance::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { return callp(p_method, p_args, p_argcount, r_error); } diff --git a/core/object/script_instance.h b/core/object/script_instance.h index 45d51534fc..2c8132ec1f 100644 --- a/core/object/script_instance.h +++ b/core/object/script_instance.h @@ -53,6 +53,8 @@ public: virtual void get_method_list(List<MethodInfo> *p_list) const = 0; virtual bool has_method(const StringName &p_method) const = 0; + virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const; + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0; template <typename... VarArgs> diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 693c6819d4..01c1e09e4e 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -102,6 +102,22 @@ Dictionary Script::_get_script_constant_map() { return ret; } +int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + MethodInfo mi = get_method_info(p_method); + + if (mi == MethodInfo()) { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + + if (r_is_valid) { + *r_is_valid = true; + } + return mi.arguments.size(); +} + #ifdef TOOLS_ENABLED PropertyInfo Script::get_class_category() const { diff --git a/core/object/script_language.h b/core/object/script_language.h index 95e9d2b4af..be50e58d79 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -151,6 +151,8 @@ public: virtual bool has_method(const StringName &p_method) const = 0; virtual bool has_static_method(const StringName &p_method) const { return false; } + virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const; + virtual MethodInfo get_method_info(const StringName &p_method) const = 0; virtual bool is_tool() const = 0; @@ -442,6 +444,13 @@ public: virtual void get_method_list(List<MethodInfo> *p_list) const override; virtual bool has_method(const StringName &p_method) const override; + virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index ec99c7cf4e..a18ef8d4d7 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -56,6 +56,9 @@ void ScriptExtension::_bind_methods() { GDVIRTUAL_BIND(_has_method, "method"); GDVIRTUAL_BIND(_has_static_method, "method"); + + GDVIRTUAL_BIND(_get_script_method_argument_count, "method"); + GDVIRTUAL_BIND(_get_method_info, "method"); GDVIRTUAL_BIND(_is_tool); diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 18105ec8cd..efb317b839 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -101,6 +101,19 @@ public: EXBIND1RC(bool, has_method, const StringName &) EXBIND1RC(bool, has_static_method, const StringName &) + GDVIRTUAL1RC(Variant, _get_script_method_argument_count, const StringName &) + virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override { + Variant ret; + if (GDVIRTUAL_CALL(_get_script_method_argument_count, p_method, ret) && ret.get_type() == Variant::INT) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret.operator int(); + } + // Fallback to default. + return Script::get_script_method_argument_count(p_method, r_is_valid); + } + GDVIRTUAL1RC(Dictionary, _get_method_info, const StringName &) virtual MethodInfo get_method_info(const StringName &p_method) const override { Dictionary mi; @@ -807,6 +820,11 @@ public: return false; } + virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override { + // Fallback to default. + return ScriptInstance::get_method_argument_count(p_method, r_is_valid); + } + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { Variant ret; if (native_info->call_func) { diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index 6bad6f5a5b..a355cf513c 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -163,6 +163,20 @@ StringName Callable::get_method() const { return method; } +int Callable::get_argument_count(bool *r_is_valid) const { + if (is_custom()) { + bool valid = false; + return custom->get_argument_count(r_is_valid ? *r_is_valid : valid); + } else if (!is_null()) { + return get_object()->get_method_argument_count(method, r_is_valid); + } else { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } +} + int Callable::get_bound_arguments_count() const { if (!is_null() && is_custom()) { return custom->get_bound_arguments_count(); @@ -417,6 +431,11 @@ const Callable *CallableCustom::get_base_comparator() const { return nullptr; } +int CallableCustom::get_argument_count(bool &r_is_valid) const { + r_is_valid = false; + return 0; +} + int CallableCustom::get_bound_arguments_count() const { return 0; } diff --git a/core/variant/callable.h b/core/variant/callable.h index bba69d453e..63757d9d6e 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -109,6 +109,7 @@ public: ObjectID get_object_id() const; StringName get_method() const; CallableCustom *get_custom() const; + int get_argument_count(bool *r_is_valid = nullptr) const; int get_bound_arguments_count() const; void get_bound_arguments_ref(Vector<Variant> &r_arguments, int &r_argcount) const; // Internal engine use, the exposed one is below. Array get_bound_arguments() const; @@ -155,6 +156,7 @@ public: virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const = 0; virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const; virtual const Callable *get_base_comparator() const; + virtual int get_argument_count(bool &r_is_valid) const; virtual int get_bound_arguments_count() const; virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const; diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp index 6d6c60cbd4..d82aa3583d 100644 --- a/core/variant/callable_bind.cpp +++ b/core/variant/callable_bind.cpp @@ -91,6 +91,14 @@ const Callable *CallableCustomBind::get_base_comparator() const { return callable.get_base_comparator(); } +int CallableCustomBind::get_argument_count(bool &r_is_valid) const { + int ret = callable.get_argument_count(&r_is_valid); + if (r_is_valid) { + return ret - binds.size(); + } + return 0; +} + int CallableCustomBind::get_bound_arguments_count() const { return callable.get_bound_arguments_count() + binds.size(); } @@ -225,6 +233,14 @@ const Callable *CallableCustomUnbind::get_base_comparator() const { return callable.get_base_comparator(); } +int CallableCustomUnbind::get_argument_count(bool &r_is_valid) const { + int ret = callable.get_argument_count(&r_is_valid); + if (r_is_valid) { + return ret + argcount; + } + return 0; +} + int CallableCustomUnbind::get_bound_arguments_count() const { return callable.get_bound_arguments_count() - argcount; } diff --git a/core/variant/callable_bind.h b/core/variant/callable_bind.h index 5798797a3d..43cebb45f0 100644 --- a/core/variant/callable_bind.h +++ b/core/variant/callable_bind.h @@ -53,6 +53,7 @@ public: virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override; virtual const Callable *get_base_comparator() const override; + virtual int get_argument_count(bool &r_is_valid) const override; virtual int get_bound_arguments_count() const override; virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const override; Callable get_callable() { return callable; } @@ -81,6 +82,7 @@ public: virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override; virtual const Callable *get_base_comparator() const override; + virtual int get_argument_count(bool &r_is_valid) const override; virtual int get_bound_arguments_count() const override; virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const override; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 060ea007ff..40c9a588d8 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1053,6 +1053,10 @@ struct _VariantCall { r_ret = callable->bindp(p_args, p_argcount); } + static int func_Callable_get_argument_count(Callable *p_callable) { + return p_callable->get_argument_count(); + } + static void func_Signal_emit(Variant *v, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) { Signal *signal = VariantGetInternalPtr<Signal>::get_ptr(v); signal->emit(p_args, p_argcount); @@ -2048,6 +2052,7 @@ static void _register_variant_builtin_methods() { bind_method(Callable, get_object, sarray(), varray()); bind_method(Callable, get_object_id, sarray(), varray()); bind_method(Callable, get_method, sarray(), varray()); + bind_function(Callable, get_argument_count, _VariantCall::func_Callable_get_argument_count, sarray(), varray()); bind_method(Callable, get_bound_arguments_count, sarray(), varray()); bind_method(Callable, get_bound_arguments, sarray(), varray()); bind_method(Callable, hash, sarray(), varray()); diff --git a/core/variant/variant_callable.cpp b/core/variant/variant_callable.cpp index dc31b6d1ac..21f9a4f52b 100644 --- a/core/variant/variant_callable.cpp +++ b/core/variant/variant_callable.cpp @@ -68,6 +68,15 @@ ObjectID VariantCallable::get_object() const { return ObjectID(); } +int VariantCallable::get_argument_count(bool &r_is_valid) const { + if (!Variant::has_builtin_method(variant.get_type(), method)) { + r_is_valid = false; + return 0; + } + r_is_valid = true; + return Variant::get_builtin_method_argument_count(variant.get_type(), method); +} + void VariantCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { Variant v = variant; v.callp(method, p_arguments, p_argcount, r_return_value, r_call_error); diff --git a/core/variant/variant_callable.h b/core/variant/variant_callable.h index 3f2b058aaf..1811f3bb9a 100644 --- a/core/variant/variant_callable.h +++ b/core/variant/variant_callable.h @@ -50,6 +50,7 @@ public: bool is_valid() const override; StringName get_method() const override; ObjectID get_object() const override; + int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; VariantCallable(const Variant &p_variant, const StringName &p_method); diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index 3965fd4f8f..05174abb07 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -147,6 +147,12 @@ [b]Note:[/b] This method is always necessary for the [Dictionary] type, as property syntax is used to access its entries. You may also use this method when [param variant]'s type is not known in advance (for polymorphism). </description> </method> + <method name="get_argument_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the total number of arguments this [Callable] should take, including optional arguments. This means that any arguments bound with [method bind] are [i]subtracted[/i] from the result, and any arguments unbound with [method unbind] are [i]added[/i] to the result. + </description> + </method> <method name="get_bound_arguments" qualifiers="const"> <return type="Array" /> <description> diff --git a/doc/classes/ClassDB.xml b/doc/classes/ClassDB.xml index d24181c3d3..3f71406091 100644 --- a/doc/classes/ClassDB.xml +++ b/doc/classes/ClassDB.xml @@ -65,6 +65,15 @@ Returns an array with the names all the integer constants of [param class] or its ancestry. </description> </method> + <method name="class_get_method_argument_count" qualifiers="const"> + <return type="int" /> + <param index="0" name="class" type="StringName" /> + <param index="1" name="method" type="StringName" /> + <param index="2" name="no_inheritance" type="bool" default="false" /> + <description> + Returns the number of arguments of the method [param method] of [param class] or its ancestry if [param no_inheritance] is [code]false[/code]. + </description> + </method> <method name="class_get_method_list" qualifiers="const"> <return type="Dictionary[]" /> <param index="0" name="class" type="StringName" /> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 065b75382d..85b9cf16f2 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -711,6 +711,14 @@ Returns the object's metadata entry names as a [PackedStringArray]. </description> </method> + <method name="get_method_argument_count" qualifiers="const"> + <return type="int" /> + <param index="0" name="method" type="StringName" /> + <description> + Returns the number of arguments of the given [param method] by name. + [b]Note:[/b] In C#, [param method] must be in snake_case when referring to built-in Godot methods. Prefer using the names exposed in the [code]MethodName[/code] class to avoid allocating a new [StringName] on each call. + </description> + </method> <method name="get_method_list" qualifiers="const"> <return type="Dictionary[]" /> <description> diff --git a/doc/classes/ScriptExtension.xml b/doc/classes/ScriptExtension.xml index 51958a2a2a..6c7888510e 100644 --- a/doc/classes/ScriptExtension.xml +++ b/doc/classes/ScriptExtension.xml @@ -80,6 +80,12 @@ <description> </description> </method> + <method name="_get_script_method_argument_count" qualifiers="virtual const"> + <return type="Variant" /> + <param index="0" name="method" type="StringName" /> + <description> + </description> + </method> <method name="_get_script_method_list" qualifiers="virtual const"> <return type="Dictionary[]" /> <description> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 94aa077014..8e74de4242 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -354,6 +354,21 @@ bool GDScript::has_static_method(const StringName &p_method) const { return member_functions.has(p_method) && member_functions[p_method]->is_static(); } +int GDScript::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method); + if (!E) { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + + if (r_is_valid) { + *r_is_valid = true; + } + return E->value->get_argument_count(); +} + MethodInfo GDScript::get_method_info(const StringName &p_method) const { HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method); if (!E) { @@ -1916,6 +1931,25 @@ bool GDScriptInstance::has_method(const StringName &p_method) const { return false; } +int GDScriptInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + const GDScript *sptr = script.ptr(); + while (sptr) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_method); + if (E) { + if (r_is_valid) { + *r_is_valid = true; + } + return E->value->get_argument_count(); + } + sptr = sptr->_base; + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *sptr = script.ptr(); if (unlikely(p_method == SNAME("_ready"))) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 7c471c285b..fd5ad837f9 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -312,6 +312,9 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const override; virtual bool has_method(const StringName &p_method) const override; virtual bool has_static_method(const StringName &p_method) const override; + + virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override; + virtual MethodInfo get_method_info(const StringName &p_method) const override; virtual void get_script_property_list(List<PropertyInfo> *p_list) const override; @@ -376,6 +379,9 @@ public: virtual void get_method_list(List<MethodInfo> *p_list) const; virtual bool has_method(const StringName &p_method) const; + + virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const; + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 177c68533e..002fc159fa 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -511,6 +511,7 @@ public: _FORCE_INLINE_ GDScript *get_script() const { return _script; } _FORCE_INLINE_ bool is_static() const { return _static; } _FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; } + _FORCE_INLINE_ int get_argument_count() const { return _argument_count; } _FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; } _FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; } diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index f6fa17c84f..626ef6ccb0 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -78,6 +78,15 @@ StringName GDScriptLambdaCallable::get_method() const { return function->get_name(); } +int GDScriptLambdaCallable::get_argument_count(bool &r_is_valid) const { + if (function == nullptr) { + r_is_valid = false; + return 0; + } + r_is_valid = true; + return function->get_argument_count(); +} + void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { int captures_amount = captures.size(); @@ -189,6 +198,15 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const { return object->get_instance_id(); } +int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const { + if (function == nullptr) { + r_is_valid = false; + return 0; + } + r_is_valid = true; + return function->get_argument_count(); +} + void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { #ifdef DEBUG_ENABLED if (object->get_script_instance() == nullptr || object->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) { diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 2c5d01aa16..45c0235913 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -59,6 +59,7 @@ public: CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; StringName get_method() const override; + int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; GDScriptLambdaCallable(GDScriptLambdaCallable &) = delete; @@ -86,6 +87,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; GDScriptLambdaSelfCallable(GDScriptLambdaSelfCallable &) = delete; diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index df014d3cfe..3139371eb5 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -68,6 +68,10 @@ StringName GDScriptRPCCallable::get_method() const { return method; } +int GDScriptRPCCallable::get_argument_count(bool &r_is_valid) const { + return object->get_method_argument_count(method, &r_is_valid); +} + void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { r_return_value = object->callp(method, p_arguments, p_argcount, r_call_error); } diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h index 66052157be..2ca6290951 100644 --- a/modules/gdscript/gdscript_rpc_callable.h +++ b/modules/gdscript/gdscript_rpc_callable.h @@ -52,6 +52,7 @@ public: CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; StringName get_method() const override; + int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override; diff --git a/modules/gdscript/gdscript_utility_callable.cpp b/modules/gdscript/gdscript_utility_callable.cpp index 7708a18044..edd7e05b22 100644 --- a/modules/gdscript/gdscript_utility_callable.cpp +++ b/modules/gdscript/gdscript_utility_callable.cpp @@ -80,6 +80,21 @@ ObjectID GDScriptUtilityCallable::get_object() const { return ObjectID(); } +int GDScriptUtilityCallable::get_argument_count(bool &r_is_valid) const { + switch (type) { + case TYPE_INVALID: + r_is_valid = false; + return 0; + case TYPE_GLOBAL: + r_is_valid = true; + return Variant::get_utility_function_argument_count(function_name); + case TYPE_GDSCRIPT: + r_is_valid = true; + return GDScriptUtilityFunctions::get_function_argument_count(function_name); + } + ERR_FAIL_V_MSG(0, "Invalid type."); +} + void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { switch (type) { case TYPE_INVALID: diff --git a/modules/gdscript/gdscript_utility_callable.h b/modules/gdscript/gdscript_utility_callable.h index 675bc4ddd9..c5736e815f 100644 --- a/modules/gdscript/gdscript_utility_callable.h +++ b/modules/gdscript/gdscript_utility_callable.h @@ -57,6 +57,7 @@ public: bool is_valid() const override; StringName get_method() const override; ObjectID get_object() const override; + int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; GDScriptUtilityCallable(const StringName &p_function_name); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index f8cb460e40..e5b0f55df8 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -759,7 +759,7 @@ Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringN return info->info.arguments[p_arg].type; } -int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) { +int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); ERR_FAIL_NULL_V(info, 0); return info->info.arguments.size(); diff --git a/modules/gdscript/gdscript_utility_functions.h b/modules/gdscript/gdscript_utility_functions.h index 40e9379a3a..1c4e4452c8 100644 --- a/modules/gdscript/gdscript_utility_functions.h +++ b/modules/gdscript/gdscript_utility_functions.h @@ -46,7 +46,7 @@ public: static Variant::Type get_function_return_type(const StringName &p_function); static StringName get_function_return_class(const StringName &p_function); static Variant::Type get_function_argument_type(const StringName &p_function, int p_arg); - static int get_function_argument_count(const StringName &p_function, int p_arg); + static int get_function_argument_count(const StringName &p_function); static bool is_function_vararg(const StringName &p_function); static bool is_function_constant(const StringName &p_function); diff --git a/modules/gdscript/tests/scripts/runtime/features/argument_count.gd b/modules/gdscript/tests/scripts/runtime/features/argument_count.gd new file mode 100644 index 0000000000..c67ce25cbe --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/argument_count.gd @@ -0,0 +1,102 @@ +extends Node + +func my_func_1(_foo, _bar): + pass + +func my_func_2(_foo, _bar, _baz): + pass + +static func my_static_func_1(_foo, _bar): + pass + +static func my_static_func_2(_foo, _bar, _baz): + pass + +@rpc +func my_rpc_func_1(_foo, _bar): + pass + +@rpc +func my_rpc_func_2(_foo, _bar, _baz): + pass + +func test(): + # Test built-in methods. + var builtin_callable_1 : Callable = add_to_group + print(builtin_callable_1.get_argument_count()) # Should print 2. + var builtin_callable_2 : Callable = find_child + print(builtin_callable_2.get_argument_count()) # Should print 3. + + # Test built-in vararg methods. + var builtin_vararg_callable_1 : Callable = call_thread_safe + print(builtin_vararg_callable_1.get_argument_count()) # Should print 1. + var builtin_vararg_callable_2 : Callable = rpc_id + print(builtin_vararg_callable_2.get_argument_count()) # Should print 2. + + # Test plain methods. + var callable_1 : Callable = my_func_1 + print(callable_1.get_argument_count()) # Should print 2. + var callable_2 : Callable = my_func_2 + print(callable_2.get_argument_count()) # Should print 3. + + # Test static methods. + var static_callable_1 : Callable = my_static_func_1 + print(static_callable_1.get_argument_count()) # Should print 2. + var static_callable_2 : Callable = my_static_func_2 + print(static_callable_2.get_argument_count()) # Should print 3. + + # Test rpc methods. + var rpc_callable_1 : Callable = my_rpc_func_1 + print(rpc_callable_1.get_argument_count()) # Should print 2. + var rpc_callable_2 : Callable = my_rpc_func_2 + print(rpc_callable_2.get_argument_count()) # Should print 3. + + # Test lambdas. + var lambda_callable_1 : Callable = func(_foo, _bar): pass + print(lambda_callable_1.get_argument_count()) # Should print 2. + var lambda_callable_2 : Callable = func(_foo, _bar, _baz): pass + print(lambda_callable_2.get_argument_count()) # Should print 3. + + # Test lambas with self. + var lambda_self_callable_1 : Callable = func(_foo, _bar): return self + print(lambda_self_callable_1.get_argument_count()) # Should print 2. + var lambda_self_callable_2 : Callable = func(_foo, _bar, _baz): return self + print(lambda_self_callable_2.get_argument_count()) # Should print 3. + + # Test bind. + var bind_callable_1 : Callable = my_func_2.bind(1) + print(bind_callable_1.get_argument_count()) # Should print 2. + var bind_callable_2 : Callable = my_func_2.bind(1, 2) + print(bind_callable_2.get_argument_count()) # Should print 1. + + # Test unbind. + var unbind_callable_1 : Callable = my_func_2.unbind(1) + print(unbind_callable_1.get_argument_count()) # Should print 4. + var unbind_callable_2 : Callable = my_func_2.unbind(2) + print(unbind_callable_2.get_argument_count()) # Should print 5. + + # Test variant callables. + var string_tmp := String() + var variant_callable_1 : Callable = string_tmp.replace + print(variant_callable_1.get_argument_count()) # Should print 2. + var variant_callable_2 : Callable = string_tmp.rsplit + print(variant_callable_2.get_argument_count()) # Should print 3. + + # Test variant vararg callables. + var callable_tmp := Callable() + var variant_vararg_callable_1 : Callable = callable_tmp.call + print(variant_vararg_callable_1.get_argument_count()) # Should print 0. + var variant_vararg_callable_2 : Callable = callable_tmp.rpc_id + print(variant_vararg_callable_2.get_argument_count()) # Should print 1. + + # Test global methods. + var global_callable_1 = is_equal_approx + print(global_callable_1.get_argument_count()) # Should print 2. + var global_callable_2 = inverse_lerp + print(global_callable_2.get_argument_count()) # Should print 3. + + # Test GDScript methods. + var gdscript_callable_1 = char + print(gdscript_callable_1.get_argument_count()) # Should print 1. + var gdscript_callable_2 = is_instance_of + print(gdscript_callable_2.get_argument_count()) # Should print 2. diff --git a/modules/gdscript/tests/scripts/runtime/features/argument_count.out b/modules/gdscript/tests/scripts/runtime/features/argument_count.out new file mode 100644 index 0000000000..42c4ece37d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/argument_count.out @@ -0,0 +1,27 @@ +GDTEST_OK +2 +3 +1 +2 +2 +3 +2 +3 +2 +3 +2 +3 +2 +3 +2 +1 +4 +5 +2 +3 +0 +1 +2 +3 +1 +2 diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 93fb5f1dc6..858d1d3e4e 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1735,6 +1735,34 @@ bool CSharpInstance::has_method(const StringName &p_method) const { gchandle.get_intptr(), &p_method); } +int CSharpInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + if (!script->is_valid() || !script->valid) { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + + const CSharpScript *top = script.ptr(); + while (top != nullptr) { + for (const CSharpScript::CSharpMethodInfo &E : top->methods) { + if (E.name == p_method) { + if (r_is_valid) { + *r_is_valid = true; + } + return E.method_info.arguments.size(); + } + } + + top = top->base_script.ptr(); + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { ERR_FAIL_COND_V(!script.is_valid(), Variant()); @@ -2579,6 +2607,29 @@ bool CSharpScript::has_method(const StringName &p_method) const { return false; } +int CSharpScript::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + if (!valid) { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + + for (const CSharpMethodInfo &E : methods) { + if (E.name == p_method) { + if (r_is_valid) { + *r_is_valid = true; + } + return E.method_info.arguments.size(); + } + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { if (!valid) { return MethodInfo(); diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 7821420620..06d526f494 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -278,6 +278,7 @@ public: void get_script_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; + virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override; MethodInfo get_method_info(const StringName &p_method) const override; Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; @@ -346,6 +347,7 @@ public: void get_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; + virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override; Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; void mono_object_disposed(GCHandleIntPtr p_gchandle_to_free); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index 6c34d7c29d..c7be0464b6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -12,6 +12,7 @@ namespace Godot.Bridge public delegate* unmanaged<IntPtr, void*, godot_variant**, int, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs; public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals; public delegate* unmanaged<IntPtr, int> DelegateUtils_DelegateHash; + public delegate* unmanaged<IntPtr, godot_bool*, int> DelegateUtils_GetArgumentCount; public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle; public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle; public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback; @@ -55,6 +56,7 @@ namespace Godot.Bridge DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs, DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals, DelegateUtils_DelegateHash = &DelegateUtils.DelegateHash, + DelegateUtils_GetArgumentCount = &DelegateUtils.GetArgumentCount, DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle, DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle, ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index c680142638..ef3c9c79d4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -46,6 +46,29 @@ namespace Godot } [UnmanagedCallersOnly] + internal static unsafe int GetArgumentCount(IntPtr delegateGCHandle, godot_bool* outIsValid) + { + try + { + var @delegate = (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target; + int? argCount = @delegate?.Method?.GetParameters().Length; + if (argCount is null) + { + *outIsValid = godot_bool.False; + return 0; + } + *outIsValid = godot_bool.True; + return argCount.Value; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outIsValid = godot_bool.False; + return 0; + } + } + + [UnmanagedCallersOnly] internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, void* trampoline, godot_variant** args, int argc, godot_variant* outRet) { diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index c55c5d8111..7c48110199 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -85,6 +85,10 @@ ObjectID ManagedCallable::get_object() const { return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id(); } +int ManagedCallable::get_argument_count(bool &r_is_valid) const { + return GDMonoCache::managed_callbacks.DelegateUtils_GetArgumentCount(delegate_handle, &r_is_valid); +} + void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better r_return_value = Variant(); diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h index 290d49be14..388c321d5d 100644 --- a/modules/mono/managed_callable.h +++ b/modules/mono/managed_callable.h @@ -56,6 +56,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; _FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; } diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 145f4cee90..5292bcd1ea 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -53,6 +53,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs); CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals); CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateHash); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, GetArgumentCount); CHECK_CALLBACK_NOT_NULL(DelegateUtils, TrySerializeDelegateWithGCHandle); CHECK_CALLBACK_NOT_NULL(DelegateUtils, TryDeserializeDelegateWithGCHandle); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, FrameCallback); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index 46e9ab10cb..7c24f28b3a 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -78,6 +78,7 @@ struct ManagedCallbacks { using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, void *, const Variant **, int32_t, const Variant *); using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr); using FuncDelegateUtils_DelegateHash = int32_t(GD_CLR_STDCALL *)(GCHandleIntPtr); + using FuncDelegateUtils_GetArgumentCount = int32_t(GD_CLR_STDCALL *)(GCHandleIntPtr, bool *); using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *); using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *); using FuncScriptManagerBridge_FrameCallback = void(GD_CLR_STDCALL *)(); @@ -115,6 +116,7 @@ struct ManagedCallbacks { FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs; FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals; FuncDelegateUtils_DelegateHash DelegateUtils_DelegateHash; + FuncDelegateUtils_GetArgumentCount DelegateUtils_GetArgumentCount; FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle; FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle; FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback; diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index e5d91db650..3a3013a102 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -95,6 +95,12 @@ public: bool has_method(const StringName &p_method) const override { return false; } + int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { return Variant(); } diff --git a/tests/core/variant/test_callable.h b/tests/core/variant/test_callable.h new file mode 100644 index 0000000000..3228e0a583 --- /dev/null +++ b/tests/core/variant/test_callable.h @@ -0,0 +1,140 @@ +/**************************************************************************/ +/* test_callable.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_CALLABLE_H +#define TEST_CALLABLE_H + +#include "core/object/class_db.h" +#include "core/object/object.h" + +#include "tests/test_macros.h" + +namespace TestCallable { + +class TestClass : public Object { + GDCLASS(TestClass, Object); + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("test_func_1", "foo", "bar"), &TestClass::test_func_1); + ClassDB::bind_method(D_METHOD("test_func_2", "foo", "bar", "baz"), &TestClass::test_func_2); + ClassDB::bind_static_method("TestClass", D_METHOD("test_func_5", "foo", "bar"), &TestClass::test_func_5); + ClassDB::bind_static_method("TestClass", D_METHOD("test_func_6", "foo", "bar", "baz"), &TestClass::test_func_6); + + { + MethodInfo mi; + mi.name = "test_func_7"; + mi.arguments.push_back(PropertyInfo(Variant::INT, "foo")); + mi.arguments.push_back(PropertyInfo(Variant::INT, "bar")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func_7", &TestClass::test_func_7, mi, varray(), false); + } + + { + MethodInfo mi; + mi.name = "test_func_8"; + mi.arguments.push_back(PropertyInfo(Variant::INT, "foo")); + mi.arguments.push_back(PropertyInfo(Variant::INT, "bar")); + mi.arguments.push_back(PropertyInfo(Variant::INT, "baz")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func_8", &TestClass::test_func_8, mi, varray(), false); + } + } + +public: + void test_func_1(int p_foo, int p_bar) {} + void test_func_2(int p_foo, int p_bar, int p_baz) {} + + int test_func_3(int p_foo, int p_bar) const { return 0; } + int test_func_4(int p_foo, int p_bar, int p_baz) const { return 0; } + + static void test_func_5(int p_foo, int p_bar) {} + static void test_func_6(int p_foo, int p_bar, int p_baz) {} + + void test_func_7(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {} + void test_func_8(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {} +}; + +TEST_CASE("[Callable] Argument count") { + TestClass *my_test = memnew(TestClass); + + // Bound methods tests. + + // Test simple methods. + Callable callable_1 = Callable(my_test, "test_func_1"); + CHECK_EQ(callable_1.get_argument_count(), 2); + Callable callable_2 = Callable(my_test, "test_func_2"); + CHECK_EQ(callable_2.get_argument_count(), 3); + Callable callable_3 = Callable(my_test, "test_func_5"); + CHECK_EQ(callable_3.get_argument_count(), 2); + Callable callable_4 = Callable(my_test, "test_func_6"); + CHECK_EQ(callable_4.get_argument_count(), 3); + + // Test vararg methods. + Callable callable_vararg_1 = Callable(my_test, "test_func_7"); + CHECK_MESSAGE(callable_vararg_1.get_argument_count() == 2, "vararg Callable should return the number of declared arguments"); + Callable callable_vararg_2 = Callable(my_test, "test_func_8"); + CHECK_MESSAGE(callable_vararg_2.get_argument_count() == 3, "vararg Callable should return the number of declared arguments"); + + // Callable MP tests. + + // Test simple methods. + Callable callable_mp_1 = callable_mp(my_test, &TestClass::test_func_1); + CHECK_EQ(callable_mp_1.get_argument_count(), 2); + Callable callable_mp_2 = callable_mp(my_test, &TestClass::test_func_2); + CHECK_EQ(callable_mp_2.get_argument_count(), 3); + Callable callable_mp_3 = callable_mp(my_test, &TestClass::test_func_3); + CHECK_EQ(callable_mp_3.get_argument_count(), 2); + Callable callable_mp_4 = callable_mp(my_test, &TestClass::test_func_4); + CHECK_EQ(callable_mp_4.get_argument_count(), 3); + + // Test static methods. + Callable callable_mp_static_1 = callable_mp_static(&TestClass::test_func_5); + CHECK_EQ(callable_mp_static_1.get_argument_count(), 2); + Callable callable_mp_static_2 = callable_mp_static(&TestClass::test_func_6); + CHECK_EQ(callable_mp_static_2.get_argument_count(), 3); + + // Test bind. + Callable callable_mp_bind_1 = callable_mp_2.bind(1); + CHECK_MESSAGE(callable_mp_bind_1.get_argument_count() == 2, "bind should subtract from the argument count"); + Callable callable_mp_bind_2 = callable_mp_2.bind(1, 2); + CHECK_MESSAGE(callable_mp_bind_2.get_argument_count() == 1, "bind should subtract from the argument count"); + + // Test unbind. + Callable callable_mp_unbind_1 = callable_mp_2.unbind(1); + CHECK_MESSAGE(callable_mp_unbind_1.get_argument_count() == 4, "unbind should add to the argument count"); + Callable callable_mp_unbind_2 = callable_mp_2.unbind(2); + CHECK_MESSAGE(callable_mp_unbind_2.get_argument_count() == 5, "unbind should add to the argument count"); + + memdelete(my_test); +} +} // namespace TestCallable + +#endif // TEST_CALLABLE_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 423932e2cd..24eb84127b 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -93,6 +93,7 @@ #include "tests/core/test_time.h" #include "tests/core/threads/test_worker_thread_pool.h" #include "tests/core/variant/test_array.h" +#include "tests/core/variant/test_callable.h" #include "tests/core/variant/test_dictionary.h" #include "tests/core/variant/test_variant.h" #include "tests/core/variant/test_variant_utility.h" |