diff --git a/binding_generator.py b/binding_generator.py index 7a6fe248..5f9bf66b 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -70,6 +70,136 @@ def generate_wrappers(target): f.write(txt) +def generate_virtual_version(argcount, const=False, returns=False): + s = """#define GDVIRTUAL$VER($RET m_name $ARG)\\ + StringName _gdvirtual_##m_name##_sn = #m_name;\\ + template \\ + _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\ + if (::godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn)) { \\ + GDExtensionCallError ce;\\ + $CALLSIARGS\\ + $CALLSIBEGIN::godot::internal::gdextension_interface_object_call_script_method(_owner, &_gdvirtual_##m_name##_sn, $CALLSIARGPASS, $CALLSIRETPASS, &ce);\\ + if (ce.error == GDEXTENSION_CALL_OK) {\\ + $CALLSIRET\\ + return true;\\ + }\\ + }\\ + if (required) {\\ + ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\ + $RVOID\\ + }\\ + return false;\\ + }\\ + _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\ + return godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn); \\ + }\\ + _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() {\\ + MethodInfo method_info;\\ + method_info.name = #m_name;\\ + method_info.flags = $METHOD_FLAGS;\\ + $FILL_METHOD_INFO\\ + return method_info;\\ + } + +""" + + sproto = str(argcount) + method_info = "" + if returns: + sproto += "R" + s = s.replace("$RET", "m_ret,") + s = s.replace("$RVOID", "(void)r_ret;") # If required, may lead to uninitialized errors + method_info += "method_info.return_val = GetTypeInfo::get_class_info();\\\n" + method_info += "\t\tmethod_info.return_val_metadata = GetTypeInfo::METADATA;" + else: + s = s.replace("$RET ", "") + s = s.replace("\t\t\t$RVOID\\\n", "") + + if const: + sproto += "C" + s = s.replace("$CONST", "const") + s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL | METHOD_FLAG_CONST") + else: + s = s.replace("$CONST ", "") + s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL") + + s = s.replace("$VER", sproto) + argtext = "" + callargtext = "" + callsiargs = "" + callsiargptrs = "" + if argcount > 0: + argtext += ", " + callsiargs = f"Variant vargs[{argcount}] = {{ " + callsiargptrs = f"\t\t\tconst Variant *vargptrs[{argcount}] = {{ " + for i in range(argcount): + if i > 0: + argtext += ", " + callargtext += ", " + callsiargs += ", " + callsiargptrs += ", " + argtext += f"m_type{i + 1}" + callargtext += f"m_type{i + 1} arg{i + 1}" + callsiargs += f"Variant(arg{i + 1})" + callsiargptrs += f"&vargs[{i}]" + if method_info: + method_info += "\\\n\t\t" + method_info += f"method_info.arguments.push_back(GetTypeInfo::get_class_info());\\\n" + method_info += f"\t\tmethod_info.arguments_metadata.push_back(GetTypeInfo::METADATA);" + + if argcount: + callsiargs += " };\\\n" + callsiargptrs += " };" + s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs) + s = s.replace("$CALLSIARGPASS", f"(const GDExtensionConstVariantPtr *)vargptrs, {argcount}") + else: + s = s.replace("\t\t\t$CALLSIARGS\\\n", "") + s = s.replace("$CALLSIARGPASS", "nullptr, 0") + + if returns: + if argcount > 0: + callargtext += ", " + callargtext += "m_ret &r_ret" + s = s.replace("$CALLSIBEGIN", "Variant ret;\\\n\t\t\t") + s = s.replace("$CALLSIRETPASS", "&ret") + s = s.replace("$CALLSIRET", "r_ret = VariantCaster::cast(ret);") + else: + s = s.replace("$CALLSIBEGIN", "") + s = s.replace("$CALLSIRETPASS", "nullptr") + s = s.replace("\t\t\t\t$CALLSIRET\\\n", "") + + s = s.replace(" $ARG", argtext) + s = s.replace("$CALLARGS", callargtext) + if method_info: + s = s.replace("$FILL_METHOD_INFO", method_info) + else: + s = s.replace("\t\t$FILL_METHOD_INFO\\\n", method_info) + + return s + + +def generate_virtuals(target): + max_versions = 12 + + txt = """/* THIS FILE IS GENERATED DO NOT EDIT */ +#ifndef GDEXTENSION_GDVIRTUAL_GEN_H +#define GDEXTENSION_GDVIRTUAL_GEN_H + +""" + + for i in range(max_versions + 1): + txt += f"/* {i} Arguments */\n\n" + txt += generate_virtual_version(i, False, False) + txt += generate_virtual_version(i, False, True) + txt += generate_virtual_version(i, True, False) + txt += generate_virtual_version(i, True, True) + + txt += "#endif // GDEXTENSION_GDVIRTUAL_GEN_H\n" + + with open(target, "w", encoding="utf-8") as f: + f.write(txt) + + def get_file_list(api_filepath, output_dir, headers=False, sources=False): api = {} files = [] @@ -81,6 +211,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False): source_gen_folder = Path(output_dir) / "gen" / "src" files.append(str((core_gen_folder / "ext_wrappers.gen.inc").as_posix())) + files.append(str((core_gen_folder / "gdvirtual.gen.inc").as_posix())) for builtin_class in api["builtin_classes"]: if is_pod_type(builtin_class["name"]): @@ -204,6 +335,7 @@ def generate_builtin_bindings(api, output_dir, build_config): source_gen_folder.mkdir(parents=True, exist_ok=True) generate_wrappers(core_gen_folder / "ext_wrappers.gen.inc") + generate_virtuals(core_gen_folder / "gdvirtual.gen.inc") # Store types beforehand. for builtin_api in api["builtin_classes"]: diff --git a/gdextension/gdextension_interface.h b/gdextension/gdextension_interface.h index d58f0226..765952cf 100644 --- a/gdextension/gdextension_interface.h +++ b/gdextension/gdextension_interface.h @@ -364,13 +364,18 @@ typedef struct { GDExtensionClassMethodPtrCall ptrcall_func; uint32_t method_flags; // Bitfield of `GDExtensionClassMethodFlags`. - /* If `has_return_value` is false, `return_value_info` and `return_value_metadata` are ignored. */ + /* If `has_return_value` is false, `return_value_info` and `return_value_metadata` are ignored. + * + * @todo Consider dropping `has_return_value` and making the other two properties match `GDExtensionMethodInfo` and `GDExtensionClassVirtualMethod` for consistency in future version of this struct. + */ GDExtensionBool has_return_value; GDExtensionPropertyInfo *return_value_info; GDExtensionClassMethodArgumentMetadata return_value_metadata; /* Arguments: `arguments_info` and `arguments_metadata` are array of size `argument_count`. * Name and hint information for the argument can be omitted in release builds. Class name should always be present if it applies. + * + * @todo Consider renaming `arguments_info` to `arguments` for consistency in future version of this struct. */ uint32_t argument_count; GDExtensionPropertyInfo *arguments_info; @@ -381,6 +386,18 @@ typedef struct { GDExtensionVariantPtr *default_arguments; } GDExtensionClassMethodInfo; +typedef struct { + GDExtensionStringNamePtr name; + uint32_t method_flags; // Bitfield of `GDExtensionClassMethodFlags`. + + GDExtensionPropertyInfo return_value; + GDExtensionClassMethodArgumentMetadata return_value_metadata; + + uint32_t argument_count; + GDExtensionPropertyInfo *arguments; + GDExtensionClassMethodArgumentMetadata *arguments_metadata; +} GDExtensionClassVirtualMethodInfo; + typedef void (*GDExtensionCallableCustomCall)(void *callable_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); typedef GDExtensionBool (*GDExtensionCallableCustomIsValid)(void *callable_userdata); typedef void (*GDExtensionCallableCustomFree)(void *callable_userdata); @@ -2268,6 +2285,34 @@ typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectGetInstanceFromId)(GDOb */ typedef GDObjectInstanceID (*GDExtensionInterfaceObjectGetInstanceId)(GDExtensionConstObjectPtr p_object); +/** + * @name object_has_script_method + * @since 4.3 + * + * Checks if this object has a script with the given method. + * + * @param p_object A pointer to the Object. + * @param p_method A pointer to a StringName identifying the method. + * + * @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. + */ +typedef GDExtensionBool (*GDExtensionInterfaceObjectHasScriptMethod)(GDExtensionConstObjectPtr p_object, GDExtensionConstStringNamePtr p_method); + +/** + * @name object_call_script_method + * @since 4.3 + * + * Call the given script method on this object. + * + * @param p_object A pointer to the Object. + * @param p_method A pointer to a StringName identifying the method. + * @param p_args A pointer to a C array of Variant. + * @param p_argument_count The number of arguments. + * @param r_return A pointer a Variant which will be assigned the return value. + * @param r_error A pointer the structure which will hold error information. + */ +typedef void (*GDExtensionInterfaceObjectCallScriptMethod)(GDExtensionObjectPtr p_object, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error); + /* INTERFACE: Reference */ /** @@ -2483,6 +2528,20 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl */ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info); +/** + * @name classdb_register_extension_class_virtual_method + * @since 4.3 + * + * Registers a virtual method on an extension class in ClassDB, that can be implemented by scripts or other extensions. + * + * Provided struct can be safely freed once the function returns. + * + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param p_class_name A pointer to a StringName with the class name. + * @param p_method_info A pointer to a GDExtensionClassMethodInfo struct. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info); + /** * @name classdb_register_extension_class_integer_constant * @since 4.1 diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index 32caf39b..25a93dfc 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -36,6 +36,7 @@ #include #include +#include #include @@ -107,6 +108,26 @@ public: GodotObject *_owner = nullptr; }; +_FORCE_INLINE_ void snarray_add_str(Vector &arr) { +} + +_FORCE_INLINE_ void snarray_add_str(Vector &arr, const StringName &p_str) { + arr.push_back(p_str); +} + +template +_FORCE_INLINE_ void snarray_add_str(Vector &arr, const StringName &p_str, P... p_args) { + arr.push_back(p_str); + snarray_add_str(arr, p_args...); +} + +template +_FORCE_INLINE_ Vector snarray(P... p_args) { + Vector arr; + snarray_add_str(arr, p_args...); + return arr; +} + namespace internal { GDExtensionPropertyInfo *create_c_property_list(const ::godot::List<::godot::PropertyInfo> &plist_cpp, uint32_t *r_size); @@ -445,4 +466,14 @@ private: // Don't use this for your classes, use GDCLASS() instead. #define GDEXTENSION_CLASS(m_class, m_inherits) GDEXTENSION_CLASS_ALIAS(m_class, m_class, m_inherits) +#define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call(__VA_ARGS__) +#define GDVIRTUAL_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call(__VA_ARGS__) + +#define GDVIRTUAL_REQUIRED_CALL(m_name, ...) _gdvirtual_##m_name##_call(__VA_ARGS__) +#define GDVIRTUAL_REQUIRED_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call(__VA_ARGS__) + +#define GDVIRTUAL_BIND(m_name, ...) ::godot::ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info(), ::godot::snarray(__VA_ARGS__)); +#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden() +#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden() + #endif // GODOT_WRAPPED_HPP diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index af394f04..e5bccf3d 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -165,7 +165,10 @@ public: static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1); static void add_signal(const StringName &p_class, const MethodInfo &p_signal); 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); + // Binds an implementation of a virtual method defined in Godot. static void bind_virtual_method(const StringName &p_class, const StringName &p_method, GDExtensionClassCallVirtual p_call); + // Add a new virtual method that can be implemented by scripts. + static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, const Vector &p_arg_names = Vector()); static MethodBind *get_method(const StringName &p_class, const StringName &p_method); diff --git a/include/godot_cpp/core/object.hpp b/include/godot_cpp/core/object.hpp index 79f8fbfe..c1f10692 100644 --- a/include/godot_cpp/core/object.hpp +++ b/include/godot_cpp/core/object.hpp @@ -68,6 +68,8 @@ struct MethodInfo { int id = 0; std::vector arguments; std::vector default_arguments; + GDExtensionClassMethodArgumentMetadata return_val_metadata; + std::vector arguments_metadata; inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id; } inline bool operator<(const MethodInfo &p_method) const { return id == p_method.id ? (name < p_method.name) : (id < p_method.id); } diff --git a/include/godot_cpp/core/property_info.hpp b/include/godot_cpp/core/property_info.hpp index 0ecfa322..f610f3fd 100644 --- a/include/godot_cpp/core/property_info.hpp +++ b/include/godot_cpp/core/property_info.hpp @@ -80,6 +80,17 @@ struct PropertyInfo { p_info->usage = usage; *(reinterpret_cast(p_info->class_name)) = class_name; } + + GDExtensionPropertyInfo _to_gdextension() const { + return { + (GDExtensionVariantType)type, + name._native_ptr(), + class_name._native_ptr(), + hint, + hint_string._native_ptr(), + usage, + }; + } }; } // namespace godot diff --git a/include/godot_cpp/godot.hpp b/include/godot_cpp/godot.hpp index c9e90226..1b420d38 100644 --- a/include/godot_cpp/godot.hpp +++ b/include/godot_cpp/godot.hpp @@ -165,6 +165,8 @@ extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_g 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" GDExtensionInterfaceObjectHasScriptMethod gdextension_interface_object_has_script_method; +extern "C" GDExtensionInterfaceObjectCallScriptMethod gdextension_interface_object_call_script_method; 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; @@ -177,6 +179,7 @@ extern "C" GDExtensionInterfaceClassdbGetMethodBind gdextension_interface_classd extern "C" GDExtensionInterfaceClassdbGetClassTag gdextension_interface_classdb_get_class_tag; extern "C" GDExtensionInterfaceClassdbRegisterExtensionClass2 gdextension_interface_classdb_register_extension_class2; extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassMethod gdextension_interface_classdb_register_extension_class_method; +extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod gdextension_interface_classdb_register_extension_class_virtual_method; extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant gdextension_interface_classdb_register_extension_class_integer_constant; extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassProperty gdextension_interface_classdb_register_extension_class_property; extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed gdextension_interface_classdb_register_extension_class_property_indexed; diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 1f4b135d..acead8bf 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -32,6 +32,7 @@ #include #include +#include #include @@ -337,6 +338,46 @@ void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p type.virtual_methods[p_method] = p_call; } +void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, const Vector &p_arg_names) { + std::unordered_map::iterator type_it = classes.find(p_class); + ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class))); + + GDExtensionClassVirtualMethodInfo mi; + mi.name = (GDExtensionStringNamePtr)&p_method.name; + mi.method_flags = p_method.flags; + mi.return_value = p_method.return_val._to_gdextension(); + mi.return_value_metadata = p_method.return_val_metadata; + mi.argument_count = p_method.arguments.size(); + if (mi.argument_count > 0) { + mi.arguments = (GDExtensionPropertyInfo *)memalloc(sizeof(GDExtensionPropertyInfo) * mi.argument_count); + mi.arguments_metadata = (GDExtensionClassMethodArgumentMetadata *)memalloc(sizeof(GDExtensionClassMethodArgumentMetadata) * mi.argument_count); + for (int i = 0; i < mi.argument_count; i++) { + mi.arguments[i] = p_method.arguments[i]._to_gdextension(); + mi.arguments_metadata[i] = p_method.arguments_metadata[i]; + } + } else { + mi.arguments = nullptr; + mi.arguments_metadata = nullptr; + } + + if (p_arg_names.size() != mi.argument_count) { + WARN_PRINT("Mismatch argument name count for virtual method: " + String(p_class) + "::" + p_method.name); + } else { + for (int i = 0; i < p_arg_names.size(); i++) { + mi.arguments[i].name = (GDExtensionStringNamePtr)&p_arg_names[i]; + } + } + + internal::gdextension_interface_classdb_register_extension_class_virtual_method(internal::library, &p_class, &mi); + + if (mi.arguments) { + memfree(mi.arguments); + } + if (mi.arguments_metadata) { + memfree(mi.arguments_metadata); + } +} + void ClassDB::initialize_class(const ClassInfo &p_cl) { } diff --git a/src/godot.cpp b/src/godot.cpp index 5c2aaa65..a80ad90d 100644 --- a/src/godot.cpp +++ b/src/godot.cpp @@ -171,6 +171,8 @@ GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_na GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr; GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id = nullptr; GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id = nullptr; +GDExtensionInterfaceObjectHasScriptMethod gdextension_interface_object_has_script_method = nullptr; +GDExtensionInterfaceObjectCallScriptMethod gdextension_interface_object_call_script_method = nullptr; GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create = nullptr; GDExtensionInterfaceCallableCustomGetUserData gdextension_interface_callable_custom_get_userdata = nullptr; GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object = nullptr; @@ -183,6 +185,7 @@ GDExtensionInterfaceClassdbGetMethodBind gdextension_interface_classdb_get_metho GDExtensionInterfaceClassdbGetClassTag gdextension_interface_classdb_get_class_tag = nullptr; GDExtensionInterfaceClassdbRegisterExtensionClass2 gdextension_interface_classdb_register_extension_class2 = nullptr; GDExtensionInterfaceClassdbRegisterExtensionClassMethod gdextension_interface_classdb_register_extension_class_method = nullptr; +GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod gdextension_interface_classdb_register_extension_class_virtual_method = nullptr; GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant gdextension_interface_classdb_register_extension_class_integer_constant = nullptr; GDExtensionInterfaceClassdbRegisterExtensionClassProperty gdextension_interface_classdb_register_extension_class_property = nullptr; GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed gdextension_interface_classdb_register_extension_class_property_indexed = nullptr; @@ -408,6 +411,8 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge LOAD_PROC_ADDRESS(object_cast_to, GDExtensionInterfaceObjectCastTo); LOAD_PROC_ADDRESS(object_get_instance_from_id, GDExtensionInterfaceObjectGetInstanceFromId); LOAD_PROC_ADDRESS(object_get_instance_id, GDExtensionInterfaceObjectGetInstanceId); + LOAD_PROC_ADDRESS(object_has_script_method, GDExtensionInterfaceObjectHasScriptMethod); + LOAD_PROC_ADDRESS(object_call_script_method, GDExtensionInterfaceObjectCallScriptMethod); LOAD_PROC_ADDRESS(callable_custom_create, GDExtensionInterfaceCallableCustomCreate); LOAD_PROC_ADDRESS(callable_custom_get_userdata, GDExtensionInterfaceCallableCustomGetUserData); LOAD_PROC_ADDRESS(ref_get_object, GDExtensionInterfaceRefGetObject); @@ -420,6 +425,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge LOAD_PROC_ADDRESS(classdb_get_class_tag, GDExtensionInterfaceClassdbGetClassTag); LOAD_PROC_ADDRESS(classdb_register_extension_class2, GDExtensionInterfaceClassdbRegisterExtensionClass2); LOAD_PROC_ADDRESS(classdb_register_extension_class_method, GDExtensionInterfaceClassdbRegisterExtensionClassMethod); + LOAD_PROC_ADDRESS(classdb_register_extension_class_virtual_method, GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod); LOAD_PROC_ADDRESS(classdb_register_extension_class_integer_constant, GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant); LOAD_PROC_ADDRESS(classdb_register_extension_class_property, GDExtensionInterfaceClassdbRegisterExtensionClassProperty); LOAD_PROC_ADDRESS(classdb_register_extension_class_property_indexed, GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed); diff --git a/test/project/example.gd b/test/project/example.gd new file mode 100644 index 00000000..b20280a3 --- /dev/null +++ b/test/project/example.gd @@ -0,0 +1,5 @@ +extends Example + +func _do_something_virtual(p_name, p_value): + custom_signal.emit(p_name, p_value) + return "Implemented" diff --git a/test/project/main.gd b/test/project/main.gd index 59cab6dc..d2cbd261 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -241,6 +241,10 @@ func _ready(): assert_equal(new_example_ref.was_post_initialized(), true) assert_equal(example.test_post_initialize(), true) + # Test a virtual method defined in GDExtension and implemented in script. + assert_equal(example.test_virtual_implemented_in_script("Virtual", 939), "Implemented") + assert_equal(custom_signal_emitted, ["Virtual", 939]) + exit_with_status() func _on_Example_custom_signal(signal_name, value): diff --git a/test/project/main.tscn b/test/project/main.tscn index 2b98e0f0..1f175974 100644 --- a/test/project/main.tscn +++ b/test/project/main.tscn @@ -1,11 +1,13 @@ -[gd_scene load_steps=2 format=3 uid="uid://dmx2xuigcpvt4"] +[gd_scene load_steps=3 format=3 uid="uid://dmx2xuigcpvt4"] [ext_resource type="Script" path="res://main.gd" id="1_qesh5"] +[ext_resource type="Script" path="res://example.gd" id="2_jju25"] [node name="Node" type="Node"] script = ExtResource("1_qesh5") [node name="Example" type="Example" parent="."] +script = ExtResource("2_jju25") [node name="ExampleMin" type="ExampleMin" parent="Example"] layout_mode = 0 diff --git a/test/src/example.cpp b/test/src/example.cpp index 5372d70a..53d11f4a 100644 --- a/test/src/example.cpp +++ b/test/src/example.cpp @@ -230,6 +230,9 @@ void Example::_bind_methods() { ClassDB::bind_method(D_METHOD("callable_bind"), &Example::callable_bind); ClassDB::bind_method(D_METHOD("test_post_initialize"), &Example::test_post_initialize); + GDVIRTUAL_BIND(_do_something_virtual, "name", "value"); + ClassDB::bind_method(D_METHOD("test_virtual_implemented_in_script"), &Example::test_virtual_implemented_in_script); + ClassDB::bind_static_method("Example", D_METHOD("test_static", "a", "b"), &Example::test_static); ClassDB::bind_static_method("Example", D_METHOD("test_static2"), &Example::test_static2); @@ -626,3 +629,11 @@ void Example::_input(const Ref &event) { emit_custom_signal(String("_input: ") + key_event->get_key_label(), key_event->get_unicode()); } } + +String Example::test_virtual_implemented_in_script(const String &p_name, int p_value) { + String ret; + if (GDVIRTUAL_CALL(_do_something_virtual, p_name, p_value, ret)) { + return ret; + } + return "Unimplemented"; +} diff --git a/test/src/example.h b/test/src/example.h index c86a51fd..1c577203 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -24,6 +24,7 @@ #include #include +#include using namespace godot; @@ -181,6 +182,9 @@ public: // Virtual function override (no need to bind manually). virtual bool _has_point(const Vector2 &point) const override; virtual void _input(const Ref &event) override; + + GDVIRTUAL2R(String, _do_something_virtual, String, int); + String test_virtual_implemented_in_script(const String &p_name, int p_value); }; VARIANT_ENUM_CAST(Example::Constants);