diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml new file mode 100644 index 000000000000..58c2aa906071 --- /dev/null +++ b/.github/actions/download-artifact/action.yml @@ -0,0 +1,18 @@ +name: Download Godot artifact +description: Download the Godot artifact. +inputs: + name: + description: The artifact name. + default: "${{ github.job }}" + path: + description: The path to download and extract to. + required: true + default: "./" +runs: + using: "composite" + steps: + - name: Download Godot Artifact + uses: actions/download-artifact@v3 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} diff --git a/.github/actions/godot-api-dump/action.yml b/.github/actions/godot-api-dump/action.yml new file mode 100644 index 000000000000..47b675ae99fc --- /dev/null +++ b/.github/actions/godot-api-dump/action.yml @@ -0,0 +1,24 @@ +name: Dump Godot API +description: Dump Godot API for GDExtension +inputs: + bin: + description: The path to the Godot executable + required: true +runs: + using: "composite" + steps: + # Dump GDExtension interface and API + - name: Dump GDExtension interface and API for godot-cpp build + shell: sh + run: | + ${{ inputs.bin }} --headless --dump-gdextension-interface --dump-extension-api + mkdir godot-api + cp -f gdextension_interface.h godot-api/ + cp -f extension_api.json godot-api/ + + - name: Upload API dump + uses: ./.github/actions/upload-artifact + with: + name: 'godot-api-dump' + path: './godot-api/*' + diff --git a/.github/actions/godot-cache/action.yml b/.github/actions/godot-cache/action.yml index 2d7afc8514b8..09ad2099cc3c 100644 --- a/.github/actions/godot-cache/action.yml +++ b/.github/actions/godot-cache/action.yml @@ -16,7 +16,20 @@ runs: with: path: ${{inputs.scons-cache}} key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + + # We try to match an existing cache to restore from it. Each potential key is checked against + # all existing caches as a prefix. E.g. 'linux-template-minimal' would match any cache that + # starts with "linux-template-minimal", such as "linux-template-minimal-master-refs/heads/master-6588a4a29af1621086feac0117d5d4d37af957fd". + # + # We check these prefixes in this order: + # + # 1. The exact match, including the base branch, the commit reference, and the SHA hash of the commit. + # 2. A partial match for the same base branch and the same commit reference. + # 3. A partial match for the same base branch and the base branch commit reference. + # 4. A partial match for the same base branch only (not ideal, matches any PR with the same base branch). + 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}}-refs/heads/${{env.GODOT_BASE_BRANCH}} ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}} diff --git a/.github/actions/godot-converter-test/action.yml b/.github/actions/godot-converter-test/action.yml new file mode 100644 index 000000000000..919a76e69375 --- /dev/null +++ b/.github/actions/godot-converter-test/action.yml @@ -0,0 +1,18 @@ +name: Test Godot project converter +description: Test the Godot project converter. +inputs: + bin: + description: The path to the Godot executable + required: true +runs: + using: "composite" + steps: + - name: Test 3-to-4 conversion + shell: sh + run: | + mkdir converter_test + cd converter_test + touch project.godot + ../${{ inputs.bin }} --headless --validate-conversion-3to4 + cd .. + rm converter_test -rf diff --git a/.github/actions/godot-project-test/action.yml b/.github/actions/godot-project-test/action.yml new file mode 100644 index 000000000000..fd8c024a37df --- /dev/null +++ b/.github/actions/godot-project-test/action.yml @@ -0,0 +1,37 @@ +name: Test Godot project +description: Run the test Godot project. +inputs: + bin: + description: The path to the Godot executable + required: true +runs: + using: "composite" + steps: + # Download and extract zip archive with project, folder is renamed to be able to easy change used project + - name: Download test project + shell: sh + run: | + wget https://github.com/godotengine/regression-test-project/archive/4.0.zip + unzip 4.0.zip + mv "regression-test-project-4.0" "test_project" + + # Editor is quite complicated piece of software, so it is easy to introduce bug here. + + - name: Open and close editor (Vulkan) + shell: sh + run: | + xvfb-run ${{ inputs.bin }} --audio-driver Dummy --editor --quit --path test_project 2>&1 | tee sanitizers_log.txt || true + misc/scripts/check_ci_log.py sanitizers_log.txt + + - name: Open and close editor (GLES3) + shell: sh + run: | + DRI_PRIME=0 xvfb-run ${{ inputs.bin }} --audio-driver Dummy --rendering-driver opengl3 --editor --quit --path test_project 2>&1 | tee sanitizers_log.txt || true + misc/scripts/check_ci_log.py sanitizers_log.txt + + # Run test project + - name: Run project + shell: sh + run: | + xvfb-run ${{ inputs.bin }} 40 --audio-driver Dummy --path test_project 2>&1 | tee sanitizers_log.txt || true + misc/scripts/check_ci_log.py sanitizers_log.txt diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml new file mode 100644 index 000000000000..2051920f3071 --- /dev/null +++ b/.github/workflows/godot_cpp_test.yml @@ -0,0 +1,54 @@ +name: 🪲 Godot CPP +on: + workflow_call: + +# Global Settings +env: + # Used for the cache key, and godot-cpp checkout. Add version suffix to force clean build. + GODOT_BASE_BRANCH: master + +concurrency: + group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-cpp-tests + cancel-in-progress: true + +jobs: + godot-cpp-tests: + runs-on: "ubuntu-20.04" + name: "Build and test Godot CPP" + steps: + - uses: actions/checkout@v3 + + - name: Setup python and scons + uses: ./.github/actions/godot-deps + + # Checkout godot-cpp + - name: Checkout godot-cpp + uses: actions/checkout@v3 + with: + repository: godotengine/godot-cpp + ref: ${{ env.GODOT_BASE_BRANCH }} + submodules: 'recursive' + path: 'godot-cpp' + + # Download generated API dump + - name: Download GDExtension interface and API dump + uses: ./.github/actions/download-artifact + with: + name: 'godot-api-dump' + path: './godot-api' + + # Extract and override existing files with generated files + - name: Extract GDExtension interface and API dump + run: | + cp -f godot-api/gdextension_interface.h godot-cpp/gdextension/ + cp -f godot-api/extension_api.json godot-cpp/gdextension/ + + # TODO: Add caching to the scons build and store it for CI via the godot-cache + # action. + + # Build godot-cpp test extension + - name: Build godot-cpp test extension + run: | + cd godot-cpp/test + scons target=template_debug dev_build=yes + cd ../.. diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index aef8f83a53e1..c812996fd472 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -4,11 +4,12 @@ on: # Global Settings env: - # Used for the cache key, and godot-cpp checkout. Add version suffix to force clean build. + # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true + TSAN_OPTIONS: suppressions=misc/error_suppressions/tsan.txt concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux @@ -25,53 +26,62 @@ jobs: - name: Editor w/ Mono (target=editor) cache-name: linux-editor-mono target: editor - tests: false # Disabled due freeze caused by mix Mono build and CI sconsflags: module_mono_enabled=yes - doc-test: true bin: "./bin/godot.linuxbsd.editor.x86_64.mono" build-mono: true + tests: false # Disabled due freeze caused by mix Mono build and CI + doc-test: true proj-conv: true + api-compat: true artifact: true - compat: true - name: Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold) cache-name: linux-editor-double-sanitizers target: editor - tests: true # Debug symbols disabled as they're huge on this build and we hit the 14 GB limit for runners. sconsflags: dev_build=yes scu_build=yes debug_symbols=no precision=double use_asan=yes use_ubsan=yes linker=gold - proj-test: true - # Can be turned off for PRs that intentionally break compat with godot-cpp, - # until both the upstream PR and the matching godot-cpp changes are merged. - godot-cpp-test: true bin: "./bin/godot.linuxbsd.editor.dev.double.x86_64.san" build-mono: false + tests: true + proj-test: true + # Generate an API dump for godot-cpp tests. + api-dump: true # Skip 2GiB artifact speeding up action. artifact: false - name: Editor with clang sanitizers (target=editor, tests=yes, dev_build=yes, use_asan=yes, use_ubsan=yes, use_llvm=yes, linker=lld) cache-name: linux-editor-llvm-sanitizers target: editor - tests: true sconsflags: dev_build=yes use_asan=yes use_ubsan=yes use_llvm=yes linker=lld bin: "./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san" build-mono: false + tests: true + # Skip 2GiB artifact speeding up action. + artifact: false + + - name: Editor with ThreadSanitizer (target=editor, tests=yes, dev_build=yes, use_tsan=yes, use_llvm=yes, linker=lld) + cache-name: linux-editor-thread-sanitizer + target: editor + tests: true + sconsflags: dev_build=yes use_tsan=yes use_llvm=yes linker=lld + bin: "./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san" + build-mono: false # Skip 2GiB artifact speeding up action. artifact: false - name: Template w/ Mono (target=template_release) cache-name: linux-template-mono target: template_release - tests: false sconsflags: module_mono_enabled=yes build-mono: false + tests: false artifact: true - name: Minimal template (target=template_release, everything disabled) cache-name: linux-template-minimal target: template_release - tests: false sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no + tests: false artifact: true steps: @@ -85,6 +95,12 @@ jobs: sudo add-apt-repository ppa:kisak/kisak-mesa sudo apt-get install -qq mesa-vulkan-drivers + - name: Free disk space on runner + run: | + echo "Disk usage before:" && df -h + sudo rm -rf /usr/local/lib/android + echo "Disk usage after:" && df -h + - name: Setup Godot build cache uses: ./.github/actions/godot-cache with: @@ -121,6 +137,24 @@ jobs: run: | ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir=./bin --godot-platform=linuxbsd + - name: Prepare artifact + if: ${{ matrix.artifact }} + run: | + strip bin/godot.* + chmod +x bin/godot.* + + - name: Upload artifact + uses: ./.github/actions/upload-artifact + if: ${{ matrix.artifact }} + with: + name: ${{ matrix.cache-name }} + + - name: Dump Godot API + uses: ./.github/actions/godot-api-dump + if: ${{ matrix.api-dump }} + with: + bin: ${{ matrix.bin }} + # Execute unit tests for the editor - name: Unit tests if: ${{ matrix.tests }} @@ -138,84 +172,22 @@ jobs: ${{ matrix.bin }} --doctool --headless 2>&1 > /dev/null || true git diff --color --exit-code && ! git ls-files --others --exclude-standard | sed -e 's/^/New doc file missing in PR: /' | grep 'xml$' - # Test 3.x -> 4.x project converter - - name: Test project converter - if: ${{ matrix.proj-conv }} - run: | - mkdir converter_test - cd converter_test - touch project.godot - ../${{ matrix.bin }} --headless --validate-conversion-3to4 - cd .. - rm converter_test -rf - - # Download and extract zip archive with project, folder is renamed to be able to easy change used project - - name: Download test project - if: ${{ matrix.proj-test }} - run: | - wget https://github.com/godotengine/regression-test-project/archive/4.0.zip - unzip 4.0.zip - mv "regression-test-project-4.0" "test_project" - - # Editor is quite complicated piece of software, so it is easy to introduce bug here - - name: Open and close editor (Vulkan) - if: ${{ matrix.proj-test }} - run: | - xvfb-run ${{ matrix.bin }} --audio-driver Dummy --editor --quit --path test_project 2>&1 | tee sanitizers_log.txt || true - misc/scripts/check_ci_log.py sanitizers_log.txt - - - name: Open and close editor (GLES3) - if: ${{ matrix.proj-test }} + # Check API backwards compatibility + - name: Check for GDExtension compatibility + if: ${{ matrix.api-compat }} run: | - DRI_PRIME=0 xvfb-run ${{ matrix.bin }} --audio-driver Dummy --rendering-driver opengl3 --editor --quit --path test_project 2>&1 | tee sanitizers_log.txt || true - misc/scripts/check_ci_log.py sanitizers_log.txt + ./misc/scripts/validate_extension_api.sh "${{ matrix.bin }}" - # Run test project - - name: Run project + # Download and run the test project + - name: Test Godot project + uses: ./.github/actions/godot-project-test if: ${{ matrix.proj-test }} - run: | - xvfb-run ${{ matrix.bin }} 40 --audio-driver Dummy --path test_project 2>&1 | tee sanitizers_log.txt || true - misc/scripts/check_ci_log.py sanitizers_log.txt - - # Checkout godot-cpp - - name: Checkout godot-cpp - if: ${{ matrix.godot-cpp-test }} - uses: actions/checkout@v3 with: - repository: godotengine/godot-cpp - ref: ${{ env.GODOT_BASE_BRANCH }} - submodules: 'recursive' - path: 'godot-cpp' - - # Dump GDExtension interface and API - - name: Dump GDExtension interface and API for godot-cpp build - if: ${{ matrix.godot-cpp-test }} - run: | - ${{ matrix.bin }} --headless --dump-gdextension-interface --dump-extension-api - cp -f gdextension_interface.h godot-cpp/gdextension/ - cp -f extension_api.json godot-cpp/gdextension/ + bin: ${{ matrix.bin }} - # Build godot-cpp test extension - - name: Build godot-cpp test extension - if: ${{ matrix.godot-cpp-test }} - run: | - cd godot-cpp/test - scons target=template_debug dev_build=yes - cd ../.. - - - name: Check for GDExtension compatibility - if: ${{ matrix.compat }} - run: | - ./misc/scripts/validate_extension_api.sh "${{ matrix.bin }}" || true # don't fail the CI for now - - - name: Prepare artifact - if: ${{ matrix.artifact }} - run: | - strip bin/godot.* - chmod +x bin/godot.* - - - name: Upload artifact - uses: ./.github/actions/upload-artifact - if: ${{ matrix.artifact }} + # Test the project converter + - name: Test project converter + uses: ./.github/actions/godot-converter-test + if: ${{ matrix.proj-conv }} with: - name: ${{ matrix.cache-name }} + bin: ${{ matrix.bin }} diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 6e0fbbf461b0..ae6f452bc2a3 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -56,14 +56,6 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} - # Execute unit tests for the editor - - name: Unit tests - if: ${{ matrix.tests }} - run: | - ${{ matrix.bin }} --version - ${{ matrix.bin }} --help - ${{ matrix.bin }} --test - - name: Prepare artifact run: | strip bin/godot.* @@ -73,3 +65,11 @@ jobs: uses: ./.github/actions/upload-artifact with: name: ${{ matrix.cache-name }} + + # Execute unit tests for the editor + - name: Unit tests + if: ${{ matrix.tests }} + run: | + ${{ matrix.bin }} --version + ${{ matrix.bin }} --help + ${{ matrix.bin }} --test diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml index be255b54688a..34b6af43074d 100644 --- a/.github/workflows/runner.yml +++ b/.github/workflows/runner.yml @@ -6,36 +6,60 @@ concurrency: 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. + android-build: + if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🤖 Android needs: static-checks uses: ./.github/workflows/android_builds.yml ios-build: + if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🍏 iOS needs: static-checks uses: ./.github/workflows/ios_builds.yml linux-build: + if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🐧 Linux needs: static-checks uses: ./.github/workflows/linux_builds.yml macos-build: + if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🍎 macOS needs: static-checks uses: ./.github/workflows/macos_builds.yml windows-build: + if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🏁 Windows needs: static-checks uses: ./.github/workflows/windows_builds.yml web-build: + if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🌐 Web needs: static-checks uses: ./.github/workflows/web_builds.yml + + # Third stage: Run auxiliary tests using build artifacts from previous jobs. + + # Can be turned off for PRs that intentionally break compat with godot-cpp, + # until both the upstream PR and the matching godot-cpp changes are merged. + godot-cpp-test: + if: ${{ vars.DISABLE_GODOT_CI == '' }} + name: 🪲 Godot CPP + # This can be changed to depend on another platform, if we decide to use it for + # godot-cpp instead. Make sure to move the .github/actions/godot-api-dump step + # appropriately. + needs: linux-build + uses: ./.github/workflows/godot_cpp_test.yml diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 6133780688a9..b47ef135a287 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -86,6 +86,7 @@ jobs: - name: Documentation checks run: | + doc/tools/doc_status.py doc/classes modules/*/doc_classes platform/*/doc_classes doc/tools/make_rst.py --dry-run --color doc/classes modules platform - name: Style checks via clang-format (clang_format.sh) diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 182ae2fc8c41..ab62dca5cbca 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -60,14 +60,6 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} - # Execute unit tests for the editor - - name: Unit tests - if: ${{ matrix.tests }} - run: | - ${{ matrix.bin }} --version - ${{ matrix.bin }} --help - ${{ matrix.bin }} --test - - name: Prepare artifact run: | Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force @@ -76,3 +68,11 @@ jobs: uses: ./.github/actions/upload-artifact with: name: ${{ matrix.cache-name }} + + # Execute unit tests for the editor + - name: Unit tests + if: ${{ matrix.tests }} + run: | + ${{ matrix.bin }} --version + ${{ matrix.bin }} --help + ${{ matrix.bin }} --test diff --git a/CHANGELOG.md b/CHANGELOG.md index 97850763a273..735cca72122c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -161,6 +161,7 @@ See the [release announcement](https://godotengine.org/article/godot-4-1-is-here #### Core +- The strings returned by `ResourceLoader::get_dependencies()` now include paths in addition to UIDs ([GH-73131](https://github.com/godotengine/godot/pull/73131)). - Optimize Node children management ([GH-75627](https://github.com/godotengine/godot/pull/75627)). - Deprecate `NOTIFICATION_MOVED_IN_PARENT` for `NOTIFICATION_CHILD_ORDER_CHANGED` ([GH-75701](https://github.com/godotengine/godot/pull/75701)). - Optimize `Node::add_child` validation ([GH-75760](https://github.com/godotengine/godot/pull/75760)). diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 9f01e9c414d4..582784d78ea4 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -409,7 +409,7 @@ License: Apache-2.0 Files: ./thirdparty/openxr/ Comment: OpenXR Loader -Copyright: 2020-2022, The Khronos Group Inc. +Copyright: 2020-2023, The Khronos Group Inc. License: Apache-2.0 Files: ./thirdparty/pcre2/ diff --git a/SConstruct b/SConstruct index 80c535ae7f89..d9af954e7914 100644 --- a/SConstruct +++ b/SConstruct @@ -222,6 +222,7 @@ opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise e opts.Add(BoolVariable("scu_build", "Use single compilation unit build", False)) # Thirdparty libraries +opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True)) opts.Add(BoolVariable("builtin_embree", "Use the built-in Embree library", True)) opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet library", True)) @@ -239,6 +240,7 @@ opts.Add(BoolVariable("builtin_libwebp", "Use the built-in libwebp library", Tru opts.Add(BoolVariable("builtin_wslay", "Use the built-in wslay library", True)) opts.Add(BoolVariable("builtin_mbedtls", "Use the built-in mbedTLS library", True)) opts.Add(BoolVariable("builtin_miniupnpc", "Use the built-in miniupnpc library", True)) +opts.Add(BoolVariable("builtin_openxr", "Use the built-in OpenXR library", True)) opts.Add(BoolVariable("builtin_pcre2", "Use the built-in PCRE2 library", True)) opts.Add(BoolVariable("builtin_pcre2_with_jit", "Use JIT compiler for the built-in PCRE2 library", True)) opts.Add(BoolVariable("builtin_recastnavigation", "Use the built-in Recast navigation library", True)) @@ -296,21 +298,21 @@ else: if selected_platform in ["macos", "osx"]: if selected_platform == "osx": # Deprecated alias kept for compatibility. - print('Platform "osx" has been renamed to "macos" in Godot 4.0. Building for platform "macos".') + print('Platform "osx" has been renamed to "macos" in Godot 4. Building for platform "macos".') # Alias for convenience. selected_platform = "macos" if selected_platform in ["ios", "iphone"]: if selected_platform == "iphone": # Deprecated alias kept for compatibility. - print('Platform "iphone" has been renamed to "ios" in Godot 4.0. Building for platform "ios".') + print('Platform "iphone" has been renamed to "ios" in Godot 4. Building for platform "ios".') # Alias for convenience. selected_platform = "ios" if selected_platform in ["linux", "bsd", "x11"]: if selected_platform == "x11": # Deprecated alias kept for compatibility. - print('Platform "x11" has been renamed to "linuxbsd" in Godot 4.0. Building for platform "linuxbsd".') + print('Platform "x11" has been renamed to "linuxbsd" in Godot 4. Building for platform "linuxbsd".') # Alias for convenience. selected_platform = "linuxbsd" diff --git a/core/SCsub b/core/SCsub index a0176f6c3345..ab78eeedc714 100644 --- a/core/SCsub +++ b/core/SCsub @@ -65,7 +65,7 @@ thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_mis env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources) # Brotli -if env["brotli"]: +if env["brotli"] and env["builtin_brotli"]: thirdparty_brotli_dir = "#thirdparty/brotli/" thirdparty_brotli_sources = [ "common/constants.c", @@ -97,7 +97,6 @@ if env["builtin_zlib"]: "compress.c", "crc32.c", "deflate.c", - "infback.c", "inffast.c", "inflate.c", "inftrees.c", diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 79fab508826e..973162a06640 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1260,6 +1260,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC("application/config/name", ""); GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), ""); + GLOBAL_DEF_BASIC("application/config/version", ""); GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray()); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/run/main_scene", PROPERTY_HINT_FILE, "*.tscn,*.scn,*.res"), ""); GLOBAL_DEF("application/run/disable_stdout", false); @@ -1344,7 +1345,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/occlusion_culling/bvh_build_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), 2); GLOBAL_DEF(PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1"), 60); // No negative and limit to 500 due to crashes. GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false); - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_RANGE, "Based on Locale,Left-to-Right,Right-to-Left"), 0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Locale,Left-to-Right,Right-to-Left"), 0); GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 2d0d24406c92..4e220d08394d 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -442,6 +442,10 @@ bool OS::has_feature(const String &p_feature) const { } } +bool OS::is_sandboxed() const { + return ::OS::get_singleton()->is_sandboxed(); +} + uint64_t OS::get_static_memory_usage() const { return ::OS::get_singleton()->get_static_memory_usage(); } @@ -545,6 +549,10 @@ Vector OS::get_granted_permissions() const { return ::OS::get_singleton()->get_granted_permissions(); } +void OS::revoke_granted_permissions() { + ::OS::get_singleton()->revoke_granted_permissions(); +} + String OS::get_unique_id() const { return ::OS::get_singleton()->get_unique_id(); } @@ -636,10 +644,12 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_main_thread_id"), &OS::get_main_thread_id); ClassDB::bind_method(D_METHOD("has_feature", "tag_name"), &OS::has_feature); + ClassDB::bind_method(D_METHOD("is_sandboxed"), &OS::is_sandboxed); ClassDB::bind_method(D_METHOD("request_permission", "name"), &OS::request_permission); ClassDB::bind_method(D_METHOD("request_permissions"), &OS::request_permissions); ClassDB::bind_method(D_METHOD("get_granted_permissions"), &OS::get_granted_permissions); + ClassDB::bind_method(D_METHOD("revoke_granted_permissions"), &OS::revoke_granted_permissions); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec"); @@ -951,6 +961,11 @@ Vector3 Geometry3D::get_closest_point_to_segment_uncapped(const Vector3 &p_point return ::Geometry3D::get_closest_point_to_segment_uncapped(p_point, s); } +Vector3 Geometry3D::get_triangle_barycentric_coords(const Vector3 &p_point, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2) { + Vector3 res = ::Geometry3D::triangle_get_barycentric_coords(p_v0, p_v1, p_v2, p_point); + return res; +} + Variant Geometry3D::ray_intersects_triangle(const Vector3 &p_from, const Vector3 &p_dir, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2) { Vector3 res; if (::Geometry3D::ray_intersects_triangle(p_from, p_dir, p_v0, p_v1, p_v2, &res)) { @@ -1024,6 +1039,8 @@ void Geometry3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_closest_point_to_segment_uncapped", "point", "s1", "s2"), &Geometry3D::get_closest_point_to_segment_uncapped); + ClassDB::bind_method(D_METHOD("get_triangle_barycentric_coords", "point", "a", "b", "c"), &Geometry3D::get_triangle_barycentric_coords); + ClassDB::bind_method(D_METHOD("ray_intersects_triangle", "from", "dir", "a", "b", "c"), &Geometry3D::ray_intersects_triangle); ClassDB::bind_method(D_METHOD("segment_intersects_triangle", "from", "to", "a", "b", "c"), &Geometry3D::segment_intersects_triangle); ClassDB::bind_method(D_METHOD("segment_intersects_sphere", "from", "to", "sphere_position", "sphere_radius"), &Geometry3D::segment_intersects_sphere); diff --git a/core/core_bind.h b/core/core_bind.h index 6b25510b1431..1cbbcdd25183 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -230,14 +230,16 @@ class OS : public Object { String get_cache_dir() const; Error set_thread_name(const String &p_name); - Thread::ID get_thread_caller_id() const; - Thread::ID get_main_thread_id() const; + ::Thread::ID get_thread_caller_id() const; + ::Thread::ID get_main_thread_id() const; bool has_feature(const String &p_feature) const; + bool is_sandboxed() const; bool request_permission(const String &p_name); bool request_permissions(); Vector get_granted_permissions() const; + void revoke_granted_permissions(); static OS *get_singleton() { return singleton; } @@ -324,6 +326,7 @@ class Geometry3D : public Object { Vector get_closest_points_between_segments(const Vector3 &p1, const Vector3 &p2, const Vector3 &q1, const Vector3 &q2); Vector3 get_closest_point_to_segment(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b); Vector3 get_closest_point_to_segment_uncapped(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b); + Vector3 get_triangle_barycentric_coords(const Vector3 &p_point, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2); Variant ray_intersects_triangle(const Vector3 &p_from, const Vector3 &p_dir, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2); Variant segment_intersects_triangle(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2); diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp index 591b44869f57..3e6b7501c7ee 100644 --- a/core/debugger/debugger_marshalls.cpp +++ b/core/debugger/debugger_marshalls.cpp @@ -67,6 +67,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { Array arr; arr.push_back(name); arr.push_back(type); + arr.push_back(value.get_type()); Variant var = value; if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) { @@ -74,7 +75,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { } int len = 0; - Error err = encode_variant(var, nullptr, len, true); + Error err = encode_variant(var, nullptr, len, false); if (err != OK) { ERR_PRINT("Failed to encode variant."); } @@ -88,11 +89,12 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { } bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) { - CHECK_SIZE(p_arr, 3, "ScriptStackVariable"); + CHECK_SIZE(p_arr, 4, "ScriptStackVariable"); name = p_arr[0]; type = p_arr[1]; - value = p_arr[2]; - CHECK_END(p_arr, 3, "ScriptStackVariable"); + var_type = p_arr[2]; + value = p_arr[3]; + CHECK_END(p_arr, 4, "ScriptStackVariable"); return true; } diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h index 8ba93c309246..1b81623688ed 100644 --- a/core/debugger/debugger_marshalls.h +++ b/core/debugger/debugger_marshalls.h @@ -38,6 +38,7 @@ struct DebuggerMarshalls { String name; Variant value; int type = -1; + int var_type = -1; Array serialize(int max_size = 1 << 20); // 1 MiB default. bool deserialize(const Array &p_arr); diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp index 6c9293a2cfef..32dc060aa2b3 100644 --- a/core/debugger/engine_debugger.cpp +++ b/core/debugger/engine_debugger.cpp @@ -111,14 +111,6 @@ Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_ms return cap.capture(cap.data, p_msg, p_args, r_captured); } -void EngineDebugger::line_poll() { - // The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught - if (poll_every % 2048 == 0) { - poll_events(false); - } - poll_every++; -} - void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) { frame_time = USEC_TO_SEC(p_frame_ticks); process_time = USEC_TO_SEC(p_process_ticks); diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h index 1bae71e37ac8..88d54907948b 100644 --- a/core/debugger/engine_debugger.h +++ b/core/debugger/engine_debugger.h @@ -126,7 +126,13 @@ class EngineDebugger { void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array()); Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured); - void line_poll(); + void line_poll() { + // The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught. + if (unlikely(poll_every % 2048) == 0) { + poll_events(false); + } + poll_every++; + } virtual void poll_events(bool p_is_idle) {} virtual void send_message(const String &p_msg, const Array &p_data) = 0; diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index b7471d7c82d2..b4d6fa417469 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -94,6 +94,7 @@ class RemoteDebugger::PerformanceProfiler : public EngineProfiler { Error RemoteDebugger::_put_msg(String p_message, Array p_data) { Array msg; msg.push_back(p_message); + msg.push_back(Thread::get_caller_id()); msg.push_back(p_data); Error err = peer->put_message(msg); if (err != OK) { @@ -185,9 +186,9 @@ RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String } void RemoteDebugger::flush_output() { + MutexLock lock(mutex); flush_thread = Thread::get_caller_id(); flushing = true; - MutexLock lock(mutex); if (!is_peer_connected()) { return; } @@ -348,18 +349,65 @@ Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, boo return capture_parse(cap, msg, p_data, r_captured); } +void RemoteDebugger::_poll_messages() { + MutexLock mutex_lock(mutex); + + peer->poll(); + while (peer->has_message()) { + Array cmd = peer->get_message(); + ERR_CONTINUE(cmd.size() != 3); + ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); + ERR_CONTINUE(cmd[1].get_type() != Variant::INT); + ERR_CONTINUE(cmd[2].get_type() != Variant::ARRAY); + + Thread::ID thread = cmd[1]; + + if (!messages.has(thread)) { + continue; // This thread is not around to receive the messages + } + + Message msg; + msg.message = cmd[0]; + msg.data = cmd[2]; + messages[thread].push_back(msg); + } +} + +bool RemoteDebugger::_has_messages() { + MutexLock mutex_lock(mutex); + return messages.has(Thread::get_caller_id()) && !messages[Thread::get_caller_id()].is_empty(); +} + +Array RemoteDebugger::_get_message() { + MutexLock mutex_lock(mutex); + ERR_FAIL_COND_V(!messages.has(Thread::get_caller_id()), Array()); + List &message_list = messages[Thread::get_caller_id()]; + ERR_FAIL_COND_V(message_list.is_empty(), Array()); + + Array msg; + msg.resize(2); + msg[0] = message_list.front()->get().message; + msg[1] = message_list.front()->get().data; + message_list.pop_front(); + return msg; +} + void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { //this function is called when there is a debugger break (bug on script) //or when execution is paused from editor - if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) { - return; - } + { + MutexLock lock(mutex); + // Tests that require mutex. + if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) { + return; + } - ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); + ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); - if (!peer->can_block()) { - return; // Peer does not support blocking IO. We could at least send the error though. + if (!peer->can_block()) { + return; // Peer does not support blocking IO. We could at least send the error though. + } } ScriptLanguage *script_lang = script_debugger->get_break_language(); @@ -369,22 +417,33 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { msg.push_back(error_str); ERR_FAIL_COND(!script_lang); msg.push_back(script_lang->debug_get_stack_level_count() > 0); + msg.push_back(Thread::get_caller_id() == Thread::get_main_id() ? String(RTR("Main Thread")) : itos(Thread::get_caller_id())); if (allow_focus_steal_fn) { allow_focus_steal_fn(); } send_message("debug_enter", msg); - Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode(); - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE; + + if (Thread::get_caller_id() == Thread::get_main_id()) { + mouse_mode = Input::get_singleton()->get_mouse_mode(); + if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + } + } else { + MutexLock mutex_lock(mutex); + messages.insert(Thread::get_caller_id(), List()); } + mutex.lock(); while (is_peer_connected()) { + mutex.unlock(); flush_output(); - peer->poll(); - if (peer->has_message()) { - Array cmd = peer->get_message(); + _poll_messages(); + + if (_has_messages()) { + Array cmd = _get_message(); ERR_CONTINUE(cmd.size() != 2); ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); @@ -479,14 +538,22 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { } } else { OS::get_singleton()->delay_usec(10000); - OS::get_singleton()->process_and_drop_events(); + if (Thread::get_caller_id() == Thread::get_main_id()) { + // If this is a busy loop on the main thread, events still need to be processed. + OS::get_singleton()->process_and_drop_events(); + } } } send_message("debug_exit", Array()); - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { - Input::get_singleton()->set_mouse_mode(mouse_mode); + if (Thread::get_caller_id() == Thread::get_main_id()) { + if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { + Input::get_singleton()->set_mouse_mode(mouse_mode); + } + } else { + MutexLock mutex_lock(mutex); + messages.erase(Thread::get_caller_id()); } } @@ -496,9 +563,11 @@ void RemoteDebugger::poll_events(bool p_is_idle) { } flush_output(); - peer->poll(); - while (peer->has_message()) { - Array arr = peer->get_message(); + + _poll_messages(); + + while (_has_messages()) { + Array arr = _get_message(); ERR_CONTINUE(arr.size() != 2); ERR_CONTINUE(arr[0].get_type() != Variant::STRING); @@ -604,6 +673,8 @@ RemoteDebugger::RemoteDebugger(Ref p_peer) { eh.errfunc = _err_handler; eh.userdata = this; add_error_handler(&eh); + + messages.insert(Thread::get_main_id(), List()); } RemoteDebugger::~RemoteDebugger() { diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index 24283b0ed663..7c399178c66f 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -80,6 +80,17 @@ class RemoteDebugger : public EngineDebugger { bool flushing = false; Thread::ID flush_thread = 0; + struct Message { + String message; + Array data; + }; + + HashMap> messages; + + void _poll_messages(); + bool _has_messages(); + Array _get_message(); + PrintHandlerList phl; static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich); ErrorHandlerList eh; diff --git a/core/debugger/script_debugger.cpp b/core/debugger/script_debugger.cpp index 32725b76c1d7..e7d8654a0b97 100644 --- a/core/debugger/script_debugger.cpp +++ b/core/debugger/script_debugger.cpp @@ -32,22 +32,19 @@ #include "core/debugger/engine_debugger.h" +thread_local int ScriptDebugger::lines_left = -1; +thread_local int ScriptDebugger::depth = -1; +thread_local ScriptLanguage *ScriptDebugger::break_lang = nullptr; +thread_local Vector ScriptDebugger::error_stack_info; + void ScriptDebugger::set_lines_left(int p_left) { lines_left = p_left; } -int ScriptDebugger::get_lines_left() const { - return lines_left; -} - void ScriptDebugger::set_depth(int p_depth) { depth = p_depth; } -int ScriptDebugger::get_depth() const { - return depth; -} - void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) { if (!breakpoints.has(p_line)) { breakpoints[p_line] = HashSet(); @@ -66,13 +63,6 @@ void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) { } } -bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const { - if (!breakpoints.has(p_line)) { - return false; - } - return breakpoints[p_line].has(p_source); -} - String ScriptDebugger::breakpoint_find_source(const String &p_source) const { return p_source; } @@ -100,7 +90,7 @@ void ScriptDebugger::send_error(const String &p_func, const String &p_file, int // Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way. error_stack_info.append_array(p_stack_info); EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type); - error_stack_info.clear(); + error_stack_info.clear(); // Clear because this is thread local } Vector ScriptDebugger::get_error_stack_info() const { diff --git a/core/debugger/script_debugger.h b/core/debugger/script_debugger.h index edce089179b0..ee037b91fa9d 100644 --- a/core/debugger/script_debugger.h +++ b/core/debugger/script_debugger.h @@ -40,21 +40,25 @@ class ScriptDebugger { typedef ScriptLanguage::StackInfo StackInfo; - int lines_left = -1; - int depth = -1; bool skip_breakpoints = false; HashMap> breakpoints; - ScriptLanguage *break_lang = nullptr; - Vector error_stack_info; + static thread_local int lines_left; + static thread_local int depth; + static thread_local ScriptLanguage *break_lang; + static thread_local Vector error_stack_info; public: void set_lines_left(int p_left); - int get_lines_left() const; + _ALWAYS_INLINE_ int get_lines_left() const { + return lines_left; + } void set_depth(int p_depth); - int get_depth() const; + _ALWAYS_INLINE_ int get_depth() const { + return depth; + } String breakpoint_find_source(const String &p_source) const; void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; } @@ -63,7 +67,12 @@ class ScriptDebugger { bool is_skipping_breakpoints(); void insert_breakpoint(int p_line, const StringName &p_source); void remove_breakpoint(int p_line, const StringName &p_source); - bool is_breakpoint(int p_line, const StringName &p_source) const; + _ALWAYS_INLINE_ bool is_breakpoint(int p_line, const StringName &p_source) const { + if (likely(!breakpoints.has(p_line))) { + return false; + } + return breakpoints[p_line].has(p_source); + } void clear_breakpoints(); const HashMap> &get_breakpoints() const { return breakpoints; } diff --git a/core/doc_data.h b/core/doc_data.h index 0fe7414b9890..b8c92a4b6776 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -532,6 +532,42 @@ class DocData { } }; + struct EnumDoc { + String description; + bool is_deprecated = false; + bool is_experimental = false; + static EnumDoc from_dict(const Dictionary &p_dict) { + EnumDoc doc; + + if (p_dict.has("description")) { + doc.description = p_dict["description"]; + } + + if (p_dict.has("is_deprecated")) { + doc.is_deprecated = p_dict["is_deprecated"]; + } + + if (p_dict.has("is_experimental")) { + doc.is_experimental = p_dict["is_experimental"]; + } + + return doc; + } + static Dictionary to_dict(const EnumDoc &p_doc) { + Dictionary dict; + + if (!p_doc.description.is_empty()) { + dict["description"] = p_doc.description; + } + + dict["is_deprecated"] = p_doc.is_deprecated; + + dict["is_experimental"] = p_doc.is_experimental; + + return dict; + } + }; + struct ClassDoc { String name; String inherits; @@ -543,7 +579,7 @@ class DocData { Vector operators; Vector signals; Vector constants; - HashMap enums; + HashMap enums; Vector properties; Vector annotations; Vector theme_properties; @@ -626,7 +662,7 @@ class DocData { enums = p_dict["enums"]; } for (int i = 0; i < enums.size(); i++) { - doc.enums[enums.get_key_at_index(i)] = enums.get_value_at_index(i); + doc.enums[enums.get_key_at_index(i)] = EnumDoc::from_dict(enums.get_value_at_index(i)); } Array properties; @@ -740,8 +776,8 @@ class DocData { if (!p_doc.enums.is_empty()) { Dictionary enums; - for (const KeyValue &E : p_doc.enums) { - enums[E.key] = E.value; + for (const KeyValue &E : p_doc.enums) { + enums[E.key] = EnumDoc::to_dict(E.value); } dict["enums"] = enums; } diff --git a/core/error/error_macros.h b/core/error/error_macros.h index 65804b779647..c8182975d57b 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -786,8 +786,19 @@ void _err_flush_stdout(); ((void)0) /** - * This should be a 'free' assert for program flow and should not be needed in any releases, - * only used in dev builds. + * Note: IN MOST CASES YOU SHOULD NOT USE THIS MACRO. + * Do not use unless you understand the trade-offs. + * + * DEV macros will be compiled out in releases, they are wrapped in DEV_ENABLED. + * + * Prefer WARNINGS / ERR_FAIL macros (which fail without crashing) - ERR_FAIL should be used in most cases. + * Then CRASH_NOW_MSG macros (on rare occasions where error cannot be recovered). + * + * DEV_ASSERT should generally only be used when both of the following conditions are met: + * 1) Bottleneck code where a check in release would be too expensive. + * 2) Situations where the check would fail obviously and straight away during the maintenance of the code + * (i.e. strict conditions that should be true no matter what) + * and that can't fail for other contributors once the code is finished and merged. */ #ifdef DEV_ENABLED #define DEV_ASSERT(m_cond) \ diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 73526fae3e2a..67b55db3db7e 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -363,6 +363,10 @@ void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLib } void GDExtension::_register_extension_class_property(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter) { + _register_extension_class_property_indexed(p_library, p_class_name, p_info, p_setter, p_getter, -1); +} + +void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter, GDExtensionInt p_index) { GDExtension *self = reinterpret_cast(p_library); StringName class_name = *reinterpret_cast(p_class_name); @@ -371,10 +375,9 @@ void GDExtension::_register_extension_class_property(GDExtensionClassLibraryPtr String property_name = *reinterpret_cast(p_info->name); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'."); - //Extension *extension = &self->extension_classes[class_name]; PropertyInfo pinfo(*p_info); - ClassDB::add_property(class_name, pinfo, setter, getter); + ClassDB::add_property(class_name, pinfo, setter, getter, p_index); } void GDExtension::_register_extension_class_property_group(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_group_name, GDExtensionConstStringPtr p_prefix) { @@ -542,6 +545,7 @@ void GDExtension::initialize_gdextensions() { register_interface_function("classdb_register_extension_class_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_method); register_interface_function("classdb_register_extension_class_integer_constant", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_integer_constant); register_interface_function("classdb_register_extension_class_property", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property); + register_interface_function("classdb_register_extension_class_property_indexed", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_indexed); register_interface_function("classdb_register_extension_class_property_group", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_group); register_interface_function("classdb_register_extension_class_property_subgroup", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_subgroup); register_interface_function("classdb_register_extension_class_signal", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_signal); diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 77ec458d3066..b935f8706f9e 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -54,6 +54,7 @@ class GDExtension : public Resource { static void _register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info); static void _register_extension_class_integer_constant(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); static void _register_extension_class_property(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter); + static void _register_extension_class_property_indexed(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter, GDExtensionInt p_index); static void _register_extension_class_property_group(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_group_name, GDExtensionConstStringNamePtr p_prefix); static void _register_extension_class_property_subgroup(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_subgroup_name, GDExtensionConstStringNamePtr p_prefix); static void _register_extension_class_signal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count); diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 7fbf2d00a16f..7ef956a470fe 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -726,6 +726,11 @@ static void gdextension_string_operator_plus_eq_c32str(GDExtensionStringPtr p_se *self += p_b; } +static GDExtensionInt gdextension_string_resize(GDExtensionStringPtr p_self, GDExtensionInt p_length) { + String *self = (String *)p_self; + return (*self).resize(p_length); +} + static GDExtensionInt gdextension_xml_parser_open_buffer(GDExtensionObjectPtr p_instance, const uint8_t *p_buffer, size_t p_size) { XMLParser *xml = (XMLParser *)p_instance; return (GDExtensionInt)xml->_open_buffer(p_buffer, p_size); @@ -1167,6 +1172,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(string_operator_plus_eq_cstr); REGISTER_INTERFACE_FUNC(string_operator_plus_eq_wcstr); REGISTER_INTERFACE_FUNC(string_operator_plus_eq_c32str); + REGISTER_INTERFACE_FUNC(string_resize); REGISTER_INTERFACE_FUNC(xml_parser_open_buffer); REGISTER_INTERFACE_FUNC(file_access_store_buffer); REGISTER_INTERFACE_FUNC(file_access_get_buffer); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 4d7bdf950233..6c05f3988b01 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -1526,6 +1526,25 @@ typedef void (*GDExtensionInterfaceStringOperatorPlusEqWcstr)(GDExtensionStringP */ typedef void (*GDExtensionInterfaceStringOperatorPlusEqC32str)(GDExtensionStringPtr p_self, const char32_t *p_b); +/** + * @name string_resize + * @since 4.2 + * + * Resizes the underlying string data to the given number of characters. + * + * Space needs to be allocated for the null terminating character ('\0') which + * also must be added manually, in order for all string functions to work correctly. + * + * Warning: This is an error-prone operation - only use it if there's no other + * efficient way to accomplish your goal. + * + * @param p_self A pointer to the String. + * @param p_resize The new length for the String. + * + * @return Error code signifying if the operation successful. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringResize)(GDExtensionStringPtr p_self, GDExtensionInt p_resize); + /* INTERFACE: XMLParser Utilities */ /** @@ -2211,6 +2230,23 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant) */ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassProperty)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter); +/** + * @name classdb_register_extension_class_property_indexed + * @since 4.2 + * + * Registers an indexed property on an extension class in the ClassDB. + * + * Provided struct can be safely freed once the function returns. + * + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param p_class_name A pointer to a StringName with the class name. + * @param p_info A pointer to a GDExtensionPropertyInfo struct. + * @param p_setter A pointer to a StringName with the name of the setter method. + * @param p_getter A pointer to a StringName with the name of the getter method. + * @param p_index The index to pass as the first argument to the getter and setter methods. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter, GDExtensionInt p_index); + /** * @name classdb_register_extension_class_property_group * @since 4.1 diff --git a/core/input/input.cpp b/core/input/input.cpp index cf8d71b9a744..4a32abfafa98 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -113,6 +113,8 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("get_joy_axis", "device", "axis"), &Input::get_joy_axis); ClassDB::bind_method(D_METHOD("get_joy_name", "device"), &Input::get_joy_name); ClassDB::bind_method(D_METHOD("get_joy_guid", "device"), &Input::get_joy_guid); + ClassDB::bind_method(D_METHOD("get_joy_info", "device"), &Input::get_joy_info); + ClassDB::bind_method(D_METHOD("should_ignore_device", "vendor_id", "product_id"), &Input::should_ignore_device); ClassDB::bind_method(D_METHOD("get_connected_joypads"), &Input::get_connected_joypads); ClassDB::bind_method(D_METHOD("get_joy_vibration_strength", "device"), &Input::get_joy_vibration_strength); ClassDB::bind_method(D_METHOD("get_joy_vibration_duration", "device"), &Input::get_joy_vibration_duration); @@ -436,11 +438,12 @@ static String _hex_str(uint8_t p_byte) { return ret; } -void Input::joy_connection_changed(int p_idx, bool p_connected, String p_name, String p_guid) { +void Input::joy_connection_changed(int p_idx, bool p_connected, String p_name, String p_guid, Dictionary p_joypad_info) { _THREAD_SAFE_METHOD_ Joypad js; js.name = p_connected ? p_name : ""; js.uid = p_connected ? p_guid : ""; + js.info = p_connected ? p_joypad_info : Dictionary(); if (p_connected) { String uidname = p_guid; @@ -1498,6 +1501,16 @@ String Input::get_joy_guid(int p_device) const { return joy_names[p_device].uid; } +Dictionary Input::get_joy_info(int p_device) const { + ERR_FAIL_COND_V(!joy_names.has(p_device), Dictionary()); + return joy_names[p_device].info; +} + +bool Input::should_ignore_device(int p_vendor_id, int p_product_id) const { + uint32_t full_id = (((uint32_t)p_vendor_id) << 16) | ((uint16_t)p_product_id); + return ignored_device_ids.has(full_id); +} + TypedArray Input::get_connected_joypads() { TypedArray ret; HashMap::Iterator elem = joy_names.begin(); @@ -1542,6 +1555,27 @@ Input::Input() { } } + String env_ignore_devices = OS::get_singleton()->get_environment("SDL_GAMECONTROLLER_IGNORE_DEVICES"); + if (!env_ignore_devices.is_empty()) { + Vector entries = env_ignore_devices.split(","); + for (int i = 0; i < entries.size(); i++) { + Vector vid_pid = entries[i].split("/"); + + if (vid_pid.size() < 2) { + continue; + } + + print_verbose(vformat("Device Ignored -- Vendor: %s Product: %s", vid_pid[0], vid_pid[1])); + const uint16_t vid_unswapped = vid_pid[0].hex_to_int(); + const uint16_t pid_unswapped = vid_pid[1].hex_to_int(); + const uint16_t vid = BSWAP16(vid_unswapped); + const uint16_t pid = BSWAP16(pid_unswapped); + + uint32_t full_id = (((uint32_t)vid) << 16) | ((uint16_t)pid); + ignored_device_ids.insert(full_id); + } + } + legacy_just_pressed_behavior = GLOBAL_DEF("input_devices/compatibility/legacy_just_pressed_behavior", false); if (Engine::get_singleton()->is_editor_hint()) { // Always use standard behavior in the editor. diff --git a/core/input/input.h b/core/input/input.h index 9cc596ee903d..c63a4e52e31e 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -149,11 +149,15 @@ class Input : public Object { HatMask last_hat = HatMask::CENTER; int mapping = -1; int hat_current = 0; + Dictionary info; }; VelocityTrack mouse_velocity_track; HashMap touch_velocity_track; HashMap joy_names; + + HashSet ignored_device_ids; + int fallback_mapping = -1; CursorShape default_shape = CURSOR_ARROW; @@ -273,7 +277,7 @@ class Input : public Object { Vector2 get_joy_vibration_strength(int p_device); float get_joy_vibration_duration(int p_device); uint64_t get_joy_vibration_timestamp(int p_device); - void joy_connection_changed(int p_idx, bool p_connected, String p_name, String p_guid = ""); + void joy_connection_changed(int p_idx, bool p_connected, String p_name, String p_guid = "", Dictionary p_joypad_info = Dictionary()); Vector3 get_gravity() const; Vector3 get_accelerometer() const; @@ -328,6 +332,8 @@ class Input : public Object { bool is_joy_known(int p_device); String get_joy_guid(int p_device) const; + bool should_ignore_device(int p_vendor_id, int p_product_id) const; + Dictionary get_joy_info(int p_device) const; void set_fallback_mapping(String p_guid); void flush_buffered_events(); diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index e547b04d0b16..e37886cbe996 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1192,7 +1192,7 @@ static const char *_joy_button_descriptions[(size_t)JoyButton::SDL_MAX] = { TTRC("Top Action, Sony Triangle, Xbox Y, Nintendo X"), TTRC("Back, Sony Select, Xbox Back, Nintendo -"), TTRC("Guide, Sony PS, Xbox Home"), - TTRC("Start, Nintendo +"), + TTRC("Start, Xbox Menu, Nintendo +"), TTRC("Left Stick, Sony L3, Xbox L/LS"), TTRC("Right Stick, Sony R3, Xbox R/RS"), TTRC("Left Shoulder, Sony L1, Xbox LB"), diff --git a/core/io/compression.cpp b/core/io/compression.cpp index ac4a63759738..e36fb0afa4fd 100644 --- a/core/io/compression.cpp +++ b/core/io/compression.cpp @@ -35,13 +35,13 @@ #include "thirdparty/misc/fastlz.h" -#ifdef BROTLI_ENABLED -#include "thirdparty/brotli/include/brotli/decode.h" -#endif - #include #include +#ifdef BROTLI_ENABLED +#include +#endif + int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode) { switch (p_mode) { case MODE_BROTLI: { diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index 064353476f27..c7f1a73f9718 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -47,7 +47,7 @@ static void *godot_open(voidpf opaque, const char *p_fname, int mode) { return nullptr; } - Ref f = FileAccess::open(p_fname, FileAccess::READ); + Ref f = FileAccess::open(String::utf8(p_fname), FileAccess::READ); ERR_FAIL_COND_V(f.is_null(), nullptr); ZipData *zd = memnew(ZipData); diff --git a/core/io/image.cpp b/core/io/image.cpp index 9bb987b670c5..a5fea09113f1 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -517,21 +517,31 @@ void Image::convert(Format p_new_format) { return; } + // Includes the main image. + const int mipmap_count = get_mipmap_count() + 1; + if (format > FORMAT_RGBE9995 || p_new_format > FORMAT_RGBE9995) { ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { //use put/set pixel which is slower but works with non byte formats - Image new_img(width, height, false, p_new_format); + Image new_img(width, height, mipmaps, p_new_format); - for (int i = 0; i < width; i++) { - for (int j = 0; j < height; j++) { - new_img.set_pixel(i, j, get_pixel(i, j)); + for (int mip = 0; mip < mipmap_count; mip++) { + Ref src_mip = get_image_from_mipmap(mip); + Ref new_mip = new_img.get_image_from_mipmap(mip); + + for (int y = 0; y < src_mip->height; y++) { + for (int x = 0; x < src_mip->width; x++) { + new_mip->set_pixel(x, y, src_mip->get_pixel(x, y)); + } } - } - if (has_mipmaps()) { - new_img.generate_mipmaps(); + int mip_offset = 0; + int mip_size = 0; + new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); + + memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size); } _copy_internals_from(new_img); @@ -539,113 +549,115 @@ void Image::convert(Format p_new_format) { return; } - Image new_img(width, height, false, p_new_format); - - const uint8_t *rptr = data.ptr(); - uint8_t *wptr = new_img.data.ptrw(); + Image new_img(width, height, mipmaps, p_new_format); int conversion_type = format | p_new_format << 8; - switch (conversion_type) { - case FORMAT_L8 | (FORMAT_LA8 << 8): - _convert<1, false, 1, true, true, true>(width, height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_R8 << 8): - _convert<1, false, 1, false, true, false>(width, height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_RG8 << 8): - _convert<1, false, 2, false, true, false>(width, height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_RGB8 << 8): - _convert<1, false, 3, false, true, false>(width, height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_RGBA8 << 8): - _convert<1, false, 3, true, true, false>(width, height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_L8 << 8): - _convert<1, true, 1, false, true, true>(width, height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_R8 << 8): - _convert<1, true, 1, false, true, false>(width, height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_RG8 << 8): - _convert<1, true, 2, false, true, false>(width, height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_RGB8 << 8): - _convert<1, true, 3, false, true, false>(width, height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_RGBA8 << 8): - _convert<1, true, 3, true, true, false>(width, height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_L8 << 8): - _convert<1, false, 1, false, false, true>(width, height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_LA8 << 8): - _convert<1, false, 1, true, false, true>(width, height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_RG8 << 8): - _convert<1, false, 2, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_RGB8 << 8): - _convert<1, false, 3, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_RGBA8 << 8): - _convert<1, false, 3, true, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_L8 << 8): - _convert<2, false, 1, false, false, true>(width, height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_LA8 << 8): - _convert<2, false, 1, true, false, true>(width, height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_R8 << 8): - _convert<2, false, 1, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_RGB8 << 8): - _convert<2, false, 3, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_RGBA8 << 8): - _convert<2, false, 3, true, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_L8 << 8): - _convert<3, false, 1, false, false, true>(width, height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_LA8 << 8): - _convert<3, false, 1, true, false, true>(width, height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_R8 << 8): - _convert<3, false, 1, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_RG8 << 8): - _convert<3, false, 2, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_RGBA8 << 8): - _convert<3, false, 3, true, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_L8 << 8): - _convert<3, true, 1, false, false, true>(width, height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_LA8 << 8): - _convert<3, true, 1, true, false, true>(width, height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_R8 << 8): - _convert<3, true, 1, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_RG8 << 8): - _convert<3, true, 2, false, false, false>(width, height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): - _convert<3, true, 3, false, false, false>(width, height, rptr, wptr); - break; - } - - bool gen_mipmaps = mipmaps; + for (int mip = 0; mip < mipmap_count; mip++) { + int mip_offset = 0; + int mip_size = 0; + int mip_width = 0; + int mip_height = 0; + get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); - _copy_internals_from(new_img); + const uint8_t *rptr = data.ptr() + mip_offset; + uint8_t *wptr = new_img.data.ptrw() + new_img.get_mipmap_offset(mip); - if (gen_mipmaps) { - generate_mipmaps(); + switch (conversion_type) { + case FORMAT_L8 | (FORMAT_LA8 << 8): + _convert<1, false, 1, true, true, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_R8 << 8): + _convert<1, false, 1, false, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RG8 << 8): + _convert<1, false, 2, false, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RGB8 << 8): + _convert<1, false, 3, false, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RGBA8 << 8): + _convert<1, false, 3, true, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_L8 << 8): + _convert<1, true, 1, false, true, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_R8 << 8): + _convert<1, true, 1, false, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RG8 << 8): + _convert<1, true, 2, false, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RGB8 << 8): + _convert<1, true, 3, false, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RGBA8 << 8): + _convert<1, true, 3, true, true, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_L8 << 8): + _convert<1, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_LA8 << 8): + _convert<1, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RG8 << 8): + _convert<1, false, 2, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RGB8 << 8): + _convert<1, false, 3, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RGBA8 << 8): + _convert<1, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_L8 << 8): + _convert<2, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_LA8 << 8): + _convert<2, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_R8 << 8): + _convert<2, false, 1, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_RGB8 << 8): + _convert<2, false, 3, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_RGBA8 << 8): + _convert<2, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_L8 << 8): + _convert<3, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_LA8 << 8): + _convert<3, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_R8 << 8): + _convert<3, false, 1, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_RG8 << 8): + _convert<3, false, 2, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_RGBA8 << 8): + _convert<3, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_L8 << 8): + _convert<3, true, 1, false, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_LA8 << 8): + _convert<3, true, 1, true, false, true>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_R8 << 8): + _convert<3, true, 1, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_RG8 << 8): + _convert<3, true, 2, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): + _convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr); + break; + } } + + _copy_internals_from(new_img); } Image::Format Image::get_format() const { @@ -3004,6 +3016,8 @@ ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr; ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr; ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr; ImageMemLoadFunc Image::_bmp_mem_loader_func = nullptr; +ScalableImageMemLoadFunc Image::_svg_scalable_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_dds_mem_loader_func = nullptr; void (*Image::_image_compress_bc_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_compress_bptc_func)(Image *, Image::UsedChannels) = nullptr; @@ -3475,6 +3489,10 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("load_webp_from_buffer", "buffer"), &Image::load_webp_from_buffer); ClassDB::bind_method(D_METHOD("load_tga_from_buffer", "buffer"), &Image::load_tga_from_buffer); ClassDB::bind_method(D_METHOD("load_bmp_from_buffer", "buffer"), &Image::load_bmp_from_buffer); + ClassDB::bind_method(D_METHOD("load_dds_from_buffer", "buffer"), &Image::load_dds_from_buffer); + + ClassDB::bind_method(D_METHOD("load_svg_from_buffer", "buffer", "scale"), &Image::load_svg_from_buffer, DEFVAL(1.0)); + ClassDB::bind_method(D_METHOD("load_svg_from_string", "svg_str", "scale"), &Image::load_svg_from_string, DEFVAL(1.0)); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_data", "_get_data"); @@ -3825,6 +3843,36 @@ Error Image::load_bmp_from_buffer(const Vector &p_array) { return _load_from_buffer(p_array, _bmp_mem_loader_func); } +Error Image::load_svg_from_buffer(const Vector &p_array, float scale) { + ERR_FAIL_NULL_V_MSG( + _svg_scalable_mem_loader_func, + ERR_UNAVAILABLE, + "The SVG module isn't enabled. Recompile the Godot editor or export template binary with the `module_svg_enabled=yes` SCons option."); + + int buffer_size = p_array.size(); + + ERR_FAIL_COND_V(buffer_size == 0, ERR_INVALID_PARAMETER); + + Ref image = _svg_scalable_mem_loader_func(p_array.ptr(), buffer_size, scale); + ERR_FAIL_COND_V(!image.is_valid(), ERR_PARSE_ERROR); + + copy_internals_from(image); + + return OK; +} + +Error Image::load_svg_from_string(const String &p_svg_str, float scale) { + return load_svg_from_buffer(p_svg_str.to_utf8_buffer(), scale); +} + +Error Image::load_dds_from_buffer(const Vector &p_array) { + ERR_FAIL_NULL_V_MSG( + _dds_mem_loader_func, + ERR_UNAVAILABLE, + "The DDS module isn't enabled. Recompile the Godot editor or export template binary with the `module_dds_enabled=yes` SCons option."); + return _load_from_buffer(p_array, _dds_mem_loader_func); +} + void Image::convert_rg_to_ra_rgba8() { ERR_FAIL_COND(format != FORMAT_RGBA8); ERR_FAIL_COND(!data.size()); diff --git a/core/io/image.h b/core/io/image.h index 8e353a8bb764..f68543ba2469 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -48,6 +48,7 @@ typedef Vector (*SavePNGBufferFunc)(const Ref &p_img); typedef Error (*SaveJPGFunc)(const String &p_path, const Ref &p_img, float p_quality); typedef Vector (*SaveJPGBufferFunc)(const Ref &p_img, float p_quality); typedef Ref (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size); +typedef Ref (*ScalableImageMemLoadFunc)(const uint8_t *p_data, int p_size, float p_scale); typedef Error (*SaveWebPFunc)(const String &p_path, const Ref &p_img, const bool p_lossy, const float p_quality); typedef Vector (*SaveWebPBufferFunc)(const Ref &p_img, const bool p_lossy, const float p_quality); @@ -148,6 +149,8 @@ class Image : public Resource { static ImageMemLoadFunc _webp_mem_loader_func; static ImageMemLoadFunc _tga_mem_loader_func; static ImageMemLoadFunc _bmp_mem_loader_func; + static ScalableImageMemLoadFunc _svg_scalable_mem_loader_func; + static ImageMemLoadFunc _dds_mem_loader_func; static void (*_image_compress_bc_func)(Image *, UsedChannels p_channels); static void (*_image_compress_bptc_func)(Image *, UsedChannels p_channels); @@ -400,6 +403,10 @@ class Image : public Resource { Error load_webp_from_buffer(const Vector &p_array); Error load_tga_from_buffer(const Vector &p_array); Error load_bmp_from_buffer(const Vector &p_array); + Error load_dds_from_buffer(const Vector &p_array); + + Error load_svg_from_buffer(const Vector &p_array, float scale = 1.0); + Error load_svg_from_string(const String &p_svg_str, float scale = 1.0); void convert_rg_to_ra_rgba8(); void convert_ra_rgba8_to_rg(); diff --git a/core/io/json.cpp b/core/io/json.cpp index a6e054a9fe63..496400a5ea7c 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -299,9 +299,15 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to } } break; - default: { + case '"': + case '\\': + case '/': { res = next; } break; + default: { + r_err_str = "Invalid escape sequence."; + return ERR_PARSE_ERROR; + } } str += res; diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 6b8ec8d5f667..07677337b43c 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -147,15 +147,28 @@ bool Resource::editor_can_reload_from_file() { return true; //by default yes } +void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { + if (!is_connected(CoreStringNames::get_singleton()->changed, p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) { + connect(CoreStringNames::get_singleton()->changed, p_callable, p_flags); + } +} + +void Resource::disconnect_changed(const Callable &p_callable) { + if (is_connected(CoreStringNames::get_singleton()->changed, p_callable)) { + disconnect(CoreStringNames::get_singleton()->changed, p_callable); + } +} + void Resource::reset_state() { } + Error Resource::copy_from(const Ref &p_resource) { ERR_FAIL_COND_V(p_resource.is_null(), ERR_INVALID_PARAMETER); if (get_class() != p_resource->get_class()) { return ERR_INVALID_PARAMETER; } - reset_state(); //may want to reset state + reset_state(); // May want to reset state. List pi; p_resource->get_property_list(&pi); @@ -322,23 +335,6 @@ RID Resource::get_rid() const { return RID(); } -void Resource::register_owner(Object *p_owner) { - owners.insert(p_owner->get_instance_id()); -} - -void Resource::unregister_owner(Object *p_owner) { - owners.erase(p_owner->get_instance_id()); -} - -void Resource::notify_change_to_owners() { - for (const ObjectID &E : owners) { - Object *obj = ObjectDB::get_instance(E); - ERR_CONTINUE_MSG(!obj, "Object was deleted, while still owning a resource."); //wtf - //TODO store string - obj->call("resource_changed", Ref(this)); - } -} - #ifdef TOOLS_ENABLED uint32_t Resource::hash_edited_version() const { @@ -443,6 +439,7 @@ void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene); ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene); ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene); + ClassDB::bind_method(D_METHOD("emit_changed"), &Resource::emit_changed); ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false)); @@ -469,9 +466,6 @@ Resource::~Resource() { ResourceCache::resources.erase(path_cache); ResourceCache::lock.unlock(); } - if (owners.size()) { - WARN_PRINT("Resource is still owned."); - } } HashMap ResourceCache::resources; diff --git a/core/io/resource.h b/core/io/resource.h index 5135664f3632..af8c275a1c66 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -54,8 +54,6 @@ class Resource : public RefCounted { virtual String get_base_extension() const { return "res"; } private: - HashSet owners; - friend class ResBase; friend class ResourceCache; @@ -76,10 +74,6 @@ class Resource : public RefCounted { SelfList remapped_list; protected: - void emit_changed(); - - void notify_change_to_owners(); - virtual void _resource_path_changed(); static void _bind_methods(); @@ -96,8 +90,9 @@ class Resource : public RefCounted { virtual Error copy_from(const Ref &p_resource); virtual void reload_from_file(); - void register_owner(Object *p_owner); - void unregister_owner(Object *p_owner); + void emit_changed(); + void connect_changed(const Callable &p_callable, uint32_t p_flags = 0); + void disconnect_changed(const Callable &p_callable); void set_name(const String &p_name); String get_name() const; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 2a7a675f2d0d..551d3268b8ce 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1775,9 +1775,9 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref f, const V case Variant::OBJECT: { f->store_32(VARIANT_OBJECT); Ref res = p_property; - if (res.is_null()) { + if (res.is_null() || res->get_meta(SNAME("_skip_save_"), false)) { f->store_32(OBJECT_EMPTY); - return; // don't save it + return; // Don't save it. } if (!res->is_built_in()) { @@ -1942,7 +1942,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant case Variant::OBJECT: { Ref res = p_variant; - if (res.is_null() || external_resources.has(res)) { + if (res.is_null() || external_resources.has(res) || res->get_meta(SNAME("_skip_save_"), false)) { return; } @@ -1960,6 +1960,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant return; } + resource_set.insert(res); + List property_list; res->get_property_list(&property_list); @@ -1968,14 +1970,17 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant if (E.usage & PROPERTY_USAGE_STORAGE) { Variant value = res->get(E.name); if (E.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + NonPersistentKey npk; + npk.base = res; + npk.property = E.name; + non_persistent_map[npk] = value; + Ref sres = value; if (sres.is_valid()) { - NonPersistentKey npk; - npk.base = res; - npk.property = E.name; - non_persistent_map[npk] = sres; resource_set.insert(sres); saved_resources.push_back(sres); + } else { + _find_resources(value); } } else { _find_resources(value); @@ -1983,7 +1988,6 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant } } - resource_set.insert(res); saved_resources.push_back(res); } break; diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index 30f16649838c..e64485d40439 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -139,7 +139,7 @@ class ResourceFormatSaverBinaryInstance { bool operator<(const NonPersistentKey &p_key) const { return base == p_key.base ? property < p_key.property : base < p_key.base; } }; - RBMap> non_persistent_map; + RBMap non_persistent_map; HashMap string_map; Vector strings; diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index dc1de6b9ceb1..fcf4a727ca49 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -394,6 +394,15 @@ Ref ResourceFormatImporter::get_importer_by_name(const String return Ref(); } +void ResourceFormatImporter::add_importer(const Ref &p_importer, bool p_first_priority) { + ERR_FAIL_COND(p_importer.is_null()); + if (p_first_priority) { + importers.insert(0, p_importer); + } else { + importers.push_back(p_importer); + } +} + void ResourceFormatImporter::get_importers_for_extension(const String &p_extension, List> *r_importers) { for (int i = 0; i < importers.size(); i++) { List local_exts; @@ -472,20 +481,13 @@ ResourceFormatImporter::ResourceFormatImporter() { singleton = this; } +////////////// + void ResourceImporter::_bind_methods() { BIND_ENUM_CONSTANT(IMPORT_ORDER_DEFAULT); BIND_ENUM_CONSTANT(IMPORT_ORDER_SCENE); } -void ResourceFormatImporter::add_importer(const Ref &p_importer, bool p_first_priority) { - ERR_FAIL_COND(p_importer.is_null()); - if (p_first_priority) { - importers.insert(0, p_importer); - } else { - importers.push_back(p_importer); - } -} - ///// Error ResourceFormatImporterSaver::set_uid(const String &p_path, ResourceUID::ID p_uid) { diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 1fe662b1fad2..df0253349c16 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -275,10 +275,10 @@ Ref ResourceLoader::_load(const String &p_path, const String &p_origin #ifdef TOOLS_ENABLED Ref file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); - ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref(), "Resource file not found: " + p_path + "."); + ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); #endif - ERR_FAIL_V_MSG(Ref(), "No loader found for resource: " + p_path + "."); + ERR_FAIL_V_MSG(Ref(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } void ResourceLoader::_thread_load_function(void *p_userdata) { diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index 63f7c80bdd16..9ba4c2ff9ab0 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -194,6 +194,38 @@ real_t AStarGrid2D::get_point_weight_scale(const Vector2i &p_id) const { return GET_POINT_UNCHECKED(p_id).weight_scale; } +void AStarGrid2D::fill_solid_region(const Rect2i &p_region, bool p_solid) { + ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method."); + + Rect2i safe_region = p_region.intersection(region); + int from_x = safe_region.get_position().x; + int from_y = safe_region.get_position().y; + int end_x = safe_region.get_end().x; + int end_y = safe_region.get_end().y; + + for (int x = from_x; x < end_x; x++) { + for (int y = from_y; y < end_y; y++) { + GET_POINT_UNCHECKED(Vector2i(x, y)).solid = p_solid; + } + } +} + +void AStarGrid2D::fill_weight_scale_region(const Rect2i &p_region, real_t p_weight_scale) { + ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method."); + ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale)); + + Rect2i safe_region = p_region.intersection(region); + int from_x = safe_region.get_position().x; + int from_y = safe_region.get_position().y; + int end_x = safe_region.get_end().x; + int end_y = safe_region.get_end().y; + for (int x = from_x; x < end_x; x++) { + for (int y = from_y; y < end_y; y++) { + GET_POINT_UNCHECKED(Vector2i(x, y)).weight_scale = p_weight_scale; + } + } +} + AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) { if (!p_to || p_to->solid) { return nullptr; @@ -606,6 +638,8 @@ void AStarGrid2D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_point_solid", "id"), &AStarGrid2D::is_point_solid); ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStarGrid2D::set_point_weight_scale); ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStarGrid2D::get_point_weight_scale); + ClassDB::bind_method(D_METHOD("fill_solid_region", "region", "solid"), &AStarGrid2D::fill_solid_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("fill_weight_scale_region", "region", "weight_scale"), &AStarGrid2D::fill_weight_scale_region); ClassDB::bind_method(D_METHOD("clear"), &AStarGrid2D::clear); ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStarGrid2D::get_point_position); diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index 50df58e0e95c..dd5f9d0575b9 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -177,6 +177,9 @@ class AStarGrid2D : public RefCounted { void set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale); real_t get_point_weight_scale(const Vector2i &p_id) const; + void fill_solid_region(const Rect2i &p_region, bool p_solid = true); + void fill_weight_scale_region(const Rect2i &p_region, real_t p_weight_scale); + void clear(); Vector2 get_point_position(const Vector2i &p_id) const; diff --git a/core/object/callable_method_pointer.cpp b/core/object/callable_method_pointer.cpp index b53985e6b725..ed400788b1ca 100644 --- a/core/object/callable_method_pointer.cpp +++ b/core/object/callable_method_pointer.cpp @@ -38,13 +38,10 @@ bool CallableCustomMethodPointerBase::compare_equal(const CallableCustom *p_a, c return false; } - for (uint32_t i = 0; i < a->comp_size; i++) { - if (a->comp_ptr[i] != b->comp_ptr[i]) { - return false; - } - } - - return true; + // Avoid sorting by memory address proximity, which leads to unpredictable performance over time + // due to the reuse of old addresses for newer objects. Use byte-wise comparison to leverage the + // backwards encoding of little-endian systems as a way to decouple spatiality and time. + return memcmp(a->comp_ptr, b->comp_ptr, a->comp_size * 4) == 0; } bool CallableCustomMethodPointerBase::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -55,15 +52,8 @@ bool CallableCustomMethodPointerBase::compare_less(const CallableCustom *p_a, co return a->comp_size < b->comp_size; } - for (uint32_t i = 0; i < a->comp_size; i++) { - if (a->comp_ptr[i] == b->comp_ptr[i]) { - continue; - } - - return a->comp_ptr[i] < b->comp_ptr[i]; - } - - return false; + // See note in compare_equal(). + return memcmp(a->comp_ptr, b->comp_ptr, a->comp_size * 4) < 0; } CallableCustom::CompareEqualFunc CallableCustomMethodPointerBase::get_compare_equal_func() const { diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index cc4a29164d09..c8c50fb957be 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -53,7 +53,7 @@ MethodDefinition D_METHODP(const char *p_name, const char *const **p_args, uint3 #endif ClassDB::APIType ClassDB::current_api = API_CORE; -HashMap ClassDB::api_hashes_cache; +HashMap ClassDB::api_hashes_cache; void ClassDB::set_current_api(APIType p_api) { DEV_ASSERT(!api_hashes_cache.has(p_api)); // This API type may not be suitable for caching of hash if it can change later. @@ -163,7 +163,7 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) { return ti->api; } -uint64_t ClassDB::get_api_hash(APIType p_api) { +uint32_t ClassDB::get_api_hash(APIType p_api) { OBJTYPE_RLOCK; #ifdef DEBUG_METHODS_ENABLED diff --git a/core/object/class_db.h b/core/object/class_db.h index ce64336a45c1..3aae3b452e69 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -155,7 +155,7 @@ class ClassDB { #endif static APIType current_api; - static HashMap api_hashes_cache; + static HashMap api_hashes_cache; static void _add_class2(const StringName &p_class, const StringName &p_inherits); @@ -246,7 +246,7 @@ class ClassDB { static APIType get_api_type(const StringName &p_class); - static uint64_t get_api_hash(APIType p_api); + static uint32_t get_api_hash(APIType p_api); template struct member_function_traits; diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 18ba5d5b3006..506f8291eb2c 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -222,62 +222,66 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args } } -Error CallQueue::flush() { - LOCK_MUTEX; - - // Thread overrides are not meant to be flushed, but appended to the main one. - if (this == MessageQueue::thread_singleton) { - if (pages.size() == 0) { - return OK; - } +Error CallQueue::_transfer_messages_to_main_queue() { + if (pages.size() == 0) { + return OK; + } - CallQueue *mq = MessageQueue::main_singleton; - DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters. - - mq->mutex.lock(); - - // Here we're transferring the data from this queue to the main one. - // However, it's very unlikely big amounts of messages will be queued here, - // so PagedArray/Pool would be overkill. Also, in most cases the data will fit - // an already existing page of the main queue. - - // Let's see if our first (likely only) page fits the current target queue page. - uint32_t src_page = 0; - { - if (mq->pages_used) { - uint32_t dst_page = mq->pages_used - 1; - uint32_t dst_offset = mq->page_bytes[dst_page]; - if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) { - memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]); - mq->page_bytes[dst_page] += page_bytes[0]; - src_page++; - } + CallQueue *mq = MessageQueue::main_singleton; + DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters. + + mq->mutex.lock(); + + // Here we're transferring the data from this queue to the main one. + // However, it's very unlikely big amounts of messages will be queued here, + // so PagedArray/Pool would be overkill. Also, in most cases the data will fit + // an already existing page of the main queue. + + // Let's see if our first (likely only) page fits the current target queue page. + uint32_t src_page = 0; + { + if (mq->pages_used) { + uint32_t dst_page = mq->pages_used - 1; + uint32_t dst_offset = mq->page_bytes[dst_page]; + if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) { + memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]); + mq->page_bytes[dst_page] += page_bytes[0]; + src_page++; } } + } - // Any other possibly existing source page needs to be added. + // Any other possibly existing source page needs to be added. - if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { - ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); - mq->statistics(); - mq->mutex.unlock(); - return ERR_OUT_OF_MEMORY; - } + if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { + ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); + mq->statistics(); + mq->mutex.unlock(); + return ERR_OUT_OF_MEMORY; + } - for (; src_page < pages_used; src_page++) { - mq->_add_page(); - memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]); - mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page]; - } + for (; src_page < pages_used; src_page++) { + mq->_add_page(); + memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]); + mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page]; + } - mq->mutex.unlock(); + mq->mutex.unlock(); - page_bytes[0] = 0; - pages_used = 1; + page_bytes[0] = 0; + pages_used = 1; - return OK; + return OK; +} + +Error CallQueue::flush() { + // Thread overrides are not meant to be flushed, but appended to the main one. + if (unlikely(this == MessageQueue::thread_singleton)) { + return _transfer_messages_to_main_queue(); } + LOCK_MUTEX; + if (pages.size() == 0) { // Never allocated UNLOCK_MUTEX; diff --git a/core/object/message_queue.h b/core/object/message_queue.h index 9f567e4dd045..c2f4ad16435e 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -98,6 +98,8 @@ class CallQueue { } } + Error _transfer_messages_to_main_queue(); + void _add_page(); void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error); diff --git a/core/object/object.cpp b/core/object/object.cpp index c76188a2cd32..4ae0ecdefd67 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -486,19 +486,21 @@ void Object::get_property_list(List *p_list, bool p_reversed) cons const ObjectGDExtension *current_extension = _extension; while (current_extension) { p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + ClassDB::get_property_list(current_extension->class_name, p_list, true, this); - current_extension = current_extension->parent; - } - } - if (_extension && _extension->get_property_list) { - uint32_t pcount; - const GDExtensionPropertyInfo *pinfo = _extension->get_property_list(_extension_instance, &pcount); - for (uint32_t i = 0; i < pcount; i++) { - p_list->push_back(PropertyInfo(pinfo[i])); - } - if (_extension->free_property_list) { - _extension->free_property_list(_extension_instance, pinfo); + if (current_extension->get_property_list) { + uint32_t pcount; + const GDExtensionPropertyInfo *pinfo = current_extension->get_property_list(_extension_instance, &pcount); + for (uint32_t i = 0; i < pcount; i++) { + p_list->push_back(PropertyInfo(pinfo[i])); + } + if (current_extension->free_property_list) { + current_extension->free_property_list(_extension_instance, pinfo); + } + } + + current_extension = current_extension->parent; } } @@ -836,14 +838,16 @@ void Object::set_script(const Variant &p_script) { return; } + Ref