From 040507c54eeb3e621b178def9486d39f4c617455 Mon Sep 17 00:00:00 2001 From: Martin Rodriguez Reboredo Date: Tue, 17 Mar 2026 17:28:30 -0300 Subject: [PATCH] Bind CXX callable objects as methods Signed-off-by: Martin Rodriguez Reboredo --- include/godot_cpp/core/binder_common.hpp | 283 ++++++++++++++++++++++ include/godot_cpp/core/method_bind.hpp | 289 +++++++++++++++++++++++ 2 files changed, 572 insertions(+) diff --git a/include/godot_cpp/core/binder_common.hpp b/include/godot_cpp/core/binder_common.hpp index 4051a868c..4eb5db7ad 100644 --- a/include/godot_cpp/core/binder_common.hpp +++ b/include/godot_cpp/core/binder_common.hpp @@ -37,6 +37,7 @@ #include #include +#include namespace godot { @@ -208,6 +209,26 @@ void call_with_ptr_args_retc_helper(T *p_instance, R (T::*p_method)(P...) const, PtrToArg::encode((p_instance->*p_method)(PtrToArg

::convert(p_args[Is])...), r_ret); } +template +void call_with_ptr_args_helper(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, IndexSequence) { + p_method(*p_instance, PtrToArg

::convert(p_args[Is])...); +} + +template +void call_with_ptr_argsc_helper(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, IndexSequence) { + p_method(*p_instance, PtrToArg

::convert(p_args[Is])...); +} + +template +void call_with_ptr_args_ret_helper(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, void *r_ret, IndexSequence) { + PtrToArg::encode(p_method(*p_instance, PtrToArg

::convert(p_args[Is])...), r_ret); +} + +template +void call_with_ptr_args_retc_helper(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, void *r_ret, IndexSequence) { + PtrToArg::encode(p_method(*p_instance, PtrToArg

::convert(p_args[Is])...), r_ret); +} + template void call_with_ptr_args(T *p_instance, void (T::*p_method)(P...), const GDExtensionConstTypePtr *p_args, void * /*ret*/) { call_with_ptr_args_helper(p_instance, p_method, p_args, BuildIndexSequence{}); @@ -228,6 +249,26 @@ void call_with_ptr_args(T *p_instance, R (T::*p_method)(P...) const, const GDExt call_with_ptr_args_retc_helper(p_instance, p_method, p_args, r_ret, BuildIndexSequence{}); } +template +void call_with_ptr_args(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, void * /*ret*/) { + call_with_ptr_args_helper(p_instance, p_method, p_args, BuildIndexSequence{}); +} + +template +void call_with_ptr_args(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, void * /*ret*/) { + call_with_ptr_argsc_helper(p_instance, p_method, p_args, BuildIndexSequence{}); +} + +template +void call_with_ptr_args(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, void *r_ret) { + call_with_ptr_args_ret_helper(p_instance, p_method, p_args, r_ret, BuildIndexSequence{}); +} + +template +void call_with_ptr_args(T *p_instance, const std::function &p_method, const GDExtensionConstTypePtr *p_args, void *r_ret) { + call_with_ptr_args_retc_helper(p_instance, p_method, p_args, r_ret, BuildIndexSequence{}); +} + template void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, GDExtensionCallError &r_error, IndexSequence) { r_error.error = GDEXTENSION_CALL_OK; @@ -470,6 +511,248 @@ void call_with_variant_args_retc_dv(T *p_instance, R (T::*p_method)(P...) const, call_with_variant_args_retc_helper(p_instance, p_method, argsp.data(), r_ret, r_error, BuildIndexSequence{}); } +template +void call_with_variant_args_helper(T *p_instance, const std::function &p_method, const Variant **p_args, GDExtensionCallError &r_error, IndexSequence) { + r_error.error = GDEXTENSION_CALL_OK; + +#ifdef DEBUG_METHODS_ENABLED + p_method(*p_instance, VariantCasterAndValidate

::cast(p_args, Is, r_error)...); +#else + p_method(*p_instance, VariantCaster

::cast(*p_args[Is])...); +#endif + (void)(p_args); // Avoid warning. +} + +template +void call_with_variant_argsc_helper(T *p_instance, const std::function &p_method, const Variant **p_args, GDExtensionCallError &r_error, IndexSequence) { + r_error.error = GDEXTENSION_CALL_OK; + +#ifdef DEBUG_METHODS_ENABLED + p_method(*p_instance, VariantCasterAndValidate

::cast(p_args, Is, r_error)...); +#else + p_method(*p_instance, VariantCaster

::cast(*p_args[Is])...); +#endif + (void)(p_args); // Avoid warning. +} + +template +void call_with_variant_args_ret_helper(T *p_instance, const std::function &p_method, const Variant **p_args, Variant &r_ret, GDExtensionCallError &r_error, IndexSequence) { + r_error.error = GDEXTENSION_CALL_OK; + +#ifdef DEBUG_METHODS_ENABLED + r_ret = p_method(*p_instance, VariantCasterAndValidate

::cast(p_args, Is, r_error)...); +#else + r_ret = p_method(*p_instance, VariantCaster

::cast(*p_args[Is])...); +#endif + (void)p_args; // Avoid warning. +} + +template +void call_with_variant_args_retc_helper(T *p_instance, const std::function &p_method, const Variant **p_args, Variant &r_ret, GDExtensionCallError &r_error, IndexSequence) { + r_error.error = GDEXTENSION_CALL_OK; + +#ifdef DEBUG_METHODS_ENABLED + r_ret = p_method(*p_instance, VariantCasterAndValidate

::cast(p_args, Is, r_error)...); +#else + r_ret = p_method(*p_instance, VariantCaster

::cast(*p_args[Is])...); +#endif + (void)p_args; // Avoid warning. +} + +template +void call_with_variant_args(T *p_instance, const std::function &p_method, const Variant **p_args, int p_argcount, GDExtensionCallError &r_error) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } + + if ((size_t)p_argcount < sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + call_with_variant_args_helper(p_instance, p_method, p_args, r_error, BuildIndexSequence{}); +} + +template +void call_with_variant_args_ret(T *p_instance, const std::function &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } + + if ((size_t)p_argcount < sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + call_with_variant_args_ret_helper(p_instance, p_method, p_args, r_ret, r_error, BuildIndexSequence{}); +} + +template +void call_with_variant_args_retc(T *p_instance, const std::function &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } + + if ((size_t)p_argcount < sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + call_with_variant_args_retc_helper(p_instance, p_method, p_args, r_ret, r_error, BuildIndexSequence{}); +} + +template +void call_with_variant_args_dv(T *p_instance, const std::function &p_method, const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const LocalVector &default_values) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + int32_t missing = (int32_t)sizeof...(P) - (int32_t)p_argcount; + + int32_t dvs = (int32_t)default_values.size(); +#ifdef DEBUG_ENABLED + if (missing > dvs) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + Variant args[sizeof...(P) == 0 ? 1 : sizeof...(P)]; // Avoid zero sized array. + std::array argsp; + for (int32_t i = 0; i < (int32_t)sizeof...(P); i++) { + if (i < p_argcount) { + args[i] = Variant(p_args[i]); + } else { + args[i] = default_values[i - p_argcount + (dvs - missing)]; + } + argsp[i] = &args[i]; + } + + call_with_variant_args_helper(p_instance, p_method, argsp.data(), r_error, BuildIndexSequence{}); +} + +template +void call_with_variant_argsc_dv(T *p_instance, const std::function &p_method, const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const LocalVector &default_values) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + int32_t missing = (int32_t)sizeof...(P) - (int32_t)p_argcount; + + int32_t dvs = (int32_t)default_values.size(); +#ifdef DEBUG_ENABLED + if (missing > dvs) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + Variant args[sizeof...(P) == 0 ? 1 : sizeof...(P)]; // Avoid zero sized array. + std::array argsp; + for (int32_t i = 0; i < (int32_t)sizeof...(P); i++) { + if (i < p_argcount) { + args[i] = Variant(p_args[i]); + } else { + args[i] = default_values[i - p_argcount + (dvs - missing)]; + } + argsp[i] = &args[i]; + } + + call_with_variant_argsc_helper(p_instance, p_method, argsp.data(), r_error, BuildIndexSequence{}); +} + +template +void call_with_variant_args_ret_dv(T *p_instance, const std::function &p_method, const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const LocalVector &default_values) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + int32_t missing = (int32_t)sizeof...(P) - (int32_t)p_argcount; + + int32_t dvs = (int32_t)default_values.size(); +#ifdef DEBUG_ENABLED + if (missing > dvs) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + Variant args[sizeof...(P) == 0 ? 1 : sizeof...(P)]; // Avoid zero sized array. + std::array argsp; + for (int32_t i = 0; i < (int32_t)sizeof...(P); i++) { + if (i < p_argcount) { + args[i] = Variant(p_args[i]); + } else { + args[i] = default_values[i - p_argcount + (dvs - missing)]; + } + argsp[i] = &args[i]; + } + + call_with_variant_args_ret_helper(p_instance, p_method, argsp.data(), r_ret, r_error, BuildIndexSequence{}); +} + +template +void call_with_variant_args_retc_dv(T *p_instance, const std::function &p_method, const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const LocalVector &default_values) { +#ifdef DEBUG_ENABLED + if ((size_t)p_argcount > sizeof...(P)) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + int32_t missing = (int32_t)sizeof...(P) - (int32_t)p_argcount; + + int32_t dvs = (int32_t)default_values.size(); +#ifdef DEBUG_ENABLED + if (missing > dvs) { + r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = (int32_t)sizeof...(P); + return; + } +#endif + + Variant args[sizeof...(P) == 0 ? 1 : sizeof...(P)]; // Avoid zero sized array. + std::array argsp; + for (int32_t i = 0; i < (int32_t)sizeof...(P); i++) { + if (i < p_argcount) { + args[i] = Variant(p_args[i]); + } else { + args[i] = default_values[i - p_argcount + (dvs - missing)]; + } + argsp[i] = &args[i]; + } + + call_with_variant_args_retc_helper(p_instance, p_method, argsp.data(), r_ret, r_error, BuildIndexSequence{}); +} + // GCC raises "parameter 'p_args' set but not used" when P = {}, // it's not clever enough to treat other P values as making this branch valid. #if defined(DEBUG_METHODS_ENABLED) && defined(__GNUC__) && !defined(__clang__) diff --git a/include/godot_cpp/core/method_bind.hpp b/include/godot_cpp/core/method_bind.hpp index 59fb85d44..4f78dc7b5 100644 --- a/include/godot_cpp/core/method_bind.hpp +++ b/include/godot_cpp/core/method_bind.hpp @@ -42,6 +42,36 @@ namespace godot { +template +using NthType = typename std::tuple_element_t>; + +template +struct RemoveClass {}; + +template +struct RemoveClass { + using type = R(P...); +}; + +template +struct RemoveClass { + using type = R(P...); +}; + +template +struct StripFunctionObject { + using type = typename RemoveClass::type; +}; + +template > +using FunctionSignature = std::conditional_t< + std::is_function_v, + F, + typename std::conditional_t< + std::is_pointer_v || std::is_member_pointer_v, + std::remove_pointer, + StripFunctionObject>::type>; + class MethodBind { uint32_t hint_flags = METHOD_FLAGS_DEFAULT; StringName name; @@ -594,6 +624,265 @@ MethodBind *create_method_bind(R (T::*p_method)(P...) const) { return a; } +// CALLABLE OBJECTS + +// No return, not const. + +template +class MethodBindTL : public MethodBind { + std::function method; + +protected: +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual GDExtensionVariantType gen_argument_type(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + return call_get_argument_type(p_arg); + } else { + return GDEXTENSION_VARIANT_TYPE_NIL; + } + } + + virtual PropertyInfo gen_argument_type_info(int p_arg) const { + PropertyInfo pi; + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + call_get_argument_type_info(p_arg, pi); + } else { + pi = PropertyInfo(); + } + return pi; + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +public: + virtual GDExtensionClassMethodArgumentMetadata get_argument_metadata(int p_argument) const { + return call_get_argument_metadata(p_argument); + } + + virtual Variant call(GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionCallError &r_error) const { + call_with_variant_args_dv(static_cast(p_instance), method, p_args, (int)p_argument_count, r_error, get_default_arguments()); + return Variant(); + } + virtual void ptrcall(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) const { + call_with_ptr_args(static_cast(p_instance), method, p_args, nullptr); + } + + MethodBindTL(std::function &&p_method) { + method = p_method; + _generate_argument_types(sizeof...(P)); + set_argument_count(sizeof...(P)); + } +}; + +template +MethodBind *create_method_bind(F &&p_method, void (*)(T &, P...)) { + MethodBind *a = memnew((MethodBindTL)(std::function(p_method))); + a->set_instance_class(T::get_class_static()); + return a; +} + +// No return, const. + +template +class MethodBindTLC : public MethodBind { + std::function method; + +protected: +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual GDExtensionVariantType gen_argument_type(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + return call_get_argument_type(p_arg); + } else { + return GDEXTENSION_VARIANT_TYPE_NIL; + } + } + + virtual PropertyInfo gen_argument_type_info(int p_arg) const { + PropertyInfo pi; + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + call_get_argument_type_info(p_arg, pi); + } else { + pi = PropertyInfo(); + } + return pi; + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +public: + virtual GDExtensionClassMethodArgumentMetadata get_argument_metadata(int p_argument) const { + return call_get_argument_metadata(p_argument); + } + + virtual Variant call(GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionCallError &r_error) const { + call_with_variant_argsc_dv(static_cast(p_instance), method, p_args, (int)p_argument_count, r_error, get_default_arguments()); + return Variant(); + } + virtual void ptrcall(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) const { + call_with_ptr_args(static_cast(p_instance), method, p_args, nullptr); + } + + MethodBindTLC(std::function &&p_method) { + method = p_method; + _generate_argument_types(sizeof...(P)); + set_argument_count(sizeof...(P)); + } +}; + +template +MethodBind *create_method_bind(F &&p_method, void (*)(const T &, P...)) { + MethodBind *a = memnew((MethodBindTLC)(std::function(p_method))); + a->set_instance_class(T::get_class_static()); + return a; +} + +// Return, not const. + +template +class MethodBindTLR : public MethodBind { + std::function method; + +protected: +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual GDExtensionVariantType gen_argument_type(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + return call_get_argument_type(p_arg); + } else { + return GDExtensionVariantType(GetTypeInfo::VARIANT_TYPE); + } + } + + virtual PropertyInfo gen_argument_type_info(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + PropertyInfo pi; + call_get_argument_type_info(p_arg, pi); + return pi; + } else { + return GetTypeInfo::get_class_info(); + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +public: + virtual GDExtensionClassMethodArgumentMetadata get_argument_metadata(int p_argument) const { + if (p_argument >= 0) { + return call_get_argument_metadata(p_argument); + } else { + return GetTypeInfo::METADATA; + } + } + + virtual Variant call(GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionCallError &r_error) const { + Variant ret; + call_with_variant_args_ret_dv(static_cast(p_instance), method, p_args, (int)p_argument_count, ret, r_error, get_default_arguments()); + return ret; + } + virtual void ptrcall(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) const { + call_with_ptr_args(static_cast(p_instance), method, p_args, r_ret); + } + + MethodBindTLR(std::function &&p_method) { + method = p_method; + _generate_argument_types(sizeof...(P)); + set_argument_count(sizeof...(P)); + _set_returns(true); + } +}; + +template +std::enable_if_t, MethodBind *> create_method_bind(F &&p_method, R (*)(T &, P...)) { + MethodBind *a = memnew((MethodBindTLR)(std::function(p_method))); + a->set_instance_class(T::get_class_static()); + return a; +} + +// Return, const. + +template +class MethodBindTLRC : public MethodBind { + std::function method; + +protected: +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual GDExtensionVariantType gen_argument_type(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + return call_get_argument_type(p_arg); + } else { + return GDExtensionVariantType(GetTypeInfo::VARIANT_TYPE); + } + } + + virtual PropertyInfo gen_argument_type_info(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + PropertyInfo pi; + call_get_argument_type_info(p_arg, pi); + return pi; + } else { + return GetTypeInfo::get_class_info(); + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +public: + virtual GDExtensionClassMethodArgumentMetadata get_argument_metadata(int p_argument) const { + if (p_argument >= 0) { + return call_get_argument_metadata(p_argument); + } else { + return GetTypeInfo::METADATA; + } + } + + virtual Variant call(GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionCallError &r_error) const { + Variant ret; + call_with_variant_args_retc_dv(static_cast(p_instance), method, p_args, (int)p_argument_count, ret, r_error, get_default_arguments()); + return ret; + } + virtual void ptrcall(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret) const { + call_with_ptr_args(static_cast(p_instance), method, p_args, r_ret); + } + + MethodBindTLRC(std::function &&p_method) { + method = p_method; + _generate_argument_types(sizeof...(P)); + set_argument_count(sizeof...(P)); + _set_returns(true); + } +}; + +template +std::enable_if_t, MethodBind *> create_method_bind(F &&p_method, R (*)(const T &, P...)) { + MethodBind *a = memnew((MethodBindTLRC)(std::function(p_method))); + a->set_instance_class(T::get_class_static()); + return a; +} + +template +MethodBind *create_method_bind(F &&p_method) { + return create_method_bind(std::forward(p_method), (FunctionSignature *)nullptr); +} + // STATIC BINDS // no return