From aeb8be98ed1e9ab930df78f9579d5c4aa4126dc7 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Tue, 30 Apr 2024 19:06:51 +0200 Subject: [PATCH 01/14] [SCons] Add option to build without threads This is relevant for the Web platform, where builds with and without threads are incompatible. (cherry picked from commit b0296bb562af0ff812b732586c06ae6b3f96ef84) --- test/project/example.gdextension | 6 ++++-- tools/godotcpp.py | 7 +++++++ tools/web.py | 5 +++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/test/project/example.gdextension b/test/project/example.gdextension index 4f599ced2..8e2f794d1 100644 --- a/test/project/example.gdextension +++ b/test/project/example.gdextension @@ -29,8 +29,10 @@ android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so" android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so" ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework" ios.release = "res://bin/libgdexample.ios.template_release.xcframework" -web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm" -web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm" +web.debug.threads.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm" +web.release.threads.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm" +web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.nothreads.wasm" +web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.nothreads.wasm" [dependencies] ios.debug = { diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 87a426c6d..c2af52677 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -267,6 +267,8 @@ def options(opts, env): ) ) + opts.Add(BoolVariable(key="threads", help="Enable threading support", default=env.get("threads", True))) + # compiledb opts.Add( BoolVariable( @@ -403,6 +405,9 @@ def generate(env): tool.generate(env) + if env["threads"]: + env.Append(CPPDEFINES=["THREADS_ENABLED"]) + if env.use_hot_reload: env.Append(CPPDEFINES=["HOT_RELOAD_ENABLED"]) @@ -446,6 +451,8 @@ def generate(env): suffix += "." + env["arch"] if env["ios_simulator"]: suffix += ".simulator" + if not env["threads"]: + suffix += ".nothreads" env["suffix"] = suffix # Exposed when included from another project env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] diff --git a/tools/web.py b/tools/web.py index 79b4b24ce..8953325c6 100644 --- a/tools/web.py +++ b/tools/web.py @@ -34,8 +34,9 @@ def generate(env): env["SHLIBSUFFIX"] = ".wasm" # Thread support (via SharedArrayBuffer). - env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) + if env["threads"]: + env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) # Build as side module (shared library). env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"]) From 806c38e189de3296b095f585591f51cf0a1ebc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Verschelde?= Date: Thu, 18 Jul 2024 10:35:16 +0200 Subject: [PATCH 02/14] SCons: Remove old Python 2 compat code (cherry picked from commit 958776dfc3ea845e3a92384cf5b57b54229d9b28) --- tools/ios.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/tools/ios.py b/tools/ios.py index 7240a97aa..9675ab1ac 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -1,3 +1,4 @@ +import codecs import os import subprocess import sys @@ -5,17 +6,6 @@ import common_compiler_flags from SCons.Variables import BoolVariable -if sys.version_info < (3,): - - def decode_utf8(x): - return x - -else: - import codecs - - def decode_utf8(x): - return codecs.utf_8_decode(x)[0] - def has_ios_osxcross(): return "OSXCROSS_IOS" in os.environ @@ -53,9 +43,9 @@ def generate(env): if sys.platform == "darwin": if env["IOS_SDK_PATH"] == "": try: - env["IOS_SDK_PATH"] = decode_utf8( + env["IOS_SDK_PATH"] = codecs.utf_8_decode( subprocess.check_output(["xcrun", "--sdk", sdk_name, "--show-sdk-path"]).strip() - ) + )[0] except (subprocess.CalledProcessError, OSError): raise ValueError( "Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name) From 18321318bbc11826a46c9034ac64b1aaa7bf47b5 Mon Sep 17 00:00:00 2001 From: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Date: Sat, 13 Jul 2024 16:24:13 +0200 Subject: [PATCH 03/14] [CI] Upload build cache before running tests (cherry picked from commit 76b38de01a31c5c1762c4b8239e7a51ebea84bef) --- .../action.yml | 9 ++++----- .github/actions/godot-cache-save/action.yml | 17 +++++++++++++++++ .github/workflows/ci.yml | 10 ++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) rename .github/actions/{godot-cache => godot-cache-restore}/action.yml (77%) create mode 100644 .github/actions/godot-cache-save/action.yml diff --git a/.github/actions/godot-cache/action.yml b/.github/actions/godot-cache-restore/action.yml similarity index 77% rename from .github/actions/godot-cache/action.yml rename to .github/actions/godot-cache-restore/action.yml index 2d7afc851..5df577656 100644 --- a/.github/actions/godot-cache/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -1,5 +1,5 @@ -name: Setup Godot build cache -description: Setup Godot build cache. +name: Restore Godot build cache +description: Restore Godot build cache. inputs: cache-name: description: The cache base name (job name by default). @@ -10,9 +10,8 @@ inputs: runs: using: "composite" steps: - # Upload cache on completion and check it out now - - name: Load .scons_cache directory - uses: actions/cache@v3 + - name: Restore .scons_cache directory + uses: actions/cache/restore@v3 with: path: ${{inputs.scons-cache}} key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml new file mode 100644 index 000000000..b7cbf91f9 --- /dev/null +++ b/.github/actions/godot-cache-save/action.yml @@ -0,0 +1,17 @@ +name: Save Godot build cache +description: Save Godot build cache. +inputs: + cache-name: + description: The cache base name (job name by default). + default: "${{github.job}}" + scons-cache: + description: The SCons cache path. + default: "${{github.workspace}}/.scons-cache/" +runs: + using: "composite" + steps: + - name: Save SCons cache directory + uses: actions/cache/save@v4 + with: + path: ${{inputs.scons-cache}} + key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1012d8a5..02ee1dd65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,8 +99,8 @@ jobs: with: submodules: recursive - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore with: cache-name: ${{ matrix.cache-name }} continue-on-error: true @@ -153,6 +153,12 @@ jobs: cd test scons platform=${{ matrix.platform }} verbose=yes target=template_release ${{ matrix.flags }} + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + with: + cache-name: ${{ matrix.cache-name }} + continue-on-error: true + - name: Download latest Godot artifacts uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9 if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }} From 13299e810bdfc0031560f501b6e19c663f73d8fe Mon Sep 17 00:00:00 2001 From: Joakim Stien Date: Sat, 9 Dec 2023 15:13:32 +0100 Subject: [PATCH 04/14] Added hot reload support to CMakeLists.txt (cherry picked from commit 31179ee47c9b6a0d59dc09467b5c2619e6853427) --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7884a06b9..4240625ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ project(godot-cpp LANGUAGES CXX) option(GENERATE_TEMPLATE_GET_NODE "Generate a template version of the Node class's get_node." ON) option(GODOT_CPP_SYSTEM_HEADERS "Expose headers as SYSTEM." ON) option(GODOT_CPP_WARNING_AS_ERROR "Treat warnings as errors" OFF) +option(GODOT_ENABLE_HOT_RELOAD "Build with hot reload support" OFF) # Add path to modules list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) @@ -116,6 +117,10 @@ else() endif() endif() +if (GODOT_ENABLE_HOT_RELOAD) + set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -D HOT_RELOAD_ENABLED") +endif() + # Generate source from the bindings file find_package(Python3 3.4 REQUIRED) # pathlib should be present if(GENERATE_TEMPLATE_GET_NODE) From 90e0c1515d4840aa49ef717bb0f3f58768a5ac69 Mon Sep 17 00:00:00 2001 From: Joakim Stien Date: Sun, 10 Dec 2023 11:25:38 +0100 Subject: [PATCH 05/14] =?UTF-8?q?PR=20comments=20=E2=80=94=20added=20doc,?= =?UTF-8?q?=20default=20'ON'=20in=20Debug,=20'OFF'=20in=20Release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 0a078d9ec95baba15783bfe4159b6377380cb3cf) --- CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4240625ed..96090611d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ # GODOT_GDEXTENSION_DIR: Path to the directory containing GDExtension interface header and API JSON file # GODOT_CPP_SYSTEM_HEADERS Mark the header files as SYSTEM. This may be useful to suppress warnings in projects including this one. # GODOT_CPP_WARNING_AS_ERROR Treat any warnings as errors +# GODOT_ENABLE_HOT_RELOAD Build with hot reload support. Defaults to YES for Debug-builds and NO for Release-builds. # GODOT_CUSTOM_API_FILE: Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`) # FLOAT_PRECISION: Floating-point precision level ("single", "double") # @@ -43,7 +44,6 @@ project(godot-cpp LANGUAGES CXX) option(GENERATE_TEMPLATE_GET_NODE "Generate a template version of the Node class's get_node." ON) option(GODOT_CPP_SYSTEM_HEADERS "Expose headers as SYSTEM." ON) option(GODOT_CPP_WARNING_AS_ERROR "Treat warnings as errors" OFF) -option(GODOT_ENABLE_HOT_RELOAD "Build with hot reload support" OFF) # Add path to modules list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) @@ -58,6 +58,13 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "") set(CMAKE_BUILD_TYPE Debug) endif() +# Hot reload is enabled by default in Debug-builds +if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + option(GODOT_ENABLE_HOT_RELOAD "Build with hot reload support" ON) +else() + option(GODOT_ENABLE_HOT_RELOAD "Build with hot reload support" OFF) +endif() + if(NOT DEFINED BITS) set(BITS 32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) From cc70884db30a1f2775ec68b928d6df13d143404f Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Wed, 31 Jul 2024 20:06:07 -0400 Subject: [PATCH 06/14] Make sure `_get` and `_set` dispatch up the class hierarchy (cherry picked from commit c77d44f3f6abbacfc5b4a5efff580387e8b297b8) --- include/godot_cpp/classes/wrapped.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index 0f9da9934..73d6de402 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -234,23 +234,27 @@ public: } \ \ static GDExtensionBool set_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionConstVariantPtr p_value) { \ - if (p_instance && m_class::_get_set()) { \ + if (p_instance) { \ + if (m_inherits::set_bind(p_instance, p_name, p_value)) { \ + return true; \ + } \ if (m_class::_get_set() != m_inherits::_get_set()) { \ m_class *cls = reinterpret_cast(p_instance); \ return cls->_set(*reinterpret_cast(p_name), *reinterpret_cast(p_value)); \ } \ - return m_inherits::set_bind(p_instance, p_name, p_value); \ } \ return false; \ } \ \ static GDExtensionBool get_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { \ - if (p_instance && m_class::_get_get()) { \ + if (p_instance) { \ + if (m_inherits::get_bind(p_instance, p_name, r_ret)) { \ + return true; \ + } \ if (m_class::_get_get() != m_inherits::_get_get()) { \ m_class *cls = reinterpret_cast(p_instance); \ return cls->_get(*reinterpret_cast(p_name), *reinterpret_cast<::godot::Variant *>(r_ret)); \ } \ - return m_inherits::get_bind(p_instance, p_name, r_ret); \ } \ return false; \ } \ From deaf37120fe8b964b2b9d6b9d79c9553dde4cafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Thu, 8 Aug 2024 02:10:41 +0200 Subject: [PATCH 07/14] removes warnings generated by GDCLASS usage This change removes the warnings (unused parameters) coming from code injected by the GDCLASS macro. Contrary to warnings coming from the normal source code which can be suppressed with most compiles by specifying the include directories of this library as external or system, when the code is injected through a macro it is considered in the context of the user, which is the source code of user of the library. That forces the users to modify their code to hide the warnings coming from the mandatory `GDCLASS` here. That's why it's important to remove these warning from that specific macro and ideally any other macro that the user must use. (cherry picked from commit 738859f49bf34b176116729f56dd2e57e2fd9b86) --- include/godot_cpp/classes/wrapped.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index 73d6de402..293341723 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -138,7 +138,7 @@ struct EngineClassRegistration { // every line of the macro different #define GDCLASS(m_class, m_inherits) /***********************************************************************************************************************************************/ \ private: \ - void operator=(const m_class &p_rval) {} \ + void operator=(const m_class & /*p_rval*/) {} \ friend class ::godot::ClassDB; \ \ protected: \ @@ -333,7 +333,7 @@ public: } \ } \ \ - static void free(void *data, GDExtensionClassInstancePtr ptr) { \ + static void free(void * /*data*/, GDExtensionClassInstancePtr ptr) { \ if (ptr) { \ m_class *cls = reinterpret_cast(ptr); \ cls->~m_class(); \ @@ -341,14 +341,14 @@ public: } \ } \ \ - static void *_gde_binding_create_callback(void *p_token, void *p_instance) { \ + static void *_gde_binding_create_callback(void * /*p_token*/, void * /*p_instance*/) { \ return nullptr; \ } \ \ - static void _gde_binding_free_callback(void *p_token, void *p_instance, void *p_binding) { \ + static void _gde_binding_free_callback(void * /*p_token*/, void * /*p_instance*/, void * /*p_binding*/) { \ } \ \ - static GDExtensionBool _gde_binding_reference_callback(void *p_token, void *p_instance, GDExtensionBool p_reference) { \ + static GDExtensionBool _gde_binding_reference_callback(void * /*p_token*/, void * /*p_instance*/, GDExtensionBool /*p_reference*/) { \ return true; \ } \ \ From 12f6eecf65df925bddc58a7cbd21401fffa9ac35 Mon Sep 17 00:00:00 2001 From: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:53:21 +0200 Subject: [PATCH 08/14] Make generated code mostly style compliant (cherry picked from commit f131efb79182467541e61334d6e542690dd16fcf) --- binding_generator.py | 174 ++++++++++++++++++++++++++++--------------- 1 file changed, 112 insertions(+), 62 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index d7bce6025..fd050a2d7 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -413,8 +413,14 @@ def generate_builtin_bindings(api, output_dir, build_config): builtin_header.append("") + includes = [] for builtin in builtin_classes: - builtin_header.append(f"#include ") + includes.append(f"godot_cpp/variant/{camel_to_snake(builtin)}.hpp") + + includes.sort() + + for include in includes: + builtin_header.append(f"#include <{include}>") builtin_header.append("") @@ -470,11 +476,10 @@ def generate_builtin_class_vararg_method_implements_header(builtin_classes): continue result += make_varargs_template( - method, "is_static" in method and method["is_static"], class_name, False, False, True + method, "is_static" in method and method["is_static"], class_name, False, True ) result.append("") - result.append("") result.append(f"#endif // ! {header_guard}") return "\n".join(result) @@ -499,36 +504,50 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl # Special cases. if class_name == "String": + result.append("#include ") result.append("#include ") result.append("#include ") - result.append("#include ") + result.append("") if class_name == "PackedStringArray": result.append("#include ") + result.append("") if class_name == "PackedColorArray": result.append("#include ") + result.append("") if class_name == "PackedVector2Array": result.append("#include ") + result.append("") if class_name == "PackedVector3Array": result.append("#include ") + result.append("") if is_packed_array(class_name): result.append("#include ") result.append("#include ") + result.append("") if class_name == "Array": result.append("#include ") + result.append("") if class_name == "Callable": result.append("#include ") - - for include in fully_used_classes: - if include == "TypedArray": - result.append("#include ") - else: - result.append(f"#include ") + result.append("") if len(fully_used_classes) > 0: + includes = [] + for include in fully_used_classes: + if include == "TypedArray": + includes.append("godot_cpp/variant/typed_array.hpp") + else: + includes.append(f"godot_cpp/{get_include_path(include)}") + + includes.sort() + + for include in includes: + result.append(f"#include <{include}>") + result.append("") result.append("#include ") @@ -606,7 +625,7 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append("public:") result.append( - f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast(&opaque); }}" + f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast(&opaque); }}" ) copy_constructor_index = -1 @@ -785,7 +804,7 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append(f"\tconst {return_type} *ptr() const;") result.append(f"\t{return_type} *ptrw();") iterators = """ - struct Iterator { + struct Iterator { _FORCE_INLINE_ $TYPE &operator*() const { return *elem_ptr; } @@ -847,19 +866,17 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl } _FORCE_INLINE_ ConstIterator end() const { return ConstIterator(ptr() + size()); - } -""" + }""" result.append(iterators.replace("$TYPE", return_type)) init_list = """ - _FORCE_INLINE_ $CLASS(std::initializer_list<$TYPE> p_init) { + _FORCE_INLINE_ $CLASS(std::initializer_list<$TYPE> p_init) { ERR_FAIL_COND(resize(p_init.size()) != 0); size_t i = 0; for (const $TYPE &element : p_init) { set(i++, element); } - } -""" + }""" result.append(init_list.replace("$TYPE", return_type).replace("$CLASS", class_name)) if class_name == "Array": @@ -898,7 +915,9 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append("") result.append("} // namespace godot") + result.append("") result.append(f"#endif // ! {header_guard}") + result.append("") return "\n".join(result) @@ -912,7 +931,6 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl add_header(f"{snake_class_name}.cpp", result) - result.append("") result.append(f"#include ") result.append("") result.append("#include ") @@ -921,10 +939,16 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append("") # Only used since the "fully used" is included in header already. - for include in used_classes: - result.append(f"#include ") - if len(used_classes) > 0: + includes = [] + for included in used_classes: + includes.append(f"godot_cpp/{get_include_path(included)}") + + includes.sort() + + for included in includes: + result.append(f"#include <{included}>") + result.append("") result.append("#include ") @@ -1174,7 +1198,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"].replace("unary", "")}() const {{' ) result.append( - f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr)nullptr);' + f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr) nullptr);' ) result.append("}") result.append("") @@ -1210,6 +1234,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append("") result.append("} //namespace godot") + result.append("") return "\n".join(result) @@ -1385,11 +1410,18 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): result.append("") - for included in used_classes: - result.append(f"#include ") + if len(used_classes) > 0: + includes = [] + for included in used_classes: + includes.append(f"godot_cpp/{get_include_path(included)}") - if len(used_classes) == 0: + includes.sort() + + for include in includes: + result.append(f"#include <{include}>") + else: result.append("#include ") + result.append("") result.append("namespace godot {") @@ -1429,16 +1461,23 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") - for included in fully_used_classes: - if included == "TypedArray": - result.append("#include ") - else: - result.append(f"#include ") + if len(fully_used_classes) > 0: + includes = [] + for included in fully_used_classes: + if included == "TypedArray": + includes.append("godot_cpp/variant/typed_array.hpp") + else: + includes.append(f"godot_cpp/{get_include_path(included)}") + + includes.sort() + + for include in includes: + result.append(f"#include <{include}>") + + result.append("") if class_name == "EditorPlugin": result.append("#include ") - - if len(fully_used_classes) > 0: result.append("") if class_name != "Object" and class_name != "ClassDBSingleton": @@ -1477,7 +1516,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") result.append("public:") - result.append("") if "enums" in class_api: for enum_api in class_api["enums"]: @@ -1510,6 +1548,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us vararg = "is_vararg" in method and method["is_vararg"] + if vararg: + result.append("") + result.append("private:") + method_signature = "\t" method_signature += make_signature( class_name, method, for_header=True, use_template_get_node=use_template_get_node @@ -1517,6 +1559,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(method_signature + ";") if vararg: + result.append("") + result.append("public:") # Add templated version. result += make_varargs_template(method) @@ -1531,6 +1575,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us ) result.append(method_signature + ";") + result.append("") + result.append("protected:") # T is the custom class we want to register (from which the call initiates, going up the inheritance chain), # B is its base class (can be a custom class too, that's why we pass it). @@ -1547,7 +1593,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us # If the method is different from the base class, it means T overrides it, so it needs to be bound. # Note that with an `if constexpr`, the code inside the `if` will not even be compiled if the # condition returns false (in such cases it can't compile due to ambiguity). - f"\t\tif constexpr (!std::is_same_v) {{" + f"\t\tif constexpr (!std::is_same_v) {{" ) result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});") result.append("\t\t}") @@ -1571,7 +1617,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us if class_name == "WorkerThreadPool": result.append("\tenum {") - result.append("\tINVALID_TASK_ID = -1") + result.append("\t\tINVALID_TASK_ID = -1") result.append("\t};") result.append("\ttypedef int64_t TaskID;") result.append("\ttypedef int64_t GroupID;") @@ -1583,8 +1629,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us ) if class_name == "Object": - result.append("") - result.append("\ttemplate ") result.append("\tstatic T *cast_to(Object *p_object);") @@ -1599,7 +1643,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us "\tT *get_node(const NodePath &p_path) const { return Object::cast_to(get_node_internal(p_path)); }" ) - result.append("") result.append("};") result.append("") @@ -1683,7 +1726,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(method_body) result.append("\t} \\") - result.append("\t;") + result.append("\t") result.append("") result.append("#define CLASSDB_SINGLETON_VARIANT_CAST \\") @@ -1695,10 +1738,11 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us else: result.append(f'\tVARIANT_ENUM_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\') - result.append("\t;") + result.append("\t") result.append("") result.append(f"#endif // ! {header_guard}") + result.append("") return "\n".join(result) @@ -1720,10 +1764,16 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append("#include ") result.append("") - for included in used_classes: - result.append(f"#include ") - if len(used_classes) > 0: + includes = [] + for included in used_classes: + includes.append(f"godot_cpp/{get_include_path(included)}") + + includes.sort() + + for included in includes: + result.append(f"#include <{included}>") + result.append("") result.append("namespace godot {") @@ -1873,8 +1923,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append(method_signature) result.append("") + result.append("} // namespace godot") result.append("") - result.append("} // namespace godot ") return "\n".join(result) @@ -1902,23 +1952,24 @@ def generate_global_constants(api, output_dir): header.append("namespace godot {") header.append("") - for constant in api["global_constants"]: - header.append(f'\tconst int64_t {escape_identifier(constant["name"])} = {constant["value"]};') + if len(api["global_constants"]) > 0: + for constant in api["global_constants"]: + header.append(f'const int64_t {escape_identifier(constant["name"])} = {constant["value"]};') - header.append("") + header.append("") for enum_def in api["global_enums"]: if enum_def["name"].startswith("Variant."): continue if enum_def["is_bitfield"]: - header.append(f'\tenum {enum_def["name"]} : uint64_t {{') + header.append(f'enum {enum_def["name"]} : uint64_t {{') else: - header.append(f'\tenum {enum_def["name"]} {{') + header.append(f'enum {enum_def["name"]} {{') for value in enum_def["values"]: - header.append(f'\t\t{value["name"]} = {value["value"]},') - header.append("\t};") + header.append(f'\t{value["name"]} = {value["value"]},') + header.append("};") header.append("") header.append("} // namespace godot") @@ -2031,11 +2082,17 @@ def generate_utility_functions(api, output_dir): for function in api["utility_functions"]: vararg = "is_vararg" in function and function["is_vararg"] + if vararg: + header.append("") + header.append("private:") + function_signature = "\t" function_signature += make_signature("UtilityFunctions", function, for_header=True, static=True) header.append(function_signature + ";") if vararg: + header.append("") + header.append("public:") # Add templated version. header += make_varargs_template(function, static=True) @@ -2056,8 +2113,8 @@ def generate_utility_functions(api, output_dir): source.append("#include ") source.append("") - source.append("#include ") source.append("#include ") + source.append("#include ") source.append("") source.append("namespace godot {") source.append("") @@ -2156,7 +2213,7 @@ def make_function_parameters(parameters, include_default=False, for_builtin=Fals signature.append(parameter) if is_vararg: - signature.append("const Args&... p_args") + signature.append("const Args &...p_args") return ", ".join(signature) @@ -2214,9 +2271,6 @@ def make_signature( if "is_virtual" in function_data and function_data["is_virtual"]: function_signature += "virtual " - if is_vararg: - function_signature += "private: " - if static: function_signature += "static " @@ -2269,7 +2323,6 @@ def make_varargs_template( function_data, static=False, class_befor_signature="", - with_public_declare=True, with_indent=True, for_builtin_classes=False, ): @@ -2277,10 +2330,7 @@ def make_varargs_template( function_signature = "" - if with_public_declare: - function_signature = "public: " - - function_signature += "template " + result.append("template ") if static: function_signature += "static " @@ -2323,7 +2373,7 @@ def make_varargs_template( function_signature += " {" result.append(function_signature) - args_array = f"\tstd::array variant_args {{ " + args_array = f"\tstd::array variant_args{{ " for argument in method_arguments: if argument["type"] == "Variant": args_array += escape_argument(argument["name"]) From 63c67a9977a64ae44e886622a49ddc1c2164d9ad Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Tue, 20 Aug 2024 14:53:22 +0200 Subject: [PATCH 09/14] Avoid hardcoded type conversion for metadata The engine uses the names `int` and `float` to refer to the 64-bit types, so in the bindings generator we have a hardcoded conversion for those types. But this type conversion should not be used for metadata. Even though the underlying type should still be 64-bit for interop, metadata is meant to specify the correct type to expose. So if metadata says `float` it means the type is really meant to be a 32-bit `float` and not `double`. Other hardcoded type conversions (`int` and `Nil`) won't ever be metadata. This change corrects the `float` type, to use the right type in the generated C++ code. Before we were always using `double` due to this type conversion. (cherry picked from commit 48291990817fbaa8cc2d0307a16d7345bf62da52) --- binding_generator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index fd050a2d7..ebc256bb0 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -2622,8 +2622,6 @@ def correct_type(type_name, meta=None, use_alias=True): if meta is not None: if "int" in meta: return f"{meta}_t" - elif meta in type_conversion: - return type_conversion[type_name] else: return meta if type_name in type_conversion: From f9095a5552eafc2deb334c290629ef933ac1e9ca Mon Sep 17 00:00:00 2001 From: Mikael Hermansson Date: Wed, 21 Aug 2024 20:19:33 +0200 Subject: [PATCH 10/14] Fix incorrect generation of some C++ operators (cherry picked from commit 9949d09f3eb43827595e8eab8ada0628da77782a) --- binding_generator.py | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index ebc256bb0..bd62ed88a 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -738,14 +738,14 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl if "operators" in builtin_api: for operator in builtin_api["operators"]: - if operator["name"] not in ["in", "xor"]: + if is_valid_cpp_operator(operator["name"]): if "right_type" in operator: result.append( - f'\t{correct_type(operator["return_type"])} operator{operator["name"]}({type_for_parameter(operator["right_type"])}p_other) const;' + f'\t{correct_type(operator["return_type"])} operator{get_operator_cpp_name(operator["name"])}({type_for_parameter(operator["right_type"])}p_other) const;' ) else: result.append( - f'\t{correct_type(operator["return_type"])} operator{operator["name"].replace("unary", "")}() const;' + f'\t{correct_type(operator["return_type"])} operator{get_operator_cpp_name(operator["name"])}() const;' ) # Copy assignment. @@ -1182,10 +1182,10 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl if "operators" in builtin_api: for operator in builtin_api["operators"]: - if operator["name"] not in ["in", "xor"]: + if is_valid_cpp_operator(operator["name"]): if "right_type" in operator: result.append( - f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"]}({type_for_parameter(operator["right_type"])}p_other) const {{' + f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}({type_for_parameter(operator["right_type"])}p_other) const {{' ) (encode, arg_name) = get_encoded_arg("other", operator["right_type"], None) result += encode @@ -1195,7 +1195,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append("}") else: result.append( - f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"].replace("unary", "")}() const {{' + f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}() const {{' ) result.append( f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr) nullptr);' @@ -2733,6 +2733,38 @@ def get_operator_id_name(op): return op_id_map[op] +def get_operator_cpp_name(op): + op_cpp_map = { + "==": "==", + "!=": "!=", + "<": "<", + "<=": "<=", + ">": ">", + ">=": ">=", + "+": "+", + "-": "-", + "*": "*", + "/": "/", + "unary-": "-", + "unary+": "+", + "%": "%", + "<<": "<<", + ">>": ">>", + "&": "&", + "|": "|", + "^": "^", + "~": "~", + "and": "&&", + "or": "||", + "not": "!", + } + return op_cpp_map[op] + + +def is_valid_cpp_operator(op): + return op not in ["**", "xor", "in"] + + def get_default_value_for_type(type_name): if type_name == "int": return "0" From 42d9ac3b08d1646b9639a76972dd4c7219c28b94 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 24 Aug 2024 11:56:27 +0200 Subject: [PATCH 11/14] [Web/SCons] Use CCFLAGS for SIDE_MODULE option Was using CPPFLAGS, but should use the explicit scons CCFLAGS which makes it clear they are applied to both the C and C++ compiler. CPPFLAGS was also fine (they are preprocessor flags, also applied to both C and C++), but we should try to stay consistent with what we do in Godot. (cherry picked from commit f36acd8e312c916c7e53364e1b0bd8eec3e4410e) --- tools/web.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/web.py b/tools/web.py index 8953325c6..08aac6354 100644 --- a/tools/web.py +++ b/tools/web.py @@ -39,8 +39,8 @@ def generate(env): env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) # Build as side module (shared library). - env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"]) - env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"]) + env.Append(CCFLAGS=["-sSIDE_MODULE=1"]) + env.Append(LINKFLAGS=["-sSIDE_MODULE=1"]) env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) From 9d26e3418ca2361dcb2ddb87d12665c43177d5da Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Sun, 25 Aug 2024 07:55:58 +0000 Subject: [PATCH 12/14] Fix GCC 14 -Wtemplate-id-cdtor warning As was fixed with godotengine/godot#91208 (cherry picked from commit 7b31f39beaca1a98307402f53d69f3657f3bed86) --- include/godot_cpp/templates/safe_refcount.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/godot_cpp/templates/safe_refcount.hpp b/include/godot_cpp/templates/safe_refcount.hpp index 12e6840ae..98cb04b20 100644 --- a/include/godot_cpp/templates/safe_refcount.hpp +++ b/include/godot_cpp/templates/safe_refcount.hpp @@ -132,7 +132,7 @@ class SafeNumeric { } } - _ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast(0)) { + _ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast(0)) { set(p_value); } }; From fd31fabcfc76e994cb3573b060d5a76481ced312 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Mon, 15 Jul 2024 11:57:53 -0500 Subject: [PATCH 13/14] Add a test to ensure that library path is absolute (cherry picked from commit 92ace04989bdf8d7d94846f059eeccd723f9b885) --- test/project/main.gd | 6 ++++++ test/src/example.cpp | 8 ++++++++ test/src/example.h | 2 ++ 3 files changed, 16 insertions(+) diff --git a/test/project/main.gd b/test/project/main.gd index 20c6e8381..a41b8ea83 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -257,6 +257,12 @@ func _ready(): assert_equal(example_child.get_value1(), 11) assert_equal(example_child.get_value2(), 22) + # Test that the extension's library path is absolute and valid. + var library_path = Example.test_library_path() + assert_equal(library_path.begins_with("res://"), false) + assert_equal(library_path, ProjectSettings.globalize_path(library_path)) + assert_equal(FileAccess.file_exists(library_path), true) + exit_with_status() func _on_Example_custom_signal(signal_name, value): diff --git a/test/src/example.cpp b/test/src/example.cpp index faa9edddb..b64ffbb6d 100644 --- a/test/src/example.cpp +++ b/test/src/example.cpp @@ -238,6 +238,8 @@ void Example::_bind_methods() { 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); + ClassDB::bind_static_method("Example", D_METHOD("test_library_path"), &Example::test_library_path); + { MethodInfo mi; mi.arguments.push_back(PropertyInfo(Variant::STRING, "some_argument")); @@ -675,3 +677,9 @@ void ExampleChild::_notification(int p_what) { String Example::test_use_engine_singleton() const { return OS::get_singleton()->get_name(); } + +String Example::test_library_path() { + String library_path; + internal::gdextension_interface_get_library_path(internal::library, library_path._native_ptr()); + return library_path; +} diff --git a/test/src/example.h b/test/src/example.h index 771eefb74..a40e99062 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -185,6 +185,8 @@ class Example : public Control { virtual void _input(const Ref &event) override; String test_use_engine_singleton() const; + + static String test_library_path(); }; VARIANT_ENUM_CAST(Example::Constants); From 5921734784ac35492986239b2ffef9483eda2194 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Fri, 14 Jun 2024 01:42:39 +0200 Subject: [PATCH 14/14] [Web] Force emcc to use "wasm" longjmp mode SUPPORT_LONGJMP have changed since emscripten 3.1.32 to default to "wasm" mode when exceptions are enabled, and "emscripten" mode when disabled. While we generally doesn't use exception in core, linked libraries may need them, and emscripten don't plan to support WASM EH + Emscripten SjLj in the long term. (cherry picked from commit 1bb543b6f4234a967f1d89dca78e380208177635) --- tools/web.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/web.py b/tools/web.py index 08aac6354..c8f07c555 100644 --- a/tools/web.py +++ b/tools/web.py @@ -35,13 +35,17 @@ def generate(env): # Thread support (via SharedArrayBuffer). if env["threads"]: - env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(CCFLAGS=["-sUSE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-sUSE_PTHREADS=1"]) # Build as side module (shared library). env.Append(CCFLAGS=["-sSIDE_MODULE=1"]) env.Append(LINKFLAGS=["-sSIDE_MODULE=1"]) + # Force wasm longjmp mode. + env.Append(CCFLAGS=["-sSUPPORT_LONGJMP='wasm'"]) + env.Append(LINKFLAGS=["-sSUPPORT_LONGJMP='wasm'"]) + env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) common_compiler_flags.generate(env)