Skip to content

Commit 5fcc43e

Browse files
authored
Merge pull request #1377 from dsnopek/gdextension-register-virtual-method
Allow GDExtensions to register virtual methods and call them on scripts (godot-cpp support)
2 parents 9a13efa + 8fbb7cf commit 5fcc43e

File tree

14 files changed

+316
-2
lines changed

14 files changed

+316
-2
lines changed

binding_generator.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,136 @@ def generate_wrappers(target):
7070
f.write(txt)
7171

7272

73+
def generate_virtual_version(argcount, const=False, returns=False):
74+
s = """#define GDVIRTUAL$VER($RET m_name $ARG)\\
75+
StringName _gdvirtual_##m_name##_sn = #m_name;\\
76+
template <bool required>\\
77+
_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\
78+
if (::godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn)) { \\
79+
GDExtensionCallError ce;\\
80+
$CALLSIARGS\\
81+
$CALLSIBEGIN::godot::internal::gdextension_interface_object_call_script_method(_owner, &_gdvirtual_##m_name##_sn, $CALLSIARGPASS, $CALLSIRETPASS, &ce);\\
82+
if (ce.error == GDEXTENSION_CALL_OK) {\\
83+
$CALLSIRET\\
84+
return true;\\
85+
}\\
86+
}\\
87+
if (required) {\\
88+
ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\
89+
$RVOID\\
90+
}\\
91+
return false;\\
92+
}\\
93+
_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\
94+
return godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn); \\
95+
}\\
96+
_FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() {\\
97+
MethodInfo method_info;\\
98+
method_info.name = #m_name;\\
99+
method_info.flags = $METHOD_FLAGS;\\
100+
$FILL_METHOD_INFO\\
101+
return method_info;\\
102+
}
103+
104+
"""
105+
106+
sproto = str(argcount)
107+
method_info = ""
108+
if returns:
109+
sproto += "R"
110+
s = s.replace("$RET", "m_ret,")
111+
s = s.replace("$RVOID", "(void)r_ret;") # If required, may lead to uninitialized errors
112+
method_info += "method_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n"
113+
method_info += "\t\tmethod_info.return_val_metadata = GetTypeInfo<m_ret>::METADATA;"
114+
else:
115+
s = s.replace("$RET ", "")
116+
s = s.replace("\t\t\t$RVOID\\\n", "")
117+
118+
if const:
119+
sproto += "C"
120+
s = s.replace("$CONST", "const")
121+
s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL | METHOD_FLAG_CONST")
122+
else:
123+
s = s.replace("$CONST ", "")
124+
s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL")
125+
126+
s = s.replace("$VER", sproto)
127+
argtext = ""
128+
callargtext = ""
129+
callsiargs = ""
130+
callsiargptrs = ""
131+
if argcount > 0:
132+
argtext += ", "
133+
callsiargs = f"Variant vargs[{argcount}] = {{ "
134+
callsiargptrs = f"\t\t\tconst Variant *vargptrs[{argcount}] = {{ "
135+
for i in range(argcount):
136+
if i > 0:
137+
argtext += ", "
138+
callargtext += ", "
139+
callsiargs += ", "
140+
callsiargptrs += ", "
141+
argtext += f"m_type{i + 1}"
142+
callargtext += f"m_type{i + 1} arg{i + 1}"
143+
callsiargs += f"Variant(arg{i + 1})"
144+
callsiargptrs += f"&vargs[{i}]"
145+
if method_info:
146+
method_info += "\\\n\t\t"
147+
method_info += f"method_info.arguments.push_back(GetTypeInfo<m_type{i + 1}>::get_class_info());\\\n"
148+
method_info += f"\t\tmethod_info.arguments_metadata.push_back(GetTypeInfo<m_type{i + 1}>::METADATA);"
149+
150+
if argcount:
151+
callsiargs += " };\\\n"
152+
callsiargptrs += " };"
153+
s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs)
154+
s = s.replace("$CALLSIARGPASS", f"(const GDExtensionConstVariantPtr *)vargptrs, {argcount}")
155+
else:
156+
s = s.replace("\t\t\t$CALLSIARGS\\\n", "")
157+
s = s.replace("$CALLSIARGPASS", "nullptr, 0")
158+
159+
if returns:
160+
if argcount > 0:
161+
callargtext += ", "
162+
callargtext += "m_ret &r_ret"
163+
s = s.replace("$CALLSIBEGIN", "Variant ret;\\\n\t\t\t")
164+
s = s.replace("$CALLSIRETPASS", "&ret")
165+
s = s.replace("$CALLSIRET", "r_ret = VariantCaster<m_ret>::cast(ret);")
166+
else:
167+
s = s.replace("$CALLSIBEGIN", "")
168+
s = s.replace("$CALLSIRETPASS", "nullptr")
169+
s = s.replace("\t\t\t\t$CALLSIRET\\\n", "")
170+
171+
s = s.replace(" $ARG", argtext)
172+
s = s.replace("$CALLARGS", callargtext)
173+
if method_info:
174+
s = s.replace("$FILL_METHOD_INFO", method_info)
175+
else:
176+
s = s.replace("\t\t$FILL_METHOD_INFO\\\n", method_info)
177+
178+
return s
179+
180+
181+
def generate_virtuals(target):
182+
max_versions = 12
183+
184+
txt = """/* THIS FILE IS GENERATED DO NOT EDIT */
185+
#ifndef GDEXTENSION_GDVIRTUAL_GEN_H
186+
#define GDEXTENSION_GDVIRTUAL_GEN_H
187+
188+
"""
189+
190+
for i in range(max_versions + 1):
191+
txt += f"/* {i} Arguments */\n\n"
192+
txt += generate_virtual_version(i, False, False)
193+
txt += generate_virtual_version(i, False, True)
194+
txt += generate_virtual_version(i, True, False)
195+
txt += generate_virtual_version(i, True, True)
196+
197+
txt += "#endif // GDEXTENSION_GDVIRTUAL_GEN_H\n"
198+
199+
with open(target, "w", encoding="utf-8") as f:
200+
f.write(txt)
201+
202+
73203
def get_file_list(api_filepath, output_dir, headers=False, sources=False):
74204
api = {}
75205
files = []
@@ -81,6 +211,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
81211
source_gen_folder = Path(output_dir) / "gen" / "src"
82212

83213
files.append(str((core_gen_folder / "ext_wrappers.gen.inc").as_posix()))
214+
files.append(str((core_gen_folder / "gdvirtual.gen.inc").as_posix()))
84215

85216
for builtin_class in api["builtin_classes"]:
86217
if is_pod_type(builtin_class["name"]):
@@ -204,6 +335,7 @@ def generate_builtin_bindings(api, output_dir, build_config):
204335
source_gen_folder.mkdir(parents=True, exist_ok=True)
205336

206337
generate_wrappers(core_gen_folder / "ext_wrappers.gen.inc")
338+
generate_virtuals(core_gen_folder / "gdvirtual.gen.inc")
207339

208340
# Store types beforehand.
209341
for builtin_api in api["builtin_classes"]:

gdextension/gdextension_interface.h

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,18 @@ typedef struct {
364364
GDExtensionClassMethodPtrCall ptrcall_func;
365365
uint32_t method_flags; // Bitfield of `GDExtensionClassMethodFlags`.
366366

367-
/* If `has_return_value` is false, `return_value_info` and `return_value_metadata` are ignored. */
367+
/* If `has_return_value` is false, `return_value_info` and `return_value_metadata` are ignored.
368+
*
369+
* @todo Consider dropping `has_return_value` and making the other two properties match `GDExtensionMethodInfo` and `GDExtensionClassVirtualMethod` for consistency in future version of this struct.
370+
*/
368371
GDExtensionBool has_return_value;
369372
GDExtensionPropertyInfo *return_value_info;
370373
GDExtensionClassMethodArgumentMetadata return_value_metadata;
371374

372375
/* Arguments: `arguments_info` and `arguments_metadata` are array of size `argument_count`.
373376
* Name and hint information for the argument can be omitted in release builds. Class name should always be present if it applies.
377+
*
378+
* @todo Consider renaming `arguments_info` to `arguments` for consistency in future version of this struct.
374379
*/
375380
uint32_t argument_count;
376381
GDExtensionPropertyInfo *arguments_info;
@@ -381,6 +386,18 @@ typedef struct {
381386
GDExtensionVariantPtr *default_arguments;
382387
} GDExtensionClassMethodInfo;
383388

389+
typedef struct {
390+
GDExtensionStringNamePtr name;
391+
uint32_t method_flags; // Bitfield of `GDExtensionClassMethodFlags`.
392+
393+
GDExtensionPropertyInfo return_value;
394+
GDExtensionClassMethodArgumentMetadata return_value_metadata;
395+
396+
uint32_t argument_count;
397+
GDExtensionPropertyInfo *arguments;
398+
GDExtensionClassMethodArgumentMetadata *arguments_metadata;
399+
} GDExtensionClassVirtualMethodInfo;
400+
384401
typedef void (*GDExtensionCallableCustomCall)(void *callable_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
385402
typedef GDExtensionBool (*GDExtensionCallableCustomIsValid)(void *callable_userdata);
386403
typedef void (*GDExtensionCallableCustomFree)(void *callable_userdata);
@@ -2268,6 +2285,34 @@ typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectGetInstanceFromId)(GDOb
22682285
*/
22692286
typedef GDObjectInstanceID (*GDExtensionInterfaceObjectGetInstanceId)(GDExtensionConstObjectPtr p_object);
22702287

2288+
/**
2289+
* @name object_has_script_method
2290+
* @since 4.3
2291+
*
2292+
* Checks if this object has a script with the given method.
2293+
*
2294+
* @param p_object A pointer to the Object.
2295+
* @param p_method A pointer to a StringName identifying the method.
2296+
*
2297+
* @returns true if the object has a script and that script has a method with the given name. Returns false if the object has no script.
2298+
*/
2299+
typedef GDExtensionBool (*GDExtensionInterfaceObjectHasScriptMethod)(GDExtensionConstObjectPtr p_object, GDExtensionConstStringNamePtr p_method);
2300+
2301+
/**
2302+
* @name object_call_script_method
2303+
* @since 4.3
2304+
*
2305+
* Call the given script method on this object.
2306+
*
2307+
* @param p_object A pointer to the Object.
2308+
* @param p_method A pointer to a StringName identifying the method.
2309+
* @param p_args A pointer to a C array of Variant.
2310+
* @param p_argument_count The number of arguments.
2311+
* @param r_return A pointer a Variant which will be assigned the return value.
2312+
* @param r_error A pointer the structure which will hold error information.
2313+
*/
2314+
typedef void (*GDExtensionInterfaceObjectCallScriptMethod)(GDExtensionObjectPtr p_object, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error);
2315+
22712316
/* INTERFACE: Reference */
22722317

22732318
/**
@@ -2483,6 +2528,20 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl
24832528
*/
24842529
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info);
24852530

2531+
/**
2532+
* @name classdb_register_extension_class_virtual_method
2533+
* @since 4.3
2534+
*
2535+
* Registers a virtual method on an extension class in ClassDB, that can be implemented by scripts or other extensions.
2536+
*
2537+
* Provided struct can be safely freed once the function returns.
2538+
*
2539+
* @param p_library A pointer the library received by the GDExtension's entry point function.
2540+
* @param p_class_name A pointer to a StringName with the class name.
2541+
* @param p_method_info A pointer to a GDExtensionClassMethodInfo struct.
2542+
*/
2543+
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info);
2544+
24862545
/**
24872546
* @name classdb_register_extension_class_integer_constant
24882547
* @since 4.1

include/godot_cpp/classes/wrapped.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <godot_cpp/core/property_info.hpp>
3737

3838
#include <godot_cpp/templates/list.hpp>
39+
#include <godot_cpp/templates/vector.hpp>
3940

4041
#include <godot_cpp/godot.hpp>
4142

@@ -107,6 +108,26 @@ class Wrapped {
107108
GodotObject *_owner = nullptr;
108109
};
109110

111+
_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr) {
112+
}
113+
114+
_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr, const StringName &p_str) {
115+
arr.push_back(p_str);
116+
}
117+
118+
template <class... P>
119+
_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr, const StringName &p_str, P... p_args) {
120+
arr.push_back(p_str);
121+
snarray_add_str(arr, p_args...);
122+
}
123+
124+
template <class... P>
125+
_FORCE_INLINE_ Vector<StringName> snarray(P... p_args) {
126+
Vector<StringName> arr;
127+
snarray_add_str(arr, p_args...);
128+
return arr;
129+
}
130+
110131
namespace internal {
111132

112133
GDExtensionPropertyInfo *create_c_property_list(const ::godot::List<::godot::PropertyInfo> &plist_cpp, uint32_t *r_size);
@@ -445,4 +466,14 @@ public:
445466
// Don't use this for your classes, use GDCLASS() instead.
446467
#define GDEXTENSION_CLASS(m_class, m_inherits) GDEXTENSION_CLASS_ALIAS(m_class, m_class, m_inherits)
447468

469+
#define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call<false>(__VA_ARGS__)
470+
#define GDVIRTUAL_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call<false>(__VA_ARGS__)
471+
472+
#define GDVIRTUAL_REQUIRED_CALL(m_name, ...) _gdvirtual_##m_name##_call<true>(__VA_ARGS__)
473+
#define GDVIRTUAL_REQUIRED_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call<true>(__VA_ARGS__)
474+
475+
#define GDVIRTUAL_BIND(m_name, ...) ::godot::ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info(), ::godot::snarray(__VA_ARGS__));
476+
#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden()
477+
#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden()
478+
448479
#endif // GODOT_WRAPPED_HPP

include/godot_cpp/core/class_db.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ class ClassDB {
165165
static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1);
166166
static void add_signal(const StringName &p_class, const MethodInfo &p_signal);
167167
static void bind_integer_constant(const StringName &p_class_name, const StringName &p_enum_name, const StringName &p_constant_name, GDExtensionInt p_constant_value, bool p_is_bitfield = false);
168+
// Binds an implementation of a virtual method defined in Godot.
168169
static void bind_virtual_method(const StringName &p_class, const StringName &p_method, GDExtensionClassCallVirtual p_call);
170+
// Add a new virtual method that can be implemented by scripts.
171+
static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, const Vector<StringName> &p_arg_names = Vector<StringName>());
169172

170173
static MethodBind *get_method(const StringName &p_class, const StringName &p_method);
171174

include/godot_cpp/core/object.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ struct MethodInfo {
6868
int id = 0;
6969
std::vector<PropertyInfo> arguments;
7070
std::vector<Variant> default_arguments;
71+
GDExtensionClassMethodArgumentMetadata return_val_metadata;
72+
std::vector<GDExtensionClassMethodArgumentMetadata> arguments_metadata;
7173

7274
inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id; }
7375
inline bool operator<(const MethodInfo &p_method) const { return id == p_method.id ? (name < p_method.name) : (id < p_method.id); }

include/godot_cpp/core/property_info.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ struct PropertyInfo {
8080
p_info->usage = usage;
8181
*(reinterpret_cast<StringName *>(p_info->class_name)) = class_name;
8282
}
83+
84+
GDExtensionPropertyInfo _to_gdextension() const {
85+
return {
86+
(GDExtensionVariantType)type,
87+
name._native_ptr(),
88+
class_name._native_ptr(),
89+
hint,
90+
hint_string._native_ptr(),
91+
usage,
92+
};
93+
}
8394
};
8495

8596
} // namespace godot

include/godot_cpp/godot.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_g
165165
extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to;
166166
extern "C" GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id;
167167
extern "C" GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id;
168+
extern "C" GDExtensionInterfaceObjectHasScriptMethod gdextension_interface_object_has_script_method;
169+
extern "C" GDExtensionInterfaceObjectCallScriptMethod gdextension_interface_object_call_script_method;
168170
extern "C" GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create;
169171
extern "C" GDExtensionInterfaceCallableCustomGetUserData gdextension_interface_callable_custom_get_userdata;
170172
extern "C" GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object;
@@ -177,6 +179,7 @@ extern "C" GDExtensionInterfaceClassdbGetMethodBind gdextension_interface_classd
177179
extern "C" GDExtensionInterfaceClassdbGetClassTag gdextension_interface_classdb_get_class_tag;
178180
extern "C" GDExtensionInterfaceClassdbRegisterExtensionClass2 gdextension_interface_classdb_register_extension_class2;
179181
extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassMethod gdextension_interface_classdb_register_extension_class_method;
182+
extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod gdextension_interface_classdb_register_extension_class_virtual_method;
180183
extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant gdextension_interface_classdb_register_extension_class_integer_constant;
181184
extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassProperty gdextension_interface_classdb_register_extension_class_property;
182185
extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed gdextension_interface_classdb_register_extension_class_property_indexed;

0 commit comments

Comments
 (0)