From 8f8ea90088215733036ae31314c099fc5c603b80 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 19 Apr 2024 14:43:31 -0400 Subject: [PATCH 1/9] Implement to/from dict helpers for PropertyInfo/MethodInfo (cherry picked from commit 2a041b5240b5f8d22e56ffa0f96e6d5b91acd95f) --- include/godot_cpp/core/property_info.hpp | 34 ++++++++++++++ src/core/object.cpp | 58 ++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/include/godot_cpp/core/property_info.hpp b/include/godot_cpp/core/property_info.hpp index 901cc9514..39f959776 100644 --- a/include/godot_cpp/core/property_info.hpp +++ b/include/godot_cpp/core/property_info.hpp @@ -72,6 +72,40 @@ struct PropertyInfo { PropertyInfo(const GDExtensionPropertyInfo *p_info) : PropertyInfo(p_info->type, *reinterpret_cast(p_info->name), (PropertyHint)p_info->hint, *reinterpret_cast(p_info->hint_string), p_info->usage, *reinterpret_cast(p_info->class_name)) {} + operator Dictionary() const { + Dictionary dict; + dict["name"] = name; + dict["class_name"] = class_name; + dict["type"] = type; + dict["hint"] = hint; + dict["hint_string"] = hint_string; + dict["usage"] = usage; + return dict; + } + + static PropertyInfo from_dict(const Dictionary &p_dict) { + PropertyInfo pi; + if (p_dict.has("type")) { + pi.type = Variant::Type(int(p_dict["type"])); + } + if (p_dict.has("name")) { + pi.name = p_dict["name"]; + } + if (p_dict.has("class_name")) { + pi.class_name = p_dict["class_name"]; + } + if (p_dict.has("hint")) { + pi.hint = PropertyHint(int(p_dict["hint"])); + } + if (p_dict.has("hint_string")) { + pi.hint_string = p_dict["hint_string"]; + } + if (p_dict.has("usage")) { + pi.usage = p_dict["usage"]; + } + return pi; + } + void _update(GDExtensionPropertyInfo *p_info) { p_info->type = (GDExtensionVariantType)type; *(reinterpret_cast(p_info->name)) = name; diff --git a/src/core/object.cpp b/src/core/object.cpp index dc3c87983..d2e10ffb9 100644 --- a/src/core/object.cpp +++ b/src/core/object.cpp @@ -60,8 +60,66 @@ Object *get_object_instance_binding(GodotObject *p_engine_object) { return reinterpret_cast(gdextension_interface_object_get_instance_binding(p_engine_object, token, binding_callbacks)); } +TypedArray convert_property_list(const std::vector &p_list) { + TypedArray va; + for (const PropertyInfo &pi : p_list) { + va.push_back(Dictionary(pi)); + } + return va; +} + } // namespace internal +MethodInfo::operator Dictionary() const { + Dictionary dict; + dict["name"] = name; + dict["args"] = internal::convert_property_list(arguments); + Array da; + for (int i = 0; i < default_arguments.size(); i++) { + da.push_back(default_arguments[i]); + } + dict["default_args"] = da; + dict["flags"] = flags; + dict["id"] = id; + Dictionary r = return_val; + dict["return"] = r; + return dict; +} + +MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { + MethodInfo mi; + + if (p_dict.has("name")) { + mi.name = p_dict["name"]; + } + Array args; + if (p_dict.has("args")) { + args = p_dict["args"]; + } + + for (int i = 0; i < args.size(); i++) { + Dictionary d = args[i]; + mi.arguments.push_back(PropertyInfo::from_dict(d)); + } + Array defargs; + if (p_dict.has("default_args")) { + defargs = p_dict["default_args"]; + } + for (int i = 0; i < defargs.size(); i++) { + mi.default_arguments.push_back(defargs[i]); + } + + if (p_dict.has("return")) { + mi.return_val = PropertyInfo::from_dict(p_dict["return"]); + } + + if (p_dict.has("flags")) { + mi.flags = p_dict["flags"]; + } + + return mi; +} + MethodInfo::MethodInfo() : flags(GDEXTENSION_METHOD_FLAG_NORMAL) {} From 3d814f6e870606a89e25587e23a7a99cc1562ba0 Mon Sep 17 00:00:00 2001 From: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:18:46 +0200 Subject: [PATCH 2/9] [Math] Add `is_finite` methods (cherry picked from commit d38917190503b75b307654f54bf5b44c83d0aea4) --- include/godot_cpp/core/math.hpp | 8 ++++++++ include/godot_cpp/variant/aabb.hpp | 1 + include/godot_cpp/variant/basis.hpp | 1 + include/godot_cpp/variant/plane.hpp | 1 + include/godot_cpp/variant/quaternion.hpp | 1 + include/godot_cpp/variant/rect2.hpp | 1 + include/godot_cpp/variant/transform2d.hpp | 1 + include/godot_cpp/variant/transform3d.hpp | 1 + include/godot_cpp/variant/vector2.hpp | 1 + include/godot_cpp/variant/vector3.hpp | 1 + include/godot_cpp/variant/vector4.hpp | 1 + src/variant/aabb.cpp | 4 ++++ src/variant/basis.cpp | 4 ++++ src/variant/plane.cpp | 4 ++++ src/variant/quaternion.cpp | 4 ++++ src/variant/rect2.cpp | 4 ++++ src/variant/transform2d.cpp | 4 ++++ src/variant/transform3d.cpp | 4 ++++ src/variant/vector2.cpp | 4 ++++ src/variant/vector3.cpp | 4 ++++ src/variant/vector4.cpp | 4 ++++ 21 files changed, 58 insertions(+) diff --git a/include/godot_cpp/core/math.hpp b/include/godot_cpp/core/math.hpp index 2cbbe2726..1949360a9 100644 --- a/include/godot_cpp/core/math.hpp +++ b/include/godot_cpp/core/math.hpp @@ -613,6 +613,14 @@ inline bool is_inf(double p_val) { return std::isinf(p_val); } +inline bool is_finite(float p_val) { + return std::isfinite(p_val); +} + +inline bool is_finite(double p_val) { + return std::isfinite(p_val); +} + inline bool is_equal_approx(float a, float b) { // Check for exact equality first, required to handle "infinity" values. if (a == b) { diff --git a/include/godot_cpp/variant/aabb.hpp b/include/godot_cpp/variant/aabb.hpp index 7706d5115..f344f2cd6 100644 --- a/include/godot_cpp/variant/aabb.hpp +++ b/include/godot_cpp/variant/aabb.hpp @@ -65,6 +65,7 @@ struct _NO_DISCARD_ AABB { bool operator!=(const AABB &p_rval) const; bool is_equal_approx(const AABB &p_aabb) const; + bool is_finite() const; _FORCE_INLINE_ bool intersects(const AABB &p_aabb) const; /// Both AABBs overlap _FORCE_INLINE_ bool intersects_inclusive(const AABB &p_aabb) const; /// Both AABBs (or their faces) overlap _FORCE_INLINE_ bool encloses(const AABB &p_aabb) const; /// p_aabb is completely inside this diff --git a/include/godot_cpp/variant/basis.hpp b/include/godot_cpp/variant/basis.hpp index a365b024f..e740a64a7 100644 --- a/include/godot_cpp/variant/basis.hpp +++ b/include/godot_cpp/variant/basis.hpp @@ -128,6 +128,7 @@ struct _NO_DISCARD_ Basis { } bool is_equal_approx(const Basis &p_basis) const; + bool is_finite() const; bool operator==(const Basis &p_matrix) const; bool operator!=(const Basis &p_matrix) const; diff --git a/include/godot_cpp/variant/plane.hpp b/include/godot_cpp/variant/plane.hpp index 727f4f543..829f801f4 100644 --- a/include/godot_cpp/variant/plane.hpp +++ b/include/godot_cpp/variant/plane.hpp @@ -77,6 +77,7 @@ struct _NO_DISCARD_ Plane { Plane operator-() const { return Plane(-normal, -d); } bool is_equal_approx(const Plane &p_plane) const; bool is_equal_approx_any_side(const Plane &p_plane) const; + bool is_finite() const; _FORCE_INLINE_ bool operator==(const Plane &p_plane) const; _FORCE_INLINE_ bool operator!=(const Plane &p_plane) const; diff --git a/include/godot_cpp/variant/quaternion.hpp b/include/godot_cpp/variant/quaternion.hpp index 3816b6668..5de91b207 100644 --- a/include/godot_cpp/variant/quaternion.hpp +++ b/include/godot_cpp/variant/quaternion.hpp @@ -55,6 +55,7 @@ struct _NO_DISCARD_ Quaternion { } _FORCE_INLINE_ real_t length_squared() const; bool is_equal_approx(const Quaternion &p_quaternion) const; + bool is_finite() const; real_t length() const; void normalize(); Quaternion normalized() const; diff --git a/include/godot_cpp/variant/rect2.hpp b/include/godot_cpp/variant/rect2.hpp index cfd24b2fb..c37134d00 100644 --- a/include/godot_cpp/variant/rect2.hpp +++ b/include/godot_cpp/variant/rect2.hpp @@ -209,6 +209,7 @@ struct _NO_DISCARD_ Rect2 { } bool is_equal_approx(const Rect2 &p_rect) const; + bool is_finite() const; bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; } bool operator!=(const Rect2 &p_rect) const { return position != p_rect.position || size != p_rect.size; } diff --git a/include/godot_cpp/variant/transform2d.hpp b/include/godot_cpp/variant/transform2d.hpp index 5a4839851..d73323f31 100644 --- a/include/godot_cpp/variant/transform2d.hpp +++ b/include/godot_cpp/variant/transform2d.hpp @@ -99,6 +99,7 @@ struct _NO_DISCARD_ Transform2D { void orthonormalize(); Transform2D orthonormalized() const; bool is_equal_approx(const Transform2D &p_transform) const; + bool is_finite() const; Transform2D looking_at(const Vector2 &p_target) const; diff --git a/include/godot_cpp/variant/transform3d.hpp b/include/godot_cpp/variant/transform3d.hpp index 3a54c0b2d..6fa5999ed 100644 --- a/include/godot_cpp/variant/transform3d.hpp +++ b/include/godot_cpp/variant/transform3d.hpp @@ -78,6 +78,7 @@ struct _NO_DISCARD_ Transform3D { void orthogonalize(); Transform3D orthogonalized() const; bool is_equal_approx(const Transform3D &p_transform) const; + bool is_finite() const; bool operator==(const Transform3D &p_transform) const; bool operator!=(const Transform3D &p_transform) const; diff --git a/include/godot_cpp/variant/vector2.hpp b/include/godot_cpp/variant/vector2.hpp index 13c0da6fe..fe4d05aa5 100644 --- a/include/godot_cpp/variant/vector2.hpp +++ b/include/godot_cpp/variant/vector2.hpp @@ -123,6 +123,7 @@ struct _NO_DISCARD_ Vector2 { bool is_equal_approx(const Vector2 &p_v) const; bool is_zero_approx() const; + bool is_finite() const; Vector2 operator+(const Vector2 &p_v) const; void operator+=(const Vector2 &p_v); diff --git a/include/godot_cpp/variant/vector3.hpp b/include/godot_cpp/variant/vector3.hpp index a8d96ed0c..1107bca8d 100644 --- a/include/godot_cpp/variant/vector3.hpp +++ b/include/godot_cpp/variant/vector3.hpp @@ -146,6 +146,7 @@ struct _NO_DISCARD_ Vector3 { bool is_equal_approx(const Vector3 &p_v) const; bool is_zero_approx() const; + bool is_finite() const; /* Operators */ diff --git a/include/godot_cpp/variant/vector4.hpp b/include/godot_cpp/variant/vector4.hpp index 26c57c357..b20915aaa 100644 --- a/include/godot_cpp/variant/vector4.hpp +++ b/include/godot_cpp/variant/vector4.hpp @@ -81,6 +81,7 @@ struct _NO_DISCARD_ Vector4 { _FORCE_INLINE_ real_t length_squared() const; bool is_equal_approx(const Vector4 &p_vec4) const; bool is_zero_approx() const; + bool is_finite() const; real_t length() const; void normalize(); Vector4 normalized() const; diff --git a/src/variant/aabb.cpp b/src/variant/aabb.cpp index 92e751b44..ded17d2ba 100644 --- a/src/variant/aabb.cpp +++ b/src/variant/aabb.cpp @@ -78,6 +78,10 @@ bool AABB::is_equal_approx(const AABB &p_aabb) const { return position.is_equal_approx(p_aabb.position) && size.is_equal_approx(p_aabb.size); } +bool AABB::is_finite() const { + return position.is_finite() && size.is_finite(); +} + AABB AABB::intersection(const AABB &p_aabb) const { #ifdef MATH_CHECKS if (unlikely(size.x < 0 || size.y < 0 || size.z < 0 || p_aabb.size.x < 0 || p_aabb.size.y < 0 || p_aabb.size.z < 0)) { diff --git a/src/variant/basis.cpp b/src/variant/basis.cpp index 8f78d9f02..8d4176e67 100644 --- a/src/variant/basis.cpp +++ b/src/variant/basis.cpp @@ -692,6 +692,10 @@ bool Basis::is_equal_approx(const Basis &p_basis) const { return rows[0].is_equal_approx(p_basis.rows[0]) && rows[1].is_equal_approx(p_basis.rows[1]) && rows[2].is_equal_approx(p_basis.rows[2]); } +bool Basis::is_finite() const { + return rows[0].is_finite() && rows[1].is_finite() && rows[2].is_finite(); +} + bool Basis::operator==(const Basis &p_matrix) const { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { diff --git a/src/variant/plane.cpp b/src/variant/plane.cpp index 53dd439a5..caea516e1 100644 --- a/src/variant/plane.cpp +++ b/src/variant/plane.cpp @@ -178,6 +178,10 @@ bool Plane::is_equal_approx(const Plane &p_plane) const { return normal.is_equal_approx(p_plane.normal) && Math::is_equal_approx(d, p_plane.d); } +bool Plane::is_finite() const { + return normal.is_finite() && Math::is_finite(d); +} + Plane::operator String() const { return "[N: " + normal.operator String() + ", D: " + String::num_real(d, false) + "]"; } diff --git a/src/variant/quaternion.cpp b/src/variant/quaternion.cpp index 9d4d838d8..c01085051 100644 --- a/src/variant/quaternion.cpp +++ b/src/variant/quaternion.cpp @@ -81,6 +81,10 @@ bool Quaternion::is_equal_approx(const Quaternion &p_quaternion) const { return Math::is_equal_approx(x, p_quaternion.x) && Math::is_equal_approx(y, p_quaternion.y) && Math::is_equal_approx(z, p_quaternion.z) && Math::is_equal_approx(w, p_quaternion.w); } +bool Quaternion::is_finite() const { + return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z) && Math::is_finite(w); +} + real_t Quaternion::length() const { return Math::sqrt(length_squared()); } diff --git a/src/variant/rect2.cpp b/src/variant/rect2.cpp index a70fee638..62730a9d4 100644 --- a/src/variant/rect2.cpp +++ b/src/variant/rect2.cpp @@ -40,6 +40,10 @@ bool Rect2::is_equal_approx(const Rect2 &p_rect) const { return position.is_equal_approx(p_rect.position) && size.is_equal_approx(p_rect.size); } +bool Rect2::is_finite() const { + return position.is_finite() && size.is_finite(); +} + bool Rect2::intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos, Point2 *r_normal) const { #ifdef MATH_CHECKS if (unlikely(size.x < 0 || size.y < 0)) { diff --git a/src/variant/transform2d.cpp b/src/variant/transform2d.cpp index 530a99e36..3b2c0b09e 100644 --- a/src/variant/transform2d.cpp +++ b/src/variant/transform2d.cpp @@ -170,6 +170,10 @@ bool Transform2D::is_equal_approx(const Transform2D &p_transform) const { return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]); } +bool Transform2D::is_finite() const { + return columns[0].is_finite() && columns[1].is_finite() && columns[2].is_finite(); +} + Transform2D Transform2D::looking_at(const Vector2 &p_target) const { Transform2D return_trans = Transform2D(get_rotation(), get_origin()); Vector2 target_position = affine_inverse().xform(p_target); diff --git a/src/variant/transform3d.cpp b/src/variant/transform3d.cpp index 1a4189e2b..d71e91911 100644 --- a/src/variant/transform3d.cpp +++ b/src/variant/transform3d.cpp @@ -175,6 +175,10 @@ bool Transform3D::is_equal_approx(const Transform3D &p_transform) const { return basis.is_equal_approx(p_transform.basis) && origin.is_equal_approx(p_transform.origin); } +bool Transform3D::is_finite() const { + return basis.is_finite() && origin.is_finite(); +} + bool Transform3D::operator==(const Transform3D &p_transform) const { return (basis == p_transform.basis && origin == p_transform.origin); } diff --git a/src/variant/vector2.cpp b/src/variant/vector2.cpp index df8708053..ca1ab8f33 100644 --- a/src/variant/vector2.cpp +++ b/src/variant/vector2.cpp @@ -188,6 +188,10 @@ bool Vector2::is_zero_approx() const { return Math::is_zero_approx(x) && Math::is_zero_approx(y); } +bool Vector2::is_finite() const { + return Math::is_finite(x) && Math::is_finite(y); +} + Vector2::operator String() const { return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")"; } diff --git a/src/variant/vector3.cpp b/src/variant/vector3.cpp index 615365802..9f04340a3 100644 --- a/src/variant/vector3.cpp +++ b/src/variant/vector3.cpp @@ -141,6 +141,10 @@ bool Vector3::is_zero_approx() const { return Math::is_zero_approx(x) && Math::is_zero_approx(y) && Math::is_zero_approx(z); } +bool Vector3::is_finite() const { + return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z); +} + Vector3::operator String() const { return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ")"; } diff --git a/src/variant/vector4.cpp b/src/variant/vector4.cpp index 72c79d1e5..483545e27 100644 --- a/src/variant/vector4.cpp +++ b/src/variant/vector4.cpp @@ -67,6 +67,10 @@ bool Vector4::is_zero_approx() const { return Math::is_zero_approx(x) && Math::is_zero_approx(y) && Math::is_zero_approx(z) && Math::is_zero_approx(w); } +bool Vector4::is_finite() const { + return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z) && Math::is_finite(w); +} + real_t Vector4::length() const { return Math::sqrt(length_squared()); } From 897280444bd6588258cdbdc6955fdfa523d5d6b5 Mon Sep 17 00:00:00 2001 From: pupil1337 <2500698263@qq.com> Date: Sun, 7 Apr 2024 21:07:01 +0800 Subject: [PATCH 3/9] Add static_assert() for register_class (cherry picked from commit 1fa7a9cb1985bdf6c098492563e0e76cf72fd91a) --- include/godot_cpp/core/class_db.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 23a5ebdba..18d2cc793 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -198,6 +198,7 @@ class ClassDB { template void ClassDB::_register_class(bool p_virtual, bool p_exposed) { static_assert(TypesAreSame::value, "Class not declared properly, please use GDCLASS."); + static_assert(!std::is_abstract_v || is_abstract, "Class is abstract, please use GDREGISTER_ABSTRACT_CLASS."); instance_binding_callbacks[T::get_class_static()] = &T::_gde_binding_callbacks; // Register this class within our plugin From 8aef77a64d29e3b8c42ba2c9851e63cc2f703b99 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Wed, 24 Apr 2024 14:26:06 -0500 Subject: [PATCH 4/9] Give compile-time error if registering a class without its own `_bind_methods()` function (cherry picked from commit ca46ef4d256c727a116366334b705ecda0a06c5d) --- include/godot_cpp/classes/wrapped.hpp | 5 +++++ include/godot_cpp/core/class_db.hpp | 1 + include/godot_cpp/core/type_info.hpp | 10 ++++++++++ 3 files changed, 16 insertions(+) diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index 49f89a462..0452e42da 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -193,6 +193,7 @@ protected: \ public: \ typedef m_class self_type; \ + typedef m_inherits parent_type; \ \ static void initialize_class() { \ static bool initialized = false; \ @@ -359,6 +360,7 @@ public: private: \ inline static ::godot::internal::EngineClassRegistration _gde_engine_class_registration_helper; \ void operator=(const m_class &p_rval) {} \ + friend class ::godot::ClassDB; \ \ protected: \ virtual const GDExtensionInstanceBindingCallbacks *_get_bindings_callbacks() const override { \ @@ -368,6 +370,8 @@ protected: m_class(const char *p_godot_class) : m_inherits(p_godot_class) {} \ m_class(GodotObject *p_godot_object) : m_inherits(p_godot_object) {} \ \ + static void _bind_methods() {} \ + \ static void (*_get_bind_methods())() { \ return nullptr; \ } \ @@ -410,6 +414,7 @@ protected: \ public: \ typedef m_class self_type; \ + typedef m_inherits parent_type; \ \ static void initialize_class() {} \ \ diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 18d2cc793..7b1967e7d 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -198,6 +198,7 @@ class ClassDB { template void ClassDB::_register_class(bool p_virtual, bool p_exposed) { static_assert(TypesAreSame::value, "Class not declared properly, please use GDCLASS."); + static_assert(!FunctionsAreSame::value, "Class must declare 'static void _bind_methods'."); static_assert(!std::is_abstract_v || is_abstract, "Class is abstract, please use GDREGISTER_ABSTRACT_CLASS."); instance_binding_callbacks[T::get_class_static()] = &T::_gde_binding_callbacks; diff --git a/include/godot_cpp/core/type_info.hpp b/include/godot_cpp/core/type_info.hpp index 2c4f8e404..e1f2b2052 100644 --- a/include/godot_cpp/core/type_info.hpp +++ b/include/godot_cpp/core/type_info.hpp @@ -58,6 +58,16 @@ struct TypesAreSame { static bool const value = true; }; +template +struct FunctionsAreSame { + static bool const value = false; +}; + +template +struct FunctionsAreSame { + static bool const value = true; +}; + template struct TypeInherits { static D *get_d(); From ddfcca62a85e4c44d4d02eadea750ef078a384be Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 5 Mar 2024 11:11:29 -0600 Subject: [PATCH 5/9] Correctly handle `Object *` arguments that were encoded as `nullptr` (cherry picked from commit 37542dc2ec1e98fbe93e2daa8f11e7fb5428cb0e) --- include/godot_cpp/core/method_ptrcall.hpp | 8 ++++---- test/project/main.gd | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/godot_cpp/core/method_ptrcall.hpp b/include/godot_cpp/core/method_ptrcall.hpp index ca3327ed9..b12a7e640 100644 --- a/include/godot_cpp/core/method_ptrcall.hpp +++ b/include/godot_cpp/core/method_ptrcall.hpp @@ -170,11 +170,11 @@ template struct PtrToArg { static_assert(std::is_base_of::value, "Cannot encode non-Object value as an Object"); _FORCE_INLINE_ static T *convert(const void *p_ptr) { - return reinterpret_cast(godot::internal::get_object_instance_binding(*reinterpret_cast(const_cast(p_ptr)))); + return likely(p_ptr) ? reinterpret_cast(godot::internal::get_object_instance_binding(*reinterpret_cast(const_cast(p_ptr)))) : nullptr; } typedef Object *EncodeT; _FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) { - *reinterpret_cast(p_ptr) = p_var ? p_var->_owner : nullptr; + *reinterpret_cast(p_ptr) = likely(p_var) ? p_var->_owner : nullptr; } }; @@ -182,11 +182,11 @@ template struct PtrToArg { static_assert(std::is_base_of::value, "Cannot encode non-Object value as an Object"); _FORCE_INLINE_ static const T *convert(const void *p_ptr) { - return reinterpret_cast(godot::internal::get_object_instance_binding(*reinterpret_cast(const_cast(p_ptr)))); + return likely(p_ptr) ? reinterpret_cast(godot::internal::get_object_instance_binding(*reinterpret_cast(const_cast(p_ptr)))) : nullptr; } typedef const Object *EncodeT; _FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) { - *reinterpret_cast(p_ptr) = p_var ? p_var->_owner : nullptr; + *reinterpret_cast(p_ptr) = likely(p_var) ? p_var->_owner : nullptr; } }; diff --git a/test/project/main.gd b/test/project/main.gd index 26966b41a..2a9426fdd 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -185,6 +185,10 @@ func _ready(): control.queue_free() sprite.queue_free() + # Test that passing null for objects works as expected too. + var example_null : Example = null + assert_equal(example.test_object_cast_to_node(example_null), false) + # Test conversions to and from Variant. assert_equal(example.test_variant_vector2i_conversion(Vector2i(1, 1)), Vector2i(1, 1)) assert_equal(example.test_variant_vector2i_conversion(Vector2(1.0, 1.0)), Vector2i(1, 1)) From acee69a3f6c121b77e4372aaa3ba11eb4df94c22 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 23 Apr 2024 13:37:43 -0500 Subject: [PATCH 6/9] Allow forwarding from `ClassDB` to `ClassDBSingleton` to support enumerations (cherry picked from commit e1b3b32db5a20c8704650f1cd60e134540ada387) --- binding_generator.py | 66 ++++++++++++++++++++++++----- include/godot_cpp/core/class_db.hpp | 2 + 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index 0186e1a05..459c20294 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -181,6 +181,10 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision= generate_utility_functions(api, target_dir) +CLASS_ALIASES = { + "ClassDB": "ClassDBSingleton", +} + builtin_classes = [] # Key is class name, value is boolean where True means the class is refcounted. @@ -1127,9 +1131,9 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): # First create map of classes and singletons. for class_api in api["classes"]: # Generate code for the ClassDB singleton under a different name. - if class_api["name"] == "ClassDB": - class_api["name"] = "ClassDBSingleton" - class_api["alias_for"] = "ClassDB" + if class_api["name"] in CLASS_ALIASES: + class_api["alias_for"] = class_api["name"] + class_api["name"] = CLASS_ALIASES[class_api["alias_for"]] engine_classes[class_api["name"]] = class_api["is_refcounted"] for native_struct in api["native_structures"]: if native_struct["name"] == "ObjectID": @@ -1139,9 +1143,9 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): for singleton in api["singletons"]: # Generate code for the ClassDB singleton under a different name. - if singleton["name"] == "ClassDB": - singleton["name"] = "ClassDBSingleton" - singleton["alias_for"] = "ClassDB" + if singleton["name"] in CLASS_ALIASES: + singleton["alias_for"] = singleton["name"] + singleton["name"] = CLASS_ALIASES[singleton["name"]] singletons.append(singleton["name"]) for class_api in api["classes"]: @@ -1346,6 +1350,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("#include ") result.append("") + if class_name == "ClassDBSingleton": + result.append("#include ") + result.append("") + result.append("namespace godot {") result.append("") @@ -1504,6 +1512,19 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us if class_name == "ClassDBSingleton": result.append("#define CLASSDB_SINGLETON_FORWARD_METHODS \\") + + if "enums" in class_api: + for enum_api in class_api["enums"]: + if enum_api["is_bitfield"]: + result.append(f'\tenum {enum_api["name"]} : uint64_t {{ \\') + else: + result.append(f'\tenum {enum_api["name"]} {{ \\') + + for value in enum_api["values"]: + result.append(f'\t\t{value["name"]} = {value["value"]}, \\') + result.append("\t}; \\") + result.append("\t \\") + for method in class_api["methods"]: # ClassDBSingleton shouldn't have any static or vararg methods, but if some appear later, lets skip them. if vararg: @@ -1512,12 +1533,17 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us continue method_signature = "\tstatic " + return_type = None if "return_type" in method: - method_signature += f'{correct_type(method["return_type"])} ' + return_type = correct_type(method["return_type"].replace("ClassDBSingleton", "ClassDB"), None, False) elif "return_value" in method: - method_signature += ( - correct_type(method["return_value"]["type"], method["return_value"].get("meta", None)) + " " + return_type = correct_type( + method["return_value"]["type"].replace("ClassDBSingleton", "ClassDB"), + method["return_value"].get("meta", None), + False, ) + if return_type is not None: + method_signature += return_type + " " else: method_signature += "void " @@ -1536,8 +1562,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(method_signature) method_body = "\t\t" - if "return_type" in method or "return_value" in method: + if return_type is not None: method_body += "return " + if "alias_for" in class_api and return_type.startswith(class_api["alias_for"] + "::"): + method_body += f"({return_type})" method_body += f'ClassDBSingleton::get_singleton()->{method["name"]}(' method_body += ", ".join(map(lambda x: escape_identifier(x["name"]), method_arguments)) method_body += "); \\" @@ -1547,6 +1575,18 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("\t;") result.append("") + result.append("#define CLASSDB_SINGLETON_VARIANT_CAST \\") + + if "enums" in class_api: + for enum_api in class_api["enums"]: + if enum_api["is_bitfield"]: + result.append(f'\tVARIANT_BITFIELD_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\') + else: + result.append(f'\tVARIANT_ENUM_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\') + + result.append("\t;") + result.append("") + result.append(f"#endif // ! {header_guard}") return "\n".join(result) @@ -2384,7 +2424,7 @@ def correct_typed_array(type_name): return type_name -def correct_type(type_name, meta=None): +def correct_type(type_name, meta=None, use_alias=True): type_conversion = {"float": "double", "int": "int64_t", "Nil": "Variant"} if meta != None: if "int" in meta: @@ -2400,11 +2440,15 @@ def correct_type(type_name, meta=None): if is_enum(type_name): if is_bitfield(type_name): base_class = get_enum_class(type_name) + if use_alias and base_class in CLASS_ALIASES: + base_class = CLASS_ALIASES[base_class] if base_class == "GlobalConstants": return f"BitField<{get_enum_name(type_name)}>" return f"BitField<{base_class}::{get_enum_name(type_name)}>" else: base_class = get_enum_class(type_name) + if use_alias and base_class in CLASS_ALIASES: + base_class = CLASS_ALIASES[base_class] if base_class == "GlobalConstants": return f"{get_enum_name(type_name)}" return f"{base_class}::{get_enum_name(type_name)}" diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 7b1967e7d..bef2f4c14 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -327,4 +327,6 @@ MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p } // namespace godot +CLASSDB_SINGLETON_VARIANT_CAST; + #endif // GODOT_CLASS_DB_HPP From 18354f9b9bfcf87c0db54b597507d5cca2b49c4b Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 7 May 2024 12:54:35 -0500 Subject: [PATCH 7/9] Clean up instance bindings for engine singletons to prevent crash (cherry picked from commit 88df025aa0ab92ed176f91dd835c004fa26f0ed0) --- binding_generator.py | 24 +++++++++++++++++++++++- include/godot_cpp/core/class_db.hpp | 18 ++++++++++++++++++ include/godot_cpp/godot.hpp | 1 + src/core/class_db.cpp | 18 ++++++++++++++++++ src/godot.cpp | 2 ++ test/project/main.gd | 3 +++ test/src/example.cpp | 7 +++++++ test/src/example.h | 2 ++ 8 files changed, 74 insertions(+), 1 deletion(-) diff --git a/binding_generator.py b/binding_generator.py index 459c20294..bb1bc3e00 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -1375,6 +1375,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})") result.append("") + if is_singleton: + result.append(f"\tstatic {class_name} *singleton;") + result.append("") + result.append("public:") result.append("") @@ -1455,6 +1459,11 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("\t}") result.append("") + + if is_singleton: + result.append(f"\t~{class_name}();") + result.append("") + result.append("public:") # Special cases. @@ -1604,6 +1613,7 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append(f"#include ") result.append("") + result.append("#include ") result.append("#include ") result.append("#include ") result.append("") @@ -1618,9 +1628,10 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append("") if is_singleton: + result.append(f"{class_name} *{class_name}::singleton = nullptr;") + result.append("") result.append(f"{class_name} *{class_name}::get_singleton() {{") # We assume multi-threaded access is OK because each assignment will assign the same value every time - result.append(f"\tstatic {class_name} *singleton = nullptr;") result.append("\tif (unlikely(singleton == nullptr)) {") result.append( f"\t\tGDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());" @@ -1634,11 +1645,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append("#ifdef DEBUG_ENABLED") result.append("\t\tERR_FAIL_NULL_V(singleton, nullptr);") result.append("#endif // DEBUG_ENABLED") + result.append("\t\tif (likely(singleton)) {") + result.append(f"\t\t\tClassDB::_register_engine_singleton({class_name}::get_class_static(), singleton);") + result.append("\t\t}") result.append("\t}") result.append("\treturn singleton;") result.append("}") result.append("") + result.append(f"{class_name}::~{class_name}() {{") + result.append("\tif (singleton == this) {") + result.append(f"\t\tClassDB::_unregister_engine_singleton({class_name}::get_class_static());") + result.append("\t\tsingleton = nullptr;") + result.append("\t}") + result.append("}") + result.append("") + if "methods" in class_api: for method in class_api["methods"]: if method["is_virtual"]: diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index bef2f4c14..9cc24b578 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -104,6 +105,8 @@ class ClassDB { static std::unordered_map instance_binding_callbacks; // Used to remember the custom class registration order. static std::vector class_register_order; + static std::unordered_map engine_singletons; + static std::mutex engine_singletons_mutex; static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const void **p_defs, int p_defcount); static void initialize_class(const ClassInfo &cl); @@ -151,6 +154,21 @@ class ClassDB { instance_binding_callbacks[p_name] = p_callbacks; } + static void _register_engine_singleton(const StringName &p_class_name, Object *p_singleton) { + std::lock_guard lock(engine_singletons_mutex); + std::unordered_map::const_iterator i = engine_singletons.find(p_class_name); + if (i != engine_singletons.end()) { + ERR_FAIL_COND((*i).second != p_singleton); + return; + } + engine_singletons[p_class_name] = p_singleton; + } + + static void _unregister_engine_singleton(const StringName &p_class_name) { + std::lock_guard lock(engine_singletons_mutex); + engine_singletons.erase(p_class_name); + } + template static MethodBind *bind_method(N p_method_name, M p_method, VarArgs... p_args); diff --git a/include/godot_cpp/godot.hpp b/include/godot_cpp/godot.hpp index c9e902260..e432f303e 100644 --- a/include/godot_cpp/godot.hpp +++ b/include/godot_cpp/godot.hpp @@ -160,6 +160,7 @@ extern "C" GDExtensionInterfaceObjectDestroy gdextension_interface_object_destro extern "C" GDExtensionInterfaceGlobalGetSingleton gdextension_interface_global_get_singleton; extern "C" GDExtensionInterfaceObjectGetInstanceBinding gdextension_interface_object_get_instance_binding; extern "C" GDExtensionInterfaceObjectSetInstanceBinding gdextension_interface_object_set_instance_binding; +extern "C" GDExtensionInterfaceObjectFreeInstanceBinding gdextension_interface_object_free_instance_binding; extern "C" GDExtensionInterfaceObjectSetInstance gdextension_interface_object_set_instance; extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_name; extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to; diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 1f4b135d2..2f82a352f 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -42,6 +42,8 @@ namespace godot { std::unordered_map ClassDB::classes; std::unordered_map ClassDB::instance_binding_callbacks; std::vector ClassDB::class_register_order; +std::unordered_map ClassDB::engine_singletons; +std::mutex ClassDB::engine_singletons_mutex; GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE; MethodDefinition D_METHOD(StringName p_name) { @@ -378,6 +380,22 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { }); class_register_order.erase(it, class_register_order.end()); } + + if (p_level == GDEXTENSION_INITIALIZATION_CORE) { + // Make a new list of the singleton objects, since freeing the instance bindings will lead to + // elements getting removed from engine_singletons. + std::vector singleton_objects; + { + std::lock_guard lock(engine_singletons_mutex); + singleton_objects.reserve(engine_singletons.size()); + for (const std::pair &pair : engine_singletons) { + singleton_objects.push_back(pair.second); + } + } + for (std::vector::iterator i = singleton_objects.begin(); i != singleton_objects.end(); i++) { + internal::gdextension_interface_object_free_instance_binding((*i)->_owner, internal::token); + } + } } } // namespace godot diff --git a/src/godot.cpp b/src/godot.cpp index 5c2aaa65d..9df68b199 100644 --- a/src/godot.cpp +++ b/src/godot.cpp @@ -166,6 +166,7 @@ GDExtensionInterfaceObjectDestroy gdextension_interface_object_destroy = nullptr GDExtensionInterfaceGlobalGetSingleton gdextension_interface_global_get_singleton = nullptr; GDExtensionInterfaceObjectGetInstanceBinding gdextension_interface_object_get_instance_binding = nullptr; GDExtensionInterfaceObjectSetInstanceBinding gdextension_interface_object_set_instance_binding = nullptr; +GDExtensionInterfaceObjectFreeInstanceBinding gdextension_interface_object_free_instance_binding = nullptr; GDExtensionInterfaceObjectSetInstance gdextension_interface_object_set_instance = nullptr; GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_name = nullptr; GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr; @@ -403,6 +404,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge LOAD_PROC_ADDRESS(global_get_singleton, GDExtensionInterfaceGlobalGetSingleton); LOAD_PROC_ADDRESS(object_get_instance_binding, GDExtensionInterfaceObjectGetInstanceBinding); LOAD_PROC_ADDRESS(object_set_instance_binding, GDExtensionInterfaceObjectSetInstanceBinding); + LOAD_PROC_ADDRESS(object_free_instance_binding, GDExtensionInterfaceObjectFreeInstanceBinding); LOAD_PROC_ADDRESS(object_set_instance, GDExtensionInterfaceObjectSetInstance); LOAD_PROC_ADDRESS(object_get_class_name, GDExtensionInterfaceObjectGetClassName); LOAD_PROC_ADDRESS(object_cast_to, GDExtensionInterfaceObjectCastTo); diff --git a/test/project/main.gd b/test/project/main.gd index 2a9426fdd..dc92a90dc 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -246,6 +246,9 @@ func _ready(): assert_equal(new_example_ref.was_post_initialized(), true) assert_equal(example.test_post_initialize(), true) + # Test that we can access an engine singleton. + assert_equal(example.test_use_engine_singleton(), OS.get_name()) + # Test that notifications happen on both parent and child classes. var example_child = $ExampleChild assert_equal(example_child.get_value1(), 11) diff --git a/test/src/example.cpp b/test/src/example.cpp index 3436d17ba..0520f9951 100644 --- a/test/src/example.cpp +++ b/test/src/example.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include using namespace godot; @@ -231,6 +232,8 @@ 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); + ClassDB::bind_method(D_METHOD("test_use_engine_singleton"), &Example::test_use_engine_singleton); + 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); @@ -654,3 +657,7 @@ void ExampleChild::_notification(int p_what) { value2 = 33; } } + +String Example::test_use_engine_singleton() const { + return OS::get_singleton()->get_name(); +} diff --git a/test/src/example.h b/test/src/example.h index 64b3a2eda..fba01a492 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -182,6 +182,8 @@ class Example : public Control { // Virtual function override (no need to bind manually). virtual bool _has_point(const Vector2 &point) const override; virtual void _input(const Ref &event) override; + + String test_use_engine_singleton() const; }; VARIANT_ENUM_CAST(Example::Constants); From e8e424ade8d5eb960515079b367229c865230794 Mon Sep 17 00:00:00 2001 From: Daylily-Zeleen Date: Fri, 17 May 2024 02:06:59 +0800 Subject: [PATCH 8/9] mark return value of `get_class_static` and `get_parent_class_static` as const (cherry picked from commit 3db8549e19641d49dfcdf3055da6917699c07d1f) --- include/godot_cpp/classes/wrapped.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index 0452e42da..e4042991c 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -94,8 +94,8 @@ class Wrapped { virtual ~Wrapped() {} public: - static StringName &get_class_static() { - static StringName string_name = StringName("Wrapped"); + static const StringName &get_class_static() { + static const StringName string_name = StringName("Wrapped"); return string_name; } @@ -208,12 +208,12 @@ public: initialized = true; \ } \ \ - static ::godot::StringName &get_class_static() { \ - static ::godot::StringName string_name = ::godot::StringName(#m_class); \ + static const ::godot::StringName &get_class_static() { \ + static const ::godot::StringName string_name = ::godot::StringName(#m_class); \ return string_name; \ } \ \ - static ::godot::StringName &get_parent_class_static() { \ + static const ::godot::StringName &get_parent_class_static() { \ return m_inherits::get_class_static(); \ } \ \ @@ -418,12 +418,12 @@ public: \ static void initialize_class() {} \ \ - static ::godot::StringName &get_class_static() { \ - static ::godot::StringName string_name = ::godot::StringName(#m_alias_for); \ + static const ::godot::StringName &get_class_static() { \ + static const ::godot::StringName string_name = ::godot::StringName(#m_alias_for); \ return string_name; \ } \ \ - static ::godot::StringName &get_parent_class_static() { \ + static const ::godot::StringName &get_parent_class_static() { \ return m_inherits::get_class_static(); \ } \ \ From a246aaaaf650345c6da12e037081353a96f1ee5f Mon Sep 17 00:00:00 2001 From: David Snopek Date: Wed, 24 Apr 2024 13:01:53 -0500 Subject: [PATCH 9/9] Fix NOTIFICATION_POSTINITIALIZE sent twice to native parent class (cherry picked from commit 06373ce1cf737a33d6a7b2b14cfe25ed729b9c48) --- include/godot_cpp/classes/wrapped.hpp | 6 ++++++ src/classes/wrapped.cpp | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index e4042991c..0f9da9934 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -88,6 +88,7 @@ class Wrapped { ::godot::List<::godot::PropertyInfo> plist_owned; void _postinitialize(); + virtual void _notificationv(int32_t p_what, bool p_reversed = false) {} Wrapped(const StringName p_godot_class); Wrapped(GodotObject *p_godot_object); @@ -353,6 +354,11 @@ public: _gde_binding_reference_callback, \ }; \ \ +protected: \ + virtual void _notificationv(int32_t p_what, bool p_reversed = false) override { \ + m_class::notification_bind(this, p_what, p_reversed); \ + } \ + \ private: // Don't use this for your classes, use GDCLASS() instead. diff --git a/src/classes/wrapped.cpp b/src/classes/wrapped.cpp index ad0eefb10..594cfefff 100644 --- a/src/classes/wrapped.cpp +++ b/src/classes/wrapped.cpp @@ -51,10 +51,7 @@ void Wrapped::_postinitialize() { } godot::internal::gdextension_interface_object_set_instance_binding(_owner, godot::internal::token, this, _get_bindings_callbacks()); if (extension_class) { - Object *obj = dynamic_cast(this); - if (obj) { - obj->notification(Object::NOTIFICATION_POSTINITIALIZE); - } + _notificationv(Object::NOTIFICATION_POSTINITIALIZE); } }