From 575f317bf124291a7bdadc66dff30edf25eb9fad Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 28 Jan 2025 10:37:08 -0600 Subject: [PATCH 01/18] [4.3] Run tests against Godot 4.3-stable --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13b7243e0..953dc7be5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ env: # Only used for the cache key. Increment version to force clean build. GODOT_BASE_BRANCH: master # Used to select the version of Godot to run the tests with. - GODOT_TEST_VERSION: master + GODOT_TEST_VERSION: 4.3-stable concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}} From 9ded2402dc9294cebff48a9f5c0f3f9d1a94f78f Mon Sep 17 00:00:00 2001 From: David Snopek Date: Thu, 12 Sep 2024 09:11:00 -0500 Subject: [PATCH 02/18] Fix crash in `ClassDB::add_virtual_method()` if arguments metadata is the wrong size (cherry picked from commit 1e169bb8091e18c0cf98261af9cd843602eb8175) --- src/core/class_db.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 5ded79915..ae74f2c27 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -353,9 +353,14 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_ if (mi.argument_count > 0) { mi.arguments = (GDExtensionPropertyInfo *)memalloc(sizeof(GDExtensionPropertyInfo) * mi.argument_count); mi.arguments_metadata = (GDExtensionClassMethodArgumentMetadata *)memalloc(sizeof(GDExtensionClassMethodArgumentMetadata) * mi.argument_count); + if (mi.argument_count != p_method.arguments_metadata.size()) { + WARN_PRINT("Mismatch argument metadata count for virtual method: " + String(p_class) + "::" + p_method.name); + } for (uint32_t i = 0; i < mi.argument_count; i++) { mi.arguments[i] = p_method.arguments[i]._to_gdextension(); - mi.arguments_metadata[i] = p_method.arguments_metadata[i]; + if (i < p_method.arguments_metadata.size()) { + mi.arguments_metadata[i] = p_method.arguments_metadata[i]; + } } } else { mi.arguments = nullptr; From d5bdde9509630234789e38f4bc0c039587497ac2 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 17 Sep 2024 14:53:39 -0500 Subject: [PATCH 03/18] Avoid `thread_local` on MacOS to prevent issues with hot reload (cherry picked from commit 91833c852e47a83ed01deb26213b9d3184de736d) --- include/godot_cpp/classes/wrapped.hpp | 21 ++++++++++++++++++--- include/godot_cpp/core/class_db.hpp | 3 +++ src/classes/wrapped.cpp | 15 ++++++++++++--- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp index a0bcec7b5..d27210818 100644 --- a/include/godot_cpp/classes/wrapped.hpp +++ b/include/godot_cpp/classes/wrapped.hpp @@ -40,6 +40,14 @@ #include +#if defined(MACOS_ENABLED) && defined(HOT_RELOAD_ENABLED) +#include +#define _GODOT_CPP_AVOID_THREAD_LOCAL +#define _GODOT_CPP_THREAD_LOCAL +#else +#define _GODOT_CPP_THREAD_LOCAL thread_local +#endif + namespace godot { class ClassDB; @@ -58,11 +66,15 @@ class Wrapped { template ::value, bool>> friend _ALWAYS_INLINE_ void _pre_initialize(); - thread_local static const StringName *_constructing_extension_class_name; - thread_local static const GDExtensionInstanceBindingCallbacks *_constructing_class_binding_callbacks; +#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL + static std::recursive_mutex _constructing_mutex; +#endif + + _GODOT_CPP_THREAD_LOCAL static const StringName *_constructing_extension_class_name; + _GODOT_CPP_THREAD_LOCAL static const GDExtensionInstanceBindingCallbacks *_constructing_class_binding_callbacks; #ifdef HOT_RELOAD_ENABLED - thread_local static GDExtensionObjectPtr _constructing_recreate_owner; + _GODOT_CPP_THREAD_LOCAL static GDExtensionObjectPtr _constructing_recreate_owner; #endif template @@ -121,6 +133,9 @@ class Wrapped { template ::value, bool>> _ALWAYS_INLINE_ void _pre_initialize() { +#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL + Wrapped::_constructing_mutex.lock(); +#endif Wrapped::_set_construct_info(); } diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 85bc0fb7b..d9dce792b 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -129,6 +129,9 @@ class ClassDB { static GDExtensionClassInstancePtr _recreate_instance_func(void *data, GDExtensionObjectPtr obj) { if constexpr (!std::is_abstract_v) { #ifdef HOT_RELOAD_ENABLED +#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL + std::lock_guard lk(Wrapped::_constructing_mutex); +#endif Wrapped::_constructing_recreate_owner = obj; T *new_instance = (T *)memalloc(sizeof(T)); memnew_placement(new_instance, T); diff --git a/src/classes/wrapped.cpp b/src/classes/wrapped.cpp index d397d46d3..3eb17cadf 100644 --- a/src/classes/wrapped.cpp +++ b/src/classes/wrapped.cpp @@ -39,11 +39,16 @@ #include namespace godot { -thread_local const StringName *Wrapped::_constructing_extension_class_name = nullptr; -thread_local const GDExtensionInstanceBindingCallbacks *Wrapped::_constructing_class_binding_callbacks = nullptr; + +#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL +std::recursive_mutex Wrapped::_constructing_mutex; +#endif + +_GODOT_CPP_THREAD_LOCAL const StringName *Wrapped::_constructing_extension_class_name = nullptr; +_GODOT_CPP_THREAD_LOCAL const GDExtensionInstanceBindingCallbacks *Wrapped::_constructing_class_binding_callbacks = nullptr; #ifdef HOT_RELOAD_ENABLED -thread_local GDExtensionObjectPtr Wrapped::_constructing_recreate_owner = nullptr; +_GODOT_CPP_THREAD_LOCAL GDExtensionObjectPtr Wrapped::_constructing_recreate_owner = nullptr; #endif const StringName *Wrapped::_get_extension_class_name() { @@ -51,6 +56,10 @@ const StringName *Wrapped::_get_extension_class_name() { } void Wrapped::_postinitialize() { +#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL + Wrapped::_constructing_mutex.unlock(); +#endif + // Only send NOTIFICATION_POSTINITIALIZE for extension classes. if (_is_extension_class()) { _notificationv(Object::NOTIFICATION_POSTINITIALIZE); From 06fbf3ce95fb8ac25cc789df9cb20dca6670827d Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Sat, 21 Sep 2024 12:52:50 +0200 Subject: [PATCH 04/18] Add lto scons option, defaulting to "none". (cherry picked from commit 5f7cf050433fb04c9d1e1a01176998fc38784142) --- tools/android.py | 5 +++++ tools/common_compiler_flags.py | 30 ++++++++++++++++++++++++++++++ tools/godotcpp.py | 8 ++++++++ tools/ios.py | 5 +++++ tools/linux.py | 4 ++++ tools/macos.py | 5 +++++ tools/web.py | 4 ++++ tools/windows.py | 8 ++++++++ 8 files changed, 69 insertions(+) diff --git a/tools/android.py b/tools/android.py index 0222121eb..fee4ed255 100644 --- a/tools/android.py +++ b/tools/android.py @@ -120,4 +120,9 @@ def generate(env): env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/android/detect.py + # LTO benefits for Android (size, performance) haven't been clearly established yet. + if env["lto"] == "auto": + env["lto"] = "none" + common_compiler_flags.generate(env) diff --git a/tools/common_compiler_flags.py b/tools/common_compiler_flags.py index 6a1fb6931..e645f390f 100644 --- a/tools/common_compiler_flags.py +++ b/tools/common_compiler_flags.py @@ -22,6 +22,10 @@ def exists(env): def generate(env): + assert env["lto"] in ["thin", "full", "none"], "Unrecognized lto: {}".format(env["lto"]) + if env["lto"] != "none": + print("Using LTO: " + env["lto"]) + # Require C++17 if env.get("is_msvc", False): env.Append(CXXFLAGS=["/std:c++17"]) @@ -64,6 +68,22 @@ def generate(env): env.Append(LINKFLAGS=["/OPT:REF"]) elif env["optimize"] == "debug" or env["optimize"] == "none": env.Append(CCFLAGS=["/Od"]) + + if env["lto"] == "thin": + if not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + env.Exit(255) + + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["lto"] == "full": + if env["use_llvm"]: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) + else: + env.AppendUnique(CCFLAGS=["/GL"]) + env.AppendUnique(ARFLAGS=["/LTCG"]) + env.AppendUnique(LINKFLAGS=["/LTCG"]) else: if env["debug_symbols"]: # Adding dwarf-4 explicitly makes stacktraces work with clang builds, @@ -91,3 +111,13 @@ def generate(env): env.Append(CCFLAGS=["-Og"]) elif env["optimize"] == "none": env.Append(CCFLAGS=["-O0"]) + + if env["lto"] == "thin": + if (env["platform"] == "windows" or env["platform"] == "linux") and not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + env.Exit(255) + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["lto"] == "full": + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) diff --git a/tools/godotcpp.py b/tools/godotcpp.py index b2a63dc1e..77a0740fc 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -326,6 +326,14 @@ def options(opts, env): ("none", "custom", "debug", "speed", "speed_trace", "size"), ) ) + opts.Add( + EnumVariable( + "lto", + "Link-time optimization", + "none", + ("none", "auto", "thin", "full"), + ) + ) opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", True)) opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) diff --git a/tools/ios.py b/tools/ios.py index 9675ab1ac..25c17db31 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -97,4 +97,9 @@ def generate(env): env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/ios/detect.py: + # Disable by default as it makes linking in Xcode very slow. + if env["lto"] == "auto": + env["lto"] = "none" + common_compiler_flags.generate(env) diff --git a/tools/linux.py b/tools/linux.py index 0b2687880..9e85d8803 100644 --- a/tools/linux.py +++ b/tools/linux.py @@ -39,4 +39,8 @@ def generate(env): env.Append(CPPDEFINES=["LINUX_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/linuxbsd/detect.py + if env["lto"] == "auto": + env["lto"] = "full" + common_compiler_flags.generate(env) diff --git a/tools/macos.py b/tools/macos.py index 741815051..f88e47ff1 100644 --- a/tools/macos.py +++ b/tools/macos.py @@ -73,4 +73,9 @@ def generate(env): env.Append(CPPDEFINES=["MACOS_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/macos/detect.py + # LTO benefits for macOS (size, performance) haven't been clearly established yet. + if env["lto"] == "auto": + env["lto"] = "none" + common_compiler_flags.generate(env) diff --git a/tools/web.py b/tools/web.py index e878a78f3..f1a441869 100644 --- a/tools/web.py +++ b/tools/web.py @@ -52,4 +52,8 @@ def generate(env): env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/web/detect.py + if env["lto"] == "auto": + env["lto"] = "full" + common_compiler_flags.generate(env) diff --git a/tools/windows.py b/tools/windows.py index 2e8d609ea..490b9f718 100644 --- a/tools/windows.py +++ b/tools/windows.py @@ -198,4 +198,12 @@ def generate(env): env.Append(CPPDEFINES=["WINDOWS_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/windows/detect.py + if env["lto"] == "auto": + if env.get("is_msvc", False): + # No LTO by default for MSVC, doesn't help. + env["lto"] = "none" + else: # Release + env["lto"] = "full" + common_compiler_flags.generate(env) From 0899cf60988089c8c2d53f996ae4ba84d85e1783 Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Thu, 26 Sep 2024 15:04:06 +0200 Subject: [PATCH 05/18] Rename Vector4.components -> coords. The use of .components is deprecated. (cherry picked from commit 23c9d41d2a56095efaa9169f9619d4dd6ade30e1) --- include/godot_cpp/variant/vector4.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/godot_cpp/variant/vector4.hpp b/include/godot_cpp/variant/vector4.hpp index 866e522ba..e0e197f82 100644 --- a/include/godot_cpp/variant/vector4.hpp +++ b/include/godot_cpp/variant/vector4.hpp @@ -55,16 +55,17 @@ struct _NO_DISCARD_ Vector4 { real_t z; real_t w; }; - real_t components[4] = { 0, 0, 0, 0 }; + [[deprecated("Use coord instead")]] real_t components[4]; + real_t coord[4] = { 0, 0, 0, 0 }; }; _FORCE_INLINE_ real_t &operator[](const int p_axis) { DEV_ASSERT((unsigned int)p_axis < 4); - return components[p_axis]; + return coord[p_axis]; } _FORCE_INLINE_ const real_t &operator[](const int p_axis) const { DEV_ASSERT((unsigned int)p_axis < 4); - return components[p_axis]; + return coord[p_axis]; } Vector4::Axis min_axis_index() const; From 3449c4e1d3912ae288bd2f02862c42c1319335a7 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Mon, 7 Oct 2024 11:22:52 -0500 Subject: [PATCH 06/18] Don't print an error when decoding a null Ref (cherry picked from commit 7f02301a91d3cb6688e510a89bc13550d956df0d) --- include/godot_cpp/classes/ref.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/godot_cpp/classes/ref.hpp b/include/godot_cpp/classes/ref.hpp index 137b677bf..8511fe4c2 100644 --- a/include/godot_cpp/classes/ref.hpp +++ b/include/godot_cpp/classes/ref.hpp @@ -230,7 +230,9 @@ template struct PtrToArg> { _FORCE_INLINE_ static Ref convert(const void *p_ptr) { GDExtensionRefPtr ref = (GDExtensionRefPtr)p_ptr; - ERR_FAIL_NULL_V(p_ptr, Ref()); + if (unlikely(!p_ptr)) { + return Ref(); + } return Ref(reinterpret_cast(godot::internal::get_object_instance_binding(godot::internal::gdextension_interface_ref_get_object(ref)))); } @@ -254,7 +256,9 @@ struct PtrToArg &> { _FORCE_INLINE_ static Ref convert(const void *p_ptr) { GDExtensionRefPtr ref = const_cast(p_ptr); - ERR_FAIL_NULL_V(p_ptr, Ref()); + if (unlikely(!p_ptr)) { + return Ref(); + } return Ref(reinterpret_cast(godot::internal::get_object_instance_binding(godot::internal::gdextension_interface_ref_get_object(ref)))); } }; From c9da56cca2fe263417dffbb4ce372fef2b23d23a Mon Sep 17 00:00:00 2001 From: Zhehang Ding Date: Mon, 7 Oct 2024 20:51:51 +0800 Subject: [PATCH 07/18] Use namespace in defs.hpp A global alias of godot::real_t is defined for backward compatibility (cherry picked from commit 450c3d65cd182b3e33a1deeae250143e699f7972) --- include/godot_cpp/core/defs.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/godot_cpp/core/defs.hpp b/include/godot_cpp/core/defs.hpp index 16812c2b4..5b985ffc3 100644 --- a/include/godot_cpp/core/defs.hpp +++ b/include/godot_cpp/core/defs.hpp @@ -35,6 +35,8 @@ #include #include +namespace godot { + #if !defined(GDE_EXPORT) #if defined(_WIN32) #define GDE_EXPORT __declspec(dllexport) @@ -127,4 +129,10 @@ struct BuildIndexSequence : BuildIndexSequence {}; template struct BuildIndexSequence<0, Is...> : IndexSequence {}; +} //namespace godot + +// To maintain compatibility an alias is defined outside the namespace. +// Consider it deprecated. +using real_t = godot::real_t; + #endif // GODOT_DEFS_HPP From f2303ba0cc085d8c02ffbd16fbdc9640204a1465 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Fri, 11 Oct 2024 10:58:28 -0500 Subject: [PATCH 08/18] CI: Add `runner` workflow to call other workflows (cherry picked from commit c1524f7c8641b8f600f80add38a30b9b969f6a89) --- .../actions/godot-cache-restore/action.yml | 25 +++++++++-------- .github/actions/godot-cache-save/action.yml | 11 ++++---- .github/workflows/ci.yml | 27 ++++++++++--------- .github/workflows/runner.yml | 21 +++++++++++++++ .github/workflows/static_checks.yml | 5 ++-- 5 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/runner.yml diff --git a/.github/actions/godot-cache-restore/action.yml b/.github/actions/godot-cache-restore/action.yml index 5df577656..f10222bf5 100644 --- a/.github/actions/godot-cache-restore/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -3,19 +3,22 @@ description: Restore Godot build cache. inputs: cache-name: description: The cache base name (job name by default). - default: "${{github.job}}" + default: ${{ github.job }} scons-cache: - description: The scons cache path. - default: "${{github.workspace}}/.scons-cache/" + description: The SCons cache path. + default: ${{ github.workspace }}/.scons-cache/ + runs: - using: "composite" + using: composite steps: - - name: Restore .scons_cache directory - uses: actions/cache/restore@v3 + - name: Restore SCons cache directory + uses: actions/cache/restore@v4 with: - path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + path: ${{ inputs.scons-cache }} + key: ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} + restore-keys: | - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-refs/heads/${{ env.GODOT_BASE_BRANCH }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml index b7cbf91f9..df877cec6 100644 --- a/.github/actions/godot-cache-save/action.yml +++ b/.github/actions/godot-cache-save/action.yml @@ -3,15 +3,16 @@ description: Save Godot build cache. inputs: cache-name: description: The cache base name (job name by default). - default: "${{github.job}}" + default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: "${{github.workspace}}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ + runs: - using: "composite" + 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}} + 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 953dc7be5..85c12683a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,6 @@ name: Continuous integration -on: [push, pull_request] +on: + workflow_call: env: # Only used for the cache key. Increment version to force clean build. @@ -8,7 +9,7 @@ env: GODOT_TEST_VERSION: 4.3-stable concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}} + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -91,7 +92,7 @@ jobs: env: SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ EM_VERSION: 3.1.39 - EM_CACHE_FOLDER: "emsdk-cache" + EM_CACHE_FOLDER: emsdk-cache steps: - name: Checkout @@ -108,24 +109,24 @@ jobs: - name: Set up Python (for SCons) uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: 3.x - name: Android dependencies - if: ${{ matrix.platform == 'android' }} + if: matrix.platform == 'android' uses: nttld/setup-ndk@v1 with: ndk-version: r23c link-to-sdk: true - name: Web dependencies - if: ${{ matrix.platform == 'web' }} + if: matrix.platform == 'web' uses: mymindstorm/setup-emsdk@v14 with: - version: ${{env.EM_VERSION}} - actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + version: ${{ env.EM_VERSION }} + actions-cache-folder: ${{ env.EM_CACHE_FOLDER }} - name: Setup MinGW for Windows/MinGW build - if: ${{ matrix.platform == 'windows' && matrix.flags == 'use_mingw=yes' }} + if: matrix.platform == 'windows' && matrix.flags == 'use_mingw=yes' uses: egor-tensin/setup-mingw@v2 with: version: 12.2.0 @@ -161,7 +162,7 @@ jobs: - name: Download latest Godot artifacts uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9 - if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }} + if: matrix.run-tests && env.GODOT_TEST_VERSION == 'master' with: repo: godotengine/godot branch: master @@ -175,13 +176,13 @@ jobs: path: godot-artifacts - name: Prepare Godot artifacts for testing - if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }} + if: matrix.run-tests && env.GODOT_TEST_VERSION == 'master' run: | chmod +x ./godot-artifacts/godot.linuxbsd.editor.x86_64.mono echo "GODOT=$(pwd)/godot-artifacts/godot.linuxbsd.editor.x86_64.mono" >> $GITHUB_ENV - name: Download requested Godot version for testing - if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION != 'master' }} + if: matrix.run-tests && env.GODOT_TEST_VERSION != 'master' run: | wget "https://github.com/godotengine/godot-builds/releases/download/${GODOT_TEST_VERSION}/Godot_v${GODOT_TEST_VERSION}_linux.x86_64.zip" -O Godot.zip unzip -a Godot.zip @@ -189,7 +190,7 @@ jobs: echo "GODOT=$(pwd)/Godot_v${GODOT_TEST_VERSION}_linux.x86_64" >> $GITHUB_ENV - name: Run tests - if: ${{ matrix.run-tests }} + if: matrix.run-tests run: | $GODOT --headless --version cd test diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml new file mode 100644 index 000000000..a2e4f91b8 --- /dev/null +++ b/.github/workflows/runner.yml @@ -0,0 +1,21 @@ +name: 🔗 GHA +on: [push, pull_request, merge_group] + +concurrency: + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-runner + cancel-in-progress: true + +jobs: + # First stage: Only static checks, fast and prevent expensive builds from running. + + static-checks: + if: '!vars.DISABLE_GODOT_CI' + name: 📊 Static Checks + uses: ./.github/workflows/static_checks.yml + + # Second stage: Run all the builds and some of the tests. + + ci: + name: 🛠️ Continuous Integration + needs: static-checks + uses: ./.github/workflows/ci.yml diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 6899248ea..c8d271393 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -1,8 +1,9 @@ name: 📊 Static Checks -on: [push, pull_request] +on: + workflow_call: concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-static + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-static cancel-in-progress: true jobs: From d6a47a28c7206c2f19420e39e4f9e09d991752b8 Mon Sep 17 00:00:00 2001 From: ruffenman Date: Mon, 14 Oct 2024 20:33:20 -0400 Subject: [PATCH 09/18] Remove unimplemented static variant functions 'blend' and 'interpolate'. If a user attempts to call either of these it will introduce a linker error and it may not be immediately clear to them why. Also, variant interpolation can already be accessed via 'UtilityFunctions::lerp', making at least the interpolate function unecessary here. (cherry picked from commit 42a35a1852cec7dad37132836955bcd462409fc8) --- include/godot_cpp/variant/variant.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/godot_cpp/variant/variant.hpp b/include/godot_cpp/variant/variant.hpp index b0d57fbbd..a868a7cc6 100644 --- a/include/godot_cpp/variant/variant.hpp +++ b/include/godot_cpp/variant/variant.hpp @@ -327,8 +327,6 @@ class Variant { bool booleanize() const; String stringify() const; Variant duplicate(bool deep = false) const; - static void blend(const Variant &a, const Variant &b, float c, Variant &r_dst); - static void interpolate(const Variant &a, const Variant &b, float c, Variant &r_dst); static String get_type_name(Variant::Type type); static bool can_convert(Variant::Type from, Variant::Type to); From 2cd6221286c3ce0a8250b3877dc67fb8d07a0395 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Mon, 28 Oct 2024 15:37:45 -0500 Subject: [PATCH 10/18] Sync `Quaternion` with the version in Godot (cherry picked from commit 2004af63a0bf97b2f719a1f3e71327faea8b776a) --- include/godot_cpp/variant/quaternion.hpp | 86 ++++++++++++------------ src/variant/quaternion.cpp | 68 ++++++++----------- 2 files changed, 70 insertions(+), 84 deletions(-) diff --git a/include/godot_cpp/variant/quaternion.hpp b/include/godot_cpp/variant/quaternion.hpp index 5de91b207..8d0afd788 100644 --- a/include/godot_cpp/variant/quaternion.hpp +++ b/include/godot_cpp/variant/quaternion.hpp @@ -31,6 +31,7 @@ #ifndef GODOT_QUATERNION_HPP #define GODOT_QUATERNION_HPP +#include #include #include @@ -47,11 +48,11 @@ struct _NO_DISCARD_ Quaternion { real_t components[4] = { 0, 0, 0, 1.0 }; }; - _FORCE_INLINE_ real_t &operator[](int idx) { - return components[idx]; + _FORCE_INLINE_ real_t &operator[](int p_idx) { + return components[p_idx]; } - _FORCE_INLINE_ const real_t &operator[](int idx) const { - return components[idx]; + _FORCE_INLINE_ const real_t &operator[](int p_idx) const { + return components[p_idx]; } _FORCE_INLINE_ real_t length_squared() const; bool is_equal_approx(const Quaternion &p_quaternion) const; @@ -66,14 +67,13 @@ struct _NO_DISCARD_ Quaternion { _FORCE_INLINE_ real_t dot(const Quaternion &p_q) const; real_t angle_to(const Quaternion &p_to) const; - Vector3 get_euler_xyz() const; - Vector3 get_euler_yxz() const; - Vector3 get_euler() const { return get_euler_yxz(); } + Vector3 get_euler(EulerOrder p_order = EulerOrder::EULER_ORDER_YXZ) const; + static Quaternion from_euler(const Vector3 &p_euler); - Quaternion slerp(const Quaternion &p_to, const real_t &p_weight) const; - Quaternion slerpni(const Quaternion &p_to, const real_t &p_weight) const; - Quaternion spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight) const; - Quaternion spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const; + Quaternion slerp(const Quaternion &p_to, real_t p_weight) const; + Quaternion slerpni(const Quaternion &p_to, real_t p_weight) const; + Quaternion spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, real_t p_weight) const; + Quaternion spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, real_t p_weight, real_t p_b_t, real_t p_pre_a_t, real_t p_post_b_t) const; Vector3 get_axis() const; real_t get_angle() const; @@ -89,28 +89,28 @@ struct _NO_DISCARD_ Quaternion { void operator*=(const Quaternion &p_q); Quaternion operator*(const Quaternion &p_q) const; - _FORCE_INLINE_ Vector3 xform(const Vector3 &v) const { + _FORCE_INLINE_ Vector3 xform(const Vector3 &p_v) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), v, "The quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), p_v, "The quaternion " + operator String() + " must be normalized."); #endif Vector3 u(x, y, z); - Vector3 uv = u.cross(v); - return v + ((uv * w) + u.cross(uv)) * ((real_t)2); + Vector3 uv = u.cross(p_v); + return p_v + ((uv * w) + u.cross(uv)) * ((real_t)2); } - _FORCE_INLINE_ Vector3 xform_inv(const Vector3 &v) const { - return inverse().xform(v); + _FORCE_INLINE_ Vector3 xform_inv(const Vector3 &p_v) const { + return inverse().xform(p_v); } _FORCE_INLINE_ void operator+=(const Quaternion &p_q); _FORCE_INLINE_ void operator-=(const Quaternion &p_q); - _FORCE_INLINE_ void operator*=(const real_t &s); - _FORCE_INLINE_ void operator/=(const real_t &s); - _FORCE_INLINE_ Quaternion operator+(const Quaternion &q2) const; - _FORCE_INLINE_ Quaternion operator-(const Quaternion &q2) const; + _FORCE_INLINE_ void operator*=(real_t p_s); + _FORCE_INLINE_ void operator/=(real_t p_s); + _FORCE_INLINE_ Quaternion operator+(const Quaternion &p_q2) const; + _FORCE_INLINE_ Quaternion operator-(const Quaternion &p_q2) const; _FORCE_INLINE_ Quaternion operator-() const; - _FORCE_INLINE_ Quaternion operator*(const real_t &s) const; - _FORCE_INLINE_ Quaternion operator/(const real_t &s) const; + _FORCE_INLINE_ Quaternion operator*(real_t p_s) const; + _FORCE_INLINE_ Quaternion operator/(real_t p_s) const; _FORCE_INLINE_ bool operator==(const Quaternion &p_quaternion) const; _FORCE_INLINE_ bool operator!=(const Quaternion &p_quaternion) const; @@ -128,8 +128,6 @@ struct _NO_DISCARD_ Quaternion { Quaternion(const Vector3 &p_axis, real_t p_angle); - Quaternion(const Vector3 &p_euler); - Quaternion(const Quaternion &p_q) : x(p_q.x), y(p_q.y), @@ -144,9 +142,9 @@ struct _NO_DISCARD_ Quaternion { w = p_q.w; } - Quaternion(const Vector3 &v0, const Vector3 &v1) { // Shortest arc. - Vector3 c = v0.cross(v1); - real_t d = v0.dot(v1); + Quaternion(const Vector3 &p_v0, const Vector3 &p_v1) { // Shortest arc. + Vector3 c = p_v0.cross(p_v1); + real_t d = p_v0.dot(p_v1); if (d < -1.0f + (real_t)CMP_EPSILON) { x = 0; @@ -187,25 +185,25 @@ void Quaternion::operator-=(const Quaternion &p_q) { w -= p_q.w; } -void Quaternion::operator*=(const real_t &s) { - x *= s; - y *= s; - z *= s; - w *= s; +void Quaternion::operator*=(real_t p_s) { + x *= p_s; + y *= p_s; + z *= p_s; + w *= p_s; } -void Quaternion::operator/=(const real_t &s) { - *this *= 1.0f / s; +void Quaternion::operator/=(real_t p_s) { + *this *= 1.0f / p_s; } -Quaternion Quaternion::operator+(const Quaternion &q2) const { +Quaternion Quaternion::operator+(const Quaternion &p_q2) const { const Quaternion &q1 = *this; - return Quaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w); + return Quaternion(q1.x + p_q2.x, q1.y + p_q2.y, q1.z + p_q2.z, q1.w + p_q2.w); } -Quaternion Quaternion::operator-(const Quaternion &q2) const { +Quaternion Quaternion::operator-(const Quaternion &p_q2) const { const Quaternion &q1 = *this; - return Quaternion(q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w); + return Quaternion(q1.x - p_q2.x, q1.y - p_q2.y, q1.z - p_q2.z, q1.w - p_q2.w); } Quaternion Quaternion::operator-() const { @@ -213,12 +211,12 @@ Quaternion Quaternion::operator-() const { return Quaternion(-q2.x, -q2.y, -q2.z, -q2.w); } -Quaternion Quaternion::operator*(const real_t &s) const { - return Quaternion(x * s, y * s, z * s, w * s); +Quaternion Quaternion::operator*(real_t p_s) const { + return Quaternion(x * p_s, y * p_s, z * p_s, w * p_s); } -Quaternion Quaternion::operator/(const real_t &s) const { - return *this * (1.0f / s); +Quaternion Quaternion::operator/(real_t p_s) const { + return *this * (1.0f / p_s); } bool Quaternion::operator==(const Quaternion &p_quaternion) const { @@ -229,7 +227,7 @@ bool Quaternion::operator!=(const Quaternion &p_quaternion) const { return x != p_quaternion.x || y != p_quaternion.y || z != p_quaternion.z || w != p_quaternion.w; } -_FORCE_INLINE_ Quaternion operator*(const real_t &p_real, const Quaternion &p_quaternion) { +_FORCE_INLINE_ Quaternion operator*(real_t p_real, const Quaternion &p_quaternion) { return p_quaternion * p_real; } diff --git a/src/variant/quaternion.cpp b/src/variant/quaternion.cpp index c01085051..3dd7af54a 100644 --- a/src/variant/quaternion.cpp +++ b/src/variant/quaternion.cpp @@ -37,28 +37,15 @@ namespace godot { real_t Quaternion::angle_to(const Quaternion &p_to) const { real_t d = dot(p_to); - return Math::acos(CLAMP(d * d * 2 - 1, -1, 1)); + // acos does clamping. + return Math::acos(d * d * 2 - 1); } -// get_euler_xyz returns a vector containing the Euler angles in the format -// (ax,ay,az), where ax is the angle of rotation around x axis, -// and similar for other axes. -// This implementation uses XYZ convention (Z is the first rotation). -Vector3 Quaternion::get_euler_xyz() const { - Basis m(*this); - return m.get_euler(EULER_ORDER_XYZ); -} - -// get_euler_yxz returns a vector containing the Euler angles in the format -// (ax,ay,az), where ax is the angle of rotation around x axis, -// and similar for other axes. -// This implementation uses YXZ convention (Z is the first rotation). -Vector3 Quaternion::get_euler_yxz() const { +Vector3 Quaternion::get_euler(EulerOrder p_order) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion " + operator String() + " must be normalized."); #endif - Basis m(*this); - return m.get_euler(EULER_ORDER_YXZ); + return Basis(*this).get_euler(p_order); } void Quaternion::operator*=(const Quaternion &p_q) { @@ -103,7 +90,7 @@ bool Quaternion::is_normalized() const { Quaternion Quaternion::inverse() const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The quaternion " + operator String() + " must be normalized."); #endif return Quaternion(-x, -y, -z, w); } @@ -125,10 +112,10 @@ Quaternion Quaternion::exp() const { return Quaternion(src_v, theta); } -Quaternion Quaternion::slerp(const Quaternion &p_to, const real_t &p_weight) const { +Quaternion Quaternion::slerp(const Quaternion &p_to, real_t p_weight) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion " + p_to.operator String() + " must be normalized."); #endif Quaternion to1; real_t omega, cosom, sinom, scale0, scale1; @@ -166,10 +153,10 @@ Quaternion Quaternion::slerp(const Quaternion &p_to, const real_t &p_weight) con scale0 * w + scale1 * to1.w); } -Quaternion Quaternion::slerpni(const Quaternion &p_to, const real_t &p_weight) const { +Quaternion Quaternion::slerpni(const Quaternion &p_to, real_t p_weight) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion " + p_to.operator String() + " must be normalized."); #endif const Quaternion &from = *this; @@ -190,10 +177,10 @@ Quaternion Quaternion::slerpni(const Quaternion &p_to, const real_t &p_weight) c invFactor * from.w + newFactor * p_to.w); } -Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight) const { +Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, real_t p_weight) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion " + p_b.operator String() + " must be normalized."); #endif Quaternion from_q = *this; Quaternion pre_q = p_pre_a; @@ -236,15 +223,15 @@ Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const ln.z = Math::cubic_interpolate(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight); Quaternion q2 = to_q * ln.exp(); - // To cancel error made by Expmap ambiguity, do blends. + // To cancel error made by Expmap ambiguity, do blending. return q1.slerp(q2, p_weight); } -Quaternion Quaternion::spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight, - const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const { +Quaternion Quaternion::spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, real_t p_weight, + real_t p_b_t, real_t p_pre_a_t, real_t p_post_b_t) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion " + p_b.operator String() + " must be normalized."); #endif Quaternion from_q = *this; Quaternion pre_q = p_pre_a; @@ -287,7 +274,7 @@ Quaternion Quaternion::spherical_cubic_interpolate_in_time(const Quaternion &p_b ln.z = Math::cubic_interpolate_in_time(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t); Quaternion q2 = to_q * ln.exp(); - // To cancel error made by Expmap ambiguity, do blends. + // To cancel error made by Expmap ambiguity, do blending. return q1.slerp(q2, p_weight); } @@ -309,7 +296,7 @@ real_t Quaternion::get_angle() const { Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) { #ifdef MATH_CHECKS - ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 must be normalized."); + ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 " + p_axis.operator String() + " must be normalized."); #endif real_t d = p_axis.length(); if (d == 0) { @@ -332,7 +319,7 @@ Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) { // (ax, ay, az), where ax is the angle of rotation around x axis, // and similar for other axes. // This implementation uses YXZ convention (Z is the first rotation). -Quaternion::Quaternion(const Vector3 &p_euler) { +Quaternion Quaternion::from_euler(const Vector3 &p_euler) { real_t half_a1 = p_euler.y * 0.5f; real_t half_a2 = p_euler.x * 0.5f; real_t half_a3 = p_euler.z * 0.5f; @@ -348,10 +335,11 @@ Quaternion::Quaternion(const Vector3 &p_euler) { real_t cos_a3 = Math::cos(half_a3); real_t sin_a3 = Math::sin(half_a3); - x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3; - y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3; - z = -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3; - w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; + return Quaternion( + sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3, + sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3, + -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3, + sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3); } } // namespace godot From 2d96b62774dfbef0d438af89a612a9f31ef8a60e Mon Sep 17 00:00:00 2001 From: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Date: Sat, 9 Nov 2024 16:21:59 +0100 Subject: [PATCH 11/18] [Web] Don't cache emsdk Due to how caches are accessed this cache is almost useless, it only matters if it is from the same branch or a base branch, and is identical between branches, so caching it just clutters the build cache (cherry picked from commit 1e3b24f658c0c60f1d2b3a16dcf1ee99ffd6f1a0) --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85c12683a..d9ff31b53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,6 @@ jobs: env: SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ EM_VERSION: 3.1.39 - EM_CACHE_FOLDER: emsdk-cache steps: - name: Checkout @@ -123,7 +122,7 @@ jobs: uses: mymindstorm/setup-emsdk@v14 with: version: ${{ env.EM_VERSION }} - actions-cache-folder: ${{ env.EM_CACHE_FOLDER }} + no-cache: true - name: Setup MinGW for Windows/MinGW build if: matrix.platform == 'windows' && matrix.flags == 'use_mingw=yes' From 9dc2e15d9060e4b8450fcea37fc4866e7604877c Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Tue, 26 Nov 2024 01:49:30 -0800 Subject: [PATCH 12/18] Add `print_line` for compatibility with engine modules (cherry picked from commit ac466e47664fee8e08191a8243e005915ae6f154) --- include/godot_cpp/core/class_db.hpp | 1 + include/godot_cpp/core/print_string.hpp | 75 +++++++++++++++++++++++++ src/core/print_string.cpp | 39 +++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 include/godot_cpp/core/print_string.hpp create mode 100644 src/core/print_string.cpp diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index d9dce792b..c173e737e 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -37,6 +37,7 @@ #include #include #include +#include #include diff --git a/include/godot_cpp/core/print_string.hpp b/include/godot_cpp/core/print_string.hpp new file mode 100644 index 000000000..3d70c796e --- /dev/null +++ b/include/godot_cpp/core/print_string.hpp @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* print_string.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. */ +/**************************************************************************/ + +#ifndef GODOT_PRINT_STRING_HPP +#define GODOT_PRINT_STRING_HPP + +#include + +namespace godot { +inline void print_error(const Variant &p_variant) { + UtilityFunctions::printerr(p_variant); +} + +inline void print_line(const Variant &p_variant) { + UtilityFunctions::print(p_variant); +} + +inline void print_line_rich(const Variant &p_variant) { + UtilityFunctions::print_rich(p_variant); +} + +template +void print_error(const Variant &p_variant, Args... p_args) { + UtilityFunctions::printerr(p_variant, p_args...); +} + +template +void print_line(const Variant &p_variant, Args... p_args) { + UtilityFunctions::print(p_variant, p_args...); +} + +template +void print_line_rich(const Variant &p_variant, Args... p_args) { + UtilityFunctions::print_rich(p_variant, p_args...); +} + +bool is_print_verbose_enabled(); + +// Checking the condition before evaluating the text to be printed avoids processing unless it actually has to be printed, saving some CPU usage. +#define print_verbose(m_variant) \ + { \ + if (is_print_verbose_enabled()) { \ + print_line(m_variant); \ + } \ + } +} // namespace godot + +#endif // GODOT_PRINT_STRING_HPP diff --git a/src/core/print_string.cpp b/src/core/print_string.cpp new file mode 100644 index 000000000..1170137d4 --- /dev/null +++ b/src/core/print_string.cpp @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* print_string.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include + +#include + +namespace godot { +bool is_print_verbose_enabled() { + return OS::get_singleton()->is_stdout_verbose(); +} +} // namespace godot From 59ad323dd10308d10d41fbf08930cfa6487f560c Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Wed, 27 Nov 2024 00:24:02 +0100 Subject: [PATCH 13/18] Add a separate setup-godot-cpp github action. (cherry picked from commit 9943675dcbfb1df48b74d2405b910371ff0d1ec3) --- .github/actions/setup-godot-cpp/action.yml | 62 ++++++++++++++++++++++ .github/workflows/ci.yml | 31 ++--------- 2 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 .github/actions/setup-godot-cpp/action.yml diff --git a/.github/actions/setup-godot-cpp/action.yml b/.github/actions/setup-godot-cpp/action.yml new file mode 100644 index 000000000..287fb2382 --- /dev/null +++ b/.github/actions/setup-godot-cpp/action.yml @@ -0,0 +1,62 @@ +name: Setup godot-cpp +description: Setup build dependencies for godot-cpp. + +inputs: + platform: + required: true + description: Target platform. + em-version: + default: 3.1.62 + description: Emscripten version. + windows-compiler: + required: true + description: The compiler toolchain to use on Windows ('mingw' or 'msvc'). + type: choice + options: + - mingw + - msvc + default: mingw + mingw-version: + default: 12.2.0 + description: MinGW version. + ndk-version: + default: r23c + description: Android NDK version. + scons-version: + default: 4.4.0 + description: SCons version. + +runs: + using: composite + steps: + - name: Setup Python (for SCons) + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - name: Setup Android dependencies + if: inputs.platform == 'android' + uses: nttld/setup-ndk@v1 + with: + ndk-version: ${{ inputs.ndk-version }} + link-to-sdk: true + + - name: Setup Web dependencies + if: inputs.platform == 'web' + uses: mymindstorm/setup-emsdk@v14 + with: + version: ${{ inputs.em-version }} + no-cache: true + + - name: Setup MinGW for Windows/MinGW build + if: inputs.platform == 'windows' && inputs.windows-compiler == 'mingw' + uses: egor-tensin/setup-mingw@v2 + with: + version: ${{ inputs.mingw-version }} + + - name: Setup SCons + shell: bash + run: | + python -c "import sys; print(sys.version)" + python -m pip install scons==${{ inputs.scons-version }} + scons --version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9ff31b53..b5c24530f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,34 +105,11 @@ jobs: cache-name: ${{ matrix.cache-name }} continue-on-error: true - - name: Set up Python (for SCons) - uses: actions/setup-python@v5 + - name: Setup godot-cpp + uses: ./.github/actions/setup-godot-cpp with: - python-version: 3.x - - - name: Android dependencies - if: matrix.platform == 'android' - uses: nttld/setup-ndk@v1 - with: - ndk-version: r23c - link-to-sdk: true - - - name: Web dependencies - if: matrix.platform == 'web' - uses: mymindstorm/setup-emsdk@v14 - with: - version: ${{ env.EM_VERSION }} - no-cache: true - - - name: Setup MinGW for Windows/MinGW build - if: matrix.platform == 'windows' && matrix.flags == 'use_mingw=yes' - uses: egor-tensin/setup-mingw@v2 - with: - version: 12.2.0 - - - name: Install scons - run: | - python -m pip install scons==4.0.0 + platform: ${{ matrix.platform }} + windows-compiler: ${{ contains(matrix.flags, 'use_mingw=yes') && 'mingw' || 'msvc' }} - name: Generate godot-cpp sources only run: | From fba9ecd0da83988a4ebcd28d1e7b4171b797f741 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Mon, 9 Dec 2024 11:33:57 -0600 Subject: [PATCH 14/18] Fix `print_verbose()` macro conflicting with `UtilityFunctions::print_verbose()` (cherry picked from commit 47d9cb9bed021662ec8f5b8f34db1ff4b507a68a) --- include/godot_cpp/core/print_string.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/include/godot_cpp/core/print_string.hpp b/include/godot_cpp/core/print_string.hpp index 3d70c796e..61f4330cf 100644 --- a/include/godot_cpp/core/print_string.hpp +++ b/include/godot_cpp/core/print_string.hpp @@ -61,15 +61,13 @@ void print_line_rich(const Variant &p_variant, Args... p_args) { UtilityFunctions::print_rich(p_variant, p_args...); } +template +void print_verbose(const Variant &p_variant, Args... p_args) { + UtilityFunctions::print_verbose(p_variant, p_args...); +} + bool is_print_verbose_enabled(); -// Checking the condition before evaluating the text to be printed avoids processing unless it actually has to be printed, saving some CPU usage. -#define print_verbose(m_variant) \ - { \ - if (is_print_verbose_enabled()) { \ - print_line(m_variant); \ - } \ - } } // namespace godot #endif // GODOT_PRINT_STRING_HPP From 4b9cd6ae9d12583b4b166c39d201c20867ebfdf1 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Fri, 20 Dec 2024 03:31:59 +0100 Subject: [PATCH 15/18] [Bindings] Build profile now strips methods and skip files This allows removing dependencies that are not explicitly unused by the gdextension being built and is implemented using an intermediate json API file with the methods and classes stripped (i.e. without touching the file generators). (cherry picked from commit c4f1abe3f99343c4731bfcf3057297b3e38498b8) --- binding_generator.py | 150 ++++---------------------------- build_profile.py | 183 ++++++++++++++++++++++++++++++++++++++++ test/build_profile.json | 6 +- tools/godotcpp.py | 34 +++++++- 4 files changed, 236 insertions(+), 137 deletions(-) create mode 100644 build_profile.py diff --git a/binding_generator.py b/binding_generator.py index 75e56fde1..ab4453068 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -197,13 +197,16 @@ def generate_virtuals(target): f.write(txt) -def get_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""): +def get_file_list(api_filepath, output_dir, headers=False, sources=False): api = {} - files = [] with open(api_filepath, encoding="utf-8") as api_file: api = json.load(api_file) - build_profile = parse_build_profile(profile_filepath, api) + return _get_file_list(api, output_dir, headers, sources) + + +def _get_file_list(api, output_dir, headers=False, sources=False): + files = [] core_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp" / "core" include_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp" @@ -235,7 +238,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False, profil source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp") if headers: files.append(str(header_filename.as_posix())) - if sources and is_class_included(engine_class["name"], build_profile): + if sources: files.append(str(source_filename.as_posix())) for native_struct in api["native_structures"]: @@ -267,128 +270,19 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False, profil return files -def print_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""): - print(*get_file_list(api_filepath, output_dir, headers, sources, profile_filepath), sep=";", end=None) - - -def parse_build_profile(profile_filepath, api): - if profile_filepath == "": - return {} - print("Using feature build profile: " + profile_filepath) - - with open(profile_filepath, encoding="utf-8") as profile_file: - profile = json.load(profile_file) - - api_dict = {} - parents = {} - children = {} - for engine_class in api["classes"]: - api_dict[engine_class["name"]] = engine_class - parent = engine_class.get("inherits", "") - child = engine_class["name"] - parents[child] = parent - if parent == "": - continue - children[parent] = children.get(parent, []) - children[parent].append(child) - - # Parse methods dependencies - deps = {} - reverse_deps = {} - for name, engine_class in api_dict.items(): - ref_cls = set() - for method in engine_class.get("methods", []): - rtype = method.get("return_value", {}).get("type", "") - args = [a["type"] for a in method.get("arguments", [])] - if rtype in api_dict: - ref_cls.add(rtype) - elif is_enum(rtype) and get_enum_class(rtype) in api_dict: - ref_cls.add(get_enum_class(rtype)) - for arg in args: - if arg in api_dict: - ref_cls.add(arg) - elif is_enum(arg) and get_enum_class(arg) in api_dict: - ref_cls.add(get_enum_class(arg)) - deps[engine_class["name"]] = set(filter(lambda x: x != name, ref_cls)) - for acls in ref_cls: - if acls == name: - continue - reverse_deps[acls] = reverse_deps.get(acls, set()) - reverse_deps[acls].add(name) - - included = [] - front = list(profile.get("enabled_classes", [])) - if front: - # These must always be included - front.append("WorkerThreadPool") - front.append("ClassDB") - front.append("ClassDBSingleton") - while front: - cls = front.pop() - if cls in included: - continue - included.append(cls) - parent = parents.get(cls, "") - if parent: - front.append(parent) - for rcls in deps.get(cls, set()): - if rcls in included or rcls in front: - continue - front.append(rcls) - - excluded = [] - front = list(profile.get("disabled_classes", [])) - while front: - cls = front.pop() - if cls in excluded: - continue - excluded.append(cls) - front += children.get(cls, []) - for rcls in reverse_deps.get(cls, set()): - if rcls in excluded or rcls in front: - continue - front.append(rcls) - - if included and excluded: - print( - "WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored." - ) - - return { - "enabled_classes": included, - "disabled_classes": excluded, - } - - -def scons_emit_files(target, source, env): - profile_filepath = env.get("build_profile", "") - if profile_filepath and not Path(profile_filepath).is_absolute(): - profile_filepath = str((Path(env.Dir("#").abspath) / profile_filepath).as_posix()) - - files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True, profile_filepath)] - env.Clean(target, files) - env["godot_cpp_gen_dir"] = target[0].abspath - return files, source - - -def scons_generate_bindings(target, source, env): - generate_bindings( - str(source[0]), - env["generate_template_get_node"], - "32" if "32" in env["arch"] else "64", - env["precision"], - env["godot_cpp_gen_dir"], - ) - return None +def print_file_list(api_filepath, output_dir, headers=False, sources=False): + print(*get_file_list(api_filepath, output_dir, headers, sources), sep=";", end=None) def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."): - api = None - - target_dir = Path(output_dir) / "gen" - + api = {} with open(api_filepath, encoding="utf-8") as api_file: api = json.load(api_file) + _generate_bindings(api, use_template_get_node, bits, precision, output_dir) + + +def _generate_bindings(api, use_template_get_node, bits="64", precision="single", output_dir="."): + target_dir = Path(output_dir) / "gen" shutil.rmtree(target_dir, ignore_errors=True) target_dir.mkdir(parents=True) @@ -2646,20 +2540,6 @@ def is_refcounted(type_name): return type_name in engine_classes and engine_classes[type_name] -def is_class_included(class_name, build_profile): - """ - Check if an engine class should be included. - This removes classes according to a build profile of enabled or disabled classes. - """ - included = build_profile.get("enabled_classes", []) - excluded = build_profile.get("disabled_classes", []) - if included: - return class_name in included - if excluded: - return class_name not in excluded - return True - - def is_included(type_name, current_type): """ Check if a builtin type should be included. diff --git a/build_profile.py b/build_profile.py new file mode 100644 index 000000000..b4d19ded6 --- /dev/null +++ b/build_profile.py @@ -0,0 +1,183 @@ +import json +import sys + + +def parse_build_profile(profile_filepath, api): + if profile_filepath == "": + return {} + + with open(profile_filepath, encoding="utf-8") as profile_file: + profile = json.load(profile_file) + + api_dict = {} + parents = {} + children = {} + for engine_class in api["classes"]: + api_dict[engine_class["name"]] = engine_class + parent = engine_class.get("inherits", "") + child = engine_class["name"] + parents[child] = parent + if parent == "": + continue + children[parent] = children.get(parent, []) + children[parent].append(child) + + included = [] + front = list(profile.get("enabled_classes", [])) + if front: + # These must always be included + front.append("WorkerThreadPool") + front.append("ClassDB") + front.append("ClassDBSingleton") + # In src/classes/low_level.cpp + front.append("FileAccess") + front.append("Image") + front.append("XMLParser") + # In include/godot_cpp/templates/thread_work_pool.hpp + front.append("Semaphore") + while front: + cls = front.pop() + if cls in included: + continue + included.append(cls) + parent = parents.get(cls, "") + if parent: + front.append(parent) + + excluded = [] + front = list(profile.get("disabled_classes", [])) + while front: + cls = front.pop() + if cls in excluded: + continue + excluded.append(cls) + front += children.get(cls, []) + + if included and excluded: + print( + "WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored." + ) + + return { + "enabled_classes": included, + "disabled_classes": excluded, + } + + +def generate_trimmed_api(source_api_filepath, profile_filepath): + with open(source_api_filepath, encoding="utf-8") as api_file: + api = json.load(api_file) + + if profile_filepath == "": + return api + + build_profile = parse_build_profile(profile_filepath, api) + + engine_classes = {} + for class_api in api["classes"]: + engine_classes[class_api["name"]] = class_api["is_refcounted"] + for native_struct in api["native_structures"]: + if native_struct["name"] == "ObjectID": + continue + engine_classes[native_struct["name"]] = False + + classes = [] + for class_api in api["classes"]: + if not is_class_included(class_api["name"], build_profile): + continue + if "methods" in class_api: + methods = [] + for method in class_api["methods"]: + if not is_method_included(method, build_profile, engine_classes): + continue + methods.append(method) + class_api["methods"] = methods + classes.append(class_api) + api["classes"] = classes + + return api + + +def is_class_included(class_name, build_profile): + """ + Check if an engine class should be included. + This removes classes according to a build profile of enabled or disabled classes. + """ + included = build_profile.get("enabled_classes", []) + excluded = build_profile.get("disabled_classes", []) + if included: + return class_name in included + if excluded: + return class_name not in excluded + return True + + +def is_method_included(method, build_profile, engine_classes): + """ + Check if an engine class method should be included. + This removes methods according to a build profile of enabled or disabled classes. + """ + included = build_profile.get("enabled_classes", []) + excluded = build_profile.get("disabled_classes", []) + ref_cls = set() + rtype = get_base_type(method.get("return_value", {}).get("type", "")) + args = [get_base_type(a["type"]) for a in method.get("arguments", [])] + if rtype in engine_classes: + ref_cls.add(rtype) + elif is_enum(rtype) and get_enum_class(rtype) in engine_classes: + ref_cls.add(get_enum_class(rtype)) + for arg in args: + if arg in engine_classes: + ref_cls.add(arg) + elif is_enum(arg) and get_enum_class(arg) in engine_classes: + ref_cls.add(get_enum_class(arg)) + for acls in ref_cls: + if len(included) > 0 and acls not in included: + return False + elif len(excluded) > 0 and acls in excluded: + return False + return True + + +def is_enum(type_name): + return type_name.startswith("enum::") or type_name.startswith("bitfield::") + + +def get_enum_class(enum_name: str): + if "." in enum_name: + if is_bitfield(enum_name): + return enum_name.replace("bitfield::", "").split(".")[0] + else: + return enum_name.replace("enum::", "").split(".")[0] + else: + return "GlobalConstants" + + +def get_base_type(type_name): + if type_name.startswith("const "): + type_name = type_name[6:] + if type_name.endswith("*"): + type_name = type_name[:-1] + if type_name.startswith("typedarray::"): + type_name = type_name.replace("typedarray::", "") + return type_name + + +def is_bitfield(type_name): + return type_name.startswith("bitfield::") + + +if __name__ == "__main__": + if len(sys.argv) < 3 or len(sys.argv) > 4: + print("Usage: %s BUILD_PROFILE INPUT_JSON [OUTPUT_JSON]" % (sys.argv[0])) + sys.exit(1) + profile = sys.argv[1] + infile = sys.argv[2] + outfile = sys.argv[3] if len(sys.argv) > 3 else "" + api = generate_trimmed_api(infile, profile) + + if outfile: + with open(outfile, "w", encoding="utf-8") as f: + json.dump(api, f) + else: + json.dump(api, sys.stdout) diff --git a/test/build_profile.json b/test/build_profile.json index 3587651c1..57d847a11 100644 --- a/test/build_profile.json +++ b/test/build_profile.json @@ -1,9 +1,13 @@ { "enabled_classes": [ "Control", + "InputEventKey", "Label", + "MultiplayerAPI", + "MultiplayerPeer", "OS", "TileMap", - "InputEventKey" + "TileSet", + "Viewport" ] } diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 77a0740fc..58392e3a4 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -10,7 +10,8 @@ from SCons.Variables import BoolVariable, EnumVariable, PathVariable from SCons.Variables.BoolVariable import _text2bool -from binding_generator import scons_emit_files, scons_generate_bindings +from binding_generator import _generate_bindings, _get_file_list, get_file_list +from build_profile import generate_trimmed_api def add_sources(sources, dir, extension): @@ -129,6 +130,37 @@ def no_verbose(env): env.Append(GENCOMSTR=[generated_file_message]) +def scons_emit_files(target, source, env): + profile_filepath = env.get("build_profile", "") + if profile_filepath: + profile_filepath = normalize_path(profile_filepath, env) + + # Always clean all files + env.Clean(target, [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True)]) + + api = generate_trimmed_api(str(source[0]), profile_filepath) + files = [env.File(f) for f in _get_file_list(api, target[0].abspath, True, True)] + env["godot_cpp_gen_dir"] = target[0].abspath + return files, source + + +def scons_generate_bindings(target, source, env): + profile_filepath = env.get("build_profile", "") + if profile_filepath: + profile_filepath = normalize_path(profile_filepath, env) + + api = generate_trimmed_api(str(source[0]), profile_filepath) + + _generate_bindings( + api, + env["generate_template_get_node"], + "32" if "32" in env["arch"] else "64", + env["precision"], + env["godot_cpp_gen_dir"], + ) + return None + + platforms = ["linux", "macos", "windows", "android", "ios", "web"] # CPU architecture options. From dc87cb6b975363f7c4c58bad967ea47e4316f7eb Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Mon, 30 Dec 2024 17:10:36 +0100 Subject: [PATCH 16/18] [CI] Re-add generated files consistency check (cherry picked from commit 0cfe01eff23459a1c0c516e02940cf21317f777d) --- .github/workflows/static_checks.yml | 4 +++ misc/scripts/check_get_file_list.py | 48 +++++++++++++++++++---------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index c8d271393..a3d813226 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -32,3 +32,7 @@ jobs: uses: pre-commit/action@v3.0.1 with: extra_args: --verbose --hook-stage manual --files ${{ env.CHANGED_FILES }} + + - name: Check generated files consistency + run: + python misc/scripts/check_get_file_list.py diff --git a/misc/scripts/check_get_file_list.py b/misc/scripts/check_get_file_list.py index 33bc6b624..ac90777a5 100755 --- a/misc/scripts/check_get_file_list.py +++ b/misc/scripts/check_get_file_list.py @@ -6,26 +6,40 @@ sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "..")) -from binding_generator import generate_bindings, get_file_list +from binding_generator import _generate_bindings, _get_file_list +from build_profile import generate_trimmed_api api_filepath = "gdextension/extension_api.json" bits = "64" precision = "single" output_dir = "self_test" -generate_bindings(api_filepath, use_template_get_node=False, bits=bits, precision=precision, output_dir=output_dir) -flist = get_file_list(api_filepath, output_dir, headers=True, sources=True) - -p = Path(output_dir) / "gen" -allfiles = [str(f.as_posix()) for f in p.glob("**/*.*")] -missing = list(filter((lambda f: f not in flist), allfiles)) -extras = list(filter((lambda f: f not in allfiles), flist)) -if len(missing) > 0 or len(extras) > 0: - print("Error!") - for f in missing: - print("MISSING: " + str(f)) - for f in extras: - print("EXTRA: " + str(f)) - sys.exit(1) -else: - print("OK!") + +def test(profile_filepath=""): + api = generate_trimmed_api(api_filepath, profile_filepath) + _generate_bindings( + api, + use_template_get_node=False, + bits=bits, + precision=precision, + output_dir=output_dir, + ) + flist = _get_file_list(api, output_dir, headers=True, sources=True) + + p = Path(output_dir) / "gen" + allfiles = [str(f.as_posix()) for f in p.glob("**/*.*")] + missing = list(filter((lambda f: f not in flist), allfiles)) + extras = list(filter((lambda f: f not in allfiles), flist)) + if len(missing) > 0 or len(extras) > 0: + print("Error!") + for f in missing: + print("MISSING: " + str(f)) + for f in extras: + print("EXTRA: " + str(f)) + sys.exit(1) + else: + print("OK!") + + +test() +test("test/build_profile.json") From 86d0dbe695592a9d8c499d5fedfbf446f3bcec4f Mon Sep 17 00:00:00 2001 From: Brecht Kuppens Date: Mon, 20 Jan 2025 09:25:59 +0100 Subject: [PATCH 17/18] Update README.md with new pre-commit instructions (cherry picked from commit bd3cf478c6912977eac0578c702e33665ff56bc8) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 005c8e723..843f7b34e 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,7 @@ 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 copy the files in `misc/hooks` into `.git/hooks` -so formatting is done before your changes are submitted. +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. ## Getting started From 08e4c89da97fa8b708c55126bf562e6aae0d5f24 Mon Sep 17 00:00:00 2001 From: Brecht Kuppens Date: Mon, 20 Jan 2025 10:15:47 +0100 Subject: [PATCH 18/18] Fix buffer overrun with enums pointers cast to int64_t* when enum is only 32-bit (cherry picked from commit 7576dc5930123a088526cbb9abab8bc0246efa6e) --- binding_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/binding_generator.py b/binding_generator.py index ab4453068..30217bd38 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -2224,6 +2224,10 @@ def get_encoded_arg(arg_name, type_name, type_meta): result.append(f"\t{get_gdextension_type(arg_type)} {name}_encoded;") result.append(f"\tPtrToArg<{correct_type(type_name)}>::encode({name}, &{name}_encoded);") name = f"&{name}_encoded" + elif is_enum(type_name) and not is_bitfield(type_name): + result.append(f"\tint64_t {name}_encoded;") + result.append(f"\tPtrToArg::encode({name}, &{name}_encoded);") + name = f"&{name}_encoded" elif is_engine_class(type_name): # `{name}` is a C++ wrapper, it contains a field which is the object's pointer Godot expects. # We have to check `nullptr` because when the caller sends `nullptr`, the wrapper itself will be null.