diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | binding_generator.py | 148 | ||||
-rw-r--r-- | gdextension/gdextension_interface.h | 61 | ||||
-rw-r--r-- | include/godot_cpp/classes/wrapped.hpp | 31 | ||||
-rw-r--r-- | include/godot_cpp/core/builtin_ptrcall.hpp | 12 | ||||
-rw-r--r-- | include/godot_cpp/core/class_db.hpp | 3 | ||||
-rw-r--r-- | include/godot_cpp/core/object.hpp | 2 | ||||
-rw-r--r-- | include/godot_cpp/core/property_info.hpp | 11 | ||||
-rw-r--r-- | include/godot_cpp/core/type_info.hpp | 6 | ||||
-rw-r--r-- | include/godot_cpp/godot.hpp | 3 | ||||
-rw-r--r-- | include/godot_cpp/variant/typed_array.hpp | 9 | ||||
-rw-r--r-- | src/core/class_db.cpp | 41 | ||||
-rw-r--r-- | src/godot.cpp | 6 | ||||
-rw-r--r-- | test/project/example.gd | 5 | ||||
-rw-r--r-- | test/project/main.gd | 4 | ||||
-rw-r--r-- | test/project/main.tscn | 4 | ||||
-rw-r--r-- | test/src/example.cpp | 11 | ||||
-rw-r--r-- | test/src/example.h | 4 | ||||
-rw-r--r-- | tools/godotcpp.py | 44 |
19 files changed, 397 insertions, 10 deletions
@@ -58,7 +58,7 @@ first-party `godot-cpp` extension. Some compatibility breakage is to be expected as GDExtension and `godot-cpp` get more used, documented, and critical issues get resolved. See the [Godot issue tracker](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aopen+label%3Atopic%3Agdextension) -and the [godot-cpp issue tracker](https://github.com/godotengine/godot/issues) +and the [godot-cpp issue tracker](https://github.com/godotengine/godot-cpp/issues) for a list of known issues, and be sure to provide feedback on issues and PRs which affect your use of this extension. diff --git a/binding_generator.py b/binding_generator.py index 7a6fe24..cd2a86e 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 <bool required>\\ + _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<m_ret>::get_class_info();\\\n" + method_info += "\t\tmethod_info.return_val_metadata = GetTypeInfo<m_ret>::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<m_type{i + 1}>::get_class_info());\\\n" + method_info += f"\t\tmethod_info.arguments_metadata.push_back(GetTypeInfo<m_type{i + 1}>::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<m_ret>::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"]: @@ -964,8 +1096,19 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append(method_signature + "{") method_call = "\t" + is_ref = False + if "return_type" in method: - method_call += f'return internal::_call_builtin_method_ptr_ret<{correct_type(method["return_type"])}>(' + return_type = method["return_type"] + if is_enum(return_type): + method_call += f"return ({get_gdextension_type(correct_type(return_type))})internal::_call_builtin_method_ptr_ret<int64_t>(" + elif is_pod_type(return_type) or is_variant(return_type): + method_call += f"return internal::_call_builtin_method_ptr_ret<{get_gdextension_type(correct_type(return_type))}>(" + elif is_refcounted(return_type): + method_call += f"return Ref<{return_type}>::_gde_internal_constructor(internal::_call_builtin_method_ptr_ret_obj<{return_type}>(" + is_ref = True + else: + method_call += f"return internal::_call_builtin_method_ptr_ret_obj<{return_type}>(" else: method_call += "internal::_call_builtin_method_ptr_no_ret(" method_call += f'_method_bindings.method_{method["name"]}, ' @@ -986,6 +1129,9 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result += encode arguments.append(arg_name) method_call += ", ".join(arguments) + + if is_ref: + method_call += ")" # Close Ref<> constructor. method_call += ");" result.append(method_call) diff --git a/gdextension/gdextension_interface.h b/gdextension/gdextension_interface.h index d58f022..765952c 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 */ /** @@ -2484,6 +2529,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 32caf39..25a93df 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -36,6 +36,7 @@ #include <godot_cpp/core/property_info.hpp> #include <godot_cpp/templates/list.hpp> +#include <godot_cpp/templates/vector.hpp> #include <godot_cpp/godot.hpp> @@ -107,6 +108,26 @@ public: GodotObject *_owner = nullptr; }; +_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr) { +} + +_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr, const StringName &p_str) { + arr.push_back(p_str); +} + +template <class... P> +_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr, const StringName &p_str, P... p_args) { + arr.push_back(p_str); + snarray_add_str(arr, p_args...); +} + +template <class... P> +_FORCE_INLINE_ Vector<StringName> snarray(P... p_args) { + Vector<StringName> 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<false>(__VA_ARGS__) +#define GDVIRTUAL_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call<false>(__VA_ARGS__) + +#define GDVIRTUAL_REQUIRED_CALL(m_name, ...) _gdvirtual_##m_name##_call<true>(__VA_ARGS__) +#define GDVIRTUAL_REQUIRED_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call<true>(__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/builtin_ptrcall.hpp b/include/godot_cpp/core/builtin_ptrcall.hpp index 87311b8..19250d8 100644 --- a/include/godot_cpp/core/builtin_ptrcall.hpp +++ b/include/godot_cpp/core/builtin_ptrcall.hpp @@ -32,6 +32,7 @@ #define GODOT_BUILTIN_PTRCALL_HPP #include <gdextension_interface.h> +#include <godot_cpp/core/object.hpp> #include <array> @@ -39,6 +40,17 @@ namespace godot { namespace internal { +template <class O, class... Args> +O *_call_builtin_method_ptr_ret_obj(const GDExtensionPtrBuiltInMethod method, GDExtensionTypePtr base, const Args &...args) { + GodotObject *ret = nullptr; + std::array<GDExtensionConstTypePtr, sizeof...(Args)> call_args = { { (GDExtensionConstTypePtr)args... } }; + method(base, call_args.data(), &ret, sizeof...(Args)); + if (ret == nullptr) { + return nullptr; + } + return reinterpret_cast<O *>(internal::get_object_instance_binding(ret)); +} + template <class... Args> void _call_builtin_constructor(const GDExtensionPtrConstructor constructor, GDExtensionTypePtr base, Args... args) { std::array<GDExtensionConstTypePtr, sizeof...(Args)> call_args = { { (GDExtensionConstTypePtr)args... } }; diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index af394f0..e5bccf3 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<StringName> &p_arg_names = Vector<StringName>()); 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 79f8fbf..c1f1069 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<PropertyInfo> arguments; std::vector<Variant> default_arguments; + GDExtensionClassMethodArgumentMetadata return_val_metadata; + std::vector<GDExtensionClassMethodArgumentMetadata> 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 0ecfa32..f610f3f 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<StringName *>(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/core/type_info.hpp b/include/godot_cpp/core/type_info.hpp index f0edda5..a5cb174 100644 --- a/include/godot_cpp/core/type_info.hpp +++ b/include/godot_cpp/core/type_info.hpp @@ -370,11 +370,14 @@ MAKE_TYPED_ARRAY_INFO(Rect2i, Variant::RECT2I) MAKE_TYPED_ARRAY_INFO(Vector3, Variant::VECTOR3) MAKE_TYPED_ARRAY_INFO(Vector3i, Variant::VECTOR3I) MAKE_TYPED_ARRAY_INFO(Transform2D, Variant::TRANSFORM2D) +MAKE_TYPED_ARRAY_INFO(Vector4, Variant::VECTOR4) +MAKE_TYPED_ARRAY_INFO(Vector4i, Variant::VECTOR4I) MAKE_TYPED_ARRAY_INFO(Plane, Variant::PLANE) MAKE_TYPED_ARRAY_INFO(Quaternion, Variant::QUATERNION) MAKE_TYPED_ARRAY_INFO(AABB, Variant::AABB) MAKE_TYPED_ARRAY_INFO(Basis, Variant::BASIS) MAKE_TYPED_ARRAY_INFO(Transform3D, Variant::TRANSFORM3D) +MAKE_TYPED_ARRAY_INFO(Projection, Variant::PROJECTION) MAKE_TYPED_ARRAY_INFO(Color, Variant::COLOR) MAKE_TYPED_ARRAY_INFO(StringName, Variant::STRING_NAME) MAKE_TYPED_ARRAY_INFO(NodePath, Variant::NODE_PATH) @@ -393,8 +396,11 @@ MAKE_TYPED_ARRAY_INFO(Vector<String>, Variant::PACKED_STRING_ARRAY) MAKE_TYPED_ARRAY_INFO(Vector<Vector2>, Variant::PACKED_VECTOR2_ARRAY) MAKE_TYPED_ARRAY_INFO(Vector<Vector3>, Variant::PACKED_VECTOR3_ARRAY) MAKE_TYPED_ARRAY_INFO(Vector<Color>, Variant::PACKED_COLOR_ARRAY) +MAKE_TYPED_ARRAY_INFO(IPAddress, Variant::STRING) */ +#undef MAKE_TYPED_ARRAY_INFO + #define CLASS_INFO(m_type) (GetTypeInfo<m_type *>::get_class_info()) } // namespace godot diff --git a/include/godot_cpp/godot.hpp b/include/godot_cpp/godot.hpp index c9e9022..1b420d3 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/include/godot_cpp/variant/typed_array.hpp b/include/godot_cpp/variant/typed_array.hpp index 5e7084e..2261509 100644 --- a/include/godot_cpp/variant/typed_array.hpp +++ b/include/godot_cpp/variant/typed_array.hpp @@ -75,6 +75,8 @@ public: } \ }; +// All Variant::OBJECT types are intentionally omitted from this list because they are handled by +// the unspecialized TypedArray definition. MAKE_TYPED_ARRAY(bool, Variant::BOOL) MAKE_TYPED_ARRAY(uint8_t, Variant::INT) MAKE_TYPED_ARRAY(int8_t, Variant::INT) @@ -94,11 +96,14 @@ MAKE_TYPED_ARRAY(Rect2i, Variant::RECT2I) MAKE_TYPED_ARRAY(Vector3, Variant::VECTOR3) MAKE_TYPED_ARRAY(Vector3i, Variant::VECTOR3I) MAKE_TYPED_ARRAY(Transform2D, Variant::TRANSFORM2D) +MAKE_TYPED_ARRAY(Vector4, Variant::VECTOR4) +MAKE_TYPED_ARRAY(Vector4i, Variant::VECTOR4I) MAKE_TYPED_ARRAY(Plane, Variant::PLANE) MAKE_TYPED_ARRAY(Quaternion, Variant::QUATERNION) MAKE_TYPED_ARRAY(AABB, Variant::AABB) MAKE_TYPED_ARRAY(Basis, Variant::BASIS) MAKE_TYPED_ARRAY(Transform3D, Variant::TRANSFORM3D) +MAKE_TYPED_ARRAY(Projection, Variant::PROJECTION) MAKE_TYPED_ARRAY(Color, Variant::COLOR) MAKE_TYPED_ARRAY(StringName, Variant::STRING_NAME) MAKE_TYPED_ARRAY(NodePath, Variant::NODE_PATH) @@ -116,6 +121,10 @@ MAKE_TYPED_ARRAY(PackedStringArray, Variant::PACKED_STRING_ARRAY) MAKE_TYPED_ARRAY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) MAKE_TYPED_ARRAY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) MAKE_TYPED_ARRAY(PackedColorArray, Variant::PACKED_COLOR_ARRAY) +// If the IPAddress struct is added to godot-cpp, the following could also be added: +//MAKE_TYPED_ARRAY(IPAddress, Variant::STRING) + +#undef MAKE_TYPED_ARRAY } // namespace godot diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 1f4b135..acead8b 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -32,6 +32,7 @@ #include <godot_cpp/core/error_macros.hpp> #include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/vector.hpp> #include <godot_cpp/core/memory.hpp> @@ -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<StringName> &p_arg_names) { + std::unordered_map<StringName, ClassInfo>::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 5c2aaa6..a80ad90 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 0000000..b20280a --- /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 59cab6d..d2cbd26 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 2b98e0f..1f17597 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 5372d70..53d11f4 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<InputEvent> &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 c86a51f..1c57720 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -24,6 +24,7 @@ #include <godot_cpp/variant/variant.hpp> #include <godot_cpp/core/binder_common.hpp> +#include <godot_cpp/core/gdvirtual.gen.inc> 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<InputEvent> &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); diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 0b02eea..b5bf37c 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -33,7 +33,26 @@ def validate_parent_dir(key, val, env): raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val))) -platforms = ("linux", "macos", "windows", "android", "ios", "web") +def get_platform_tools_paths(env): + path = env.get("custom_tools", None) + if path is None: + return ["tools"] + return [normalize_path(path, env), "tools"] + + +def get_custom_platforms(env): + path = env.get("custom_tools", None) + if path is None: + return [] + platforms = [] + for x in os.listdir(normalize_path(path, env)): + if not x.endswith(".py"): + continue + platforms.append(x.removesuffix(".py")) + return platforms + + +platforms = ["linux", "macos", "windows", "android", "ios", "web"] # CPU architecture options. architecture_array = [ @@ -83,11 +102,24 @@ def options(opts, env): raise ValueError("Could not detect platform automatically, please specify with platform=<platform>") opts.Add( + PathVariable( + key="custom_tools", + help="Path to directory containing custom tools", + default=env.get("custom_tools", None), + validator=validate_dir, + ) + ) + + opts.Update(env) + + custom_platforms = get_custom_platforms(env) + + opts.Add( EnumVariable( key="platform", help="Target platform", default=env.get("platform", default_platform), - allowed_values=platforms, + allowed_values=platforms + custom_platforms, ignorecase=2, ) ) @@ -198,9 +230,9 @@ def options(opts, env): ) ) - # Add platform options - for pl in platforms: - tool = Tool(pl, toolpath=["tools"]) + # Add platform options (custom tools can override platforms) + for pl in sorted(set(platforms + custom_platforms)): + tool = Tool(pl, toolpath=get_platform_tools_paths(env)) if hasattr(tool, "options"): tool.options(opts) @@ -259,7 +291,7 @@ def generate(env): if env["use_hot_reload"]: env.Append(CPPDEFINES=["HOT_RELOAD_ENABLED"]) - tool = Tool(env["platform"], toolpath=["tools"]) + tool = Tool(env["platform"], toolpath=get_platform_tools_paths(env)) if tool is None or not tool.exists(env): raise ValueError("Required toolchain not found for platform " + env["platform"]) |