Skip to content

Commit 46b5cf4

Browse files
committed
Implement callable_mp() and callable_mp_static()
1 parent bfc9e0b commit 46b5cf4

File tree

10 files changed

+323
-0
lines changed

10 files changed

+323
-0
lines changed

gdextension/gdextension_interface.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,20 @@ typedef struct {
343343
GDExtensionVariantPtr *default_arguments;
344344
} GDExtensionClassMethodInfo;
345345

346+
typedef void (*GDExtensionCallableCustomCall)(void *callable_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
347+
typedef void (*GDExtensionCallableCustomFree)(void *callable_userdata);
348+
349+
typedef struct {
350+
/* Only `call_func` is strictly required, however, `object` should be passed if its not a static method.
351+
* Both `call_func` and `callable_userdata` together are used as the identity of the callable for hashing and comparison purposes.
352+
* `free_func` is necessary if `callable_userdata` needs to be cleaned up when the callable is freed.
353+
*/
354+
GDExtensionObjectPtr object;
355+
GDExtensionCallableCustomCall call_func;
356+
GDExtensionCallableCustomFree free_func;
357+
void *callable_userdata;
358+
} GDExtensionCallableCustomInfo;
359+
346360
/* SCRIPT INSTANCE EXTENSION */
347361

348362
typedef void *GDExtensionScriptInstanceDataPtr; // Pointer to custom ScriptInstance native implementation.
@@ -1948,6 +1962,19 @@ typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectGetInstanceFromId)(GDOb
19481962
*/
19491963
typedef GDObjectInstanceID (*GDExtensionInterfaceObjectGetInstanceId)(GDExtensionConstObjectPtr p_object);
19501964

1965+
/**
1966+
* @name callable_custom_create
1967+
* @since 4.2
1968+
*
1969+
* Creates a custom Callable object from a function pointer.
1970+
*
1971+
* Provided struct can be safely freed once the function returns.
1972+
*
1973+
* @param r_variant The variant to fill with the new Callable.
1974+
* @param p_callable_custom_info The info required to construct a Callable.
1975+
*/
1976+
typedef void (*GDExtensionInterfaceCallableCustomCreate)(GDExtensionUninitializedVariantPtr r_variant, GDExtensionCallableCustomInfo *p_callable_custom_info);
1977+
19511978
/* INTERFACE: Reference */
19521979

19531980
/**

include/godot_cpp/core/binder_common.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,60 @@ void call_with_variant_args_retc_helper(T *p_instance, R (T::*p_method)(P...) co
276276
(void)p_args;
277277
}
278278

279+
template <class T, class... P>
280+
void call_with_variant_args(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, int p_argcount, GDExtensionCallError &r_error) {
281+
#ifdef DEBUG_ENABLED
282+
if ((size_t)p_argcount > sizeof...(P)) {
283+
r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS;
284+
r_error.argument = (int32_t)sizeof...(P);
285+
return;
286+
}
287+
288+
if ((size_t)p_argcount < sizeof...(P)) {
289+
r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS;
290+
r_error.argument = (int32_t)sizeof...(P);
291+
return;
292+
}
293+
#endif
294+
call_with_variant_args_helper<T, P...>(p_instance, p_method, p_args, r_error, BuildIndexSequence<sizeof...(P)>{});
295+
}
296+
297+
template <class T, class R, class... P>
298+
void call_with_variant_args_ret(T *p_instance, R (T::*p_method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error) {
299+
#ifdef DEBUG_ENABLED
300+
if ((size_t)p_argcount > sizeof...(P)) {
301+
r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS;
302+
r_error.argument = (int32_t)sizeof...(P);
303+
return;
304+
}
305+
306+
if ((size_t)p_argcount < sizeof...(P)) {
307+
r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS;
308+
r_error.argument = (int32_t)sizeof...(P);
309+
return;
310+
}
311+
#endif
312+
call_with_variant_args_ret_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
313+
}
314+
315+
template <class T, class R, class... P>
316+
void call_with_variant_args_retc(T *p_instance, R (T::*p_method)(P...) const, const Variant **p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error) {
317+
#ifdef DEBUG_ENABLED
318+
if ((size_t)p_argcount > sizeof...(P)) {
319+
r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS;
320+
r_error.argument = (int32_t)sizeof...(P);
321+
return;
322+
}
323+
324+
if ((size_t)p_argcount < sizeof...(P)) {
325+
r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS;
326+
r_error.argument = (int32_t)sizeof...(P);
327+
return;
328+
}
329+
#endif
330+
call_with_variant_args_retc_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
331+
}
332+
279333
template <class T, class... P>
280334
void call_with_variant_args_dv(T *p_instance, void (T::*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const std::vector<Variant> &default_values) {
281335
#ifdef DEBUG_ENABLED

include/godot_cpp/core/class_db.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
#include <godot_cpp/core/method_bind.hpp>
3939
#include <godot_cpp/core/object.hpp>
4040

41+
// Makes callable_mp readily available in all classes connecting signals.
42+
// Needs to come after method_bind and object have been included.
43+
#include <godot_cpp/variant/callable_method_pointer.hpp>
44+
4145
#include <list>
4246
#include <set>
4347
#include <string>

include/godot_cpp/godot.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_g
163163
extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to;
164164
extern "C" GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id;
165165
extern "C" GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id;
166+
extern "C" GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create;
166167
extern "C" GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object;
167168
extern "C" GDExtensionInterfaceRefSetObject gdextension_interface_ref_set_object;
168169
extern "C" GDExtensionInterfaceScriptInstanceCreate gdextension_interface_script_instance_create;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**************************************************************************/
2+
/* callable_method_pointer.hpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#ifndef GODOT_CALLABLE_METHOD_POINTER_HPP
32+
#define GODOT_CALLABLE_METHOD_POINTER_HPP
33+
34+
#include <godot_cpp/core/binder_common.hpp>
35+
#include <godot_cpp/variant/variant.hpp>
36+
37+
namespace godot {
38+
39+
class CallableCustomMethodPointerBase {
40+
public:
41+
virtual Object *get_object() const = 0;
42+
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, GDExtensionCallError &r_call_error) const = 0;
43+
};
44+
45+
namespace internal {
46+
47+
Callable create_custom_callable(CallableCustomMethodPointerBase *p_callable_method_pointer);
48+
49+
} // namespace internal
50+
51+
//
52+
// No return value.
53+
//
54+
55+
template <class T, class... P>
56+
class CallableCustomMethodPointer : public CallableCustomMethodPointerBase {
57+
T *instance;
58+
void (T::*method)(P...);
59+
60+
public:
61+
virtual Object *get_object() const override {
62+
return instance;
63+
}
64+
65+
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, GDExtensionCallError &r_call_error) const override {
66+
call_with_variant_args(instance, method, p_arguments, p_argcount, r_call_error);
67+
}
68+
69+
CallableCustomMethodPointer(T *p_instance, void (T::*p_method)(P...)) {
70+
instance = p_instance;
71+
method = p_method;
72+
}
73+
};
74+
75+
template <class T, class... P>
76+
Callable create_custom_callable_function_pointer(T *p_instance, void (T::*p_method)(P...)) {
77+
typedef CallableCustomMethodPointer<T, P...> CCMP;
78+
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
79+
return ::godot::internal::create_custom_callable(ccmp);
80+
}
81+
82+
//
83+
// With return value.
84+
//
85+
86+
template <class T, class R, class... P>
87+
class CallableCustomMethodPointerRet : public CallableCustomMethodPointerBase {
88+
T *instance;
89+
R(T::*method)
90+
(P...);
91+
92+
public:
93+
virtual Object *get_object() const override {
94+
return instance;
95+
}
96+
97+
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, GDExtensionCallError &r_call_error) const override {
98+
call_with_variant_args_ret(instance, method, p_arguments, p_argcount, r_return_value, r_call_error);
99+
}
100+
101+
CallableCustomMethodPointerRet(T *p_instance, R (T::*p_method)(P...)) {
102+
instance = p_instance;
103+
method = p_method;
104+
}
105+
};
106+
107+
template <class T, class R, class... P>
108+
Callable create_custom_callable_function_pointer(T *p_instance, R (T::*p_method)(P...)) {
109+
typedef CallableCustomMethodPointerRet<T, R, P...> CCMP; // Messes with memnew otherwise.
110+
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
111+
return ::godot::internal::create_custom_callable(ccmp);
112+
}
113+
114+
//
115+
// Const with return value.
116+
//
117+
118+
template <class T, class R, class... P>
119+
class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase {
120+
T *instance;
121+
R(T::*method)
122+
(P...) const;
123+
124+
public:
125+
virtual Object *get_object() const override {
126+
return instance;
127+
}
128+
129+
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, GDExtensionCallError &r_call_error) const override {
130+
call_with_variant_args_retc(instance, method, p_arguments, p_argcount, r_return_value, r_call_error);
131+
}
132+
133+
CallableCustomMethodPointerRetC(T *p_instance, R (T::*p_method)(P...) const) {
134+
instance = p_instance;
135+
method = p_method;
136+
}
137+
};
138+
139+
template <class T, class R, class... P>
140+
Callable create_custom_callable_function_pointer(T *p_instance, R (T::*p_method)(P...) const) {
141+
typedef CallableCustomMethodPointerRetC<T, R, P...> CCMP; // Messes with memnew otherwise.
142+
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
143+
return ::godot::internal::create_custom_callable(ccmp);
144+
}
145+
146+
// The actual API:
147+
148+
#define callable_mp(I, M) ::godot::create_custom_callable_function_pointer(I, M)
149+
150+
} // namespace godot
151+
152+
#endif // GODOT_CALLABLE_METHOD_POINTER_HPP

src/godot.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_na
168168
GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr;
169169
GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id = nullptr;
170170
GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id = nullptr;
171+
GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create = nullptr;
171172
GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object = nullptr;
172173
GDExtensionInterfaceRefSetObject gdextension_interface_ref_set_object = nullptr;
173174
GDExtensionInterfaceScriptInstanceCreate gdextension_interface_script_instance_create = nullptr;
@@ -350,6 +351,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
350351
LOAD_PROC_ADDRESS(object_cast_to, GDExtensionInterfaceObjectCastTo);
351352
LOAD_PROC_ADDRESS(object_get_instance_from_id, GDExtensionInterfaceObjectGetInstanceFromId);
352353
LOAD_PROC_ADDRESS(object_get_instance_id, GDExtensionInterfaceObjectGetInstanceId);
354+
LOAD_PROC_ADDRESS(callable_custom_create, GDExtensionInterfaceCallableCustomCreate);
353355
LOAD_PROC_ADDRESS(ref_get_object, GDExtensionInterfaceRefGetObject);
354356
LOAD_PROC_ADDRESS(ref_set_object, GDExtensionInterfaceRefSetObject);
355357
LOAD_PROC_ADDRESS(script_instance_create, GDExtensionInterfaceScriptInstanceCreate);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**************************************************************************/
2+
/* callable_method_pointer.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include <godot_cpp/variant/callable_method_pointer.hpp>
32+
33+
//#include <godot_cpp/godot.hpp>
34+
35+
namespace godot {
36+
37+
static void call_custom_callable(void *userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) {
38+
CallableCustomMethodPointerBase *callable_method_pointer = (CallableCustomMethodPointerBase *)userdata;
39+
callable_method_pointer->call((const Variant **)p_args, p_argument_count, *(Variant *)r_return, *r_error);
40+
}
41+
42+
static void free_custom_callable(void *userdata) {
43+
CallableCustomMethodPointerBase *callable_method_pointer = (CallableCustomMethodPointerBase *)userdata;
44+
memdelete(callable_method_pointer);
45+
}
46+
47+
namespace internal {
48+
49+
Callable create_custom_callable(CallableCustomMethodPointerBase *p_callable_method_pointer) {
50+
GDExtensionCallableCustomInfo info = {};
51+
info.object = p_callable_method_pointer->get_object()->_owner;
52+
info.callable_userdata = p_callable_method_pointer;
53+
info.call_func = &call_custom_callable;
54+
info.free_func = &free_custom_callable;
55+
56+
Variant callable;
57+
// callable._native_ptr() is inaccessible here?
58+
godot::internal::gdextension_interface_callable_custom_create(&callable, &info);
59+
return callable;
60+
}
61+
62+
} // namespace internal
63+
64+
} // namespace godot

test/project/main.gd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ func _ready():
8282
# UtilityFunctions::str()
8383
assert_equal(example.test_str_utility(), "Hello, World! The answer is 42")
8484

85+
# mp_callable().
86+
var mp_callable: Callable = example.test_callable_mp()
87+
assert_equal(mp_callable.call(example, "test", 77), "Example - test - 77")
88+
8589
# PackedArray iterators
8690
assert_equal(example.test_vector_ops(), 105)
8791

test/src/example.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ void Example::_bind_methods() {
129129
ClassDB::bind_method(D_METHOD("test_string_ops"), &Example::test_string_ops);
130130
ClassDB::bind_method(D_METHOD("test_str_utility"), &Example::test_str_utility);
131131
ClassDB::bind_method(D_METHOD("test_vector_ops"), &Example::test_vector_ops);
132+
ClassDB::bind_method(D_METHOD("test_callable_mp"), &Example::test_callable_mp);
132133

133134
ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield);
134135

@@ -298,6 +299,17 @@ int Example::test_vector_ops() const {
298299
return ret;
299300
}
300301

302+
Callable Example::test_callable_mp() const {
303+
return callable_mp(const_cast<Example *>(this), &Example::unbound_method);
304+
}
305+
306+
String Example::unbound_method(Object *p_object, String p_string, int p_int) {
307+
String test = p_object->get_class();
308+
test += " - " + p_string;
309+
test += " - " + itos(p_int);
310+
return test;
311+
}
312+
301313
int Example::test_tarray_arg(const TypedArray<int64_t> &p_array) {
302314
int sum = 0;
303315
for (int i = 0; i < p_array.size(); i++) {

0 commit comments

Comments
 (0)