diff --git a/.github/actions/godot-cache-restore/action.yml b/.github/actions/godot-cache-restore/action.yml index 5df57765..f10222bf 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 b7cbf91f..df877cec 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/actions/setup-godot-cpp/action.yml b/.github/actions/setup-godot-cpp/action.yml new file mode 100644 index 00000000..287fb238 --- /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 a9238fed..de0b9977 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.2.2-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,6 @@ jobs: env: SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ EM_VERSION: 3.1.39 - EM_CACHE_FOLDER: "emsdk-cache" steps: - name: Checkout @@ -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}} - actions-cache-folder: ${{env.EM_CACHE_FOLDER}} - - - 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: | @@ -161,7 +138,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 +152,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 +166,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 00000000..a2e4f91b --- /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 6899248e..a3d81322 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: @@ -31,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/README.md b/README.md index ca9a7384..27bfa34e 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,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 diff --git a/binding_generator.py b/binding_generator.py index bd62ed88..8442153e 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -72,11 +72,14 @@ def generate_wrappers(target): def get_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""): 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" @@ -107,7 +110,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"]: @@ -139,128 +142,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) @@ -2250,6 +2144,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. @@ -2558,20 +2456,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 00000000..b4d19ded --- /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/include/godot_cpp/classes/ref.hpp b/include/godot_cpp/classes/ref.hpp index c4682bcf..a7e94873 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)))); } }; diff --git a/include/godot_cpp/core/defs.hpp b/include/godot_cpp/core/defs.hpp index 16812c2b..5b985ffc 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 diff --git a/include/godot_cpp/variant/quaternion.hpp b/include/godot_cpp/variant/quaternion.hpp index 5de91b20..8d0afd78 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/include/godot_cpp/variant/variant.hpp b/include/godot_cpp/variant/variant.hpp index 5b09a15e..122c776d 100644 --- a/include/godot_cpp/variant/variant.hpp +++ b/include/godot_cpp/variant/variant.hpp @@ -324,8 +324,6 @@ public: 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); diff --git a/include/godot_cpp/variant/vector4.hpp b/include/godot_cpp/variant/vector4.hpp index b20915aa..04f9cb6d 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; diff --git a/misc/scripts/check_get_file_list.py b/misc/scripts/check_get_file_list.py index 33bc6b62..ac90777a 100755 --- a/misc/scripts/check_get_file_list.py +++ b/misc/scripts/check_get_file_list.py @@ -6,26 +6,40 @@ from pathlib import Path 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") diff --git a/src/variant/quaternion.cpp b/src/variant/quaternion.cpp index c0108505..3dd7af54 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 diff --git a/test/build_profile.json b/test/build_profile.json index 3587651c..57d847a1 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/android.py b/tools/android.py index 0222121e..fee4ed25 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 6a1fb693..e645f390 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 5773660d..d80fafca 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -10,7 +10,8 @@ from SCons.Tool import Tool 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. @@ -326,6 +358,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 9675ab1a..25c17db3 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 0b268788..9e85d880 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 74181505..f88e47ff 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 c8f07c55..c4918807 100644 --- a/tools/web.py +++ b/tools/web.py @@ -48,4 +48,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 2e8d609e..490b9f71 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)