From 1518bd17dcbe2b2f8a92242c4f86f3906fca0c43 Mon Sep 17 00:00:00 2001 From: dementive <87823030+dementive@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:45:13 -0500 Subject: [PATCH 01/10] Remove includes in headers (cherry picked from commit e36e81ac861007797c856df5ad401ea691471385) --- include/godot_cpp/core/class_db.hpp | 1 - include/godot_cpp/core/method_bind.hpp | 3 --- src/godot.cpp | 2 ++ 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 1dcfebe3e..35384dec0 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -47,7 +47,6 @@ #include #include #include -#include #include #include diff --git a/include/godot_cpp/core/method_bind.hpp b/include/godot_cpp/core/method_bind.hpp index 6ed61fc39..aa5c8c493 100644 --- a/include/godot_cpp/core/method_bind.hpp +++ b/include/godot_cpp/core/method_bind.hpp @@ -39,11 +39,8 @@ #include -#include #include -#include - namespace godot { class MethodBind { diff --git a/src/godot.cpp b/src/godot.cpp index e73554b90..b71e5a43f 100644 --- a/src/godot.cpp +++ b/src/godot.cpp @@ -39,6 +39,8 @@ #include +#include + namespace godot { namespace internal { From 50eabd9359bac9d2f857039757171d50d8b7161c Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Thu, 11 Sep 2025 19:48:13 +0200 Subject: [PATCH 02/10] Update contributing section in README.md (cherry picked from commit c58dfa4d995db907d76f5c400c8914cc05787cf3) --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 0b572bb98..f248eadf1 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,7 @@ See [Updating your GDExtension for 4.1](https://docs.godotengine.org/en/latest/t ## Contributing We greatly appreciate help in maintaining and extending this project. If you -wish to help out, ensure you have an account on GitHub and create a "fork" of -this repository. See [Pull request workflow](https://docs.godotengine.org/en/stable/community/contributing/pr_workflow.html) -for instructions. - -Please install clang-format and the [pre-commit](https://pre-commit.com/) Python framework so formatting is done before your changes are submitted. See the [code style guidelines](https://docs.godotengine.org/en/latest/contributing/development/code_style_guidelines.html#pre-commit-hook) for instructions. +wish to help out, please visit the [godot-cpp section of the Contributing docs](https://contributing.godotengine.org/en/latest/other/godot-cpp.html). ## Getting started From fec8bd56db3598ed6aaee3e75ea4cc3ddaa485bb Mon Sep 17 00:00:00 2001 From: dementive <87823030+dementive@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:33:14 -0500 Subject: [PATCH 03/10] Use LocalVector instead of std::vector (cherry picked from commit 0b8e76817bf47104c4d4f9c07d9e1fb429716528) --- include/godot_cpp/core/binder_common.hpp | 13 +++-- include/godot_cpp/core/class_db.hpp | 9 ++-- include/godot_cpp/core/method_bind.hpp | 27 +++++----- include/godot_cpp/core/object.hpp | 20 +++----- src/core/class_db.cpp | 64 +++++++++++------------- src/core/method_bind.cpp | 4 +- src/core/object.cpp | 2 +- 7 files changed, 65 insertions(+), 74 deletions(-) diff --git a/include/godot_cpp/core/binder_common.hpp b/include/godot_cpp/core/binder_common.hpp index ba99aa38a..aed6b1534 100644 --- a/include/godot_cpp/core/binder_common.hpp +++ b/include/godot_cpp/core/binder_common.hpp @@ -37,7 +37,6 @@ #include #include -#include namespace godot { @@ -331,7 +330,7 @@ void call_with_variant_args_retc(T *p_instance, R (T::*p_method)(P...) const, co } template -void call_with_variant_args_dv(T *p_instance, void (T::*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const std::vector &default_values) { +void call_with_variant_args_dv(T *p_instance, void (T::*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const LocalVector &default_values) { #ifdef DEBUG_ENABLED if ((size_t)p_argcount > sizeof...(P)) { r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; @@ -366,7 +365,7 @@ void call_with_variant_args_dv(T *p_instance, void (T::*p_method)(P...), const G } template -void call_with_variant_argsc_dv(T *p_instance, void (T::*p_method)(P...) const, const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const std::vector &default_values) { +void call_with_variant_argsc_dv(T *p_instance, void (T::*p_method)(P...) const, const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const LocalVector &default_values) { #ifdef DEBUG_ENABLED if ((size_t)p_argcount > sizeof...(P)) { r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; @@ -401,7 +400,7 @@ void call_with_variant_argsc_dv(T *p_instance, void (T::*p_method)(P...) const, } template -void call_with_variant_args_ret_dv(T *p_instance, R (T::*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const std::vector &default_values) { +void call_with_variant_args_ret_dv(T *p_instance, R (T::*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const LocalVector &default_values) { #ifdef DEBUG_ENABLED if ((size_t)p_argcount > sizeof...(P)) { r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; @@ -436,7 +435,7 @@ void call_with_variant_args_ret_dv(T *p_instance, R (T::*p_method)(P...), const } template -void call_with_variant_args_retc_dv(T *p_instance, R (T::*p_method)(P...) const, const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const std::vector &default_values) { +void call_with_variant_args_retc_dv(T *p_instance, R (T::*p_method)(P...) const, const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const LocalVector &default_values) { #ifdef DEBUG_ENABLED if ((size_t)p_argcount > sizeof...(P)) { r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; @@ -548,7 +547,7 @@ void call_with_variant_args_static(void (*p_method)(P...), const Variant **p_arg } template -void call_with_variant_args_static_dv(void (*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const std::vector &default_values) { +void call_with_variant_args_static_dv(void (*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, GDExtensionCallError &r_error, const LocalVector &default_values) { #ifdef DEBUG_ENABLED if ((size_t)p_argcount > sizeof...(P)) { r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; @@ -640,7 +639,7 @@ void call_with_variant_args_static_ret(R (*p_method)(P...), const Variant **p_ar } template -void call_with_variant_args_static_ret_dv(R (*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const std::vector &default_values) { +void call_with_variant_args_static_ret_dv(R (*p_method)(P...), const GDExtensionConstVariantPtr *p_args, int p_argcount, Variant &r_ret, GDExtensionCallError &r_error, const LocalVector &default_values) { #ifdef DEBUG_ENABLED if ((size_t)p_argcount > sizeof...(P)) { r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS; diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 35384dec0..c0217e745 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -44,11 +44,12 @@ // Needs to come after method_bind and object have been included. #include +#include + #include #include #include #include -#include // Needed to use StringName as key in `std::unordered_map` template <> @@ -108,7 +109,7 @@ class ClassDB { static std::unordered_map classes; static std::unordered_map instance_binding_callbacks; // Used to remember the custom class registration order. - static std::vector class_register_order; + static LocalVector class_register_order; static std::unordered_map engine_singletons; static std::mutex engine_singletons_mutex; @@ -188,7 +189,7 @@ class ClassDB { static MethodBind *bind_static_method(StringName p_class, N p_method_name, M p_method, VarArgs... p_args); template - static MethodBind *bind_vararg_method(uint32_t p_flags, StringName p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const std::vector &p_default_args = std::vector{}, bool p_return_nil_is_variant = true); + static MethodBind *bind_vararg_method(uint32_t p_flags, StringName p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const LocalVector &p_default_args = LocalVector{}, bool p_return_nil_is_variant = true); static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix); static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix); @@ -328,7 +329,7 @@ MethodBind *ClassDB::bind_static_method(StringName p_class, N p_method_name, M p } template -MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p_method, const MethodInfo &p_info, const std::vector &p_default_args, bool p_return_nil_is_variant) { +MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p_method, const MethodInfo &p_info, const LocalVector &p_default_args, bool p_return_nil_is_variant) { MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant); ERR_FAIL_NULL_V(bind, nullptr); diff --git a/include/godot_cpp/core/method_bind.hpp b/include/godot_cpp/core/method_bind.hpp index aa5c8c493..59fb85d44 100644 --- a/include/godot_cpp/core/method_bind.hpp +++ b/include/godot_cpp/core/method_bind.hpp @@ -38,8 +38,7 @@ #include #include - -#include +#include namespace godot { @@ -54,9 +53,9 @@ class MethodBind { bool _returns = false; bool _vararg = false; - std::vector argument_names; + LocalVector argument_names; GDExtensionVariantType *argument_types = nullptr; - std::vector default_arguments; + LocalVector default_arguments; protected: void _set_const(bool p_const); @@ -70,7 +69,7 @@ class MethodBind { void set_argument_count(int p_count) { argument_count = p_count; } public: - _FORCE_INLINE_ const std::vector &get_default_arguments() const { return default_arguments; } + _FORCE_INLINE_ const LocalVector &get_default_arguments() const { return default_arguments; } _FORCE_INLINE_ int get_default_argument_count() const { return (int)default_arguments.size(); } _FORCE_INLINE_ Variant has_default_argument(int p_arg) const { @@ -101,8 +100,8 @@ class MethodBind { PropertyInfo get_argument_info(int p_argument) const; - std::vector get_arguments_info_list() const { - std::vector vec; + LocalVector get_arguments_info_list() const { + LocalVector vec; // First element is return value vec.reserve(argument_count + 1); for (int i = 0; i < argument_count + 1; i++) { @@ -111,8 +110,8 @@ class MethodBind { return vec; } - void set_argument_names(const std::vector &p_names); - std::vector get_argument_names() const; + void set_argument_names(const LocalVector &p_names); + LocalVector get_argument_names() const; virtual GDExtensionClassMethodArgumentMetadata get_argument_metadata(int p_argument) const = 0; @@ -133,10 +132,10 @@ class MethodBind { _FORCE_INLINE_ bool is_vararg() const { return _vararg; } _FORCE_INLINE_ bool has_return() const { return _returns; } - void set_default_arguments(const std::vector &p_default_arguments) { default_arguments = p_default_arguments; } + void set_default_arguments(const LocalVector &p_default_arguments) { default_arguments = p_default_arguments; } - std::vector get_arguments_metadata_list() const { - std::vector vec; + LocalVector get_arguments_metadata_list() const { + LocalVector vec; // First element is return value vec.reserve(argument_count + 1); for (int i = 0; i < argument_count + 1; i++) { @@ -155,7 +154,7 @@ template class MethodBindVarArgBase : public MethodBind { protected: R (T::*method)(const Variant **, GDExtensionInt, GDExtensionCallError &); - std::vector arguments; + LocalVector arguments; public: virtual PropertyInfo gen_argument_type_info(int p_arg) const { @@ -191,7 +190,7 @@ class MethodBindVarArgBase : public MethodBind { if (p_method_info.arguments.size()) { arguments = p_method_info.arguments; - std::vector names; + LocalVector names; names.reserve(p_method_info.arguments.size()); for (size_t i = 0; i < p_method_info.arguments.size(); i++) { names.push_back(p_method_info.arguments[i].name); diff --git a/include/godot_cpp/core/object.hpp b/include/godot_cpp/core/object.hpp index 9eab6e5c0..f3e028d43 100644 --- a/include/godot_cpp/core/object.hpp +++ b/include/godot_cpp/core/object.hpp @@ -38,14 +38,14 @@ #include +#include + #include #include #include -#include - #define ADD_SIGNAL(m_signal) ::godot::ClassDB::add_signal(get_class_static(), m_signal) #define ADD_GROUP(m_name, m_prefix) ::godot::ClassDB::add_property_group(get_class_static(), m_name, m_prefix) #define ADD_SUBGROUP(m_name, m_prefix) ::godot::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix) @@ -65,10 +65,10 @@ struct MethodInfo { PropertyInfo return_val; uint32_t flags; int id = 0; - std::vector arguments; - std::vector default_arguments; + LocalVector arguments; + LocalVector default_arguments; GDExtensionClassMethodArgumentMetadata return_val_metadata; - std::vector arguments_metadata; + LocalVector 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); } @@ -92,21 +92,17 @@ struct MethodInfo { template MethodInfo::MethodInfo(StringName p_name, const Args &...args) : - name(p_name), flags(GDEXTENSION_METHOD_FLAG_NORMAL) { - arguments = { args... }; -} + name(p_name), flags(GDEXTENSION_METHOD_FLAG_NORMAL), arguments({ args... }) {} template MethodInfo::MethodInfo(Variant::Type ret, StringName p_name, const Args &...args) : - name(p_name), flags(GDEXTENSION_METHOD_FLAG_NORMAL) { + name(p_name), flags(GDEXTENSION_METHOD_FLAG_NORMAL), arguments({ args... }) { return_val.type = ret; - arguments = { args... }; } template MethodInfo::MethodInfo(const PropertyInfo &p_ret, StringName p_name, const Args &...args) : - name(p_name), return_val(p_ret), flags(GDEXTENSION_METHOD_FLAG_NORMAL) { - arguments = { args... }; + name(p_name), return_val(p_ret), flags(GDEXTENSION_METHOD_FLAG_NORMAL), arguments({ args... }) { } class ObjectDB { diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 59ef63180..eedcf3275 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -36,13 +36,11 @@ #include -#include - namespace godot { std::unordered_map ClassDB::classes; std::unordered_map ClassDB::instance_binding_callbacks; -std::vector ClassDB::class_register_order; +LocalVector ClassDB::class_register_order; std::unordered_map ClassDB::engine_singletons; std::mutex ClassDB::engine_singletons_mutex; GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE; @@ -157,7 +155,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const M p_bind->set_hint_flags(p_flags); - std::vector args; + LocalVector args; args.resize(method_name.args.size()); size_t arg_index = 0; for (StringName arg : method_name.args) { @@ -166,7 +164,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const M p_bind->set_argument_names(args); - std::vector defvals; + LocalVector defvals; defvals.resize(p_defcount); for (int i = 0; i < p_defcount; i++) { @@ -186,34 +184,34 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const M } void ClassDB::bind_method_godot(const StringName &p_class_name, MethodBind *p_method) { - std::vector def_args; - const std::vector &def_args_val = p_method->get_default_arguments(); + LocalVector def_args; + const LocalVector &def_args_val = p_method->get_default_arguments(); def_args.resize(def_args_val.size()); for (size_t i = 0; i < def_args_val.size(); i++) { def_args[i] = (GDExtensionVariantPtr)&def_args_val[i]; } - std::vector return_value_and_arguments_info = p_method->get_arguments_info_list(); - std::vector return_value_and_arguments_metadata = p_method->get_arguments_metadata_list(); + LocalVector return_value_and_arguments_info = p_method->get_arguments_info_list(); + LocalVector return_value_and_arguments_metadata = p_method->get_arguments_metadata_list(); - std::vector return_value_and_arguments_gdextension_info; + LocalVector return_value_and_arguments_gdextension_info; return_value_and_arguments_gdextension_info.reserve(return_value_and_arguments_info.size()); - for (std::vector::iterator it = return_value_and_arguments_info.begin(); it != return_value_and_arguments_info.end(); it++) { + for (const PropertyInfo &info : return_value_and_arguments_info) { return_value_and_arguments_gdextension_info.push_back( GDExtensionPropertyInfo{ - static_cast(it->type), // GDExtensionVariantType type; - it->name._native_ptr(), // GDExtensionStringNamePtr name; - it->class_name._native_ptr(), // GDExtensionStringNamePtr class_name; - it->hint, // uint32_t hint; - it->hint_string._native_ptr(), // GDExtensionStringPtr hint_string; - it->usage, // uint32_t usage; + static_cast(info.type), // GDExtensionVariantType type; + info.name._native_ptr(), // GDExtensionStringNamePtr name; + info.class_name._native_ptr(), // GDExtensionStringNamePtr class_name; + info.hint, // uint32_t hint; + info.hint_string._native_ptr(), // GDExtensionStringPtr hint_string; + info.usage, // uint32_t usage; }); } - GDExtensionPropertyInfo *return_value_info = return_value_and_arguments_gdextension_info.data(); - GDExtensionClassMethodArgumentMetadata *return_value_metadata = return_value_and_arguments_metadata.data(); - GDExtensionPropertyInfo *arguments_info = return_value_and_arguments_gdextension_info.data() + 1; - GDExtensionClassMethodArgumentMetadata *arguments_metadata = return_value_and_arguments_metadata.data() + 1; + GDExtensionPropertyInfo *return_value_info = return_value_and_arguments_gdextension_info.ptr(); + GDExtensionClassMethodArgumentMetadata *return_value_metadata = return_value_and_arguments_metadata.ptr(); + GDExtensionPropertyInfo *arguments_info = return_value_and_arguments_gdextension_info.ptr() + 1; + GDExtensionClassMethodArgumentMetadata *arguments_metadata = return_value_and_arguments_metadata.ptr() + 1; StringName name = p_method->get_name(); GDExtensionClassMethodInfo method_info = { @@ -229,7 +227,7 @@ void ClassDB::bind_method_godot(const StringName &p_class_name, MethodBind *p_me arguments_info, // GDExtensionPropertyInfo * arguments_metadata, // GDExtensionClassMethodArgumentMetadata * (uint32_t)p_method->get_default_argument_count(), // uint32_t default_argument_count; - def_args.data(), // GDExtensionVariantPtr *default_arguments; + def_args.ptr(), // GDExtensionVariantPtr *default_arguments; }; internal::gdextension_interface_classdb_register_extension_class_method(internal::library, p_class_name._native_ptr(), &method_info); } @@ -252,7 +250,7 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) cl.signal_names.insert(p_signal.name); // register our signal in godot - std::vector parameters; + LocalVector parameters; parameters.reserve(p_signal.arguments.size()); for (const PropertyInfo &par : p_signal.arguments) { @@ -266,7 +264,7 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) }); } - internal::gdextension_interface_classdb_register_extension_class_signal(internal::library, cl.name._native_ptr(), p_signal.name._native_ptr(), parameters.data(), parameters.size()); + internal::gdextension_interface_classdb_register_extension_class_signal(internal::library, cl.name._native_ptr(), p_signal.name._native_ptr(), parameters.ptr(), parameters.size()); } void ClassDB::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) { @@ -404,8 +402,8 @@ void ClassDB::initialize(GDExtensionInitializationLevel p_level) { void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { std::set to_erase; - for (std::vector::reverse_iterator i = class_register_order.rbegin(); i != class_register_order.rend(); ++i) { - const StringName &name = *i; + for (int i = class_register_order.size() - 1; i >= 0; --i) { + const StringName &name = class_register_order[i]; const ClassInfo &cl = classes[name]; if (cl.level != p_level) { @@ -423,17 +421,15 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { } { - // The following is equivalent to c++20 `std::erase_if(class_register_order, [&](const StringName& name){ return to_erase.contains(name); });` - std::vector::iterator it = std::remove_if(class_register_order.begin(), class_register_order.end(), [&](const StringName &p_name) { - return to_erase.count(p_name) > 0; - }); - class_register_order.erase(it, class_register_order.end()); + for (const StringName &x : to_erase) { + class_register_order.erase(x); + } } 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; + LocalVector singleton_objects; { std::lock_guard lock(engine_singletons_mutex); singleton_objects.reserve(engine_singletons.size()); @@ -441,8 +437,8 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { 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); + for (const Object *i : singleton_objects) { + internal::gdextension_interface_object_free_instance_binding((*i)._owner, internal::token); } } } diff --git a/src/core/method_bind.cpp b/src/core/method_bind.cpp index 734b2e692..9eba6854b 100644 --- a/src/core/method_bind.cpp +++ b/src/core/method_bind.cpp @@ -56,11 +56,11 @@ void MethodBind::set_name(const StringName &p_name) { name = p_name; } -void MethodBind::set_argument_names(const std::vector &p_names) { +void MethodBind::set_argument_names(const LocalVector &p_names) { argument_names = p_names; } -std::vector MethodBind::get_argument_names() const { +LocalVector MethodBind::get_argument_names() const { return argument_names; } diff --git a/src/core/object.cpp b/src/core/object.cpp index 410daf856..d30f6cd0c 100644 --- a/src/core/object.cpp +++ b/src/core/object.cpp @@ -60,7 +60,7 @@ 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 convert_property_list(const LocalVector &p_list) { TypedArray va; for (const PropertyInfo &pi : p_list) { va.push_back(Dictionary(pi)); From 895b66453cf16fafb10687726273bc1f8ddd738a Mon Sep 17 00:00:00 2001 From: Thomas Staudinger Date: Sun, 21 Sep 2025 17:49:08 +0200 Subject: [PATCH 04/10] README: Add link to 4.5 branch and fix link to Godot compile docs Signed-off-by: Thomas Staudinger (cherry picked from commit ef63d2e657189e6eccbdd8f787b1c79c9c1e6922) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f248eadf1..55e322ab5 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ > from Godot's `master` branch. > > For users of stable branches, switch to the branch matching your target Godot version: +> - [`4.5`](https://github.com/godotengine/godot-cpp/tree/4.5) > - [`4.4`](https://github.com/godotengine/godot-cpp/tree/4.4) > - [`4.3`](https://github.com/godotengine/godot-cpp/tree/4.3) > - [`4.2`](https://github.com/godotengine/godot-cpp/tree/4.2) @@ -66,7 +67,7 @@ wish to help out, please visit the [godot-cpp section of the Contributing docs]( ## Getting started -You need the same C++ pre-requisites installed that are required for the `godot` repository. Follow the [official build instructions for your target platform](https://docs.godotengine.org/en/latest/contributing/development/compiling/index.html#building-for-target-platforms). +You need the same C++ pre-requisites installed that are required for the `godot` repository. Follow the [official build instructions for your target platform](https://docs.godotengine.org/en/latest/engine_details/development/compiling/index.html). Getting started with GDExtensions is a bit similar to what it was for 3.x but also a bit different. From 0d658486dc1346c9a61779240b6c5c3fdd8494c6 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 1 Oct 2025 21:16:20 +0200 Subject: [PATCH 05/10] Silenced compiler warning in godot::call_with_variant_args_ret_helper (cherry picked from commit 3eb3069e0955e3ecbb37297dd71aa46252ffbf51) --- include/godot_cpp/core/binder_common.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/godot_cpp/core/binder_common.hpp b/include/godot_cpp/core/binder_common.hpp index aed6b1534..61eb6a04d 100644 --- a/include/godot_cpp/core/binder_common.hpp +++ b/include/godot_cpp/core/binder_common.hpp @@ -261,6 +261,7 @@ void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), co #else r_ret = (p_instance->*p_method)(VariantCaster

::cast(*p_args[Is])...); #endif + (void)p_args; // Avoid warning. } template @@ -272,7 +273,7 @@ void call_with_variant_args_retc_helper(T *p_instance, R (T::*p_method)(P...) co #else r_ret = (p_instance->*p_method)(VariantCaster

::cast(*p_args[Is])...); #endif - (void)p_args; + (void)p_args; // Avoid warning. } template From dae8fbdebc38bde774fec79f364b2d9153614bf3 Mon Sep 17 00:00:00 2001 From: dementive <87823030+dementive@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:46:51 -0500 Subject: [PATCH 06/10] Replace unordered_map with AHashMap (cherry picked from commit 2fd41b7e16b4f3693adf95c72658b06b103f4596) --- include/godot_cpp/core/class_db.hpp | 34 +- include/godot_cpp/templates/a_hash_map.hpp | 734 +++++++++++++++++++++ src/core/class_db.cpp | 56 +- 3 files changed, 774 insertions(+), 50 deletions(-) create mode 100644 include/godot_cpp/templates/a_hash_map.hpp diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index c0217e745..670393632 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -44,20 +44,10 @@ // Needs to come after method_bind and object have been included. #include -#include - +#include #include #include #include -#include - -// Needed to use StringName as key in `std::unordered_map` -template <> -struct std::hash { - std::size_t operator()(godot::StringName const &s) const noexcept { - return s.hash(); - } -}; namespace godot { @@ -95,9 +85,9 @@ class ClassDB { StringName name; StringName parent_name; GDExtensionInitializationLevel level = GDEXTENSION_INITIALIZATION_SCENE; - std::unordered_map method_map; + AHashMap method_map; std::set signal_names; - std::unordered_map virtual_methods; + AHashMap virtual_methods; std::set property_names; std::set constant_names; // Pointer to the parent custom class, if any. Will be null if the parent class is a Godot class. @@ -106,11 +96,11 @@ class ClassDB { private: // This may only contain custom classes, not Godot classes - static std::unordered_map classes; - static std::unordered_map instance_binding_callbacks; + static AHashMap classes; + static AHashMap instance_binding_callbacks; // Used to remember the custom class registration order. static LocalVector class_register_order; - static std::unordered_map engine_singletons; + static AHashMap 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); @@ -169,9 +159,9 @@ class ClassDB { 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); + AHashMap::ConstIterator i = engine_singletons.find(p_class_name); if (i != engine_singletons.end()) { - ERR_FAIL_COND((*i).second != p_singleton); + ERR_FAIL_COND((*i).value != p_singleton); return; } engine_singletons[p_class_name] = p_singleton; @@ -241,10 +231,10 @@ void ClassDB::_register_class(bool p_virtual, bool p_exposed, bool p_runtime) { cl.name = T::get_class_static(); cl.parent_name = T::get_parent_class_static(); cl.level = current_level; - std::unordered_map::iterator parent_it = classes.find(cl.parent_name); + AHashMap::Iterator parent_it = classes.find(cl.parent_name); if (parent_it != classes.end()) { // Assign parent if it is also a custom class - cl.parent_ptr = &parent_it->second; + cl.parent_ptr = &parent_it->value; } classes[cl.name] = cl; class_register_order.push_back(cl.name); @@ -338,13 +328,13 @@ MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p StringName instance_type = bind->get_instance_class(); - std::unordered_map::iterator type_it = classes.find(instance_type); + AHashMap::Iterator type_it = classes.find(instance_type); if (type_it == classes.end()) { memdelete(bind); ERR_FAIL_V_MSG(nullptr, String("Class '{0}' doesn't exist.").format(Array::make(instance_type))); } - ClassInfo &type = type_it->second; + ClassInfo &type = type_it->value; if (type.method_map.find(p_name) != type.method_map.end()) { memdelete(bind); diff --git a/include/godot_cpp/templates/a_hash_map.hpp b/include/godot_cpp/templates/a_hash_map.hpp new file mode 100644 index 000000000..18a03df22 --- /dev/null +++ b/include/godot_cpp/templates/a_hash_map.hpp @@ -0,0 +1,734 @@ +/**************************************************************************/ +/* a_hash_map.hpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include + +namespace godot { + +struct HashMapData { + union { + uint64_t data; + struct + { + uint32_t hash; + uint32_t hash_to_key; + }; + }; +}; + +static_assert(sizeof(HashMapData) == 8); + +/** + * An array-based implementation of a hash map. It is very efficient in terms of performance and + * memory usage. Works like a dynamic array, adding elements to the end of the array, and + * allows you to access array elements by their index by using `get_by_index` method. + * Example: + * ``` + * AHashMap map; + * + * int get_object_id_by_number(int p_number) { + * int id = map.get_index(p_number); + * return id; + * } + * + * Object *get_object_by_id(int p_id) { + * map.get_by_index(p_id).value; + * } + * ``` + * Still, don`t erase the elements because ID can break. + * + * When an element erase, its place is taken by the element from the end. + * + * <------------- + * | | + * 6 8 X 9 32 -1 5 -10 7 X X X + * 6 8 7 9 32 -1 5 -10 X X X X + * + * + * Use RBMap if you need to iterate over sorted elements. + * + * Use HashMap if: + * - You need to keep an iterator or const pointer to Key and you intend to add/remove elements in the meantime. + * - You need to preserve the insertion order when using erase. + * + * It is recommended to use `HashMap` if `KeyValue` size is very large. + */ +template > +class AHashMap { +public: + // Must be a power of two. + static constexpr uint32_t INITIAL_CAPACITY = 16; + static constexpr uint32_t EMPTY_HASH = 0; + static_assert(EMPTY_HASH == 0, "EMPTY_HASH must always be 0 for the memcpy() optimization."); + +private: + typedef KeyValue MapKeyValue; + MapKeyValue *elements = nullptr; + HashMapData *map_data = nullptr; + + // Due to optimization, this is `capacity - 1`. Use + 1 to get normal capacity. + uint32_t capacity = 0; + uint32_t num_elements = 0; + + uint32_t _hash(const TKey &p_key) const { + uint32_t hash = Hasher::hash(p_key); + + if (unlikely(hash == EMPTY_HASH)) { + hash = EMPTY_HASH + 1; + } + + return hash; + } + + static _FORCE_INLINE_ uint32_t _get_resize_count(uint32_t p_capacity) { + return p_capacity ^ (p_capacity + 1) >> 2; // = get_capacity() * 0.75 - 1; Works only if p_capacity = 2^n - 1. + } + + static _FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash, uint32_t p_local_capacity) { + const uint32_t original_pos = p_hash & p_local_capacity; + return (p_pos - original_pos + p_local_capacity + 1) & p_local_capacity; + } + + bool _lookup_pos(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos) const { + if (unlikely(elements == nullptr)) { + return false; // Failed lookups, no elements. + } + return _lookup_pos_with_hash(p_key, r_pos, r_hash_pos, _hash(p_key)); + } + + bool _lookup_pos_with_hash(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos, uint32_t p_hash) const { + if (unlikely(elements == nullptr)) { + return false; // Failed lookups, no elements. + } + + uint32_t pos = p_hash & capacity; + HashMapData data = map_data[pos]; + if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) { + r_pos = data.hash_to_key; + r_hash_pos = pos; + return true; + } + + if (data.data == EMPTY_HASH) { + return false; + } + + // A collision occurred. + pos = (pos + 1) & capacity; + uint32_t distance = 1; + while (true) { + data = map_data[pos]; + if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) { + r_pos = data.hash_to_key; + r_hash_pos = pos; + return true; + } + + if (data.data == EMPTY_HASH) { + return false; + } + + if (distance > _get_probe_length(pos, data.hash, capacity)) { + return false; + } + + pos = (pos + 1) & capacity; + distance++; + } + } + + uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_index) { + uint32_t pos = p_hash & capacity; + + if (map_data[pos].data == EMPTY_HASH) { + uint64_t data = ((uint64_t)p_index << 32) | p_hash; + map_data[pos].data = data; + return pos; + } + + uint32_t distance = 1; + pos = (pos + 1) & capacity; + HashMapData c_data; + c_data.hash = p_hash; + c_data.hash_to_key = p_index; + + while (true) { + if (map_data[pos].data == EMPTY_HASH) { +#ifdef DEV_ENABLED + if (unlikely(distance > 12)) { + WARN_PRINT("Excessive collision count (" + + itos(distance) + "), is the right hash function being used?"); + } +#endif + map_data[pos] = c_data; + return pos; + } + + // Not an empty slot, let's check the probing length of the existing one. + uint32_t existing_probe_len = _get_probe_length(pos, map_data[pos].hash, capacity); + if (existing_probe_len < distance) { + SWAP(c_data, map_data[pos]); + distance = existing_probe_len; + } + + pos = (pos + 1) & capacity; + distance++; + } + } + + void _resize_and_rehash(uint32_t p_new_capacity) { + uint32_t real_old_capacity = capacity + 1; + // Capacity can't be 0 and must be 2^n - 1. + capacity = MAX(4u, p_new_capacity); + uint32_t real_capacity = next_power_of_2(capacity); + capacity = real_capacity - 1; + + HashMapData *old_map_data = map_data; + + map_data = reinterpret_cast(Memory::alloc_static(sizeof(HashMapData) * real_capacity)); + memset(map_data, 0, sizeof(HashMapData) * real_capacity); + elements = reinterpret_cast(Memory::realloc_static(elements, sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1))); + + if (num_elements != 0) { + for (uint32_t i = 0; i < real_old_capacity; i++) { + HashMapData data = old_map_data[i]; + if (data.data != EMPTY_HASH) { + _insert_with_hash(data.hash, data.hash_to_key); + } + } + } + + Memory::free_static(old_map_data); + } + + int32_t _insert_element(const TKey &p_key, const TValue &p_value, uint32_t p_hash) { + if (unlikely(elements == nullptr)) { + // Allocate on demand to save memory. + + uint32_t real_capacity = capacity + 1; + map_data = reinterpret_cast(Memory::alloc_static(sizeof(HashMapData) * real_capacity)); + memset(map_data, 0, sizeof(HashMapData) * real_capacity); + elements = reinterpret_cast(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1))); + } + + if (unlikely(num_elements > _get_resize_count(capacity))) { + _resize_and_rehash(capacity * 2); + } + + memnew_placement(&elements[num_elements], MapKeyValue(p_key, p_value)); + + _insert_with_hash(p_hash, num_elements); + num_elements++; + return num_elements - 1; + } + + void _init_from(const AHashMap &p_other) { + capacity = p_other.capacity; + uint32_t real_capacity = capacity + 1; + num_elements = p_other.num_elements; + + if (p_other.num_elements == 0) { + return; + } + + map_data = reinterpret_cast(Memory::alloc_static(sizeof(HashMapData) * real_capacity)); + elements = reinterpret_cast(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1))); + + if constexpr (std::is_trivially_copyable_v && std::is_trivially_copyable_v) { + void *destination = elements; + const void *source = p_other.elements; + memcpy(destination, source, sizeof(MapKeyValue) * num_elements); + } else { + for (uint32_t i = 0; i < num_elements; i++) { + memnew_placement(&elements[i], MapKeyValue(p_other.elements[i])); + } + } + + memcpy(map_data, p_other.map_data, sizeof(HashMapData) * real_capacity); + } + +public: + /* Standard Godot Container API */ + + _FORCE_INLINE_ uint32_t get_capacity() const { return capacity + 1; } + _FORCE_INLINE_ uint32_t size() const { return num_elements; } + + _FORCE_INLINE_ bool is_empty() const { + return num_elements == 0; + } + + void clear() { + if (elements == nullptr || num_elements == 0) { + return; + } + + memset(map_data, EMPTY_HASH, (capacity + 1) * sizeof(HashMapData)); + if constexpr (!(std::is_trivially_destructible_v && std::is_trivially_destructible_v)) { + for (uint32_t i = 0; i < num_elements; i++) { + elements[i].key.~TKey(); + elements[i].value.~TValue(); + } + } + + num_elements = 0; + } + + TValue &get(const TKey &p_key) { + uint32_t pos = 0; + uint32_t hash_pos = 0; + bool exists = _lookup_pos(p_key, pos, hash_pos); + CRASH_COND_MSG(!exists, "AHashMap key not found."); + return elements[pos].value; + } + + const TValue &get(const TKey &p_key) const { + uint32_t pos = 0; + uint32_t hash_pos = 0; + bool exists = _lookup_pos(p_key, pos, hash_pos); + CRASH_COND_MSG(!exists, "AHashMap key not found."); + return elements[pos].value; + } + + const TValue *getptr(const TKey &p_key) const { + uint32_t pos = 0; + uint32_t hash_pos = 0; + bool exists = _lookup_pos(p_key, pos, hash_pos); + + if (exists) { + return &elements[pos].value; + } + return nullptr; + } + + TValue *getptr(const TKey &p_key) { + uint32_t pos = 0; + uint32_t hash_pos = 0; + bool exists = _lookup_pos(p_key, pos, hash_pos); + + if (exists) { + return &elements[pos].value; + } + return nullptr; + } + + bool has(const TKey &p_key) const { + uint32_t _pos = 0; + uint32_t h_pos = 0; + return _lookup_pos(p_key, _pos, h_pos); + } + + bool erase(const TKey &p_key) { + uint32_t pos = 0; + uint32_t element_pos = 0; + bool exists = _lookup_pos(p_key, element_pos, pos); + + if (!exists) { + return false; + } + + uint32_t next_pos = (pos + 1) & capacity; + while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) { + SWAP(map_data[next_pos], map_data[pos]); + + pos = next_pos; + next_pos = (next_pos + 1) & capacity; + } + + map_data[pos].data = EMPTY_HASH; + elements[element_pos].key.~TKey(); + elements[element_pos].value.~TValue(); + num_elements--; + + if (element_pos < num_elements) { + void *destination = &elements[element_pos]; + const void *source = &elements[num_elements]; + memcpy(destination, source, sizeof(MapKeyValue)); + uint32_t h_pos = 0; + _lookup_pos(elements[num_elements].key, pos, h_pos); + map_data[h_pos].hash_to_key = element_pos; + } + + return true; + } + + // Replace the key of an entry in-place, without invalidating iterators or changing the entries position during iteration. + // p_old_key must exist in the map and p_new_key must not, unless it is equal to p_old_key. + bool replace_key(const TKey &p_old_key, const TKey &p_new_key) { + if (p_old_key == p_new_key) { + return true; + } + uint32_t pos = 0; + uint32_t element_pos = 0; + ERR_FAIL_COND_V(_lookup_pos(p_new_key, element_pos, pos), false); + ERR_FAIL_COND_V(!_lookup_pos(p_old_key, element_pos, pos), false); + MapKeyValue &element = elements[element_pos]; + const_cast(element.key) = p_new_key; + + uint32_t next_pos = (pos + 1) & capacity; + while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) { + SWAP(map_data[next_pos], map_data[pos]); + + pos = next_pos; + next_pos = (next_pos + 1) & capacity; + } + + map_data[pos].data = EMPTY_HASH; + + uint32_t hash = _hash(p_new_key); + _insert_with_hash(hash, element_pos); + + return true; + } + + // Reserves space for a number of elements, useful to avoid many resizes and rehashes. + // If adding a known (possibly large) number of elements at once, must be larger than old capacity. + void reserve(uint32_t p_new_capacity) { + ERR_FAIL_COND_MSG(p_new_capacity < size(), "reserve() called with a capacity smaller than the current size. This is likely a mistake."); + if (elements == nullptr) { + capacity = MAX(4u, p_new_capacity); + capacity = next_power_of_2(capacity) - 1; + return; // Unallocated yet. + } + if (p_new_capacity <= get_capacity()) { + return; + } + _resize_and_rehash(p_new_capacity); + } + + /** Iterator API **/ + + struct ConstIterator { + _FORCE_INLINE_ const MapKeyValue &operator*() const { + return *pair; + } + _FORCE_INLINE_ const MapKeyValue *operator->() const { + return pair; + } + _FORCE_INLINE_ ConstIterator &operator++() { + pair++; + return *this; + } + + _FORCE_INLINE_ ConstIterator &operator--() { + pair--; + if (pair < begin) { + pair = end; + } + return *this; + } + + _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return pair == b.pair; } + _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return pair != b.pair; } + + _FORCE_INLINE_ explicit operator bool() const { + return pair != end; + } + + _FORCE_INLINE_ ConstIterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) { + pair = p_key; + begin = p_begin; + end = p_end; + } + _FORCE_INLINE_ ConstIterator() {} + _FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) { + pair = p_it.pair; + begin = p_it.begin; + end = p_it.end; + } + _FORCE_INLINE_ void operator=(const ConstIterator &p_it) { + pair = p_it.pair; + begin = p_it.begin; + end = p_it.end; + } + + private: + MapKeyValue *pair = nullptr; + MapKeyValue *begin = nullptr; + MapKeyValue *end = nullptr; + }; + + struct Iterator { + _FORCE_INLINE_ MapKeyValue &operator*() const { + return *pair; + } + _FORCE_INLINE_ MapKeyValue *operator->() const { + return pair; + } + _FORCE_INLINE_ Iterator &operator++() { + pair++; + return *this; + } + _FORCE_INLINE_ Iterator &operator--() { + pair--; + if (pair < begin) { + pair = end; + } + return *this; + } + + _FORCE_INLINE_ bool operator==(const Iterator &b) const { return pair == b.pair; } + _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return pair != b.pair; } + + _FORCE_INLINE_ explicit operator bool() const { + return pair != end; + } + + _FORCE_INLINE_ Iterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) { + pair = p_key; + begin = p_begin; + end = p_end; + } + _FORCE_INLINE_ Iterator() {} + _FORCE_INLINE_ Iterator(const Iterator &p_it) { + pair = p_it.pair; + begin = p_it.begin; + end = p_it.end; + } + _FORCE_INLINE_ void operator=(const Iterator &p_it) { + pair = p_it.pair; + begin = p_it.begin; + end = p_it.end; + } + + operator ConstIterator() const { + return ConstIterator(pair, begin, end); + } + + private: + MapKeyValue *pair = nullptr; + MapKeyValue *begin = nullptr; + MapKeyValue *end = nullptr; + }; + + _FORCE_INLINE_ Iterator begin() { + return Iterator(elements, elements, elements + num_elements); + } + _FORCE_INLINE_ Iterator end() { + return Iterator(elements + num_elements, elements, elements + num_elements); + } + _FORCE_INLINE_ Iterator last() { + if (unlikely(num_elements == 0)) { + return Iterator(nullptr, nullptr, nullptr); + } + return Iterator(elements + num_elements - 1, elements, elements + num_elements); + } + + Iterator find(const TKey &p_key) { + uint32_t pos = 0; + uint32_t h_pos = 0; + bool exists = _lookup_pos(p_key, pos, h_pos); + if (!exists) { + return end(); + } + return Iterator(elements + pos, elements, elements + num_elements); + } + + void remove(const Iterator &p_iter) { + if (p_iter) { + erase(p_iter->key); + } + } + + _FORCE_INLINE_ ConstIterator begin() const { + return ConstIterator(elements, elements, elements + num_elements); + } + _FORCE_INLINE_ ConstIterator end() const { + return ConstIterator(elements + num_elements, elements, elements + num_elements); + } + _FORCE_INLINE_ ConstIterator last() const { + if (unlikely(num_elements == 0)) { + return ConstIterator(nullptr, nullptr, nullptr); + } + return ConstIterator(elements + num_elements - 1, elements, elements + num_elements); + } + + ConstIterator find(const TKey &p_key) const { + uint32_t pos = 0; + uint32_t h_pos = 0; + bool exists = _lookup_pos(p_key, pos, h_pos); + if (!exists) { + return end(); + } + return ConstIterator(elements + pos, elements, elements + num_elements); + } + + /* Indexing */ + + const TValue &operator[](const TKey &p_key) const { + uint32_t pos = 0; + uint32_t h_pos = 0; + bool exists = _lookup_pos(p_key, pos, h_pos); + CRASH_COND(!exists); + return elements[pos].value; + } + + TValue &operator[](const TKey &p_key) { + uint32_t pos = 0; + uint32_t h_pos = 0; + uint32_t hash = _hash(p_key); + bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash); + + if (exists) { + return elements[pos].value; + } else { + pos = _insert_element(p_key, TValue(), hash); + return elements[pos].value; + } + } + + /* Insert */ + + Iterator insert(const TKey &p_key, const TValue &p_value) { + uint32_t pos = 0; + uint32_t h_pos = 0; + uint32_t hash = _hash(p_key); + bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash); + + if (!exists) { + pos = _insert_element(p_key, p_value, hash); + } else { + elements[pos].value = p_value; + } + return Iterator(elements + pos, elements, elements + num_elements); + } + + // Inserts an element without checking if it already exists. + Iterator insert_new(const TKey &p_key, const TValue &p_value) { + DEV_ASSERT(!has(p_key)); + uint32_t hash = _hash(p_key); + uint32_t pos = _insert_element(p_key, p_value, hash); + return Iterator(elements + pos, elements, elements + num_elements); + } + + /* Array methods. */ + + // Unsafe. Changing keys and going outside the bounds of an array can lead to undefined behavior. + KeyValue *get_elements_ptr() { + return elements; + } + + // Returns the element index. If not found, returns -1. + int get_index(const TKey &p_key) { + uint32_t pos = 0; + uint32_t h_pos = 0; + bool exists = _lookup_pos(p_key, pos, h_pos); + if (!exists) { + return -1; + } + return pos; + } + + KeyValue &get_by_index(uint32_t p_index) { + CRASH_BAD_UNSIGNED_INDEX(p_index, num_elements); + return elements[p_index]; + } + + bool erase_by_index(uint32_t p_index) { + if (p_index >= size()) { + return false; + } + return erase(elements[p_index].key); + } + + /* Constructors */ + + AHashMap(const AHashMap &p_other) { + _init_from(p_other); + } + + AHashMap(const HashMap &p_other) { + reserve(p_other.size()); + for (const KeyValue &E : p_other) { + uint32_t hash = _hash(E.key); + _insert_element(E.key, E.value, hash); + } + } + + void operator=(const AHashMap &p_other) { + if (this == &p_other) { + return; // Ignore self assignment. + } + + reset(); + + _init_from(p_other); + } + + void operator=(const HashMap &p_other) { + reset(); + reserve(p_other.size()); + for (const KeyValue &E : p_other) { + uint32_t hash = _hash(E.key); + _insert_element(E.key, E.value, hash); + } + } + + AHashMap(uint32_t p_initial_capacity) { + // Capacity can't be 0 and must be 2^n - 1. + capacity = MAX(4u, p_initial_capacity); + capacity = next_power_of_2(capacity) - 1; + } + AHashMap() : + capacity(INITIAL_CAPACITY - 1) { + } + + AHashMap(std::initializer_list> p_init) { + reserve(p_init.size()); + for (const KeyValue &E : p_init) { + insert(E.key, E.value); + } + } + + void reset() { + if (elements != nullptr) { + if constexpr (!(std::is_trivially_destructible_v && std::is_trivially_destructible_v)) { + for (uint32_t i = 0; i < num_elements; i++) { + elements[i].key.~TKey(); + elements[i].value.~TValue(); + } + } + Memory::free_static(elements); + Memory::free_static(map_data); + elements = nullptr; + } + capacity = INITIAL_CAPACITY - 1; + num_elements = 0; + } + + ~AHashMap() { + reset(); + } +}; + +} //namespace godot diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index eedcf3275..1bd9c34d3 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -38,10 +38,10 @@ namespace godot { -std::unordered_map ClassDB::classes; -std::unordered_map ClassDB::instance_binding_callbacks; +AHashMap ClassDB::classes; +AHashMap ClassDB::instance_binding_callbacks; LocalVector ClassDB::class_register_order; -std::unordered_map ClassDB::engine_singletons; +AHashMap ClassDB::engine_singletons; std::mutex ClassDB::engine_singletons_mutex; GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE; @@ -114,9 +114,9 @@ MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_m ClassInfo *type = &classes[p_class]; while (type) { - std::unordered_map::iterator method = type->method_map.find(p_method); + AHashMap::Iterator method = type->method_map.find(p_method); if (method != type->method_map.end()) { - return method->second; + return method->value; } type = type->parent_ptr; continue; @@ -128,13 +128,13 @@ MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_m MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const void **p_defs, int p_defcount) { StringName instance_type = p_bind->get_instance_class(); - std::unordered_map::iterator type_it = classes.find(instance_type); + AHashMap::Iterator type_it = classes.find(instance_type); if (type_it == classes.end()) { memdelete(p_bind); ERR_FAIL_V_MSG(nullptr, String("Class '{0}' doesn't exist.").format(Array::make(instance_type))); } - ClassInfo &type = type_it->second; + ClassInfo &type = type_it->value; if (type.method_map.find(method_name.name) != type.method_map.end()) { memdelete(p_bind); @@ -233,11 +233,11 @@ void ClassDB::bind_method_godot(const StringName &p_class_name, MethodBind *p_me } void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) { - std::unordered_map::iterator type_it = classes.find(p_class); + AHashMap::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))); - ClassInfo &cl = type_it->second; + ClassInfo &cl = type_it->value; // Check if this signal is already register ClassInfo *check = &cl; @@ -268,11 +268,11 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) } void ClassDB::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) { - std::unordered_map::iterator type_it = classes.find(p_class_name); + AHashMap::Iterator type_it = classes.find(p_class_name); ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class_name))); - ClassInfo &type = type_it->second; + ClassInfo &type = type_it->value; // check if it already exists ERR_FAIL_COND_MSG(type.constant_names.find(p_constant_name) != type.constant_names.end(), String("Constant '{0}::{1}' already registered.").format(Array::make(p_class_name, p_constant_name))); @@ -290,17 +290,17 @@ GDExtensionClassCallVirtual ClassDB::get_virtual_func(void *p_userdata, GDExtens const StringName *class_name = reinterpret_cast(p_userdata); const StringName *name = reinterpret_cast(p_name); - std::unordered_map::iterator type_it = classes.find(*class_name); + AHashMap::Iterator type_it = classes.find(*class_name); ERR_FAIL_COND_V_MSG(type_it == classes.end(), nullptr, String("Class '{0}' doesn't exist.").format(Array::make(*class_name))); - const ClassInfo *type = &type_it->second; + const ClassInfo *type = &type_it->value; // Find method in current class, or any of its parent classes (Godot classes not included) while (type != nullptr) { - std::unordered_map::const_iterator method_it = type->virtual_methods.find(*name); + AHashMap::ConstIterator method_it = type->virtual_methods.find(*name); - if (method_it != type->virtual_methods.end() && method_it->second.hash == p_hash) { - return method_it->second.func; + if (method_it != type->virtual_methods.end() && method_it->value.hash == p_hash) { + return method_it->value.func; } type = type->parent_ptr; @@ -310,9 +310,9 @@ GDExtensionClassCallVirtual ClassDB::get_virtual_func(void *p_userdata, GDExtens } const GDExtensionInstanceBindingCallbacks *ClassDB::get_instance_binding_callbacks(const StringName &p_class) { - std::unordered_map::iterator callbacks_it = instance_binding_callbacks.find(p_class); + AHashMap::Iterator callbacks_it = instance_binding_callbacks.find(p_class); if (likely(callbacks_it != instance_binding_callbacks.end())) { - return callbacks_it->second; + return callbacks_it->value; } // If we don't have an instance binding callback for the given class, find the closest parent where we do. @@ -323,14 +323,14 @@ const GDExtensionInstanceBindingCallbacks *ClassDB::get_instance_binding_callbac callbacks_it = instance_binding_callbacks.find(class_name); } while (callbacks_it == instance_binding_callbacks.end()); - return callbacks_it->second; + return callbacks_it->value; } void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p_method, GDExtensionClassCallVirtual p_call, uint32_t p_hash) { - std::unordered_map::iterator type_it = classes.find(p_class); + AHashMap::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))); - ClassInfo &type = type_it->second; + ClassInfo &type = type_it->value; ERR_FAIL_COND_MSG(type.method_map.find(p_method) != type.method_map.end(), String("Method '{0}::{1}()' already registered as non-virtual.").format(Array::make(p_class, p_method))); ERR_FAIL_COND_MSG(type.virtual_methods.find(p_method) != type.virtual_methods.end(), String("Virtual '{0}::{1}()' method already registered.").format(Array::make(p_class, p_method))); @@ -342,7 +342,7 @@ void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p } 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); + AHashMap::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; @@ -390,8 +390,8 @@ void ClassDB::initialize_class(const ClassInfo &p_cl) { } void ClassDB::initialize(GDExtensionInitializationLevel p_level) { - for (const std::pair &pair : classes) { - const ClassInfo &cl = pair.second; + for (const KeyValue &pair : classes) { + const ClassInfo &cl = pair.value; if (cl.level != p_level) { continue; } @@ -412,8 +412,8 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { internal::gdextension_interface_classdb_unregister_extension_class(internal::library, name._native_ptr()); - for (const std::pair &method : cl.method_map) { - memdelete(method.second); + for (const KeyValue &method : cl.method_map) { + memdelete(method.value); } classes.erase(name); @@ -433,8 +433,8 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) { { 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 (const KeyValue &pair : engine_singletons) { + singleton_objects.push_back(pair.value); } } for (const Object *i : singleton_objects) { From d3adff00631eeac7d3a2ded3cfbf33427d6a5081 Mon Sep 17 00:00:00 2001 From: dementive <87823030+dementive@users.noreply.github.com> Date: Sat, 4 Oct 2025 17:43:25 -0500 Subject: [PATCH 07/10] Update missing hashfuncs (cherry picked from commit fc70347ef7546d61bb2264d79b3e74d6b66ef7d1) --- include/godot_cpp/templates/hashfuncs.hpp | 16 ++++ include/godot_cpp/templates/pair.hpp | 102 +++++++++------------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/include/godot_cpp/templates/hashfuncs.hpp b/include/godot_cpp/templates/hashfuncs.hpp index 566b8c71a..0413e57c9 100644 --- a/include/godot_cpp/templates/hashfuncs.hpp +++ b/include/godot_cpp/templates/hashfuncs.hpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -319,6 +320,13 @@ struct HashMapHasherDefault { template static _FORCE_INLINE_ uint32_t hash(const Ref &p_ref) { return hash_one_uint64((uint64_t)p_ref.operator->()); } + template + static _FORCE_INLINE_ uint32_t hash(const Pair &p_pair) { + uint64_t h1 = hash(p_pair.first); + uint64_t h2 = hash(p_pair.second); + return hash_one_uint64((h1 << 32) | h2); + } + static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); } static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); } static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return hash_fmix32(uint32_t(p_wchar)); } @@ -329,6 +337,7 @@ struct HashMapHasherDefault { static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); } static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); } static _FORCE_INLINE_ uint32_t hash(const ObjectID &p_id) { return hash_one_uint64(p_id); } + static _FORCE_INLINE_ uint32_t hash(const Callable &p_callable) { return p_callable.hash(); } static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); } static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash_one_uint64(uint64_t(p_int)); } @@ -376,6 +385,13 @@ struct HashMapHasherDefault { h = hash_murmur3_one_real(p_vec.w, h); return hash_fmix32(h); } + static _FORCE_INLINE_ uint32_t hash(const Color &p_vec) { + uint32_t h = hash_murmur3_one_float(p_vec.r); + h = hash_murmur3_one_float(p_vec.g, h); + h = hash_murmur3_one_float(p_vec.b, h); + h = hash_murmur3_one_float(p_vec.a, h); + return hash_fmix32(h); + } static _FORCE_INLINE_ uint32_t hash(const Rect2i &p_rect) { uint32_t h = hash_murmur3_one_32(uint32_t(p_rect.position.x)); h = hash_murmur3_one_32(uint32_t(p_rect.position.y), h); diff --git a/include/godot_cpp/templates/pair.hpp b/include/godot_cpp/templates/pair.hpp index 15c7c3567..06d73e9fc 100644 --- a/include/godot_cpp/templates/pair.hpp +++ b/include/godot_cpp/templates/pair.hpp @@ -30,86 +30,70 @@ #pragma once -#include +#include namespace godot { template struct Pair { - F first; - S second; - - Pair() : - first(), - second() { - } - - Pair(F p_first, const S &p_second) : - first(p_first), - second(p_second) { - } + F first{}; + S second{}; + + constexpr Pair() = default; + constexpr Pair(const F &p_first, const S &p_second) : + first(p_first), second(p_second) {} + + constexpr bool operator==(const Pair &p_other) const { return first == p_other.first && second == p_other.second; } + constexpr bool operator!=(const Pair &p_other) const { return first != p_other.first || second != p_other.second; } + constexpr bool operator<(const Pair &p_other) const { return first == p_other.first ? (second < p_other.second) : (first < p_other.first); } + constexpr bool operator<=(const Pair &p_other) const { return first == p_other.first ? (second <= p_other.second) : (first < p_other.first); } + constexpr bool operator>(const Pair &p_other) const { return first == p_other.first ? (second > p_other.second) : (first > p_other.first); } + constexpr bool operator>=(const Pair &p_other) const { return first == p_other.first ? (second >= p_other.second) : (first > p_other.first); } }; -template -bool operator==(const Pair &pair, const Pair &other) { - return (pair.first == other.first) && (pair.second == other.second); -} - -template -bool operator!=(const Pair &pair, const Pair &other) { - return (pair.first != other.first) || (pair.second != other.second); -} - template struct PairSort { - bool operator()(const Pair &A, const Pair &B) const { - if (A.first != B.first) { - return A.first < B.first; - } - return A.second < B.second; + constexpr bool operator()(const Pair &p_lhs, const Pair &p_rhs) const { + return p_lhs < p_rhs; } }; +// Pair is zero-constructible if and only if both constrained types are zero-constructible. template -struct PairHash { - static uint32_t hash(const Pair &P) { - uint64_t h1 = HashMapHasherDefault::hash(P.first); - uint64_t h2 = HashMapHasherDefault::hash(P.second); - return hash_one_uint64((h1 << 32) | h2); - } -}; +struct is_zero_constructible> : std::conjunction, is_zero_constructible> {}; template struct KeyValue { - const K key; - V value; - - void operator=(const KeyValue &p_kv) = delete; - _FORCE_INLINE_ KeyValue(const KeyValue &p_kv) : - key(p_kv.key), - value(p_kv.value) { - } - _FORCE_INLINE_ KeyValue(const K &p_key, const V &p_value) : - key(p_key), - value(p_value) { - } + const K key{}; + V value{}; + + KeyValue &operator=(const KeyValue &p_kv) = delete; + KeyValue &operator=(KeyValue &&p_kv) = delete; + + constexpr KeyValue(const KeyValue &p_kv) = default; + constexpr KeyValue(KeyValue &&p_kv) = default; + constexpr KeyValue(const K &p_key, const V &p_value) : + key(p_key), value(p_value) {} + constexpr KeyValue(const Pair &p_pair) : + key(p_pair.first), value(p_pair.second) {} + + constexpr bool operator==(const KeyValue &p_other) const { return key == p_other.key && value == p_other.value; } + constexpr bool operator!=(const KeyValue &p_other) const { return key != p_other.key || value != p_other.value; } + constexpr bool operator<(const KeyValue &p_other) const { return key == p_other.key ? (value < p_other.value) : (key < p_other.key); } + constexpr bool operator<=(const KeyValue &p_other) const { return key == p_other.key ? (value <= p_other.value) : (key < p_other.key); } + constexpr bool operator>(const KeyValue &p_other) const { return key == p_other.key ? (value > p_other.value) : (key > p_other.key); } + constexpr bool operator>=(const KeyValue &p_other) const { return key == p_other.key ? (value >= p_other.value) : (key > p_other.key); } }; -template -bool operator==(const KeyValue &pair, const KeyValue &other) { - return (pair.key == other.key) && (pair.value == other.value); -} - -template -bool operator!=(const KeyValue &pair, const KeyValue &other) { - return (pair.key != other.key) || (pair.value != other.value); -} - template struct KeyValueSort { - bool operator()(const KeyValue &A, const KeyValue &B) const { - return A.key < B.key; + constexpr bool operator()(const KeyValue &p_lhs, const KeyValue &p_rhs) const { + return p_lhs.key < p_rhs.key; } }; +// KeyValue is zero-constructible if and only if both constrained types are zero-constructible. +template +struct is_zero_constructible> : std::conjunction, is_zero_constructible> {}; + } // namespace godot From 8ef809b9b63f3708574a131bc6699254eaf53b9f Mon Sep 17 00:00:00 2001 From: David Snopek Date: Fri, 17 Oct 2025 15:07:22 -0500 Subject: [PATCH 08/10] Specifically handle each "meta" value, so new ones don't break code generation (cherry picked from commit a9773579cb78f7d84d01c6948175ab455349d77b) --- binding_generator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index 4032cba3f..7df2de321 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -2746,12 +2746,12 @@ def correct_typed_dictionary(type_name): def correct_type(type_name, meta=None, use_alias=True): type_conversion = {"float": "double", "int": "int64_t", "Nil": "Variant"} if meta is not None: - if "int" in meta: + if meta in ["int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64"]: return f"{meta}_t" - elif "char" in meta: - return f"{meta}_t" - else: + elif meta in ["float", "double"]: return meta + elif meta in ["char16", "char32"]: + return f"{meta}_t" if type_name in type_conversion: return type_conversion[type_name] if type_name.startswith("typedarray::"): From c1b2eb0d5a39fca4c01af13c31a0b9473b232bd7 Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Sun, 5 Oct 2025 11:10:29 +0200 Subject: [PATCH 09/10] Migrate cmake docs to the godot docs. (cherry picked from commit c7873e1355b6830a0166607699981afdea944525) --- CMakeLists.txt | 3 +- cmake/android.cmake | 6 +- cmake/linux.cmake | 3 +- cmake/windows.cmake | 4 +- doc/cmake.rst | 353 -------------------------------------------- 5 files changed, 10 insertions(+), 359 deletions(-) delete mode 100644 doc/cmake.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c2be6d8c..0d6405782 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,8 @@ compromises need to be made to resolve those differences. As we are attempting to maintain feature parity, and ease of maintenance, these CMake scripts are built to resemble the SCons build system wherever possible. Where they are not, we will attempt to document common difference in -doc/cmake.rst and platform specific differences in their respective +the docs (https://docs.godotengine.org/en/latest/tutorials/scripting/cpp/build_system/cmake.html) +and platform specific differences in their respective cmake/.cmake file. The file structure and file content are made to match, if not in content then diff --git a/cmake/android.cmake b/cmake/android.cmake index 1d3a93b68..f3cddb4c1 100644 --- a/cmake/android.cmake +++ b/cmake/android.cmake @@ -22,7 +22,7 @@ Android platforms. .. _built-in support:https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android -There is further information and examples in the doc/cmake.rst file. +There is further information and examples in the docs: https://docs.godotengine.org/en/latest/tutorials/scripting/cpp/build_system/cmake.html ]=======================================================================] @@ -30,8 +30,8 @@ There is further information and examples in the doc/cmake.rst file. function(android_options) #[[ Options from SCons - The options below are managed by CMake toolchain files, doc.cmake.rst has - more information + The options below are managed by CMake toolchain files, the docs have more information: + https://docs.godotengine.org/en/latest/tutorials/scripting/cpp/build_system/cmake.html android_api_level : Target Android API level. Default = 21 diff --git a/cmake/linux.cmake b/cmake/linux.cmake index f01d75da9..1fe163836 100644 --- a/cmake/linux.cmake +++ b/cmake/linux.cmake @@ -12,7 +12,8 @@ function(linux_options) #[[ Options from SCons use_llvm : Use the LLVM compiler Not implemented as compiler selection is managed by CMake. Look to - doc/cmake.rst for examples. + the docs (https://docs.godotengine.org/en/latest/tutorials/scripting/cpp/build_system/cmake.html) + for examples. ]] option(GODOTCPP_USE_STATIC_CPP "Link libgcc and libstdc++ statically for better portability" ON) endfunction() diff --git a/cmake/windows.cmake b/cmake/windows.cmake index 75ae4707e..7478a5a01 100644 --- a/cmake/windows.cmake +++ b/cmake/windows.cmake @@ -62,7 +62,9 @@ function(windows_options) Default: True These three options will not implemented as compiler selection is managed - by CMake toolchain files. Look to doc/cmake.rst for examples. + by CMake toolchain files. Look to the docs + (https://docs.godotengine.org/en/latest/tutorials/scripting/cpp/build_system/cmake.html) + for examples. use_mingw: Use the MinGW compiler instead of MSVC - only effective on Windows use_llvm: Use the LLVM compiler (MVSC or MinGW depending on the use_mingw flag mingw_prefix: MinGW prefix diff --git a/doc/cmake.rst b/doc/cmake.rst deleted file mode 100644 index 760d45611..000000000 --- a/doc/cmake.rst +++ /dev/null @@ -1,353 +0,0 @@ -CMake -===== - -.. warning:: - - The CMake scripts do not have feature parity with the SCons ones at this - stage and are still a work in progress. There are a number of people who - have been working on alternative CMake solutions that are frequently - referenced in the discord chats: Ivan's cmake-rewrite_ branch and - Vorlac's godot-roguelite_ Project - -.. _cmake-rewrite: https://github.com/IvanInventor/godot-cpp/tree/cmake-rewrite -.. _godot-roguelite: https://github.com/vorlac/godot-roguelite - -Introduction ------------- - -Compiling godot-cpp independently of an extension project is mainly for -godot-cpp developers, package maintainers, and CI/CD. Look to the -godot-cpp-template_ for a practical example on how to consume the godot-cpp -library as part of a Godot extension. - -Configuration examples are listed at the bottom of the page. - -.. _godot-cpp-template: https://github.com/godotengine/godot-cpp-template - -Debug vs template_debug ------------------------ - -Something I've seen come up many times is the conflation of a compilation of c++ -source code with debug symbols enabled, and compiling a Godot extension with -debug features enabled. The two concepts are not mutually inclusive. - -- debug_features - Enables a pre-processor definition to selectively compile code to help - users of a Godot extension with their own project. - - debug features are enabled in editor and template_debug builds, which can be specified during the configure phase like so - - ``cmake -S . -B cmake-build -DGODOTCPP_TARGET=`` - -- Debug - Sets compiler flags so that debug symbols are generated to help godot - extension developers debug their extension. - - ``Debug`` is the default build type for CMake projects, to select another it depends on the generator used - - For single configuration generators, add to the configure command: - - ``-DCMAKE_BUILD_TYPE=`` - - For multi-config generators add to the build command: - - ``--config `` - - where ```` is one of ``Debug``, ``Release``, ``RelWithDebInfo``, ``MinSizeRel`` - - -SCons Deviations ----------------- - -Not everything from SCons can be perfectly representable in CMake, here are -the notable differences. - -- debug_symbols - No longer has an explicit option, and is enabled via Debug-like CMake - build configurations; ``Debug``, ``RelWithDebInfo``. - -- dev_build - Does not define ``NDEBUG`` when disabled, ``NDEBUG`` is set via Release-like - CMake build configurations; ``Release``, ``MinSizeRel``. - -- arch - CMake sets the architecture via the toolchain files, macos universal is controlled vua the ``CMAKE_OSX_ARCHITECTURES`` - property which is copied to targets when they are defined. - -- debug_crt - CMake controls linking to windows runtime libraries by copying the value of ``CMAKE_MSVC_RUNTIME_LIBRARIES`` to targets as they are defined. - godot-cpp will set this variable if it isn't already set. so include it before other dependencies to have the value propagate across the projects. - -Testing Integration -------------------- -The testing target ``godot-cpp-test`` is guarded by ``GODOTCPP_ENABLE_TESTING`` which is off by default. - -To configure and build the godot-cpp project to enable the integration -testing targets the command will look something like: - -.. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build -DGODOTCPP_ENABLE_TESTING=YES - cmake --build cmake-build --target godot-cpp-test - -Basic walkthrough ------------------ - -.. topic:: Clone the git repository - - .. code-block:: - - git clone https://github.com/godotengine/godot-cpp.git - Cloning into 'godot-cpp'... - ... - cd godot-cpp - -.. topic:: Options - - To list the available options CMake use the ``-L[AH]`` option. ``A`` is for - advanced, and ``H`` is for help strings. - - .. code-block:: - - cmake .. -LH - - Options are specified on the command line when configuring eg. - - .. code-block:: - - cmake .. -DGODOTCPP_USE_HOT_RELOAD:BOOL=ON \ - -DGODOTCPP_PRECISION:STRING=double \ - -DCMAKE_BUILD_TYPE:STRING=Debug - - Review setting-build-variables_ and build-configurations_ for more information. - - .. _setting-build-variables: https://cmake.org/cmake/help/latest/guide/user-interaction/index.html#setting-build-variables - .. _build-configurations: https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#build-configurations - - A non-exhaustive list of options: - - .. code-block:: - - // Path to a custom GDExtension API JSON file (takes precedence over `GODOTCPP_GDEXTENSION_DIR`) ( /path/to/custom_api_file ) - `GODOTCPP_CUSTOM_API_FILE:FILEPATH=` - - // Force disabling exception handling code (ON|OFF) - GODOTCPP_DISABLE_EXCEPTIONS:BOOL=ON - - // Path to a custom directory containing GDExtension interface header and API JSON file ( /path/to/gdextension_dir ) - GODOTCPP_GDEXTENSION_DIR:PATH=gdextension - - // Set the floating-point precision level (single|double) - GODOTCPP_PRECISION:STRING=single - - // Enable the extra accounting required to support hot reload. (ON|OFF) - GODOTCPP_USE_HOT_RELOAD:BOOL= - -.. topic:: Configure the build - - .. code-block:: - - cmake -S . -B cmake-build -G Ninja - - ``-S .`` Specifies the source directory - - ``-B cmake-build`` Specifies the build directory - - ``-G Ninja`` Specifies the Generator - - The source directory in this example is the source code for godot-cpp. - The build directory is so that generated files do not clutter up the source tree. - CMake doesn't build the code, it generates the files that another tool uses - to build the code, in this case Ninja. - To see the list of generators run ``cmake --help``. - -.. topic:: Compiling - - Tell cmake to invoke the build system it generated in the specified directory. - The default target is template_debug and the default build configuration is Debug. - - .. code-block:: - - cmake --build cmake-build - -Examples --------- - -Windows and MSVC - Release -~~~~~~~~~~~~~~~~~~~~~~~~~~ -So long as CMake is installed from the `CMake Downloads`_ page and in the PATH, -and Microsoft Visual Studio is installed with c++ support, CMake will detect -the MSVC compiler. - -Note that Visual Studio is a Multi-Config Generator so the build configuration -needs to be specified at build time ie ``--config Release`` - -.. _CMake downloads: https://cmake.org/download/ - -.. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build -DGODOTCPP_ENABLE_TESTING=YES - cmake --build cmake-build -t godot-cpp-test --config Release - - -MSys2/clang64, "Ninja" - Debug -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Assumes the ming-w64-clang-x86_64-toolchain is installed - -Note that Ninja is a Single-Config Generator so the build type -needs to be specified at Configure time. - -Using the msys2/clang64 shell - -.. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build -G"Ninja" -DGODOTCPP_ENABLE_TESTING=YES -DCMAKE_BUILD_TYPE=Release - cmake --build cmake-build -t godot-cpp-test - -MSys2/clang64, "Ninja Multi-Config" - dev_build, Debug Symbols -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Assumes the ming-w64-clang-x86_64-toolchain is installed - -This time we are choosing the 'Ninja Multi-Config' generator, so the build -type is specified at build time. - -Using the msys2/clang64 shell - -.. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build -G"Ninja Multi-Config" -DGODOTCPP_ENABLE_TESTING=YES -DGODOTCPP_DEV_BUILD:BOOL=ON - cmake --build cmake-build -t godot-cpp-test --config Debug - -Emscripten for web platform -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -I've only tested this on windows so far. - -I cloned and installed the latest Emscripten tools to ``c:\emsdk`` -At the time of writing that was v3.1.69 - -I've been using ``C:\emsdk\emsdk.ps1 activate latest`` to enable the -environment from powershell in the current shell. - -The ``emcmake.bat`` utility adds the emscripten toolchain to the CMake command -It can also be added manually, the location is listed inside the emcmake.bat file - -.. code-block:: - - # Assuming our current directory is the godot-cpp source root - C:\emsdk\emsdk.ps1 activate latest - emcmake.bat cmake -S . -B cmake-build-web -DCMAKE_BUILD_TYPE=Release - cmake --build cmake-build-web - -Android Cross Compile from Windows -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -There are two separate paths you can choose when configuring for android. - -Use the ``CMAKE_ANDROID_*`` variables specified on the commandline or in your -own toolchain file as listed in the cmake-toolchains_ documentation - -.. _cmake-toolchains: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android-with-the-ndk - -Or use the toolchain and scripts provided by the Android SDK and make changes -using the ``ANDROID_*`` variables listed there. Where ```` is whatever -ndk version you have installed (tested with `23.2.8568313`) and ```` -is for android sdk platform, (tested with ``android-29``) - -.. warning:: - - The Android SDK website explicitly states that they do not support using - the CMake built-in method, and recommends you stick with their toolchain - files. - -.. topic:: Using your own toolchain file as described in the CMake documentation - - .. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build --toolchain my_toolchain.cmake - cmake --build cmake-build -t template_release - - Doing the equivalent on just using the command line - - .. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build \ - -DCMAKE_SYSTEM_NAME=Android \ - -DCMAKE_SYSTEM_VERSION= \ - -DCMAKE_ANDROID_ARCH_ABI= \ - -DCMAKE_ANDROID_NDK=/path/to/android-ndk - cmake --build cmake-build - -.. topic:: Using the toolchain file from the Android SDK - - Defaults to minimum supported version( android-16 in my case) and armv7-a. - - .. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build --toolchain $ANDROID_HOME/ndk//build/cmake/android.toolchain.cmake - cmake --build cmake-build - - Specify Android platform and ABI - - .. code-block:: - - # Assuming our current directory is the godot-cpp source root - cmake -S . -B cmake-build --toolchain $ANDROID_HOME/ndk//build/cmake/android.toolchain.cmake \ - -DANDROID_PLATFORM:STRING=android-29 \ - -DANDROID_ABI:STRING=armeabi-v7a - cmake --build cmake-build - - -Toolchains ----------- -This section attempts to list the host and target combinations that have been -at tested. - -Linux Host -~~~~~~~~~~ - -Macos Host -~~~~~~~~~~ - -:System: Mac Mini -:OS Name: Sequoia 15.0.1 -:Processor: Apple M2 - -* AppleClang - -Windows Host -~~~~~~~~~~~~ - -:OS Name: Windows 11 -:Processor: AMD Ryzen 7 6800HS Creator Edition - - -* `Microsoft Visual Studio 17 2022 `_ -* `LLVM `_ -* `LLVM-MinGW `_ - - * aarch64-w64-mingw32 - * armv7-w64-mingw32 - * i686-w64-mingw32 - * x86_64-w64-mingw32 - -* `AndroidSDK `_ -* `Emscripten `_ -* `MinGW-W64-builds `_ -* `Jetbrains-CLion `_ - - Jetbrains builtin compiler is just the MingW64 above. - -* `MSYS2 `_ - Necessary reading about MSYS2 `environments `_ - - * ucrt64 - * clang64 - * mingw32 - * mingw64 - * clangarm64 From 33ecd5bf8a328fd1dcfa8a379fe01694d2e4da8d Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Fri, 6 Jun 2025 20:00:14 -0700 Subject: [PATCH 10/10] Add header builders script for env.GLSL_HEADER and SVG icons (cherry picked from commit 51008e155668825352a1042f93f51eb384cff1b7) --- tools/godotcpp.py | 5 +++ tools/header_builders.py | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tools/header_builders.py diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 7be96247b..8ca48b332 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -2,6 +2,7 @@ import platform import sys +import header_builders from SCons import __version__ as scons_raw_version from SCons.Action import Action from SCons.Builder import Builder @@ -529,6 +530,10 @@ def generate(env): BUILDERS={ "GodotCPPBindings": Builder(action=Action(scons_generate_bindings, "$GENCOMSTR"), emitter=scons_emit_files), "GodotCPPDocData": Builder(action=scons_generate_doc_source), + "GLSL_HEADER": Builder( + action=header_builders.build_raw_headers_action, + suffix="glsl.gen.h", + ), } ) env.AddMethod(_godot_cpp, "GodotCPP") diff --git a/tools/header_builders.py b/tools/header_builders.py new file mode 100644 index 000000000..498170659 --- /dev/null +++ b/tools/header_builders.py @@ -0,0 +1,69 @@ +import os.path + + +## See https://github.com/godotengine/godot/blob/master/glsl_builders.py +def build_raw_header(source_filename: str, constant_name: str) -> None: + # Read the source file content. + with open(source_filename, "r") as source_file: + source_content = source_file.read() + constant_name = constant_name.replace(".", "_") + # Build header content using a C raw string literal. + header_content = ( + "/* THIS FILE IS GENERATED. EDITS WILL BE LOST. */\n\n" + "#pragma once\n\n" + f"inline constexpr const char *{constant_name}" + " = " + f'R"({source_content})"' + ";\n" + ) + # Write the header to the provided file name with a ".gen.h" suffix. + header_filename = f"{source_filename}.gen.h" + with open(header_filename, "w") as header_file: + header_file.write(header_content) + + +def build_raw_headers_action(target, source, env): + env.NoCache(target) + for src in source: + source_filename = str(src) + # To match Godot, replace ".glsl" with "_shader_glsl". Does nothing for non-GLSL files. + constant_name = os.path.basename(source_filename).replace(".glsl", "_shader_glsl") + build_raw_header(source_filename, constant_name) + + +def escape_svg(filename: str) -> str: + with open(filename, encoding="utf-8", newline="\n") as svg_file: + svg_content = svg_file.read() + return f'R"({svg_content})"' + + +## See https://github.com/godotengine/godot/blob/master/editor/icons/editor_icons_builders.py +## See https://github.com/godotengine/godot/blob/master/scene/theme/icons/default_theme_icons_builders.py +def make_svg_icons_action(target, source, env): + destination = str(target[0]) + constant_prefix = os.path.basename(destination).replace(".gen.h", "") + svg_icons = [str(x) for x in source] + # Convert the SVG icons to escaped strings and convert their names to C strings. + icon_names = [f'"{os.path.basename(fname)[:-4]}"' for fname in svg_icons] + icon_sources = [escape_svg(fname) for fname in svg_icons] + # Join them as indented comma-separated items for use in an array initializer. + icon_names_str = ",\n\t".join(icon_names) + icon_sources_str = ",\n\t".join(icon_sources) + # Write the file to disk. + with open(destination, "w", encoding="utf-8", newline="\n") as destination_file: + destination_file.write( + f"""\ +/* THIS FILE IS GENERATED. EDITS WILL BE LOST. */ + +#pragma once + +inline constexpr int {constant_prefix}_count = {len(icon_names)}; +inline constexpr const char *{constant_prefix}_sources[] = {{ + {icon_sources_str} +}}; + +inline constexpr const char *{constant_prefix}_names[] = {{ + {icon_names_str} +}}; +""" + )