diff --git a/binding_generator.py b/binding_generator.py index eb201ff77..4f2898c49 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -416,6 +416,9 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl if class_name == "Array": result.append("#include ") + if class_name == "Callable": + result.append("#include ") + for include in fully_used_classes: if include == "TypedArray": result.append("#include ") @@ -525,6 +528,9 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append(f"\t{class_name}(const wchar_t *from);") result.append(f"\t{class_name}(const char16_t *from);") result.append(f"\t{class_name}(const char32_t *from);") + if class_name == "Callable": + result.append("\tCallable(CallableCustom *p_custom);") + result.append("\tCallableCustom *get_custom() const;") if "constants" in builtin_api: axis_constants_count = 0 diff --git a/include/godot_cpp/godot.hpp b/include/godot_cpp/godot.hpp index 46911dcc7..d2184a4cd 100644 --- a/include/godot_cpp/godot.hpp +++ b/include/godot_cpp/godot.hpp @@ -166,6 +166,7 @@ extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to extern "C" GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id; extern "C" GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id; extern "C" GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create; +extern "C" GDExtensionInterfaceCallableCustomGetUserData gdextension_interface_callable_custom_get_userdata; extern "C" GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object; extern "C" GDExtensionInterfaceRefSetObject gdextension_interface_ref_set_object; extern "C" GDExtensionInterfaceScriptInstanceCreate2 gdextension_interface_script_instance_create2; diff --git a/include/godot_cpp/variant/callable_custom.hpp b/include/godot_cpp/variant/callable_custom.hpp new file mode 100644 index 000000000..f9f4c0397 --- /dev/null +++ b/include/godot_cpp/variant/callable_custom.hpp @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* callable_custom.hpp */ +/**************************************************************************/ +/* 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 GODOT_CALLABLE_CUSTOM_HPP +#define GODOT_CALLABLE_CUSTOM_HPP + +#include + +namespace godot { + +class Object; + +class CallableCustom { +public: + typedef GDExtensionBool (*CompareEqualFunc)(const CallableCustom *p_a, const CallableCustom *p_b); + typedef GDExtensionBool (*CompareLessFunc)(const CallableCustom *p_a, const CallableCustom *p_b); + + virtual uint32_t hash() const = 0; + virtual String get_as_text() const = 0; + virtual CompareEqualFunc get_compare_equal_func() const = 0; + virtual CompareLessFunc get_compare_less_func() const = 0; + virtual bool is_valid() const = 0; + virtual Object *get_object() const = 0; + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, GDExtensionCallError &r_call_error) const = 0; + + virtual ~CallableCustom() {} +}; + +} // namespace godot + +#endif // GODOT_CALLABLE_CUSTOM_HPP diff --git a/include/godot_cpp/variant/callable_method_pointer.hpp b/include/godot_cpp/variant/callable_method_pointer.hpp index d83bcba9f..881c01466 100644 --- a/include/godot_cpp/variant/callable_method_pointer.hpp +++ b/include/godot_cpp/variant/callable_method_pointer.hpp @@ -45,7 +45,7 @@ class CallableCustomMethodPointerBase { namespace internal { -Callable create_custom_callable(CallableCustomMethodPointerBase *p_callable_method_pointer); +Callable create_callable_from_ccmp(CallableCustomMethodPointerBase *p_callable_method_pointer); } // namespace internal @@ -77,7 +77,7 @@ template Callable create_custom_callable_function_pointer(T *p_instance, void (T::*p_method)(P...)) { typedef CallableCustomMethodPointer CCMP; CCMP *ccmp = memnew(CCMP(p_instance, p_method)); - return ::godot::internal::create_custom_callable(ccmp); + return ::godot::internal::create_callable_from_ccmp(ccmp); } // @@ -109,7 +109,7 @@ template Callable create_custom_callable_function_pointer(T *p_instance, R (T::*p_method)(P...)) { typedef CallableCustomMethodPointerRet CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_instance, p_method)); - return ::godot::internal::create_custom_callable(ccmp); + return ::godot::internal::create_callable_from_ccmp(ccmp); } // @@ -141,7 +141,7 @@ template Callable create_custom_callable_function_pointer(const T *p_instance, R (T::*p_method)(P...) const) { typedef CallableCustomMethodPointerRetC CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_instance, p_method)); - return ::godot::internal::create_custom_callable(ccmp); + return ::godot::internal::create_callable_from_ccmp(ccmp); } // @@ -171,7 +171,7 @@ template Callable create_custom_callable_static_function_pointer(void (*p_method)(P...)) { typedef CallableCustomStaticMethodPointer CCMP; CCMP *ccmp = memnew(CCMP(p_method)); - return ::godot::internal::create_custom_callable(ccmp); + return ::godot::internal::create_callable_from_ccmp(ccmp); } // @@ -201,7 +201,7 @@ template Callable create_custom_callable_static_function_pointer(R (*p_method)(P...)) { typedef CallableCustomStaticMethodPointerRet CCMP; CCMP *ccmp = memnew(CCMP(p_method)); - return ::godot::internal::create_custom_callable(ccmp); + return ::godot::internal::create_callable_from_ccmp(ccmp); } // diff --git a/src/godot.cpp b/src/godot.cpp index 7579cfde4..70c5978ca 100644 --- a/src/godot.cpp +++ b/src/godot.cpp @@ -172,6 +172,7 @@ GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr; GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id = nullptr; GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id = nullptr; GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create = nullptr; +GDExtensionInterfaceCallableCustomGetUserData gdextension_interface_callable_custom_get_userdata = nullptr; GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object = nullptr; GDExtensionInterfaceRefSetObject gdextension_interface_ref_set_object = nullptr; GDExtensionInterfaceScriptInstanceCreate2 gdextension_interface_script_instance_create2 = nullptr; @@ -390,6 +391,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge LOAD_PROC_ADDRESS(object_get_instance_from_id, GDExtensionInterfaceObjectGetInstanceFromId); LOAD_PROC_ADDRESS(object_get_instance_id, GDExtensionInterfaceObjectGetInstanceId); LOAD_PROC_ADDRESS(callable_custom_create, GDExtensionInterfaceCallableCustomCreate); + LOAD_PROC_ADDRESS(callable_custom_get_userdata, GDExtensionInterfaceCallableCustomGetUserData); LOAD_PROC_ADDRESS(ref_get_object, GDExtensionInterfaceRefGetObject); LOAD_PROC_ADDRESS(ref_set_object, GDExtensionInterfaceRefSetObject); LOAD_PROC_ADDRESS(script_instance_create2, GDExtensionInterfaceScriptInstanceCreate2); diff --git a/src/variant/callable_custom.cpp b/src/variant/callable_custom.cpp new file mode 100644 index 000000000..539db63fc --- /dev/null +++ b/src/variant/callable_custom.cpp @@ -0,0 +1,87 @@ +/**************************************************************************/ +/* callable_custom.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include + +#include +#include + +namespace godot { + +static void callable_custom_call(void *p_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) { + CallableCustom *callable_custom = (CallableCustom *)p_userdata; + callable_custom->call((const Variant **)p_args, p_argument_count, *(Variant *)r_return, *r_error); +} + +static GDExtensionBool callable_custom_is_valid(void *p_userdata) { + CallableCustom *callable_custom = (CallableCustom *)p_userdata; + return callable_custom->is_valid(); +} + +static void callable_custom_free(void *p_userdata) { + CallableCustom *callable_custom = (CallableCustom *)p_userdata; + memdelete(callable_custom); +} + +static uint32_t callable_custom_hash(void *p_userdata) { + CallableCustom *callable_custom = (CallableCustom *)p_userdata; + return callable_custom->hash(); +} + +static void callable_custom_to_string(void *p_userdata, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out) { + CallableCustom *callable_custom = (CallableCustom *)p_userdata; + *((String *)r_out) = callable_custom->get_as_text(); + *r_is_valid = true; +} + +Callable::Callable(CallableCustom *p_callable_custom) { + Object *object = p_callable_custom->get_object(); + + GDExtensionCallableCustomInfo info = {}; + info.callable_userdata = p_callable_custom; + info.token = internal::token; + info.object = object != nullptr ? object->_owner : nullptr; + info.call_func = &callable_custom_call; + info.is_valid_func = &callable_custom_is_valid; + info.free_func = &callable_custom_free; + info.hash_func = &callable_custom_hash; + info.equal_func = (GDExtensionCallableCustomEqual)p_callable_custom->get_compare_equal_func(); + info.less_than_func = (GDExtensionCallableCustomLessThan)p_callable_custom->get_compare_less_func(); + info.to_string_func = &callable_custom_to_string; + + ::godot::internal::gdextension_interface_callable_custom_create(_native_ptr(), &info); +} + +CallableCustom *Callable::get_custom() const { + // @todo This isn't a safe cast if this callable was created via `callable_mp()` - but if the classes shared a base class, we could use `dynamic_cast()` here. + return (CallableCustom *)::godot::internal::gdextension_interface_callable_custom_get_userdata(_native_ptr(), internal::token); +} + +} // namespace godot diff --git a/src/variant/callable_method_pointer.cpp b/src/variant/callable_method_pointer.cpp index ea43632b5..1a95fc176 100644 --- a/src/variant/callable_method_pointer.cpp +++ b/src/variant/callable_method_pointer.cpp @@ -30,31 +30,29 @@ #include -//#include - namespace godot { -static void call_custom_callable(void *userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) { +static void custom_callable_mp_call(void *userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) { CallableCustomMethodPointerBase *callable_method_pointer = (CallableCustomMethodPointerBase *)userdata; callable_method_pointer->call((const Variant **)p_args, p_argument_count, *(Variant *)r_return, *r_error); } -static void free_custom_callable(void *userdata) { +static void custom_callable_mp_free(void *userdata) { CallableCustomMethodPointerBase *callable_method_pointer = (CallableCustomMethodPointerBase *)userdata; memdelete(callable_method_pointer); } namespace internal { -Callable create_custom_callable(CallableCustomMethodPointerBase *p_callable_method_pointer) { +Callable create_callable_from_ccmp(CallableCustomMethodPointerBase *p_callable_method_pointer) { Object *object = p_callable_method_pointer->get_object(); GDExtensionCallableCustomInfo info = {}; info.callable_userdata = p_callable_method_pointer; info.token = internal::token; info.object_id = object ? object->get_instance_id() : 0; - info.call_func = &call_custom_callable; - info.free_func = &free_custom_callable; + info.call_func = &custom_callable_mp_call; + info.free_func = &custom_callable_mp_free; Callable callable; ::godot::internal::gdextension_interface_callable_custom_create(callable._native_ptr(), &info); diff --git a/test/project/main.gd b/test/project/main.gd index 41a5e0223..0f8813b34 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -121,6 +121,16 @@ func _ready(): var mp_callable_static_ret: Callable = example.test_callable_mp_static_ret() assert_equal(mp_callable_static_ret.call(example, "static-ret", 84), "unbound_static_method2: Example - static-ret - 84") + # CallableCustom. + var custom_callable: Callable = example.test_custom_callable(); + assert_equal(custom_callable.is_custom(), true); + assert_equal(custom_callable.is_valid(), true); + assert_equal(custom_callable.call(), "Hi") + assert_equal(custom_callable.hash(), 27); + assert_equal(custom_callable.get_object(), null); + assert_equal(custom_callable.get_method(), ""); + assert_equal(str(custom_callable), ""); + # PackedArray iterators assert_equal(example.test_vector_ops(), 105) diff --git a/test/src/example.cpp b/test/src/example.cpp index 3bb0cb2b7..eecf1a74c 100644 --- a/test/src/example.cpp +++ b/test/src/example.cpp @@ -15,6 +15,46 @@ using namespace godot; +class MyCallableCustom : public CallableCustom { +public: + virtual uint32_t hash() const { + return 27; + } + + virtual String get_as_text() const { + return ""; + } + + static GDExtensionBool compare_equal_func(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a == p_b; + } + + virtual CompareEqualFunc get_compare_equal_func() const { + return &MyCallableCustom::compare_equal_func; + } + + static GDExtensionBool compare_less_func(const CallableCustom *p_a, const CallableCustom *p_b) { + return (void *)p_a < (void *)p_b; + } + + virtual CompareLessFunc get_compare_less_func() const { + return &MyCallableCustom::compare_less_func; + } + + bool is_valid() const { + return true; + } + + virtual Object *get_object() const { + return nullptr; + } + + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, GDExtensionCallError &r_call_error) const { + r_return_value = "Hi"; + r_call_error.error = GDEXTENSION_CALL_OK; + } +}; + void ExampleRef::set_id(int p_id) { id = p_id; } @@ -168,6 +208,7 @@ void Example::_bind_methods() { ClassDB::bind_method(D_METHOD("test_callable_mp_retc"), &Example::test_callable_mp_retc); ClassDB::bind_method(D_METHOD("test_callable_mp_static"), &Example::test_callable_mp_static); ClassDB::bind_method(D_METHOD("test_callable_mp_static_ret"), &Example::test_callable_mp_static_ret); + ClassDB::bind_method(D_METHOD("test_custom_callable"), &Example::test_custom_callable); ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield); @@ -376,6 +417,10 @@ Callable Example::test_callable_mp_static_ret() const { return callable_mp_static(&Example::unbound_static_method2); } +Callable Example::test_custom_callable() const { + return Callable(memnew(MyCallableCustom)); +} + void Example::unbound_method1(Object *p_object, String p_string, int p_int) { String test = "unbound_method1: "; test += p_object->get_class(); diff --git a/test/src/example.h b/test/src/example.h index e922c48fd..69f2267c8 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -143,6 +143,7 @@ class Example : public Control { Callable test_callable_mp_retc() const; Callable test_callable_mp_static() const; Callable test_callable_mp_static_ret() const; + Callable test_custom_callable() const; void unbound_method1(Object *p_object, String p_string, int p_int); String unbound_method2(Object *p_object, String p_string, int p_int);