diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0b4abac1afc73..0ce3f68abec11 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -5,18 +5,19 @@ body: - type: markdown attributes: value: | - - Read our [CONTRIBUTING.md guide](https://github.com/godotengine/godot/blob/master/CONTRIBUTING.md#reporting-bugs) on reporting bugs. + - When reporting bugs, you'll make our life simpler (and the fix will come sooner) if you follow the guidelines in this template. - Write a descriptive issue title above. - - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. + - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them. + - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate. - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/stable/about/release_policy.html). - type: input attributes: label: Godot version description: > - Specify the Git commit hash if using a development or non-official build. + Specify the Godot version, including the Git commit hash if using a development or non-official build. The exact Godot version (including the commit hash) can be copied by clicking the version shown in the editor (bottom bar) or in the project manager (top bar). If you use a custom build, please test if your issue is reproducible in official builds too. - placeholder: 3.3.stable, 4.0.dev (3041becc6) + placeholder: 3.5.stable, 4.0.dev [3041becc6] validations: required: true @@ -24,9 +25,12 @@ body: attributes: label: System information description: | - Specify the OS version, and when relevant hardware information. - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan). - placeholder: Windows 10, GLES3, Intel HD Graphics 620 (27.20.100.9616) + - Specify the OS version, and when relevant hardware information. + - For issues that are likely OS-specific and/or graphics-related, please specify the CPU model and architecture. + - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan). + - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information. + - **Starting from Godot 4.1, you can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window.** + placeholder: Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 970 (nvidia, 510.85.02) - Intel Core i7-10700KF CPU @ 3.80GHz (16 Threads) validations: required: true @@ -52,8 +56,10 @@ body: attributes: label: Minimal reproduction project description: | - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). - Required, unless the reproduction steps are trivial and don't require any project files to be followed. In this case, write "N/A" in the field. - Drag and drop a ZIP archive to upload it. **Do not select another field until the project is done uploading.** + - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). + - Required, unless the reproduction steps are trivial and don't require any project files to be followed. In this case, write "N/A" in the field. + - Drag and drop a ZIP archive to upload it. **Do not select another field until the project is done uploading.** + - **Note for C# users:** If your issue is *not* Mono-specific, please upload a minimal reproduction project written in GDScript or VisualScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a Mono setup available. + - **If you've been asked by a maintainer to upload a minimal reproduction project, you *must* do so within 7 days.** Otherwise, your bug report will be closed as it'll be considered too difficult to diagnose. validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8dc712c78d0ec..3d623fe5c34f9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,4 +4,7 @@ PRs can target `3.x` if the same change was done in `master`, or is not relevant Relevant fixes are cherry-picked for stable branches as needed by maintainers. You can mention in the description if the change is compatible with `3.x`. + +To speed up the contribution process and avoid CI errors, please set up pre-commit hooks locally: +https://docs.godotengine.org/en/latest/contributing/development/code_style_guidelines.html --> diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index 1912189d1c96c..afc73a462fda9 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -4,7 +4,7 @@ on: # Global Settings env: - # Only used for the cache key. Increment version 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 debug_symbols=no module_text_server_fb_enabled=yes diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index a32b25a8e71be..63ecabd95777c 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -4,7 +4,7 @@ on: # Global Settings env: - # Only used for the cache key. Increment version 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 debug_symbols=no module_text_server_fb_enabled=yes diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 44765ee32e9ae..aef8f83a53e10 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -4,7 +4,7 @@ on: # Global Settings env: - # Only used for the cache key. Increment version to force clean build. + # Used for the cache key, and godot-cpp checkout. 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 @@ -32,13 +32,14 @@ jobs: build-mono: true proj-conv: true artifact: true + compat: true - - name: Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold) + - 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 debug_symbols=no precision=double use_asan=yes use_ubsan=yes linker=gold + 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. @@ -182,6 +183,7 @@ jobs: uses: actions/checkout@v3 with: repository: godotengine/godot-cpp + ref: ${{ env.GODOT_BASE_BRANCH }} submodules: 'recursive' path: 'godot-cpp' @@ -201,6 +203,11 @@ jobs: 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: | diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 8822566c489a6..6e0fbbf461b03 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -4,7 +4,7 @@ on: # Global Settings env: - # Only used for the cache key. Increment version 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 diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 275f6cc38cf2c..6133780688a94 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -23,19 +23,23 @@ jobs: - name: Install Python dependencies and general setup run: | - pip3 install black==22.3.0 pytest==7.1.2 mypy==0.971 + pip3 install black==23.3.0 pytest==7.1.2 mypy==0.971 git config diff.wsErrorHighlight all - name: Get changed files id: changed-files + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if [ "${{ github.event_name }}" == "pull_request" ]; then - files=$(git diff-tree --no-commit-id --name-only -r ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2> /dev/null || true) + files=$(gh pr diff ${{ github.event.pull_request.number }} --name-only) elif [ "${{ github.event_name }}" == "push" -a "${{ github.event.forced }}" == "false" -a "${{ github.event.created }}" == "false" ]; then files=$(git diff-tree --no-commit-id --name-only -r ${{ github.event.before }}..${{ github.event.after }} 2> /dev/null || true) fi echo "$files" >> changed.txt cat changed.txt + files=$(echo "$files" | grep -v 'thirdparty' | xargs -I {} sh -c 'echo "./{}"' | tr '\n' ' ') + echo "CHANGED_FILES=$files" >> $GITHUB_ENV - name: File formatting checks (file_format.sh) run: | @@ -98,9 +102,9 @@ jobs: fi - name: Spell checks via codespell - uses: codespell-project/actions-codespell@v1 + if: github.event_name == 'pull_request' && env.CHANGED_FILES != '' + uses: codespell-project/actions-codespell@v2 with: - skip: ./.*,./**/.*,./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json - check_hidden: false - ignore_words_list: curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,nd,numer,ot,te - only_warn: true + skip: "./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json" + ignore_words_list: "curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,vai" + path: ${{ env.CHANGED_FILES }} diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index 24cb0adac85a4..2ae238caa6c17 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -4,7 +4,7 @@ on: # Global Settings env: - # Only used for the cache key. Increment version 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 debug_symbols=no EM_VERSION: 3.1.18 diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index da90e9641cfd7..182ae2fc8c411 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -5,7 +5,7 @@ on: # Global Settings # SCONS_CACHE for windows must be set in the build environment env: - # Only used for the cache key. Increment version 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 SCONS_CACHE_MSVC_CONFIG: true @@ -28,7 +28,7 @@ jobs: target: editor tests: true # Skip debug symbols, they're way too big with MSVC. - sconsflags: debug_symbols=no vsproj=yes + sconsflags: debug_symbols=no vsproj=yes windows_subsystem=console bin: "./bin/godot.windows.editor.x86_64.exe" - name: Template (target=template_release) diff --git a/.gitignore b/.gitignore index 17c9a6c95af6e..060f5696b8769 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,10 @@ tests/data/*.translation # Binutils tmp linker output of the form "stXXXXXX" where "X" is alphanumeric st[A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9] +# Python development +.venv +venv + # Python generated __pycache__/ *.pyc diff --git a/.mailmap b/.mailmap index 4e1f4c240220e..880cdfc100282 100644 --- a/.mailmap +++ b/.mailmap @@ -1,4 +1,5 @@ Aaron Record +ajreckof <66184050+ajreckof@users.noreply.github.com> Alexander Holland Alexander Holland Alexander Holland @@ -105,6 +106,7 @@ marynate Mateo Kuruk Miccino Max Hilbrunner Max Hilbrunner +MewPurPur Michael Alexsander Micky <66727710+Mickeon@users.noreply.github.com> Nathan Franke diff --git a/AUTHORS.md b/AUTHORS.md index 26f01b7cea328..bd6bb63ad8d4f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -45,12 +45,14 @@ name is available. Anutrix Aren Villanueva (kurikaesu) Ariel Manzur (punto-) + AThousandShips Bartłomiej T. Listwon (Listwon) Bastiaan Olij (BastiaanOlij) Ben Brookshire (sheepandshepherd) Benjamin Larsson (Nallebeorn) Bernhard Liebl (poke1024) Bhuvan Vemula (Bhu1-V) + bitsawer Błażej Szczygieł (zaps166) Bojidar Marinov (bojidar-bg) Brian Semrau (briansemrau) @@ -104,6 +106,7 @@ name is available. Gilles Roudiere (groud) Gordon MacPherson (RevoluPowered) Guilherme Felipe de C. G. da Silva (guilhermefelipecgs) + Hakim Rouatbi (hakro) Hanif Bin Ariffin (hbina) Haoyu Qiu (timothyqiu) Hein-Pieter van Braam-Stewart (hpvb) @@ -181,6 +184,7 @@ name is available. Michał Iwańczuk (iwek7) MichiRecRoom (LikeLakers2) Micky (Mickeon) + Mikael Hermansson (mihe) MinusKube Morris "Tabor" Arroad (mortarroad) mrezai @@ -193,6 +197,7 @@ name is available. Nikita Lita (nikitalita) Nils André-Chang (NilsIrl) Noah Beard (TwistedTwigleg) + Nông Văn Tình (nongvantinh) Nuno Donato (nunodonato) ocean (they/them) (anvilfolk) Omar El Sheikh (The-O-King) @@ -248,6 +253,7 @@ name is available. thebestnom Theo Hallenius (TheoXD) Timo Schwarzer (timoschwarzer) + Timothé Bonhoure (ajreckof) Timo (toger5) Tomasz Chabora (KoBeWi) trollodel diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aea1c8c5ffcf4..30934dcf9b475 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,87 +8,10 @@ - [Contributing to Godot's translation](#contributing-to-godots-translation) - [Communicating with developers](#communicating-with-developers) -**Please read the first section before reporting a bug!** - ## Reporting bugs -The golden rule is to **always open *one* issue for *one* bug**. If you notice -several bugs and want to report them, make sure to create one new issue for -each of them. - -If you're reporting a new bug, you'll make our life simpler (and the -fix will come sooner) by following these guidelines: - -### Search first in the existing database - -Issues are often reported several times by various users. It's good practice to -**search first in the [issue tracker](https://github.com/godotengine/godot/issues) -before reporting your issue**. If you don't find a relevant match or if you're -unsure, don't hesitate to **open a new issue**. The bugsquad will handle it -from there if it's a duplicate. - -### Specify the platform - -Godot runs on a large variety of platforms and operating systems and devices. -**In your bug reports, please always specify:** - -- Operating system and version (e.g. Windows 10, macOS 10.15, Ubuntu 19.10) -- Godot version (e.g. 3.2, 3.1.2, or the Git commit hash if you're using a development branch) - -For bugs that are likely OS-specific and/or graphics-related, please also specify: - -- Device (CPU model including architecture, e.g. x86_64, arm64, etc.) -- GPU model (and the driver version in use if you know it) - -**Bug reports not including the required information may be closed at the -maintainers' discretion.** If in doubt, always include all the requested -information; it's better to include too much information than not enough -information. - -### Specify steps to reproduce - -Many bugs can't be reproduced unless specific steps are taken. Please **specify -the exact steps** that must be taken to reproduce the condition, and try to -keep them as minimal as possible. If you're describing a procedure to follow -in the editor, don't hesitate to include screenshots. - -Making your bug report easy to reproduce will make it easier for contributors -to fix the bug. - -### Provide a simple example project - -Sometimes, unexpected behavior can happen in your project. In such case, -understand that: - -- What happens to you may not happen to other users. -- We can't take the time to look at your project, understand how it is set up - and then figure out why it's failing. -- On the contributors' end, recreating a test project from scratch takes valuable - time that can be saved by uploading a *minimal* project. - -To speed up our work, **please upload a minimal project** that isolates -and reproduces the issue. This is always the **best way for us to fix it**. -We recommend attaching a ZIP file with the minimal project directly to the bug report, -by drag and dropping the file in the GitHub edition field. This ensures the file -can remain available for a long period of time. Only use third-party file hosts -if your ZIP file isn't accepted by GitHub because it's too large. - -We recommend always attaching a minimal reproduction project, even if the issue -may seem simple to reproduce manually. - -**Note for C# users:** If your issue is *not* .NET-specific, please upload a -minimal reproduction project written in GDScript. -This will make it easier for contributors to reproduce the issue -locally as not everyone has a .NET setup available. - -**If you've been asked by a maintainer to upload a minimal reproduction project, -you *must* do so within 7 days.** Otherwise, your bug report will be closed as -it'll be considered too difficult to diagnose. - -Now that you've read the guidelines, click the link below to create a -bug report: - -- **[Report a bug](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.yml)** +Report bugs [here](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.yml). +Please follow the instructions in the template when you do. ## Proposing features or improvements diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 6c157c32f4d66..5512aec234a7c 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -169,7 +169,7 @@ License: Expat Files: ./thirdparty/doctest/ Comment: doctest -Copyright: 2016-2021, Viktor Kirilov +Copyright: 2016-2023, Viktor Kirilov License: Expat Files: ./thirdparty/embree/ @@ -394,9 +394,14 @@ License: BSD-2-clause Files: ./thirdparty/msdfgen/ Comment: Multi-channel signed distance field generator -Copyright: 2016, Viktor Chlumsky +Copyright: 2016-2022, Viktor Chlumsky License: MIT +Files: ./thirdparty/nvapi/nvapi_minimal.h +Comment: Stripped down version of "nvapi.h" from the NVIDIA NVAPI SDK +Copyright: 2019-2022, NVIDIA Corporation +License: Expat + Files: ./thirdparty/oidn/ Comment: Intel Open Image Denoise Copyright: 2009-2019, Intel Corporation @@ -420,12 +425,12 @@ License: Zlib Files: ./thirdparty/rvo2/ Comment: RVO2 -Copyright: 2016, University of North Carolina at Chapel Hill +Copyright: 2008, University of North Carolina at Chapel Hill License: Apache-2.0 Files: ./thirdparty/spirv-reflect/ Comment: SPIRV-Reflect -Copyright: 2017-2018, Google Inc. +Copyright: 2017-2022, Google Inc. License: Apache-2.0 Files: ./thirdparty/squish/ @@ -452,15 +457,15 @@ License: BSD-3-clause Files: ./thirdparty/volk/ Comment: volk -Copyright: 2018-2019, Arseny Kapoulkine +Copyright: 2018-2023, Arseny Kapoulkine License: Expat Files: ./thirdparty/vulkan/ Comment: Vulkan Headers -Copyright: 2014-2021, The Khronos Group Inc. - 2014-2021, Valve Corporation - 2014-2021, LunarG, Inc. - 2015-2021, Google Inc. +Copyright: 2014-2023, The Khronos Group Inc. + 2014-2023, Valve Corporation + 2014-2023, LunarG, Inc. + 2015-2023, Google Inc. License: Apache-2.0 Files: ./thirdparty/vulkan/vk_mem_alloc.h @@ -487,7 +492,7 @@ License: Zlib Files: ./thirdparty/zstd/ Comment: Zstandard -Copyright: 2016-2021, Facebook, Inc. +Copyright: Meta Platforms, Inc. and affiliates. License: BSD-3-clause diff --git a/DONORS.md b/DONORS.md index 1adb918d4c743..6b5dd5242d56f 100644 --- a/DONORS.md +++ b/DONORS.md @@ -23,20 +23,17 @@ generous deed immortalized in the next stable release of Godot Engine. ## Silver sponsors Affray Interactive + Delton Ding Playful Studios Robot Gentleman - Striked ## Bronze sponsors - ASIFA-Hollywood Basically Games Bippinbits Brandon Lamb Bri Garry Newman - Gordon MacPherson - Hunter Dickson Isaiah smith Jb Evain Keepsake Games @@ -45,7 +42,6 @@ generous deed immortalized in the next stable release of Godot Engine. Kyle Szklenski Matthew Campbell Maxim Karsten - Moonwards Nik Rudenko TrampolineTales @@ -56,17 +52,12 @@ generous deed immortalized in the next stable release of Godot Engine. Andres Hernandez Andrew Dunai anti666 - Christian Baune - Christopher Montesano Christopher Shifflett Christoph Woinke Daniel Edwards Darrin Massena David Mydlarz - Digital Grows Edward Flick - Florian Neumann - GameDev.net Hein-Pieter van Braam Jonah Stich Justin Arnold @@ -74,19 +65,15 @@ generous deed immortalized in the next stable release of Godot Engine. Justo Delgado Baudí Kossi Selom Banybah Lloyd Bond - Marcel Kräml Marek Belski Markus Ort - Matthieu Huvé Michael Mike King Nassor Paulino da Silva nate-wilkins - Nathan Warden Neal Gompa (Conan Kudo) - Ninja_5tyl3 + Nick Macholl Patrick Horn - Patrick Schmidt Rami Ronnie Cheng Ryan Heath @@ -94,18 +81,15 @@ generous deed immortalized in the next stable release of Godot Engine. ShikadiGum Slobodan Milnovic Stephan Lanfermann - Steve Tim Yuen Violin Iliev Vladimír Chvátil ## Gold donors - Acheron Adam Brown albinaask Allen Pestaluky - Arisaka Mayuki Asher Glick Barugon Benito @@ -115,39 +99,30 @@ generous deed immortalized in the next stable release of Godot Engine. David Gehrig David Hubber David Snopek + Dima Paloskin Ed Morley - Frank Kurka + Guilherme de Oliveira Guy Kay Jay Javier Roman - Joan Fons Jonathan Wright Jon Woodward Karl Werf Klavdij Voncina - Maciej Pendolski Manuele Finocchiaro Markus Wiesner Mateo Navarrete Matthew Hillier Officine Pixel S.n.c. Pedro Silva - Retro Village - Rob Messick - Roland Fredenhagen - Ronan Zeegers Sarksus Sean - Sergey Sofox - Taylor Ritenour + Stephan Kessler Tom Langwaldt tukon - Vagabond Arcade - Victor - Xeno Coliseum ␣ - 23BLUENINJA + Acheron AdamRatai Alexander Erlemann Alexandre VALIN @@ -156,12 +131,10 @@ generous deed immortalized in the next stable release of Godot Engine. Algebrute alice gambrell Alo Mis - Andrew Cunningham - Andrew Farr Andriy Antanas Paskauskas - Antoni Batchelli Ari + Arisaka Mayuki Arthur S. Muszynski BasicIncomePlz Brandon Hawkinson @@ -172,26 +145,23 @@ generous deed immortalized in the next stable release of Godot Engine. Charlie Whitfield Chris Petrich Chris Serino - Collin Rapp + Craig Ostrin Craig Scarborough Craig Smith - CT CzechBlueBear - CzłowiekImadło + Daniel Eichler Daniel Grice Daniel Reed Daniel Tebbutt - Darrian Little Dennis Belfrage Donn Eddy Emily A. Bellows Eric Brand Eugenio Hugo Salgüero Jáñez Felix Winterhalter - flesk Florencio Olsen - foxydevloper Fransiska + Furroy Gabrielius Vaiškūnas Gary Hulst Geoffroy Warin @@ -202,26 +172,18 @@ generous deed immortalized in the next stable release of Godot Engine. Guillaume Pham Ngoc Harry Tumber Harvey Fong - Heath Hayes Horváth-Lázár Péter - Hu Hund illuxxium - Jaap Marsman Jamal Bencharki James Couzens Jared White Jason Yundt - Jennifer Wilcox Jesús Chicharro Jihun Moon Joel Fivat Johnathan Kupferer - John Stinson Josef Stumpfegger - Joshie Sparks - Joshua Flores Joshua Lesperance - Juan Velandia Judd Julian Todd JUSTIN CARROLL @@ -230,15 +192,12 @@ generous deed immortalized in the next stable release of Godot Engine. Khora kickmaniac kinfox - Laszlo Kiss leetNightshade - Leo Fidel R Liban Liam Smyth LoparPanda - Luca Junge + Lucaaa Luca Vazzano LyaaaaaGames - MadScientistCarl Marcus Dobler Marcus Richter Mark Barrett @@ -246,63 +205,58 @@ generous deed immortalized in the next stable release of Godot Engine. Martin Gulliksson Martin Kotz Martin Soucek - matt Matt Greene Matthew Dana - Max Kryschi + Matthieu Huvé Michael Dürwald Michael Policastro MikadoSC + n00sh nate etan Nicola Cocchiaro + Nicolás Monner Sans Nikita Blizniuk + Nikita Rotskov Nikola Whallon Oliver Dick Otis Clark Patrick Wuttke - Patryk Pluta (vrid) Paul Hocker - Paul Von Zimmerman Pete Goodwin Petr Malac Petrus Prinsloo Philip Woods - RAMupgrade + Rebekah Farr red1939 Reilt Rene Tailleur - Rhodochrone Rickard Hermanson Rob Rob McInroy RodZilla - Romeo Disca + Ronan Zeegers Ronnie Ashlock Ronny Mühle - Russ Ryan Breaker - Ryan Miller - Ryan Scott "Sage Automatic Systems, LLC" Samuel Hummerstone Samuel Judd schroedinger's possum Shishir Tandale - Sing Chun Lee SKison Song Junwoo spacechase0 SpicyCactuar - Stephan Hennion Steven Landow Stoned Xander + sus Talii Teslatech Thomas Bjarnelöf Thomas Kurz + Tim Nedvyga Tobias Bocanegra Tobias Raggl - Tom Glenn Torbulous toto bibi Troy Kinsella @@ -319,12 +273,11 @@ generous deed immortalized in the next stable release of Godot Engine. ## Silver donors + Aaron Adriano Aaron Mayfield Aaron Oldenburg Adam Brunnmeier Adam Carr - Adam Long - Adam McCurdy Adam N Webber Adam Smeltzer Adinimys @@ -333,17 +286,15 @@ generous deed immortalized in the next stable release of Godot Engine. Adrien de Pierres Agustinus Arya Aidan O'Flannagain + Aiguo Wang ajaxcc Aki Mimoto - Akio Yamazaki Alan Beauchamp - Albert Gyulgazyan Alberto Salazar Muñoz Alberto Vilches Alder Stefano Alejandro Saucedo AleMax - Alessandro Senese Alex Clavelle Alex de la Mare alex raeside @@ -354,6 +305,7 @@ generous deed immortalized in the next stable release of Godot Engine. Andrew Andrew Groot andrew james morris + Andy Baird Ano Nim Anthony Avina Anton Bouwer @@ -361,8 +313,8 @@ generous deed immortalized in the next stable release of Godot Engine. Arda Erol Arthur Brainville Arturo Rosales + Ash K Aubrey Falconer - Auré Franky aurelien condomines Austin Miller Azar Gurbanov @@ -372,11 +324,9 @@ generous deed immortalized in the next stable release of Godot Engine. Benedikt Ben Vercammen Ben Visness - Bernhard Werner Bill Thibault bitbrain Bjarne Voigtländer - Black Block Brady Goldsworthy Bram Brian @@ -388,7 +338,6 @@ generous deed immortalized in the next stable release of Godot Engine. Cameron Meyer Carlos Rios Carl van der Geest - Casey Cesar Ruiz Chad Steadman Checkpoint Charlie @@ -397,13 +346,14 @@ generous deed immortalized in the next stable release of Godot Engine. Chris Jagusch Chris Langford Chris Ridenour + Christer Stenbrenden + Christian Alexander Bjørklund Bøhler Christian Kaltenecker Christian Mauduit - Christian Scholz Christian Winter - Christoffer Dahlblom Christoph Czurda Christophe Gagnier + Ciyvius Codex404 Cody Parker Conall O @@ -413,11 +363,12 @@ generous deed immortalized in the next stable release of Godot Engine. CrispyPin cynwav Dakota Watkins - Daniel H. Bahr Daniel Hoffmann Danielle + Daniel Ramos Dare Looks Daren Scot Wilson + Dave Jansen Davesnothere David Baker David Bôle @@ -428,36 +379,33 @@ generous deed immortalized in the next stable release of Godot Engine. Dimitri Roche Dominik Wetzel Donovan Hutcheon - Douglas Plumley + Doug Walker Dragontrapper dragoon Dr Ewan Murray Ducky Duobix Duodecimal - edisonlee55 Eduardo Teixeira - Edward Herbert Edward Swartz Egon Elbre - Elias Nykrem + Elgenzay Elijah Anderson Emerson MX Ends Ephemeral Eric Stokes - Eric Walkingshaw Eric Williams Erkki Seppälä Ewan Holmes Faisal Alkubaisi - Fault Boy fby Felix Adam Fer DC Filip Lundby Frank - fumangy + Frank Kurka + Frying☆Pan Game Endeavor Garrett Steffen Gary Thomas @@ -473,23 +421,20 @@ generous deed immortalized in the next stable release of Godot Engine. Guo Hongci Hans Jorgensen Haplo + Helge Maus + Helianthus Games Heribert Hirth Ian Richard Kunert Ian Williams Interstice + itsybitesyspider iveks Jacob D Jaguar - Jake D Jako Danar - James - James A F Manley - James Duran James Gary James Thomas Jamie Massey - Janis Skuja - Jan Vetulani JARKKO PARVIAINEN Jason Evans Jason Uechi @@ -498,8 +443,6 @@ generous deed immortalized in the next stable release of Godot Engine. Jennifer Graves Jesse Dubay João Pedro Braz - Joe Hurdle - Joe Klemmer John Barlex John Bruce John Palgut @@ -514,67 +457,69 @@ generous deed immortalized in the next stable release of Godot Engine. Jonathan G Jon Sully Jordy Goodridge - Jose Francisco 'Yiro' Vera Girona Joseph Catrambone Josh Segall Josh Taylor Joshua Heidrich + Jozef Krcho Juanfran Juan Maggi Juan Uys Jueast Julian le Roux - Julian Murgia + Julien Kaspar Justin Hamilton Justin Spedding - Justin Zander + J Vetulani Kalydi Balázs Katsuomi Kobayashi Keedong Park Keegan Scott - Keinan Powers Keith Bradner - Kenji Kawabata Ken Minardo Kent Jofur - Kerotasma - Ketafuki killaQueen - kimbring2 + kindzadza Kodera Software + Kostas Mouratidis + Krishna Nadoor KsyTek Games kycho Kyle Burnett Kyle Jacobs Lasse le Dous - Laurent CHEA Laurent Dethoor Laxman Pradhan Lech Rozanski Leland Vakarian Leonardo Baumle + Levi Berciu Levi Lindsey - LF Linus Lind Lundgren Logan Apple - Lucas Coelho + Logan Bratton + Lonnie Cox Ludovic DELVAL Luigi Renna Luis Ernesto Del Toro Peña Luis Gaemperle Luis Morao Lukas Komischke + Luke Diasio Luke Kasz Major Haul Malcolm Mara Huldra + Marcell Simon Marcos Heitor Carvalho Markie Music Mark Tyler Markus Martin Markus Michael Egger Markus Strompen + Martin Fitzke Martin Holas + Martin Linklater Martin Liška Martin Trbola Martin Zabinski @@ -586,7 +531,6 @@ generous deed immortalized in the next stable release of Godot Engine. Maxime Blade Maxwell McStuffings - Melchor Melissa Mears Metal Demon 2000 Michael @@ -595,37 +539,29 @@ generous deed immortalized in the next stable release of Godot Engine. Mikael Nordenberg Mikail Freitas Mikayla - Mike Birkhead Mike Copley Mike McRoberts - Miss Mitchell J. Wagner MJacred ModularMind Molinghu Molly Jameson MoltenGears - Moowool Moritz Weissenberger - moulefrite MrAZIE Mrjemandem Nathaniel neguse neighty Neil Blakey-Milner - Neil Wang Neofytos Chimonas Nerdforge Nerdyninja Nicholas La Roux Nick Eldrenkamp - Nick Macholl - Nico Greve Nicolas Rosset Nicolò Brigadoi Calamari Nils Nordmark - Nobbele Noel Billig Noesis obscuresteel @@ -639,23 +575,23 @@ generous deed immortalized in the next stable release of Godot Engine. Patrick Indermühle Patrickm Patrick Nafarrete + Paul Black Paul Gieske Paul Hankins Paul Mozet Paweł Kowal Paweł Łyczkowski Pedro Henrique Martins Garcia - Peter Höglund Philip Ludington (MrPhil) Phoenix Jauregui Pierre Caye - Pille Pixel Archipel pj Point08 Portponky Preethi Vaidyanathan PsycHead + PsyCrab Puntigames pwab Quincy Quincy @@ -665,9 +601,11 @@ generous deed immortalized in the next stable release of Godot Engine. Ragnar Pettersson Rainer Amler Rammeow + Rebecca H Relintai Remi Rampin Reneator + Richard Hayes Richard Ivánek Riley Robin Ward @@ -680,21 +618,21 @@ generous deed immortalized in the next stable release of Godot Engine. Roland Rząsa Roman Papush Roy Scayged - Rudi + Russ + Russell Matney Ryan Groom - Ryan Mueller - Rykk + Sacha Waked (Shaidak) Sammy Fischer Sangeeth Pavithran Sasha Schwartz - Sean Dee Sebastian Michailidis + Sekuta SeongWan Kim Sessamekesh SeungJong k - Shaidak Shane Lillie Shane Spoor + Shaun Kohanowski simdee Simon Jonas Larsen Simon Schoenenberger @@ -705,7 +643,6 @@ generous deed immortalized in the next stable release of Godot Engine. SleepDepJoel1 smbe19 smo1704 - Soheib El-Harrache Solene Waked Sophie Winter Squidgy @@ -718,7 +655,6 @@ generous deed immortalized in the next stable release of Godot Engine. SxP tadashi endo Tarch - Techwizz Terry TheVoiceInMyHead Thibaut DECROMBECQUE @@ -732,7 +668,6 @@ generous deed immortalized in the next stable release of Godot Engine. Tim Erskine Tim Gleason Tim Klein - Tim Nedvyga Timothy B. MacDonald Tim Raveling Tim Riley @@ -740,7 +675,6 @@ generous deed immortalized in the next stable release of Godot Engine. Tom Coxon Tom Webster Torsten Crass - travis f w Trent Skinner tril zerobyte Tryggve Sollid @@ -757,22 +691,21 @@ generous deed immortalized in the next stable release of Godot Engine. Vincent Barkmann Vincent Cloutier Vincent Foulon - Vitaliy Sapronenko - Vladimir Savin Vulinux Wapiti . Wiley Thompson William Bodin William Edwards William F Siqueira + Wolfram Woonki Moon - Wyatt Goodin Xananax Yan Shi yin Zekim Zher Huei Lee Zoee Silcock + Zyphery ケルベロス 貴宏 小松 郝晨煜 diff --git a/SConstruct b/SConstruct index e5421b7887a01..f65e6bab04b29 100644 --- a/SConstruct +++ b/SConstruct @@ -55,7 +55,8 @@ _helper_module("modules.modules_builders", "modules/modules_builders.py") import methods import glsl_builders import gles3_builders -from platform_methods import architectures, architecture_aliases +import scu_builders +from platform_methods import architectures, architecture_aliases, generate_export_icons if ARGUMENTS.get("target", "editor") == "editor": _helper_module("editor.editor_builders", "editor/editor_builders.py") @@ -67,9 +68,6 @@ platform_list = [] # list of platforms platform_opts = {} # options for each platform platform_flags = {} # flags for each platform platform_doc_class_path = {} - -active_platforms = [] -active_platform_ids = [] platform_exporters = [] platform_apis = [] @@ -92,13 +90,13 @@ for x in sorted(glob.glob("platform/*")): except Exception: pass + platform_name = x[9:] + if os.path.exists(x + "/export/export.cpp"): - platform_exporters.append(x[9:]) + platform_exporters.append(platform_name) + generate_export_icons(x, platform_name) if os.path.exists(x + "/api/api.cpp"): - platform_apis.append(x[9:]) - if detect.is_active(): - active_platforms.append(detect.get_name()) - active_platform_ids.append(x) + platform_apis.append(platform_name) if detect.can_build(): x = x.replace("platform/", "") # rest of world x = x.replace("platform\\", "") # win32 @@ -108,8 +106,6 @@ for x in sorted(glob.glob("platform/*")): sys.path.remove(tmppath) sys.modules.pop("detect") -methods.save_active_platforms(active_platforms, active_platform_ids) - custom_tools = ["default"] platform_arg = ARGUMENTS.get("platform", ARGUMENTS.get("p", False)) @@ -217,8 +213,13 @@ opts.Add(BoolVariable("disable_advanced_gui", "Disable advanced GUI nodes and be opts.Add("build_profile", "Path to a file containing a feature build profile", "") opts.Add(BoolVariable("modules_enabled_by_default", "If no, disable all modules except ones explicitly enabled", True)) opts.Add(BoolVariable("no_editor_splash", "Don't use the custom splash screen for the editor", True)) -opts.Add("system_certs_path", "Use this path as SSL certificates default for editor (for package maintainers)", "") +opts.Add( + "system_certs_path", + "Use this path as TLS certificates default for editor and Linux/BSD export templates (for package maintainers)", + "", +) opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False)) +opts.Add(BoolVariable("scu_build", "Use single compilation unit build", False)) # Thirdparty libraries opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True)) @@ -241,7 +242,8 @@ opts.Add(BoolVariable("builtin_miniupnpc", "Use the built-in miniupnpc library", 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)) -opts.Add(BoolVariable("builtin_rvo2", "Use the built-in RVO2 library", True)) +opts.Add(BoolVariable("builtin_rvo2_2d", "Use the built-in RVO2 2D library", True)) +opts.Add(BoolVariable("builtin_rvo2_3d", "Use the built-in RVO2 3D library", True)) opts.Add(BoolVariable("builtin_squish", "Use the built-in squish library", True)) opts.Add(BoolVariable("builtin_xatlas", "Use the built-in xatlas library", True)) opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True)) @@ -545,6 +547,10 @@ if selected_platform in platform_list: # LTO "auto" means we handle the preferred option in each platform detect.py. env["lto"] = ARGUMENTS.get("lto", "auto") + # Run SCU file generation script if in a SCU build. + if env["scu_build"]: + methods.set_scu_folders(scu_builders.generate_scu_files(env["verbose"], env_base.dev_build == False)) + # Must happen after the flags' definition, as configure is when most flags # are actually handled to change compile options, etc. detect.configure(env) @@ -561,9 +567,12 @@ if selected_platform in platform_list: env.Append(CCFLAGS=["/Zi", "/FS"]) env.Append(LINKFLAGS=["/DEBUG:FULL"]) - if env["optimize"] == "speed" or env["optimize"] == "speed_trace": + if env["optimize"] == "speed": env.Append(CCFLAGS=["/O2"]) env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["/O2"]) + env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"]) elif env["optimize"] == "size": env.Append(CCFLAGS=["/O1"]) env.Append(LINKFLAGS=["/OPT:REF"]) @@ -697,7 +706,8 @@ if selected_platform in platform_list: if env["warnings"] == "extra": env.Append(CCFLAGS=["/W4"]) elif env["warnings"] == "all": - env.Append(CCFLAGS=["/W3"]) + # C4458 is like -Wshadow. Part of /W4 but let's apply it for the default /W3 too. + env.Append(CCFLAGS=["/W3", "/w34458"]) elif env["warnings"] == "moderate": env.Append(CCFLAGS=["/W2"]) # Disable warnings which we don't plan to fix. @@ -726,7 +736,7 @@ if selected_platform in platform_list: common_warnings = [] if methods.using_gcc(env): - common_warnings += ["-Wshadow-local", "-Wno-misleading-indentation"] + common_warnings += ["-Wshadow", "-Wno-misleading-indentation"] if cc_version_major == 7: # Bogus warning fixed in 8+. common_warnings += ["-Wno-strict-overflow"] if cc_version_major < 11: @@ -736,6 +746,7 @@ if selected_platform in platform_list: if cc_version_major >= 12: # False positives in our error macros, see GH-58747. common_warnings += ["-Wno-return-type"] elif methods.using_clang(env) or methods.using_emcc(env): + common_warnings += ["-Wshadow-field-in-constructor", "-Wshadow-uncaptured-local"] # We often implement `operator<` for structs of pointers as a requirement # for putting them in `Set` or `Map`. We don't mind about unreliable ordering. common_warnings += ["-Wno-ordered-compare-function-pointers"] diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 814ad3d0761ee..7fdea7d1aa2ae 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -33,9 +33,7 @@ #include "core/authors.gen.h" #include "core/config/project_settings.h" #include "core/donors.gen.h" -#include "core/io/json.h" #include "core/license.gen.h" -#include "core/os/os.h" #include "core/variant/typed_array.h" #include "core/version.h" @@ -319,43 +317,6 @@ Engine::Engine() { singleton = this; } -void Engine::startup_begin() { - startup_benchmark_total_from = OS::get_singleton()->get_ticks_usec(); -} - -void Engine::startup_benchmark_begin_measure(const String &p_what) { - startup_benchmark_section = p_what; - startup_benchmark_from = OS::get_singleton()->get_ticks_usec(); -} -void Engine::startup_benchmark_end_measure() { - uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_from; - double total_f = double(total) / double(1000000); - - startup_benchmark_json[startup_benchmark_section] = total_f; -} - -void Engine::startup_dump(const String &p_to_file) { - uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_total_from; - double total_f = double(total) / double(1000000); - startup_benchmark_json["total_time"] = total_f; - - if (!p_to_file.is_empty()) { - Ref f = FileAccess::open(p_to_file, FileAccess::WRITE); - if (f.is_valid()) { - Ref json; - json.instantiate(); - f->store_string(json->stringify(startup_benchmark_json, "\t", false, true)); - } - } else { - List keys; - startup_benchmark_json.get_key_list(&keys); - print_line("STARTUP BENCHMARK:"); - for (const Variant &K : keys) { - print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec."); - } - } -} - Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) : name(p_name), ptr(p_ptr), diff --git a/core/config/engine.h b/core/config/engine.h index 52408f4be175c..5ea653ba6cd4a 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -83,11 +83,6 @@ class Engine { String write_movie_path; String shader_cache_path; - Dictionary startup_benchmark_json; - String startup_benchmark_section; - uint64_t startup_benchmark_from = 0; - uint64_t startup_benchmark_total_from = 0; - public: static Engine *get_singleton(); @@ -163,11 +158,6 @@ class Engine { bool is_validation_layers_enabled() const; int32_t get_gpu_index() const; - void startup_begin(); - void startup_benchmark_begin_measure(const String &p_what); - void startup_benchmark_end_measure(); - void startup_dump(const String &p_to_file); - Engine(); virtual ~Engine() {} }; diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 9fd7ef998876d..79fab508826ec 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -36,7 +36,6 @@ #include "core/io/config_file.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" -#include "core/io/file_access_network.h" #include "core/io/file_access_pack.h" #include "core/io/marshalls.h" #include "core/os/keyboard.h" @@ -68,14 +67,6 @@ String ProjectSettings::get_resource_path() const { return resource_path; } -String ProjectSettings::get_safe_project_name() const { - String safe_name = OS::get_singleton()->get_safe_dir_name(get("application/config/name")); - if (safe_name.is_empty()) { - safe_name = "UnnamedProject"; - } - return safe_name; -} - String ProjectSettings::get_imported_files_path() const { return get_project_data_path().path_join("imported"); } @@ -502,17 +493,6 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b } } - // If looking for files in a network client, use it directly - - if (FileAccessNetworkClient::get_singleton()) { - Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary"); - if (err == OK && !p_ignore_override) { - // Optional, we don't mind if it fails - _load_settings_text("res://override.cfg"); - } - return err; - } - // Attempt with a user-defined main pack first if (!p_main_pack.is_empty()) { @@ -640,7 +620,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bool p_upwards, bool p_ignore_override) { Error err = _setup(p_path, p_main_pack, p_upwards, p_ignore_override); - if (err == OK) { + if (err == OK && !p_ignore_override) { String custom_settings = GLOBAL_GET("application/config/project_settings_override"); if (!custom_settings.is_empty()) { _load_settings_text(custom_settings); @@ -942,10 +922,26 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap dir = DirAccess::open(p_root_dir); + + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.get_extension() == "csproj") { + return true; + } + file_name = dir->_get_next(); + } + + return false; +} +#endif // TOOLS_ENABLED + Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_custom, const Vector &p_custom_features, bool p_merge_with_current) { ERR_FAIL_COND_V_MSG(p_path.is_empty(), ERR_INVALID_PARAMETER, "Project settings save path cannot be empty."); @@ -964,7 +960,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust } } // Check for the existence of a csproj file. - if (FileAccess::exists(get_resource_path().path_join(get_safe_project_name() + ".csproj"))) { + if (_csproj_exists(get_resource_path())) { // If there is a csproj file, add the C# feature if it doesn't already exist. if (!project_features.has("C#")) { project_features.append("C#"); @@ -1218,6 +1214,8 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("set_order", "name", "position"), &ProjectSettings::set_order); ClassDB::bind_method(D_METHOD("get_order", "name"), &ProjectSettings::get_order); ClassDB::bind_method(D_METHOD("set_initial_value", "name", "value"), &ProjectSettings::set_initial_value); + ClassDB::bind_method(D_METHOD("set_as_basic", "name", "basic"), &ProjectSettings::set_as_basic); + ClassDB::bind_method(D_METHOD("set_as_internal", "name", "internal"), &ProjectSettings::set_as_internal); ClassDB::bind_method(D_METHOD("add_property_info", "hint"), &ProjectSettings::_add_property_info_bind); ClassDB::bind_method(D_METHOD("set_restart_if_changed", "name", "restart"), &ProjectSettings::set_restart_if_changed); ClassDB::bind_method(D_METHOD("clear", "name"), &ProjectSettings::clear); @@ -1262,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_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); GLOBAL_DEF("application/run/disable_stderr", false); @@ -1302,6 +1301,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres"); + GLOBAL_DEF_RST("audio/general/text_to_speech", false); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/3d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); @@ -1354,6 +1354,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("rendering/rendering_device/staging_buffer/block_size_kb", 256); GLOBAL_DEF("rendering/rendering_device/staging_buffer/max_size_mb", 128); GLOBAL_DEF("rendering/rendering_device/staging_buffer/texture_upload_region_size_px", 64); + GLOBAL_DEF("rendering/rendering_device/pipeline_cache/save_chunk_size_mb", 3.0); GLOBAL_DEF("rendering/rendering_device/vulkan/max_descriptors_per_pool", 64); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index a0249ef26737c..b89e6694b0c64 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -166,7 +166,6 @@ class ProjectSettings : public Object { String get_project_data_dir_name() const; String get_project_data_path() const; String get_resource_path() const; - String get_safe_project_name() const; String get_imported_files_path() const; static ProjectSettings *get_singleton(); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 50587bb4026a3..2d0d24406c922 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -39,6 +39,7 @@ #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" #include "core/os/keyboard.h" +#include "core/os/thread_safe.h" #include "core/variant/typed_array.h" namespace core_bind { @@ -224,6 +225,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const { return ::OS::get_singleton()->get_low_processor_usage_mode_sleep_usec(); } +void OS::set_delta_smoothing(bool p_enabled) { + ::OS::get_singleton()->set_delta_smoothing(p_enabled); +} + +bool OS::is_delta_smoothing_enabled() const { + return ::OS::get_singleton()->is_delta_smoothing_enabled(); +} + void OS::alert(const String &p_alert, const String &p_title) { ::OS::get_singleton()->alert(p_alert, p_title); } @@ -423,7 +432,14 @@ ::Thread::ID OS::get_main_thread_id() const { }; bool OS::has_feature(const String &p_feature) const { - return ::OS::get_singleton()->has_feature(p_feature); + const bool *value_ptr = feature_cache.getptr(p_feature); + if (value_ptr) { + return *value_ptr; + } else { + const bool has = ::OS::get_singleton()->has_feature(p_feature); + feature_cache[p_feature] = has; + return has; + } } uint64_t OS::get_static_memory_usage() const { @@ -549,6 +565,9 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &OS::set_low_processor_usage_mode_sleep_usec); ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &OS::get_low_processor_usage_mode_sleep_usec); + ClassDB::bind_method(D_METHOD("set_delta_smoothing", "delta_smoothing_enabled"), &OS::set_delta_smoothing); + ClassDB::bind_method(D_METHOD("is_delta_smoothing_enabled"), &OS::is_delta_smoothing_enabled); + ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count); ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name); @@ -624,6 +643,7 @@ void OS::_bind_methods() { 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"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "delta_smoothing"), "set_delta_smoothing", "is_delta_smoothing_enabled"); // Those default values need to be specified for the docs generator, // to avoid using values from the documentation writer's own OS instance. @@ -975,10 +995,11 @@ Vector Geometry3D::segment_intersects_cylinder(const Vector3 &p_from, c return r; } -Vector Geometry3D::segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const Vector &p_planes) { +Vector Geometry3D::segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray &p_planes) { Vector r; Vector3 res, norm; - if (!::Geometry3D::segment_intersects_convex(p_from, p_to, p_planes.ptr(), p_planes.size(), &res, &norm)) { + Vector planes = Variant(p_planes); + if (!::Geometry3D::segment_intersects_convex(p_from, p_to, planes.ptr(), planes.size(), &res, &norm)) { return r; } @@ -1167,17 +1188,37 @@ void Thread::_start_func(void *ud) { ERR_FAIL_MSG(vformat("Could not call function '%s' on previously freed instance to start thread %s.", t->target_callable.get_method(), t->get_id())); } + // Finding out a suitable name for the thread can involve querying a node, if the target is one. + // We know this is safe (unless the user is causing life cycle race conditions, which would be a bug on their part). + set_current_thread_safe_for_nodes(true); String func_name = t->target_callable.is_custom() ? t->target_callable.get_custom()->get_as_text() : String(t->target_callable.get_method()); + set_current_thread_safe_for_nodes(false); ::Thread::set_name(func_name); + // To avoid a circular reference between the thread and the script which can possibly contain a reference + // to the thread, we will do the call (keeping a reference up to that point) and then break chains with it. + // When the call returns, we will reference the thread again if possible. + ObjectID th_instance_id = t->get_instance_id(); + Callable target_callable = t->target_callable; + t = Ref(); + Callable::CallError ce; - t->target_callable.callp(nullptr, 0, t->ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { + Variant ret; + target_callable.callp(nullptr, 0, ret, ce); + // If script properly kept a reference to the thread, we should be able to re-reference it now + // (well, or if the call failed, since we had to break chains anyway because the outcome isn't known upfront). + t = Ref(ObjectDB::get_instance(th_instance_id)); + if (t.is_valid()) { + t->ret = ret; t->running.clear(); - ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + "."); + } else { + // We could print a warning here, but the Thread object will be eventually destroyed + // noticing wait_to_finish() hasn't been called on it, and it will print a warning itself. } - t->running.clear(); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + "."); + } } Error Thread::start(const Callable &p_callable, Priority p_priority) { @@ -1219,6 +1260,11 @@ Variant Thread::wait_to_finish() { return r; } +void Thread::set_thread_safety_checks_enabled(bool p_enabled) { + ERR_FAIL_COND_MSG(::Thread::is_main_thread(), "This call is forbidden on the main thread."); + set_current_thread_safe_for_nodes(!p_enabled); +} + void Thread::_bind_methods() { ClassDB::bind_method(D_METHOD("start", "callable", "priority"), &Thread::start, DEFVAL(PRIORITY_NORMAL)); ClassDB::bind_method(D_METHOD("get_id"), &Thread::get_id); @@ -1226,6 +1272,8 @@ void Thread::_bind_methods() { ClassDB::bind_method(D_METHOD("is_alive"), &Thread::is_alive); ClassDB::bind_method(D_METHOD("wait_to_finish"), &Thread::wait_to_finish); + ClassDB::bind_static_method("Thread", D_METHOD("set_thread_safety_checks_enabled", "enabled"), &Thread::set_thread_safety_checks_enabled); + BIND_ENUM_CONSTANT(PRIORITY_LOW); BIND_ENUM_CONSTANT(PRIORITY_NORMAL); BIND_ENUM_CONSTANT(PRIORITY_HIGH); @@ -1293,11 +1341,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const { } } -bool ClassDB::has_signal(StringName p_class, StringName p_signal) const { +bool ClassDB::class_has_signal(StringName p_class, StringName p_signal) const { return ::ClassDB::has_signal(p_class, p_signal); } -Dictionary ClassDB::get_signal(StringName p_class, StringName p_signal) const { +Dictionary ClassDB::class_get_signal(StringName p_class, StringName p_signal) const { MethodInfo signal; if (::ClassDB::get_signal(p_class, p_signal, &signal)) { return signal.operator Dictionary(); @@ -1306,7 +1354,7 @@ Dictionary ClassDB::get_signal(StringName p_class, StringName p_signal) const { } } -TypedArray ClassDB::get_signal_list(StringName p_class, bool p_no_inheritance) const { +TypedArray ClassDB::class_get_signal_list(StringName p_class, bool p_no_inheritance) const { List signals; ::ClassDB::get_signal_list(p_class, &signals, p_no_inheritance); TypedArray ret; @@ -1318,7 +1366,7 @@ TypedArray ClassDB::get_signal_list(StringName p_class, bool p_no_in return ret; } -TypedArray ClassDB::get_property_list(StringName p_class, bool p_no_inheritance) const { +TypedArray ClassDB::class_get_property_list(StringName p_class, bool p_no_inheritance) const { List plist; ::ClassDB::get_property_list(p_class, &plist, p_no_inheritance); TypedArray ret; @@ -1329,13 +1377,13 @@ TypedArray ClassDB::get_property_list(StringName p_class, bool p_no_ return ret; } -Variant ClassDB::get_property(Object *p_object, const StringName &p_property) const { +Variant ClassDB::class_get_property(Object *p_object, const StringName &p_property) const { Variant ret; ::ClassDB::get_property(p_object, p_property, ret); return ret; } -Error ClassDB::set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const { +Error ClassDB::class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const { Variant ret; bool valid; if (!::ClassDB::set_property(p_object, p_property, p_value, &valid)) { @@ -1346,11 +1394,11 @@ Error ClassDB::set_property(Object *p_object, const StringName &p_property, cons return OK; } -bool ClassDB::has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const { +bool ClassDB::class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const { return ::ClassDB::has_method(p_class, p_method, p_no_inheritance); } -TypedArray ClassDB::get_method_list(StringName p_class, bool p_no_inheritance) const { +TypedArray ClassDB::class_get_method_list(StringName p_class, bool p_no_inheritance) const { List methods; ::ClassDB::get_method_list(p_class, &methods, p_no_inheritance); TypedArray ret; @@ -1368,7 +1416,7 @@ TypedArray ClassDB::get_method_list(StringName p_class, bool p_no_in return ret; } -PackedStringArray ClassDB::get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const { +PackedStringArray ClassDB::class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const { List constants; ::ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance); @@ -1382,24 +1430,24 @@ PackedStringArray ClassDB::get_integer_constant_list(const StringName &p_class, return ret; } -bool ClassDB::has_integer_constant(const StringName &p_class, const StringName &p_name) const { +bool ClassDB::class_has_integer_constant(const StringName &p_class, const StringName &p_name) const { bool success; ::ClassDB::get_integer_constant(p_class, p_name, &success); return success; } -int64_t ClassDB::get_integer_constant(const StringName &p_class, const StringName &p_name) const { +int64_t ClassDB::class_get_integer_constant(const StringName &p_class, const StringName &p_name) const { bool found; int64_t c = ::ClassDB::get_integer_constant(p_class, p_name, &found); ERR_FAIL_COND_V(!found, 0); return c; } -bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const { +bool ClassDB::class_has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const { return ::ClassDB::has_enum(p_class, p_name, p_no_inheritance); } -PackedStringArray ClassDB::get_enum_list(const StringName &p_class, bool p_no_inheritance) const { +PackedStringArray ClassDB::class_get_enum_list(const StringName &p_class, bool p_no_inheritance) const { List enums; ::ClassDB::get_enum_list(p_class, &enums, p_no_inheritance); @@ -1413,7 +1461,7 @@ PackedStringArray ClassDB::get_enum_list(const StringName &p_class, bool p_no_in return ret; } -PackedStringArray ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const { +PackedStringArray ClassDB::class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const { List constants; ::ClassDB::get_enum_constants(p_class, p_enum, &constants, p_no_inheritance); @@ -1427,7 +1475,7 @@ PackedStringArray ClassDB::get_enum_constants(const StringName &p_class, const S return ret; } -StringName ClassDB::get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const { +StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const { return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance); } @@ -1444,27 +1492,27 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate); ::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate); - ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::has_signal); - ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::get_signal); - ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::get_signal_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::class_has_signal); + ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::class_get_signal); + ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::get_property_list, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::get_property); - ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::set_property); + ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::class_get_property_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property); + ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property); - ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::has_method, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::class_has_method, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::get_method_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::get_integer_constant_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::has_integer_constant); - ::ClassDB::bind_method(D_METHOD("class_get_integer_constant", "class", "name"), &ClassDB::get_integer_constant); + ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::class_has_integer_constant); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant", "class", "name"), &ClassDB::class_get_integer_constant); - ::ClassDB::bind_method(D_METHOD("class_has_enum", "class", "name", "no_inheritance"), &ClassDB::has_enum, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_enum_list", "class", "no_inheritance"), &ClassDB::get_enum_list, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::get_enum_constants, DEFVAL(false)); - ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::get_integer_constant_enum, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_has_enum", "class", "name", "no_inheritance"), &ClassDB::class_has_enum, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_enum_list", "class", "no_inheritance"), &ClassDB::class_get_enum_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::class_get_enum_constants, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::class_get_integer_constant_enum, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled); } diff --git a/core/core_bind.h b/core/core_bind.h index 4138a854407ce..6b25510b1431d 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -119,6 +119,8 @@ class ResourceSaver : public Object { class OS : public Object { GDCLASS(OS, Object); + mutable HashMap feature_cache; + protected: static void _bind_methods(); static OS *singleton; @@ -139,6 +141,9 @@ class OS : public Object { void set_low_processor_usage_mode_sleep_usec(int p_usec); int get_low_processor_usage_mode_sleep_usec() const; + void set_delta_smoothing(bool p_enabled); + bool is_delta_smoothing_enabled() const; + void alert(const String &p_alert, const String &p_title = "ALERT!"); void crash(const String &p_message); @@ -324,7 +329,7 @@ class Geometry3D : public Object { Vector segment_intersects_sphere(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_sphere_pos, real_t p_sphere_radius); Vector segment_intersects_cylinder(const Vector3 &p_from, const Vector3 &p_to, float p_height, float p_radius); - Vector segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const Vector &p_planes); + Vector segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray &p_planes); Vector clip_polygon(const Vector &p_points, const Plane &p_plane); @@ -403,6 +408,8 @@ class Thread : public RefCounted { bool is_started() const; bool is_alive() const; Variant wait_to_finish(); + + static void set_thread_safety_checks_enabled(bool p_enabled); }; namespace special { @@ -422,26 +429,26 @@ class ClassDB : public Object { bool can_instantiate(const StringName &p_class) const; Variant instantiate(const StringName &p_class) const; - bool has_signal(StringName p_class, StringName p_signal) const; - Dictionary get_signal(StringName p_class, StringName p_signal) const; - TypedArray get_signal_list(StringName p_class, bool p_no_inheritance = false) const; + bool class_has_signal(StringName p_class, StringName p_signal) const; + Dictionary class_get_signal(StringName p_class, StringName p_signal) const; + TypedArray class_get_signal_list(StringName p_class, bool p_no_inheritance = false) const; - TypedArray get_property_list(StringName p_class, bool p_no_inheritance = false) const; - Variant get_property(Object *p_object, const StringName &p_property) const; - Error set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const; + TypedArray class_get_property_list(StringName p_class, bool p_no_inheritance = false) const; + Variant class_get_property(Object *p_object, const StringName &p_property) const; + Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const; - bool has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false) const; + bool class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false) const; - TypedArray get_method_list(StringName p_class, bool p_no_inheritance = false) const; + TypedArray class_get_method_list(StringName p_class, bool p_no_inheritance = false) const; - PackedStringArray get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const; - bool has_integer_constant(const StringName &p_class, const StringName &p_name) const; - int64_t get_integer_constant(const StringName &p_class, const StringName &p_name) const; + PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const; + bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const; + int64_t class_get_integer_constant(const StringName &p_class, const StringName &p_name) const; - bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const; - PackedStringArray get_enum_list(const StringName &p_class, bool p_no_inheritance = false) const; - PackedStringArray get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const; - StringName get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const; + bool class_has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const; + PackedStringArray class_get_enum_list(const StringName &p_class, bool p_no_inheritance = false) const; + PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const; + StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const; bool is_class_enabled(StringName p_class) const; diff --git a/core/core_builders.py b/core/core_builders.py index b0a3b85d58c2e..e40ebbb14d420 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -239,7 +239,6 @@ def next_tag(self): data_list += part["Copyright"] with open(dst, "w", encoding="utf-8") as f: - f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") f.write("#ifndef LICENSE_GEN_H\n") f.write("#define LICENSE_GEN_H\n") diff --git a/core/core_constants.cpp b/core/core_constants.cpp index d88dda66090fd..2332bc235bd14 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -645,6 +645,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_RENDER); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_AVOIDANCE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DIR); @@ -704,6 +705,7 @@ void register_global_constants() { BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_BASIC_SETTING); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_READ_ONLY); + BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SECRET); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_DEFAULT); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NO_EDITOR); diff --git a/core/crypto/SCsub b/core/crypto/SCsub index 9b7953fdc5fa2..ac79e10d19a42 100644 --- a/core/crypto/SCsub +++ b/core/crypto/SCsub @@ -20,12 +20,13 @@ if is_builtin or not has_module: # Only if the module is not enabled, we must compile here the required sources # to make a "light" build with only the necessary mbedtls files. if not has_module: - env_thirdparty = env_crypto.Clone() - env_thirdparty.disable_warnings() - # Custom config file - env_thirdparty.Append( + # Minimal mbedTLS config file + env_crypto.Append( CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')] ) + # Build minimal mbedTLS library (MD5/SHA/Base64/AES). + env_thirdparty = env_crypto.Clone() + env_thirdparty.disable_warnings() thirdparty_mbedtls_dir = "#thirdparty/mbedtls/library/" thirdparty_mbedtls_sources = [ "aes.c", @@ -40,8 +41,16 @@ if not has_module: ] thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources] env_thirdparty.add_source_files(thirdparty_obj, thirdparty_mbedtls_sources) + # Needed to force rebuilding the library when the configuration file is updated. + env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_mbedtls_config.h") env.core_sources += thirdparty_obj - +elif is_builtin: + # Module mbedTLS config file + env_crypto.Append( + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')] + ) + # Needed to force rebuilding the core files when the configuration file is updated. + thirdparty_obj = ["#thirdparty/mbedtls/include/godot_module_mbedtls_config.h"] # Godot source files diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index 939c1c298fb2c..6b1c2a9cb2c4a 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -63,6 +63,8 @@ X509Certificate *X509Certificate::create() { void X509Certificate::_bind_methods() { ClassDB::bind_method(D_METHOD("save", "path"), &X509Certificate::save); ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load); + ClassDB::bind_method(D_METHOD("save_to_string"), &X509Certificate::save_to_string); + ClassDB::bind_method(D_METHOD("load_from_string", "string"), &X509Certificate::load_from_string); } /// TLSOptions diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 999fe076d60a0..4b5bf8305fc8a 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -65,6 +65,8 @@ class X509Certificate : public Resource { virtual Error load(String p_path) = 0; virtual Error load_from_memory(const uint8_t *p_buffer, int p_len) = 0; virtual Error save(String p_path) = 0; + virtual String save_to_string() = 0; + virtual Error load_from_string(const String &string) = 0; }; class TLSOptions : public RefCounted { diff --git a/core/debugger/local_debugger.cpp b/core/debugger/local_debugger.cpp index 623b1eb0ce2e1..dc46ffc3075f2 100644 --- a/core/debugger/local_debugger.cpp +++ b/core/debugger/local_debugger.cpp @@ -138,7 +138,7 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { // Cache options String variable_prefix = options["variable_prefix"]; - if (line.is_empty()) { + if (line.is_empty() && !feof(stdin)) { print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'"); print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'"); print_line("Enter \"help\" for assistance."); @@ -267,7 +267,8 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { print_line("Added breakpoint at " + source + ":" + itos(linenr)); } - } else if (line == "q" || line == "quit") { + } else if (line == "q" || line == "quit" || + (line.is_empty() && feof(stdin))) { // Do not stop again on quit script_debugger->clear_breakpoints(); script_debugger->set_depth(-1); diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 5e09e560d56d1..7549ba884ea4a 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -51,6 +51,7 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper if (p_method.return_enum.begins_with("_")) { //proxy class p_method.return_enum = p_method.return_enum.substr(1, p_method.return_enum.length()); } + p_method.return_is_bitfield = p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD; p_method.return_type = "int"; } else if (p_retinfo.class_name != StringName()) { p_method.return_type = p_retinfo.class_name; @@ -82,6 +83,7 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const if (p_argument.enumeration.begins_with("_")) { //proxy class p_argument.enumeration = p_argument.enumeration.substr(1, p_argument.enumeration.length()); } + p_argument.is_bitfield = p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD; p_argument.type = "int"; } else if (p_arginfo.class_name != StringName()) { p_argument.type = p_arginfo.class_name; @@ -165,6 +167,7 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met void DocData::constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc) { p_const.name = p_name; p_const.value = p_value; + p_const.is_value_valid = (p_value.get_type() != Variant::OBJECT); p_const.description = p_desc; } diff --git a/core/doc_data.h b/core/doc_data.h index 4e0db89984cc2..0fe7414b9890e 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -50,6 +50,7 @@ class DocData { String name; String type; String enumeration; + bool is_bitfield = false; String default_value; bool operator<(const ArgumentDoc &p_arg) const { if (name == p_arg.name) { @@ -70,6 +71,9 @@ class DocData { if (p_dict.has("enumeration")) { doc.enumeration = p_dict["enumeration"]; + if (p_dict.has("is_bitfield")) { + doc.is_bitfield = p_dict["is_bitfield"]; + } } if (p_dict.has("default_value")) { @@ -91,6 +95,7 @@ class DocData { if (!p_doc.enumeration.is_empty()) { dict["enumeration"] = p_doc.enumeration; + dict["is_bitfield"] = p_doc.is_bitfield; } if (!p_doc.default_value.is_empty()) { @@ -105,6 +110,7 @@ class DocData { String name; String return_type; String return_enum; + bool return_is_bitfield = false; String qualifiers; String description; bool is_deprecated = false; @@ -137,7 +143,7 @@ class DocData { return arguments[0] < p_method.arguments[0]; } } - return name < p_method.name; + return name.naturalcasecmp_to(p_method.name) < 0; } static MethodDoc from_dict(const Dictionary &p_dict) { MethodDoc doc; @@ -152,6 +158,9 @@ class DocData { if (p_dict.has("return_enum")) { doc.return_enum = p_dict["return_enum"]; + if (p_dict.has("return_is_bitfield")) { + doc.return_is_bitfield = p_dict["return_is_bitfield"]; + } } if (p_dict.has("qualifiers")) { @@ -201,6 +210,7 @@ class DocData { if (!p_doc.return_enum.is_empty()) { dict["return_enum"] = p_doc.return_enum; + dict["return_is_bitfield"] = p_doc.return_is_bitfield; } if (!p_doc.qualifiers.is_empty()) { @@ -264,10 +274,9 @@ class DocData { if (p_dict.has("enumeration")) { doc.enumeration = p_dict["enumeration"]; - } - - if (p_dict.has("is_bitfield")) { - doc.is_bitfield = p_dict["is_bitfield"]; + if (p_dict.has("is_bitfield")) { + doc.is_bitfield = p_dict["is_bitfield"]; + } } if (p_dict.has("description")) { @@ -299,10 +308,9 @@ class DocData { if (!p_doc.enumeration.is_empty()) { dict["enumeration"] = p_doc.enumeration; + dict["is_bitfield"] = p_doc.is_bitfield; } - dict["is_bitfield"] = p_doc.is_bitfield; - if (!p_doc.description.is_empty()) { dict["description"] = p_doc.description; } @@ -319,6 +327,7 @@ class DocData { String name; String type; String enumeration; + bool is_bitfield = false; String description; String setter, getter; String default_value; @@ -327,7 +336,7 @@ class DocData { bool is_deprecated = false; bool is_experimental = false; bool operator<(const PropertyDoc &p_prop) const { - return name < p_prop.name; + return name.naturalcasecmp_to(p_prop.name) < 0; } static PropertyDoc from_dict(const Dictionary &p_dict) { PropertyDoc doc; @@ -342,6 +351,9 @@ class DocData { if (p_dict.has("enumeration")) { doc.enumeration = p_dict["enumeration"]; + if (p_dict.has("is_bitfield")) { + doc.is_bitfield = p_dict["is_bitfield"]; + } } if (p_dict.has("description")) { @@ -391,6 +403,7 @@ class DocData { if (!p_doc.enumeration.is_empty()) { dict["enumeration"] = p_doc.enumeration; + dict["is_bitfield"] = p_doc.is_bitfield; } if (!p_doc.description.is_empty()) { @@ -432,7 +445,7 @@ class DocData { bool operator<(const ThemeItemDoc &p_theme_item) const { // First sort by the data type, then by name. if (data_type == p_theme_item.data_type) { - return name < p_theme_item.name; + return name.naturalcasecmp_to(p_theme_item.name) < 0; } return data_type < p_theme_item.data_type; } diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 79b0ebc6414ed..c67867f65d972 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -880,6 +880,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["is_virtual"] = false; d2["hash"] = method->get_hash(); + Vector compat_hashes = ClassDB::get_method_compatibility_hashes(class_name, method_name); + if (compat_hashes.size()) { + Array compatibility; + for (int i = 0; i < compat_hashes.size(); i++) { + compatibility.push_back(compat_hashes[i]); + } + d2["hash_compatibility"] = compatibility; + } + Vector default_args = method->get_default_arguments(); Array arguments; @@ -1056,4 +1065,352 @@ void GDExtensionAPIDump::generate_extension_json_file(const String &p_path) { fa->store_string(text); } +static bool compare_value(const String &p_path, const String &p_field, const Variant &p_old_value, const Variant &p_new_value, bool p_allow_name_change) { + bool failed = false; + String path = p_path + "/" + p_field; + if (p_old_value.get_type() == Variant::ARRAY && p_new_value.get_type() == Variant::ARRAY) { + Array old_array = p_old_value; + Array new_array = p_new_value; + if (!compare_value(path, "size", old_array.size(), new_array.size(), p_allow_name_change)) { + failed = true; + } + for (int i = 0; i < old_array.size() && i < new_array.size(); i++) { + if (!compare_value(path, itos(i), old_array[i], new_array[i], p_allow_name_change)) { + failed = true; + } + } + } else if (p_old_value.get_type() == Variant::DICTIONARY && p_new_value.get_type() == Variant::DICTIONARY) { + Dictionary old_dict = p_old_value; + Dictionary new_dict = p_new_value; + Array old_keys = old_dict.keys(); + for (int i = 0; i < old_keys.size(); i++) { + Variant key = old_keys[i]; + if (!new_dict.has(key)) { + failed = true; + print_error(vformat("Validate extension JSON: Error: Field '%s': %s was removed.", p_path, key)); + continue; + } + if (p_allow_name_change && key == "name") { + continue; + } + if (!compare_value(path, key, old_dict[key], new_dict[key], p_allow_name_change)) { + failed = true; + } + } + Array new_keys = old_dict.keys(); + for (int i = 0; i < new_keys.size(); i++) { + Variant key = new_keys[i]; + if (!old_dict.has(key)) { + failed = true; + print_error(vformat("Validate extension JSON: Error: Field '%s': %s was added with value %s.", p_path, key, new_dict[key])); + } + } + } else { + bool equal = Variant::evaluate(Variant::OP_EQUAL, p_old_value, p_new_value); + if (!equal) { + print_error(vformat("Validate extension JSON: Error: Field '%s': %s changed value in new API, from %s to %s.", p_path, p_field, p_old_value.get_construct_string(), p_new_value.get_construct_string())); + return false; + } + } + return !failed; +} + +static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_new_api, const String &p_base_array, const String &p_name_field, const Vector &p_fields_to_compare, bool p_compare_hashes, const String &p_outer_class = String(), bool p_compare_operators = false, bool p_compare_enum_value = false) { + String base_array = p_outer_class + p_base_array; + if (!p_old_api.has(p_base_array)) { + return true; // May just not have this array and its still good. Probably added recently. + } + bool failed = false; + ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, "New API lacks base array: " + p_base_array); + Array new_api = p_new_api[p_base_array]; + HashMap new_api_assoc; + + for (int i = 0; i < new_api.size(); i++) { + Dictionary elem = new_api[i]; + ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field)); + String name = elem[p_name_field]; + if (p_compare_operators && elem.has("right_type")) { + name += " " + String(elem["right_type"]); + } + new_api_assoc.insert(name, elem); + } + + Array old_api = p_old_api[p_base_array]; + for (int i = 0; i < old_api.size(); i++) { + Dictionary old_elem = old_api[i]; + if (!old_elem.has(p_name_field)) { + failed = true; + print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", base_array, p_name_field)); + continue; + } + String name = old_elem[p_name_field]; + if (p_compare_operators && old_elem.has("right_type")) { + name += " " + String(old_elem["right_type"]); + } + if (!new_api_assoc.has(name)) { + failed = true; + print_error(vformat("Validate extension JSON: API was removed: %s/%s", base_array, name)); + continue; + } + + Dictionary new_elem = new_api_assoc[name]; + + for (int j = 0; j < p_fields_to_compare.size(); j++) { + String field = p_fields_to_compare[j]; + bool optional = field.begins_with("*"); + if (optional) { + // This is an optional field, but if exists it has to exist in both. + field = field.substr(1, field.length()); + } + + bool added = field.begins_with("+"); + if (added) { + // Meaning this field must either exist or contents may not exist. + field = field.substr(1, field.length()); + } + + bool enum_values = field.begins_with("$"); + if (enum_values) { + // Meaning this field is a list of enum values. + field = field.substr(1, field.length()); + } + + bool allow_name_change = field.begins_with("@"); + if (allow_name_change) { + // Meaning that when structurally comparing the old and new value, the dictionary entry 'name' may change. + field = field.substr(1, field.length()); + } + + Variant old_value; + + if (!old_elem.has(field)) { + if (optional) { + if (new_elem.has(field)) { + failed = true; + print_error(vformat("Validate extension JSON: JSON file: Field was added in a way that breaks compatibility '%s/%s': %s", base_array, name, field)); + } + } else if (added && new_elem.has(field)) { + // Should be ok, field now exists, should not be verified in prior versions where it does not. + } else { + failed = true; + print_error(vformat("Validate extension JSON: JSON file: Missing field in '%s/%s': %s", base_array, name, field)); + } + continue; + } else { + old_value = old_elem[field]; + } + + if (!new_elem.has(field)) { + failed = true; + ERR_PRINT(vformat("Validate extension JSON: Missing field in current API '%s/%s': %s. This is a bug.", base_array, name, field)); + continue; + } + + Variant new_value = new_elem[field]; + + if (p_compare_enum_value && name.ends_with("_MAX")) { + if (static_cast(new_value) > static_cast(old_value)) { + // Ignore the _MAX value of an enum increasing. + continue; + } + } + if (enum_values) { + if (!compare_dict_array(old_elem, new_elem, field, "name", { "value" }, false, base_array + "/" + name + "/", false, true)) { + failed = true; + } + } else if (!compare_value(base_array + "/" + name, field, old_value, new_value, allow_name_change)) { + failed = true; + } + } + + if (p_compare_hashes) { + if (!old_elem.has("hash")) { + if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !new_elem.has("hash")) { + continue; // No hash for virtual methods, go on. + } + + failed = true; + print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: 'hash'.", base_array)); + continue; + } + + uint64_t old_hash = old_elem["hash"]; + + if (!new_elem.has("hash")) { + failed = true; + print_error(vformat("Validate extension JSON: Error: Field '%s' is missing the field: 'hash'.", base_array)); + continue; + } + + uint64_t new_hash = new_elem["hash"]; + bool hash_found = false; + if (old_hash == new_hash) { + hash_found = true; + } else if (new_elem.has("hash_compatibility")) { + Array compatibility = new_elem["hash_compatibility"]; + for (int j = 0; j < compatibility.size(); j++) { + new_hash = compatibility[j]; + if (new_hash == old_hash) { + hash_found = true; + break; + } + } + } + + if (!hash_found) { + failed = true; + print_error(vformat("Validate extension JSON: Error: Hash changed for '%s/%s', from %08X to %08X. This means that the function has changed and no compatibility function was provided.", base_array, name, old_hash, new_hash)); + continue; + } + } + } + + return !failed; +} + +static bool compare_sub_dict_array(HashSet &r_removed_classes_registered, const String &p_outer, const String &p_outer_name, const Dictionary &p_old_api, const Dictionary &p_new_api, const String &p_base_array, const String &p_name_field, const Vector &p_fields_to_compare, bool p_compare_hashes, bool p_compare_operators = false) { + if (!p_old_api.has(p_outer)) { + return true; // May just not have this array and its still good. Probably added recently or optional. + } + bool failed = false; + ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, "New API lacks base array: " + p_outer); + Array new_api = p_new_api[p_outer]; + HashMap new_api_assoc; + + for (int i = 0; i < new_api.size(); i++) { + Dictionary elem = new_api[i]; + ERR_FAIL_COND_V_MSG(!elem.has(p_outer_name), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", p_outer, p_outer_name)); + new_api_assoc.insert(elem[p_outer_name], elem); + } + + Array old_api = p_old_api[p_outer]; + + for (int i = 0; i < old_api.size(); i++) { + Dictionary old_elem = old_api[i]; + if (!old_elem.has(p_outer_name)) { + failed = true; + print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", p_outer, p_outer_name)); + continue; + } + String name = old_elem[p_outer_name]; + if (!new_api_assoc.has(name)) { + failed = true; + if (!r_removed_classes_registered.has(name)) { + print_error(vformat("Validate extension JSON: API was removed: %s/%s", p_outer, name)); + r_removed_classes_registered.insert(name); + } + continue; + } + + Dictionary new_elem = new_api_assoc[name]; + + if (!compare_dict_array(old_elem, new_elem, p_base_array, p_name_field, p_fields_to_compare, p_compare_hashes, p_outer + "/" + name + "/", p_compare_operators)) { + failed = true; + } + } + + return !failed; +} + +Error GDExtensionAPIDump::validate_extension_json_file(const String &p_path) { + Error error; + String text = FileAccess::get_file_as_string(p_path, &error); + if (error != OK) { + ERR_PRINT(vformat("Validate extension JSON: Could not open file '%s'.", p_path)); + return error; + } + + Ref json; + json.instantiate(); + error = json->parse(text); + if (error != OK) { + ERR_PRINT(vformat("Validate extension JSON: Error parsing '%s' at line %d: %s", p_path, json->get_error_line(), json->get_error_message())); + return error; + } + + Dictionary old_api = json->get_data(); + Dictionary new_api = generate_extension_api(); + + { // Validate header: + Dictionary header = old_api["header"]; + ERR_FAIL_COND_V(!header.has("version_major"), ERR_INVALID_DATA); + ERR_FAIL_COND_V(!header.has("version_minor"), ERR_INVALID_DATA); + int major = header["version_major"]; + int minor = header["version_minor"]; + + ERR_FAIL_COND_V_MSG(major != VERSION_MAJOR, ERR_INVALID_DATA, vformat("JSON API dump is for a different engine version (%d) than this one (%d)", major, VERSION_MAJOR)); + ERR_FAIL_COND_V_MSG(minor > VERSION_MINOR, ERR_INVALID_DATA, vformat("JSON API dump is for a newer version of the engine: %d.%d", major, minor)); + } + + bool failed = false; + + HashSet removed_classes_registered; + + if (!compare_dict_array(old_api, new_api, "global_constants", "name", Vector({ "value", "is_bitfield" }), false)) { + failed = true; + } + + if (!compare_dict_array(old_api, new_api, "global_enums", "name", Vector({ "$values", "is_bitfield" }), false)) { + failed = true; + } + + if (!compare_dict_array(old_api, new_api, "utility_functions", "name", Vector({ "category", "is_vararg", "*return_type", "*@arguments" }), true)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "members", "name", { "type" }, false)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "constants", "name", { "type", "value" }, false)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "operators", "name", { "return_type" }, false, true)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "methods", "name", { "is_vararg", "is_static", "is_const", "*return_type", "*@arguments" }, true)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "constructors", "index", { "*@arguments" }, false)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "constants", "name", { "value" }, false)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "enums", "name", { "is_bitfield", "$values" }, false)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "methods", "name", { "is_virtual", "is_vararg", "is_static", "is_const", "*return_value", "*@arguments" }, true)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "signals", "name", { "*@arguments" }, false)) { + failed = true; + } + + if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "properties", "name", { "type", "*setter", "*getter", "*index" }, false)) { + failed = true; + } + + if (!compare_dict_array(old_api, new_api, "singletons", "name", Vector({ "type" }), false)) { + failed = true; + } + + if (!compare_dict_array(old_api, new_api, "native_structures", "name", Vector({ "format" }), false)) { + failed = true; + } + + if (failed) { + return ERR_INVALID_DATA; + } else { + return OK; + } +} + #endif // TOOLS_ENABLED diff --git a/core/extension/extension_api_dump.h b/core/extension/extension_api_dump.h index 7e588c9446353..11ea2cf92300d 100644 --- a/core/extension/extension_api_dump.h +++ b/core/extension/extension_api_dump.h @@ -39,6 +39,7 @@ class GDExtensionAPIDump { public: static Dictionary generate_extension_api(); static void generate_extension_json_file(const String &p_path); + static Error validate_extension_json_file(const String &p_path); }; #endif diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index f158755a85603..73526fae3e2ae 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -34,6 +34,12 @@ #include "core/object/class_db.h" #include "core/object/method_bind.h" #include "core/os/os.h" +#include "core/version.h" + +extern void gdextension_setup_interface(); +extern GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name); + +typedef GDExtensionBool (*GDExtensionLegacyInitializationFunction)(void *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); String GDExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); @@ -146,9 +152,11 @@ String GDExtension::find_extension_library(const String &p_path, Ref class GDExtensionMethodBind : public MethodBind { GDExtensionClassMethodCall call_func; + GDExtensionClassMethodValidatedCall validated_call_func; GDExtensionClassMethodPtrCall ptrcall_func; void *method_userdata; bool vararg; + uint32_t argument_count; PropertyInfo return_value_info; GodotTypeInfo::Metadata return_value_metadata; List arguments_info; @@ -191,6 +199,40 @@ class GDExtensionMethodBind : public MethodBind { r_error.expected = ce.expected; return ret; } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { + ERR_FAIL_COND_MSG(vararg, "Validated methods don't have ptrcall support. This is most likely an engine bug."); + GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance(); + + if (validated_call_func) { + // This is added here, but it's unlikely to be provided by most extensions. + validated_call_func(method_userdata, extension_instance, reinterpret_cast(p_args), (GDExtensionVariantPtr)r_ret); + } else { +#if 1 + // Slow code-path, but works for the time being. + Callable::CallError ce; + call(p_object, p_args, argument_count, ce); +#else + // This is broken, because it needs more information to do the calling properly + + // If not provided, go via ptrcall, which is faster than resorting to regular call. + const void **argptrs = (const void **)alloca(argument_count * sizeof(void *)); + for (uint32_t i = 0; i < argument_count; i++) { + argptrs[i] = VariantInternal::get_opaque_pointer(p_args[i]); + } + + bool returns = true; + void *ret_opaque; + if (returns) { + ret_opaque = VariantInternal::get_opaque_pointer(r_ret); + } else { + ret_opaque = nullptr; // May be unnecessary as this is ignored, but just in case. + } + + ptrcall(p_object, argptrs, ret_opaque); +#endif + } + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have ptrcall support. This is most likely an engine bug."); GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance(); @@ -204,6 +246,7 @@ class GDExtensionMethodBind : public MethodBind { explicit GDExtensionMethodBind(const GDExtensionClassMethodInfo *p_method_info) { method_userdata = p_method_info->method_userdata; call_func = p_method_info->call_func; + validated_call_func = nullptr; ptrcall_func = p_method_info->ptrcall_func; set_name(*reinterpret_cast(p_method_info->name)); @@ -218,7 +261,7 @@ class GDExtensionMethodBind : public MethodBind { } set_hint_flags(p_method_info->method_flags); - + argument_count = p_method_info->argument_count; vararg = p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_VARARG; _set_returns(p_method_info->has_return_value); _set_const(p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_CONST); @@ -238,8 +281,6 @@ class GDExtensionMethodBind : public MethodBind { } }; -static GDExtensionInterface gdextension_interface; - void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs) { GDExtension *self = reinterpret_cast(p_library); @@ -272,6 +313,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library parent_extension->gdextension.children.push_back(&extension->gdextension); } + extension->gdextension.library = self; extension->gdextension.parent_class_name = parent_class_name; extension->gdextension.class_name = class_name; extension->gdextension.editor_class = self->level_initialized == INITIALIZATION_LEVEL_EDITOR; @@ -388,10 +430,23 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra self->extension_classes.erase(class_name); } -void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path) { +void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) { GDExtension *self = reinterpret_cast(p_library); - *(String *)r_path = self->library_path; + memnew_placement(r_path, String(self->library_path)); +} + +HashMap gdextension_interface_functions; + +void GDExtension::register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) { + ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered."); + gdextension_interface_functions.insert(p_function_name, p_function_pointer); +} + +GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(StringName p_function_name) { + GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name); + ERR_FAIL_COND_V_MSG(function == nullptr, nullptr, "Attempt to get non-existent interface function: " + p_function_name); + return *function; } Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) { @@ -412,8 +467,9 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb } GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr; + GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization); - if (initialization_function(&gdextension_interface, this, &initialization)) { + if (ret) { level_initialized = -1; return OK; } else { @@ -479,20 +535,18 @@ GDExtension::~GDExtension() { } } -extern void gdextension_setup_interface(GDExtensionInterface *p_interface); - void GDExtension::initialize_gdextensions() { - gdextension_setup_interface(&gdextension_interface); - - gdextension_interface.classdb_register_extension_class = _register_extension_class; - gdextension_interface.classdb_register_extension_class_method = _register_extension_class_method; - gdextension_interface.classdb_register_extension_class_integer_constant = _register_extension_class_integer_constant; - gdextension_interface.classdb_register_extension_class_property = _register_extension_class_property; - gdextension_interface.classdb_register_extension_class_property_group = _register_extension_class_property_group; - gdextension_interface.classdb_register_extension_class_property_subgroup = _register_extension_class_property_subgroup; - gdextension_interface.classdb_register_extension_class_signal = _register_extension_class_signal; - gdextension_interface.classdb_unregister_extension_class = _unregister_extension_class; - gdextension_interface.get_library_path = _get_library_path; + gdextension_setup_interface(); + + register_interface_function("classdb_register_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class); + 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_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); + register_interface_function("classdb_unregister_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_unregister_extension_class); + register_interface_function("get_library_path", (GDExtensionInterfaceFunctionPtr)&GDExtension::_get_library_path); } Ref GDExtensionResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { @@ -520,6 +574,50 @@ Ref GDExtensionResourceLoader::load(const String &p_path, const String String entry_symbol = config->get_value("configuration", "entry_symbol"); + uint32_t compatibility_minimum[3] = { 0, 0, 0 }; + if (config->has_section_key("configuration", "compatibility_minimum")) { + String compat_string = config->get_value("configuration", "compatibility_minimum"); + Vector parts = compat_string.split_ints("."); + for (int i = 0; i < parts.size(); i++) { + if (i >= 3) { + break; + } + if (parts[i] >= 0) { + compatibility_minimum[i] = parts[i]; + } + } + } else { + if (r_error) { + *r_error = ERR_INVALID_DATA; + } + ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); + return Ref(); + } + + if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { + if (r_error) { + *r_error = ERR_INVALID_DATA; + } + ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); + return Ref(); + } + + bool compatible = true; + if (VERSION_MAJOR < compatibility_minimum[0]) { + compatible = false; + } else if (VERSION_MINOR < compatibility_minimum[1]) { + compatible = false; + } else if (VERSION_PATCH < compatibility_minimum[2]) { + compatible = false; + } + if (!compatible) { + if (r_error) { + *r_error = ERR_INVALID_DATA; + } + ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); + return Ref(); + } + String library_path = GDExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); }); if (library_path.is_empty()) { @@ -576,3 +674,25 @@ String GDExtensionResourceLoader::get_resource_type(const String &p_path) const } return ""; } + +#ifdef TOOLS_ENABLED +Vector GDExtensionEditorPlugins::extension_classes; +GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_add_plugin = nullptr; +GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_remove_plugin = nullptr; + +void GDExtensionEditorPlugins::add_extension_class(const StringName &p_class_name) { + if (editor_node_add_plugin) { + editor_node_add_plugin(p_class_name); + } else { + extension_classes.push_back(p_class_name); + } +} + +void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_name) { + if (editor_node_remove_plugin) { + editor_node_remove_plugin(p_class_name); + } else { + extension_classes.erase(p_class_name); + } +} +#endif // TOOLS_ENABLED diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 9d946ca7ba57b..77ec458d30669 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -88,7 +88,10 @@ class GDExtension : public Resource { void initialize_library(InitializationLevel p_level); void deinitialize_library(InitializationLevel p_level); + static void register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer); + static GDExtensionInterfaceFunctionPtr get_interface_function(StringName p_function_name); static void initialize_gdextensions(); + GDExtension(); ~GDExtension(); }; @@ -103,4 +106,28 @@ class GDExtensionResourceLoader : public ResourceFormatLoader { virtual String get_resource_type(const String &p_path) const; }; +#ifdef TOOLS_ENABLED +class GDExtensionEditorPlugins { +private: + static Vector extension_classes; + +protected: + friend class EditorNode; + + // Since this in core, we can't directly reference EditorNode, so it will + // set these function pointers in its constructor. + typedef void (*EditorPluginRegisterFunc)(const StringName &p_class_name); + static EditorPluginRegisterFunc editor_node_add_plugin; + static EditorPluginRegisterFunc editor_node_remove_plugin; + +public: + static void add_extension_class(const StringName &p_class_name); + static void remove_extension_class(const StringName &p_class_name); + + static const Vector &get_extension_classes() { + return extension_classes; + } +}; +#endif // TOOLS_ENABLED + #endif // GDEXTENSION_H diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 2bedb623e4f79..7fbf2d00a16f0 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -31,6 +31,7 @@ #include "gdextension_interface.h" #include "core/config/engine.h" +#include "core/extension/gdextension.h" #include "core/io/file_access.h" #include "core/io/xml_parser.h" #include "core/object/class_db.h" @@ -40,16 +41,28 @@ #include "core/variant/variant.h" #include "core/version.h" +// Core interface functions. +GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name) { + return GDExtension::get_interface_function(p_name); +} + +static void gdextension_get_godot_version(GDExtensionGodotVersion *r_godot_version) { + r_godot_version->major = VERSION_MAJOR; + r_godot_version->minor = VERSION_MINOR; + r_godot_version->patch = VERSION_PATCH; + r_godot_version->string = VERSION_FULL_NAME; +} + // Memory Functions -static void *gdextension_alloc(size_t p_size) { +static void *gdextension_mem_alloc(size_t p_size) { return memalloc(p_size); } -static void *gdextension_realloc(void *p_mem, size_t p_size) { +static void *gdextension_mem_realloc(void *p_mem, size_t p_size) { return memrealloc(p_mem, p_size); } -static void gdextension_free(void *p_mem) { +static void gdextension_mem_free(void *p_mem) { memfree(p_mem); } @@ -80,10 +93,10 @@ uint64_t gdextension_get_native_struct_size(GDExtensionConstStringNamePtr p_name // Variant functions -static void gdextension_variant_new_copy(GDExtensionVariantPtr r_dest, GDExtensionConstVariantPtr p_src) { +static void gdextension_variant_new_copy(GDExtensionUninitializedVariantPtr r_dest, GDExtensionConstVariantPtr p_src) { memnew_placement(reinterpret_cast(r_dest), Variant(*reinterpret_cast(p_src))); } -static void gdextension_variant_new_nil(GDExtensionVariantPtr r_dest) { +static void gdextension_variant_new_nil(GDExtensionUninitializedVariantPtr r_dest) { memnew_placement(reinterpret_cast(r_dest), Variant); } static void gdextension_variant_destroy(GDExtensionVariantPtr p_self) { @@ -92,14 +105,14 @@ static void gdextension_variant_destroy(GDExtensionVariantPtr p_self) { // variant type -static void gdextension_variant_call(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) { +static void gdextension_variant_call(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) { Variant *self = (Variant *)p_self; const StringName method = *reinterpret_cast(p_method); const Variant **args = (const Variant **)p_args; - Variant ret; Callable::CallError error; - self->callp(method, args, p_argcount, ret, error); - memnew_placement(r_return, Variant(ret)); + memnew_placement(r_return, Variant); + Variant *ret = reinterpret_cast(r_return); + self->callp(method, args, p_argcount, *ret, error); if (r_error) { r_error->error = (GDExtensionCallErrorType)(error.error); @@ -108,14 +121,14 @@ static void gdextension_variant_call(GDExtensionVariantPtr p_self, GDExtensionCo } } -static void gdextension_variant_call_static(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) { +static void gdextension_variant_call_static(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) { Variant::Type type = (Variant::Type)p_type; const StringName method = *reinterpret_cast(p_method); const Variant **args = (const Variant **)p_args; - Variant ret; Callable::CallError error; - Variant::call_static(type, method, args, p_argcount, ret, error); - memnew_placement(r_return, Variant(ret)); + memnew_placement(r_return, Variant); + Variant *ret = reinterpret_cast(r_return); + Variant::call_static(type, method, args, p_argcount, *ret, error); if (r_error) { r_error->error = (GDExtensionCallErrorType)error.error; @@ -124,12 +137,13 @@ static void gdextension_variant_call_static(GDExtensionVariantType p_type, GDExt } } -static void gdextension_variant_evaluate(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionVariantPtr r_return, GDExtensionBool *r_valid) { +static void gdextension_variant_evaluate(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionUninitializedVariantPtr r_return, GDExtensionBool *r_valid) { Variant::Operator op = (Variant::Operator)p_op; const Variant *a = (const Variant *)p_a; const Variant *b = (const Variant *)p_b; - Variant *ret = (Variant *)r_return; bool valid; + memnew_placement(r_return, Variant); + Variant *ret = reinterpret_cast(r_return); Variant::evaluate(op, *a, *b, *ret, valid); *r_valid = valid; } @@ -175,7 +189,7 @@ static void gdextension_variant_set_indexed(GDExtensionVariantPtr p_self, GDExte *r_oob = oob; } -static void gdextension_variant_get(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) { +static void gdextension_variant_get(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) { const Variant *self = (const Variant *)p_self; const Variant *key = (const Variant *)p_key; @@ -184,7 +198,7 @@ static void gdextension_variant_get(GDExtensionConstVariantPtr p_self, GDExtensi *r_valid = valid; } -static void gdextension_variant_get_named(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) { +static void gdextension_variant_get_named(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) { const Variant *self = (const Variant *)p_self; const StringName *key = (const StringName *)p_key; @@ -193,7 +207,7 @@ static void gdextension_variant_get_named(GDExtensionConstVariantPtr p_self, GDE *r_valid = valid; } -static void gdextension_variant_get_keyed(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) { +static void gdextension_variant_get_keyed(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) { const Variant *self = (const Variant *)p_self; const Variant *key = (const Variant *)p_key; @@ -202,7 +216,7 @@ static void gdextension_variant_get_keyed(GDExtensionConstVariantPtr p_self, GDE *r_valid = valid; } -static void gdextension_variant_get_indexed(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob) { +static void gdextension_variant_get_indexed(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob) { const Variant *self = (const Variant *)p_self; bool valid; @@ -213,9 +227,10 @@ static void gdextension_variant_get_indexed(GDExtensionConstVariantPtr p_self, G } /// Iteration. -static GDExtensionBool gdextension_variant_iter_init(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid) { +static GDExtensionBool gdextension_variant_iter_init(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_iter, GDExtensionBool *r_valid) { const Variant *self = (const Variant *)p_self; - Variant *iter = (Variant *)r_iter; + memnew_placement(r_iter, Variant); + Variant *iter = reinterpret_cast(r_iter); bool valid; bool ret = self->iter_init(*iter, valid); @@ -233,7 +248,7 @@ static GDExtensionBool gdextension_variant_iter_next(GDExtensionConstVariantPtr return ret; } -static void gdextension_variant_iter_get(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) { +static void gdextension_variant_iter_get(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) { const Variant *self = (const Variant *)p_self; Variant *iter = (Variant *)r_iter; @@ -264,12 +279,12 @@ static GDExtensionBool gdextension_variant_booleanize(GDExtensionConstVariantPtr return self->booleanize(); } -static void gdextension_variant_duplicate(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_ret, GDExtensionBool p_deep) { +static void gdextension_variant_duplicate(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool p_deep) { const Variant *self = (const Variant *)p_self; memnew_placement(r_ret, Variant(self->duplicate(p_deep))); } -static void gdextension_variant_stringify(GDExtensionConstVariantPtr p_self, GDExtensionStringPtr r_ret) { +static void gdextension_variant_stringify(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_ret) { const Variant *self = (const Variant *)p_self; memnew_placement(r_ret, String(*self)); } @@ -298,7 +313,7 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_ return ret; } -static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionStringPtr r_ret) { +static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) { String name = Variant::get_type_name((Variant::Type)p_type); memnew_placement(r_ret, String(name)); } @@ -395,7 +410,7 @@ static GDExtensionVariantFromTypeConstructorFunc gdextension_get_variant_from_ty ERR_FAIL_V_MSG(nullptr, "Getting Variant conversion function with invalid type"); } -static GDExtensionTypeFromVariantConstructorFunc gdextension_get_type_from_variant_constructor(GDExtensionVariantType p_type) { +static GDExtensionTypeFromVariantConstructorFunc gdextension_get_variant_to_type_constructor(GDExtensionVariantType p_type) { switch (p_type) { case GDEXTENSION_VARIANT_TYPE_BOOL: return VariantTypeConstructor::type_from_variant; @@ -498,11 +513,12 @@ static GDExtensionPtrConstructor gdextension_variant_get_ptr_constructor(GDExten static GDExtensionPtrDestructor gdextension_variant_get_ptr_destructor(GDExtensionVariantType p_type) { return (GDExtensionPtrDestructor)Variant::get_ptr_destructor(Variant::Type(p_type)); } -static void gdextension_variant_construct(GDExtensionVariantType p_type, GDExtensionVariantPtr p_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error) { - memnew_placement(p_base, Variant); +static void gdextension_variant_construct(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error) { + memnew_placement(r_base, Variant); + Variant *base = reinterpret_cast(r_base); Callable::CallError error; - Variant::construct(Variant::Type(p_type), *(Variant *)p_base, (const Variant **)p_args, p_argument_count, error); + Variant::construct(Variant::Type(p_type), *base, (const Variant **)p_args, p_argument_count, error); if (r_error) { r_error->error = (GDExtensionCallErrorType)(error.error); @@ -533,7 +549,7 @@ static GDExtensionPtrKeyedGetter gdextension_variant_get_ptr_keyed_getter(GDExte static GDExtensionPtrKeyedChecker gdextension_variant_get_ptr_keyed_checker(GDExtensionVariantType p_type) { return (GDExtensionPtrKeyedChecker)Variant::get_member_ptr_keyed_checker(Variant::Type(p_type)); } -static void gdextension_variant_get_constant_value(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionVariantPtr r_ret) { +static void gdextension_variant_get_constant_value(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionUninitializedVariantPtr r_ret) { StringName constant = *reinterpret_cast(p_constant); memnew_placement(r_ret, Variant(Variant::get_constant_value(Variant::Type(p_type), constant))); } @@ -549,77 +565,67 @@ static GDExtensionPtrUtilityFunction gdextension_variant_get_ptr_utility_functio //string helpers -static void gdextension_string_new_with_latin1_chars(GDExtensionStringPtr r_dest, const char *p_contents) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); - *dest = String(p_contents); +static void gdextension_string_new_with_latin1_chars(GDExtensionUninitializedStringPtr r_dest, const char *p_contents) { + memnew_placement(r_dest, String(p_contents)); } -static void gdextension_string_new_with_utf8_chars(GDExtensionStringPtr r_dest, const char *p_contents) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); +static void gdextension_string_new_with_utf8_chars(GDExtensionUninitializedStringPtr r_dest, const char *p_contents) { + memnew_placement(r_dest, String); + String *dest = reinterpret_cast(r_dest); dest->parse_utf8(p_contents); } -static void gdextension_string_new_with_utf16_chars(GDExtensionStringPtr r_dest, const char16_t *p_contents) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); +static void gdextension_string_new_with_utf16_chars(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents) { + memnew_placement(r_dest, String); + String *dest = reinterpret_cast(r_dest); dest->parse_utf16(p_contents); } -static void gdextension_string_new_with_utf32_chars(GDExtensionStringPtr r_dest, const char32_t *p_contents) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); - *dest = String((const char32_t *)p_contents); +static void gdextension_string_new_with_utf32_chars(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents) { + memnew_placement(r_dest, String((const char32_t *)p_contents)); } -static void gdextension_string_new_with_wide_chars(GDExtensionStringPtr r_dest, const wchar_t *p_contents) { - String *dest = (String *)r_dest; +static void gdextension_string_new_with_wide_chars(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents) { if constexpr (sizeof(wchar_t) == 2) { // wchar_t is 16 bit, parse. - memnew_placement(dest, String); + memnew_placement(r_dest, String); + String *dest = reinterpret_cast(r_dest); dest->parse_utf16((const char16_t *)p_contents); } else { // wchar_t is 32 bit, copy. - memnew_placement(dest, String); - *dest = String((const char32_t *)p_contents); + memnew_placement(r_dest, String((const char32_t *)p_contents)); } } -static void gdextension_string_new_with_latin1_chars_and_len(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); - *dest = String(p_contents, p_size); +static void gdextension_string_new_with_latin1_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) { + memnew_placement(r_dest, String(p_contents, p_size)); } -static void gdextension_string_new_with_utf8_chars_and_len(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); +static void gdextension_string_new_with_utf8_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) { + memnew_placement(r_dest, String); + String *dest = reinterpret_cast(r_dest); dest->parse_utf8(p_contents, p_size); } -static void gdextension_string_new_with_utf16_chars_and_len(GDExtensionStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); +static void gdextension_string_new_with_utf16_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size) { + memnew_placement(r_dest, String); + String *dest = reinterpret_cast(r_dest); dest->parse_utf16(p_contents, p_size); } -static void gdextension_string_new_with_utf32_chars_and_len(GDExtensionStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size) { - String *dest = (String *)r_dest; - memnew_placement(dest, String); - *dest = String((const char32_t *)p_contents, p_size); +static void gdextension_string_new_with_utf32_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size) { + memnew_placement(r_dest, String((const char32_t *)p_contents, p_size)); } -static void gdextension_string_new_with_wide_chars_and_len(GDExtensionStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size) { - String *dest = (String *)r_dest; +static void gdextension_string_new_with_wide_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size) { if constexpr (sizeof(wchar_t) == 2) { // wchar_t is 16 bit, parse. - memnew_placement(dest, String); + memnew_placement(r_dest, String); + String *dest = reinterpret_cast(r_dest); dest->parse_utf16((const char16_t *)p_contents, p_size); } else { // wchar_t is 32 bit, copy. - memnew_placement(dest, String); - *dest = String((const char32_t *)p_contents, p_size); + memnew_placement(r_dest, String((const char32_t *)p_contents, p_size)); } } @@ -680,13 +686,17 @@ static GDExtensionInt gdextension_string_to_wide_chars(GDExtensionConstStringPtr static char32_t *gdextension_string_operator_index(GDExtensionStringPtr p_self, GDExtensionInt p_index) { String *self = (String *)p_self; - ERR_FAIL_INDEX_V(p_index, self->length() + 1, nullptr); + if (unlikely(p_index < 0 || p_index >= self->length() + 1)) { + return nullptr; + } return &self->ptrw()[p_index]; } static const char32_t *gdextension_string_operator_index_const(GDExtensionConstStringPtr p_self, GDExtensionInt p_index) { const String *self = (const String *)p_self; - ERR_FAIL_INDEX_V(p_index, self->length() + 1, nullptr); + if (unlikely(p_index < 0 || p_index >= self->length() + 1)) { + return nullptr; + } return &self->ptr()[p_index]; } @@ -747,121 +757,161 @@ static int64_t gdextension_worker_thread_pool_add_native_task(GDExtensionObjectP static uint8_t *gdextension_packed_byte_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedByteArray *self = (PackedByteArray *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptrw()[p_index]; } static const uint8_t *gdextension_packed_byte_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedByteArray *self = (const PackedByteArray *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptr()[p_index]; } static GDExtensionTypePtr gdextension_packed_color_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedColorArray *self = (PackedColorArray *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionTypePtr)&self->ptrw()[p_index]; } static GDExtensionTypePtr gdextension_packed_color_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedColorArray *self = (const PackedColorArray *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionTypePtr)&self->ptr()[p_index]; } static float *gdextension_packed_float32_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedFloat32Array *self = (PackedFloat32Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptrw()[p_index]; } static const float *gdextension_packed_float32_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedFloat32Array *self = (const PackedFloat32Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptr()[p_index]; } static double *gdextension_packed_float64_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedFloat64Array *self = (PackedFloat64Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptrw()[p_index]; } static const double *gdextension_packed_float64_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedFloat64Array *self = (const PackedFloat64Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptr()[p_index]; } static int32_t *gdextension_packed_int32_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedInt32Array *self = (PackedInt32Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptrw()[p_index]; } static const int32_t *gdextension_packed_int32_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedInt32Array *self = (const PackedInt32Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptr()[p_index]; } static int64_t *gdextension_packed_int64_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedInt64Array *self = (PackedInt64Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptrw()[p_index]; } static const int64_t *gdextension_packed_int64_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedInt64Array *self = (const PackedInt64Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return &self->ptr()[p_index]; } static GDExtensionStringPtr gdextension_packed_string_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedStringArray *self = (PackedStringArray *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionStringPtr)&self->ptrw()[p_index]; } static GDExtensionStringPtr gdextension_packed_string_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedStringArray *self = (const PackedStringArray *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionStringPtr)&self->ptr()[p_index]; } static GDExtensionTypePtr gdextension_packed_vector2_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedVector2Array *self = (PackedVector2Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionTypePtr)&self->ptrw()[p_index]; } static GDExtensionTypePtr gdextension_packed_vector2_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedVector2Array *self = (const PackedVector2Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionTypePtr)&self->ptr()[p_index]; } static GDExtensionTypePtr gdextension_packed_vector3_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { PackedVector3Array *self = (PackedVector3Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionTypePtr)&self->ptrw()[p_index]; } static GDExtensionTypePtr gdextension_packed_vector3_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const PackedVector3Array *self = (const PackedVector3Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionTypePtr)&self->ptr()[p_index]; } static GDExtensionVariantPtr gdextension_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) { Array *self = (Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionVariantPtr)&self->operator[](p_index); } static GDExtensionVariantPtr gdextension_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) { const Array *self = (const Array *)p_self; - ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + if (unlikely(p_index < 0 || p_index >= self->size())) { + return nullptr; + } return (GDExtensionVariantPtr)&self->operator[](p_index); } @@ -892,14 +942,13 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten /* OBJECT API */ -static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) { +static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) { const MethodBind *mb = reinterpret_cast(p_method_bind); Object *o = (Object *)p_instance; const Variant **args = (const Variant **)p_args; Callable::CallError error; - Variant ret = mb->call(o, args, p_arg_count, error); - memnew_placement(r_return, Variant(ret)); + memnew_placement(r_return, Variant(mb->call(o, args, p_arg_count, error))); if (r_error) { r_error->error = (GDExtensionCallErrorType)(error.error); @@ -943,6 +992,19 @@ static GDExtensionObjectPtr gdextension_object_get_instance_from_id(GDObjectInst return (GDExtensionObjectPtr)ObjectDB::get_instance(ObjectID(p_instance_id)); } +static GDExtensionBool gdextension_object_get_class_name(GDExtensionConstObjectPtr p_object, GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringNamePtr r_class_name) { + if (!p_object) { + return false; + } + const Object *o = (const Object *)p_object; + + memnew_placement(r_class_name, StringName); + StringName *class_name = reinterpret_cast(r_class_name); + *class_name = o->get_class_name_for_extension((GDExtension *)p_library); + + return true; +} + static GDExtensionObjectPtr gdextension_object_cast_to(GDExtensionConstObjectPtr p_object, void *p_class_tag) { if (!p_object) { return nullptr; @@ -984,7 +1046,12 @@ static GDExtensionScriptInstancePtr gdextension_script_instance_create(const GDE static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash) { const StringName classname = *reinterpret_cast(p_classname); const StringName methodname = *reinterpret_cast(p_methodname); - MethodBind *mb = ClassDB::get_method(classname, methodname); + bool exists = false; + MethodBind *mb = ClassDB::get_method_with_compatibility(classname, methodname, p_hash, &exists); + if (!mb && exists) { + ERR_PRINT("Method '" + classname + "." + methodname + "' has changed and no compatibility fallback has been provided. Please open an issue."); + return nullptr; + } ERR_FAIL_COND_V(!mb, nullptr); if (mb->get_hash() != p_hash) { ERR_PRINT("Hash mismatch for method '" + classname + "." + methodname + "'."); @@ -1004,204 +1071,150 @@ static void *gdextension_classdb_get_class_tag(GDExtensionConstStringNamePtr p_c return class_info ? class_info->class_ptr : nullptr; } -void gdextension_setup_interface(GDExtensionInterface *p_interface) { - GDExtensionInterface &gde_interface = *p_interface; - - gde_interface.version_major = VERSION_MAJOR; - gde_interface.version_minor = VERSION_MINOR; -#if VERSION_PATCH - gde_interface.version_patch = VERSION_PATCH; -#else - gde_interface.version_patch = 0; +static void gdextension_editor_add_plugin(GDExtensionConstStringNamePtr p_classname) { +#ifdef TOOLS_ENABLED + const StringName classname = *reinterpret_cast(p_classname); + GDExtensionEditorPlugins::add_extension_class(classname); #endif - gde_interface.version_string = VERSION_FULL_NAME; - - /* GODOT CORE */ - - gde_interface.mem_alloc = gdextension_alloc; - gde_interface.mem_realloc = gdextension_realloc; - gde_interface.mem_free = gdextension_free; - - gde_interface.print_error = gdextension_print_error; - gde_interface.print_error_with_message = gdextension_print_error_with_message; - gde_interface.print_warning = gdextension_print_warning; - gde_interface.print_warning_with_message = gdextension_print_warning_with_message; - gde_interface.print_script_error = gdextension_print_script_error; - gde_interface.print_script_error_with_message = gdextension_print_script_error_with_message; - - gde_interface.get_native_struct_size = gdextension_get_native_struct_size; - - /* GODOT VARIANT */ - - // variant general - gde_interface.variant_new_copy = gdextension_variant_new_copy; - gde_interface.variant_new_nil = gdextension_variant_new_nil; - gde_interface.variant_destroy = gdextension_variant_destroy; - - gde_interface.variant_call = gdextension_variant_call; - gde_interface.variant_call_static = gdextension_variant_call_static; - gde_interface.variant_evaluate = gdextension_variant_evaluate; - gde_interface.variant_set = gdextension_variant_set; - gde_interface.variant_set_named = gdextension_variant_set_named; - gde_interface.variant_set_keyed = gdextension_variant_set_keyed; - gde_interface.variant_set_indexed = gdextension_variant_set_indexed; - gde_interface.variant_get = gdextension_variant_get; - gde_interface.variant_get_named = gdextension_variant_get_named; - gde_interface.variant_get_keyed = gdextension_variant_get_keyed; - gde_interface.variant_get_indexed = gdextension_variant_get_indexed; - gde_interface.variant_iter_init = gdextension_variant_iter_init; - gde_interface.variant_iter_next = gdextension_variant_iter_next; - gde_interface.variant_iter_get = gdextension_variant_iter_get; - gde_interface.variant_hash = gdextension_variant_hash; - gde_interface.variant_recursive_hash = gdextension_variant_recursive_hash; - gde_interface.variant_hash_compare = gdextension_variant_hash_compare; - gde_interface.variant_booleanize = gdextension_variant_booleanize; - gde_interface.variant_duplicate = gdextension_variant_duplicate; - gde_interface.variant_stringify = gdextension_variant_stringify; - - gde_interface.variant_get_type = gdextension_variant_get_type; - gde_interface.variant_has_method = gdextension_variant_has_method; - gde_interface.variant_has_member = gdextension_variant_has_member; - gde_interface.variant_has_key = gdextension_variant_has_key; - gde_interface.variant_get_type_name = gdextension_variant_get_type_name; - gde_interface.variant_can_convert = gdextension_variant_can_convert; - gde_interface.variant_can_convert_strict = gdextension_variant_can_convert_strict; - - gde_interface.get_variant_from_type_constructor = gdextension_get_variant_from_type_constructor; - gde_interface.get_variant_to_type_constructor = gdextension_get_type_from_variant_constructor; - - // ptrcalls. - - gde_interface.variant_get_ptr_operator_evaluator = gdextension_variant_get_ptr_operator_evaluator; - gde_interface.variant_get_ptr_builtin_method = gdextension_variant_get_ptr_builtin_method; - gde_interface.variant_get_ptr_constructor = gdextension_variant_get_ptr_constructor; - gde_interface.variant_get_ptr_destructor = gdextension_variant_get_ptr_destructor; - gde_interface.variant_construct = gdextension_variant_construct; - gde_interface.variant_get_ptr_setter = gdextension_variant_get_ptr_setter; - gde_interface.variant_get_ptr_getter = gdextension_variant_get_ptr_getter; - gde_interface.variant_get_ptr_indexed_setter = gdextension_variant_get_ptr_indexed_setter; - gde_interface.variant_get_ptr_indexed_getter = gdextension_variant_get_ptr_indexed_getter; - gde_interface.variant_get_ptr_keyed_setter = gdextension_variant_get_ptr_keyed_setter; - gde_interface.variant_get_ptr_keyed_getter = gdextension_variant_get_ptr_keyed_getter; - gde_interface.variant_get_ptr_keyed_checker = gdextension_variant_get_ptr_keyed_checker; - gde_interface.variant_get_constant_value = gdextension_variant_get_constant_value; - gde_interface.variant_get_ptr_utility_function = gdextension_variant_get_ptr_utility_function; - - // extra utilities - - gde_interface.string_new_with_latin1_chars = gdextension_string_new_with_latin1_chars; - gde_interface.string_new_with_utf8_chars = gdextension_string_new_with_utf8_chars; - gde_interface.string_new_with_utf16_chars = gdextension_string_new_with_utf16_chars; - gde_interface.string_new_with_utf32_chars = gdextension_string_new_with_utf32_chars; - gde_interface.string_new_with_wide_chars = gdextension_string_new_with_wide_chars; - gde_interface.string_new_with_latin1_chars_and_len = gdextension_string_new_with_latin1_chars_and_len; - gde_interface.string_new_with_utf8_chars_and_len = gdextension_string_new_with_utf8_chars_and_len; - gde_interface.string_new_with_utf16_chars_and_len = gdextension_string_new_with_utf16_chars_and_len; - gde_interface.string_new_with_utf32_chars_and_len = gdextension_string_new_with_utf32_chars_and_len; - gde_interface.string_new_with_wide_chars_and_len = gdextension_string_new_with_wide_chars_and_len; - gde_interface.string_to_latin1_chars = gdextension_string_to_latin1_chars; - gde_interface.string_to_utf8_chars = gdextension_string_to_utf8_chars; - gde_interface.string_to_utf16_chars = gdextension_string_to_utf16_chars; - gde_interface.string_to_utf32_chars = gdextension_string_to_utf32_chars; - gde_interface.string_to_wide_chars = gdextension_string_to_wide_chars; - gde_interface.string_operator_index = gdextension_string_operator_index; - gde_interface.string_operator_index_const = gdextension_string_operator_index_const; - gde_interface.string_operator_plus_eq_string = gdextension_string_operator_plus_eq_string; - gde_interface.string_operator_plus_eq_char = gdextension_string_operator_plus_eq_char; - gde_interface.string_operator_plus_eq_cstr = gdextension_string_operator_plus_eq_cstr; - gde_interface.string_operator_plus_eq_wcstr = gdextension_string_operator_plus_eq_wcstr; - gde_interface.string_operator_plus_eq_c32str = gdextension_string_operator_plus_eq_c32str; - - /* XMLParser extra utilities */ - - gde_interface.xml_parser_open_buffer = gdextension_xml_parser_open_buffer; - - /* FileAccess extra utilities */ - - gde_interface.file_access_store_buffer = gdextension_file_access_store_buffer; - gde_interface.file_access_get_buffer = gdextension_file_access_get_buffer; - - /* WorkerThreadPool extra utilities */ - - gde_interface.worker_thread_pool_add_native_group_task = gdextension_worker_thread_pool_add_native_group_task; - gde_interface.worker_thread_pool_add_native_task = gdextension_worker_thread_pool_add_native_task; - - /* Packed array functions */ - - gde_interface.packed_byte_array_operator_index = gdextension_packed_byte_array_operator_index; - gde_interface.packed_byte_array_operator_index_const = gdextension_packed_byte_array_operator_index_const; - - gde_interface.packed_color_array_operator_index = gdextension_packed_color_array_operator_index; - gde_interface.packed_color_array_operator_index_const = gdextension_packed_color_array_operator_index_const; - - gde_interface.packed_float32_array_operator_index = gdextension_packed_float32_array_operator_index; - gde_interface.packed_float32_array_operator_index_const = gdextension_packed_float32_array_operator_index_const; - gde_interface.packed_float64_array_operator_index = gdextension_packed_float64_array_operator_index; - gde_interface.packed_float64_array_operator_index_const = gdextension_packed_float64_array_operator_index_const; - - gde_interface.packed_int32_array_operator_index = gdextension_packed_int32_array_operator_index; - gde_interface.packed_int32_array_operator_index_const = gdextension_packed_int32_array_operator_index_const; - gde_interface.packed_int64_array_operator_index = gdextension_packed_int64_array_operator_index; - gde_interface.packed_int64_array_operator_index_const = gdextension_packed_int64_array_operator_index_const; - - gde_interface.packed_string_array_operator_index = gdextension_packed_string_array_operator_index; - gde_interface.packed_string_array_operator_index_const = gdextension_packed_string_array_operator_index_const; - - gde_interface.packed_vector2_array_operator_index = gdextension_packed_vector2_array_operator_index; - gde_interface.packed_vector2_array_operator_index_const = gdextension_packed_vector2_array_operator_index_const; - gde_interface.packed_vector3_array_operator_index = gdextension_packed_vector3_array_operator_index; - gde_interface.packed_vector3_array_operator_index_const = gdextension_packed_vector3_array_operator_index_const; - - gde_interface.array_operator_index = gdextension_array_operator_index; - gde_interface.array_operator_index_const = gdextension_array_operator_index_const; - gde_interface.array_ref = gdextension_array_ref; - gde_interface.array_set_typed = gdextension_array_set_typed; - - /* Dictionary functions */ - - gde_interface.dictionary_operator_index = gdextension_dictionary_operator_index; - gde_interface.dictionary_operator_index_const = gdextension_dictionary_operator_index_const; - - /* OBJECT */ - - gde_interface.object_method_bind_call = gdextension_object_method_bind_call; - gde_interface.object_method_bind_ptrcall = gdextension_object_method_bind_ptrcall; - gde_interface.object_destroy = gdextension_object_destroy; - gde_interface.global_get_singleton = gdextension_global_get_singleton; - gde_interface.object_get_instance_binding = gdextension_object_get_instance_binding; - gde_interface.object_set_instance_binding = gdextension_object_set_instance_binding; - gde_interface.object_set_instance = gdextension_object_set_instance; - - gde_interface.object_cast_to = gdextension_object_cast_to; - gde_interface.object_get_instance_from_id = gdextension_object_get_instance_from_id; - gde_interface.object_get_instance_id = gdextension_object_get_instance_id; - - /* REFERENCE */ - - gde_interface.ref_get_object = gdextension_ref_get_object; - gde_interface.ref_set_object = gdextension_ref_set_object; - - /* SCRIPT INSTANCE */ - - gde_interface.script_instance_create = gdextension_script_instance_create; - - /* CLASSDB */ - - gde_interface.classdb_construct_object = gdextension_classdb_construct_object; - gde_interface.classdb_get_method_bind = gdextension_classdb_get_method_bind; - gde_interface.classdb_get_class_tag = gdextension_classdb_get_class_tag; - - /* CLASSDB EXTENSION */ - - //these are filled by implementation, since it will want to keep track of registered classes - gde_interface.classdb_register_extension_class = nullptr; - gde_interface.classdb_register_extension_class_method = nullptr; - gde_interface.classdb_register_extension_class_integer_constant = nullptr; - gde_interface.classdb_register_extension_class_property = nullptr; - gde_interface.classdb_register_extension_class_property_group = nullptr; - gde_interface.classdb_register_extension_class_property_subgroup = nullptr; - gde_interface.classdb_register_extension_class_signal = nullptr; - gde_interface.classdb_unregister_extension_class = nullptr; +} - gde_interface.get_library_path = nullptr; +static void gdextension_editor_remove_plugin(GDExtensionConstStringNamePtr p_classname) { +#ifdef TOOLS_ENABLED + const StringName classname = *reinterpret_cast(p_classname); + GDExtensionEditorPlugins::remove_extension_class(classname); +#endif } + +#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name) + +void gdextension_setup_interface() { + REGISTER_INTERFACE_FUNC(get_godot_version); + REGISTER_INTERFACE_FUNC(mem_alloc); + REGISTER_INTERFACE_FUNC(mem_realloc); + REGISTER_INTERFACE_FUNC(mem_free); + REGISTER_INTERFACE_FUNC(print_error); + REGISTER_INTERFACE_FUNC(print_error_with_message); + REGISTER_INTERFACE_FUNC(print_warning); + REGISTER_INTERFACE_FUNC(print_warning_with_message); + REGISTER_INTERFACE_FUNC(print_script_error); + REGISTER_INTERFACE_FUNC(print_script_error_with_message); + REGISTER_INTERFACE_FUNC(get_native_struct_size); + REGISTER_INTERFACE_FUNC(variant_new_copy); + REGISTER_INTERFACE_FUNC(variant_new_nil); + REGISTER_INTERFACE_FUNC(variant_destroy); + REGISTER_INTERFACE_FUNC(variant_call); + REGISTER_INTERFACE_FUNC(variant_call_static); + REGISTER_INTERFACE_FUNC(variant_evaluate); + REGISTER_INTERFACE_FUNC(variant_set); + REGISTER_INTERFACE_FUNC(variant_set_named); + REGISTER_INTERFACE_FUNC(variant_set_keyed); + REGISTER_INTERFACE_FUNC(variant_set_indexed); + REGISTER_INTERFACE_FUNC(variant_get); + REGISTER_INTERFACE_FUNC(variant_get_named); + REGISTER_INTERFACE_FUNC(variant_get_keyed); + REGISTER_INTERFACE_FUNC(variant_get_indexed); + REGISTER_INTERFACE_FUNC(variant_iter_init); + REGISTER_INTERFACE_FUNC(variant_iter_next); + REGISTER_INTERFACE_FUNC(variant_iter_get); + REGISTER_INTERFACE_FUNC(variant_hash); + REGISTER_INTERFACE_FUNC(variant_recursive_hash); + REGISTER_INTERFACE_FUNC(variant_hash_compare); + REGISTER_INTERFACE_FUNC(variant_booleanize); + REGISTER_INTERFACE_FUNC(variant_duplicate); + REGISTER_INTERFACE_FUNC(variant_stringify); + REGISTER_INTERFACE_FUNC(variant_get_type); + REGISTER_INTERFACE_FUNC(variant_has_method); + REGISTER_INTERFACE_FUNC(variant_has_member); + REGISTER_INTERFACE_FUNC(variant_has_key); + REGISTER_INTERFACE_FUNC(variant_get_type_name); + REGISTER_INTERFACE_FUNC(variant_can_convert); + REGISTER_INTERFACE_FUNC(variant_can_convert_strict); + REGISTER_INTERFACE_FUNC(get_variant_from_type_constructor); + REGISTER_INTERFACE_FUNC(get_variant_to_type_constructor); + REGISTER_INTERFACE_FUNC(variant_get_ptr_operator_evaluator); + REGISTER_INTERFACE_FUNC(variant_get_ptr_builtin_method); + REGISTER_INTERFACE_FUNC(variant_get_ptr_constructor); + REGISTER_INTERFACE_FUNC(variant_get_ptr_destructor); + REGISTER_INTERFACE_FUNC(variant_construct); + REGISTER_INTERFACE_FUNC(variant_get_ptr_setter); + REGISTER_INTERFACE_FUNC(variant_get_ptr_getter); + REGISTER_INTERFACE_FUNC(variant_get_ptr_indexed_setter); + REGISTER_INTERFACE_FUNC(variant_get_ptr_indexed_getter); + REGISTER_INTERFACE_FUNC(variant_get_ptr_keyed_setter); + REGISTER_INTERFACE_FUNC(variant_get_ptr_keyed_getter); + REGISTER_INTERFACE_FUNC(variant_get_ptr_keyed_checker); + REGISTER_INTERFACE_FUNC(variant_get_constant_value); + REGISTER_INTERFACE_FUNC(variant_get_ptr_utility_function); + REGISTER_INTERFACE_FUNC(string_new_with_latin1_chars); + REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars); + REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars); + REGISTER_INTERFACE_FUNC(string_new_with_utf32_chars); + REGISTER_INTERFACE_FUNC(string_new_with_wide_chars); + REGISTER_INTERFACE_FUNC(string_new_with_latin1_chars_and_len); + REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars_and_len); + REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars_and_len); + REGISTER_INTERFACE_FUNC(string_new_with_utf32_chars_and_len); + REGISTER_INTERFACE_FUNC(string_new_with_wide_chars_and_len); + REGISTER_INTERFACE_FUNC(string_to_latin1_chars); + REGISTER_INTERFACE_FUNC(string_to_utf8_chars); + REGISTER_INTERFACE_FUNC(string_to_utf16_chars); + REGISTER_INTERFACE_FUNC(string_to_utf32_chars); + REGISTER_INTERFACE_FUNC(string_to_wide_chars); + REGISTER_INTERFACE_FUNC(string_operator_index); + REGISTER_INTERFACE_FUNC(string_operator_index_const); + REGISTER_INTERFACE_FUNC(string_operator_plus_eq_string); + REGISTER_INTERFACE_FUNC(string_operator_plus_eq_char); + 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(xml_parser_open_buffer); + REGISTER_INTERFACE_FUNC(file_access_store_buffer); + REGISTER_INTERFACE_FUNC(file_access_get_buffer); + REGISTER_INTERFACE_FUNC(worker_thread_pool_add_native_group_task); + REGISTER_INTERFACE_FUNC(worker_thread_pool_add_native_task); + REGISTER_INTERFACE_FUNC(packed_byte_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_byte_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_color_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_color_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_float32_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_float32_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_float64_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_float64_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_int32_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_int32_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_int64_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_int64_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_string_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_string_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_vector2_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_vector2_array_operator_index_const); + REGISTER_INTERFACE_FUNC(packed_vector3_array_operator_index); + REGISTER_INTERFACE_FUNC(packed_vector3_array_operator_index_const); + REGISTER_INTERFACE_FUNC(array_operator_index); + REGISTER_INTERFACE_FUNC(array_operator_index_const); + REGISTER_INTERFACE_FUNC(array_ref); + REGISTER_INTERFACE_FUNC(array_set_typed); + REGISTER_INTERFACE_FUNC(dictionary_operator_index); + REGISTER_INTERFACE_FUNC(dictionary_operator_index_const); + REGISTER_INTERFACE_FUNC(object_method_bind_call); + REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall); + REGISTER_INTERFACE_FUNC(object_destroy); + REGISTER_INTERFACE_FUNC(global_get_singleton); + REGISTER_INTERFACE_FUNC(object_get_instance_binding); + REGISTER_INTERFACE_FUNC(object_set_instance_binding); + REGISTER_INTERFACE_FUNC(object_set_instance); + REGISTER_INTERFACE_FUNC(object_get_class_name); + REGISTER_INTERFACE_FUNC(object_cast_to); + REGISTER_INTERFACE_FUNC(object_get_instance_from_id); + REGISTER_INTERFACE_FUNC(object_get_instance_id); + REGISTER_INTERFACE_FUNC(ref_get_object); + REGISTER_INTERFACE_FUNC(ref_set_object); + REGISTER_INTERFACE_FUNC(script_instance_create); + REGISTER_INTERFACE_FUNC(classdb_construct_object); + REGISTER_INTERFACE_FUNC(classdb_get_method_bind); + REGISTER_INTERFACE_FUNC(classdb_get_class_tag); + REGISTER_INTERFACE_FUNC(editor_add_plugin); + REGISTER_INTERFACE_FUNC(editor_remove_plugin); +} + +#undef REGISTER_INTERFACE_FUNCTION diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index f323b2aa5335e..4d7bdf9502332 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -139,16 +139,37 @@ typedef enum { } GDExtensionVariantOperator; +// In this API there are multiple functions which expect the caller to pass a pointer +// on return value as parameter. +// In order to make it clear if the caller should initialize the return value or not +// we have two flavor of types: +// - `GDExtensionXXXPtr` for pointer on an initialized value +// - `GDExtensionUninitializedXXXPtr` for pointer on uninitialized value +// +// Notes: +// - Not respecting those requirements can seems harmless, but will lead to unexpected +// segfault or memory leak (for instance with a specific compiler/OS, or when two +// native extensions start doing ptrcall on each other). +// - Initialization must be done with the function pointer returned by `variant_get_ptr_constructor`, +// zero-initializing the variable should not be considered a valid initialization method here ! +// - Some types have no destructor (see `extension_api.json`'s `has_destructor` field), for +// them it is always safe to skip the constructor for the return value if you are in a hurry ;-) + typedef void *GDExtensionVariantPtr; typedef const void *GDExtensionConstVariantPtr; +typedef void *GDExtensionUninitializedVariantPtr; typedef void *GDExtensionStringNamePtr; typedef const void *GDExtensionConstStringNamePtr; +typedef void *GDExtensionUninitializedStringNamePtr; typedef void *GDExtensionStringPtr; typedef const void *GDExtensionConstStringPtr; +typedef void *GDExtensionUninitializedStringPtr; typedef void *GDExtensionObjectPtr; typedef const void *GDExtensionConstObjectPtr; +typedef void *GDExtensionUninitializedObjectPtr; typedef void *GDExtensionTypePtr; typedef const void *GDExtensionConstTypePtr; +typedef void *GDExtensionUninitializedTypePtr; typedef const void *GDExtensionMethodBindPtr; typedef int64_t GDExtensionInt; typedef uint8_t GDExtensionBool; @@ -174,11 +195,11 @@ typedef struct { int32_t expected; } GDExtensionCallError; -typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionVariantPtr, GDExtensionTypePtr); -typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionTypePtr, GDExtensionVariantPtr); +typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr); +typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr); typedef void (*GDExtensionPtrOperatorEvaluator)(GDExtensionConstTypePtr p_left, GDExtensionConstTypePtr p_right, GDExtensionTypePtr r_result); typedef void (*GDExtensionPtrBuiltInMethod)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_return, int p_argument_count); -typedef void (*GDExtensionPtrConstructor)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args); +typedef void (*GDExtensionPtrConstructor)(GDExtensionUninitializedTypePtr p_base, const GDExtensionConstTypePtr *p_args); typedef void (*GDExtensionPtrDestructor)(GDExtensionTypePtr p_base); typedef void (*GDExtensionPtrSetter)(GDExtensionTypePtr p_base, GDExtensionConstTypePtr p_value); typedef void (*GDExtensionPtrGetter)(GDExtensionConstTypePtr p_base, GDExtensionTypePtr r_value); @@ -295,6 +316,7 @@ typedef enum { } GDExtensionClassMethodArgumentMetadata; typedef void (*GDExtensionClassMethodCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); +typedef void (*GDExtensionClassMethodValidatedCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionVariantPtr r_return); typedef void (*GDExtensionClassMethodPtrCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); typedef struct { @@ -400,214 +422,6 @@ typedef struct { } GDExtensionScriptInstanceInfo; -/* INTERFACE */ - -typedef struct { - uint32_t version_major; - uint32_t version_minor; - uint32_t version_patch; - const char *version_string; - - /* GODOT CORE */ - - void *(*mem_alloc)(size_t p_bytes); - void *(*mem_realloc)(void *p_ptr, size_t p_bytes); - void (*mem_free)(void *p_ptr); - - void (*print_error)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); - void (*print_error_with_message)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); - void (*print_warning)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); - void (*print_warning_with_message)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); - void (*print_script_error)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); - void (*print_script_error_with_message)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); - - uint64_t (*get_native_struct_size)(GDExtensionConstStringNamePtr p_name); - - /* GODOT VARIANT */ - - /* variant general */ - void (*variant_new_copy)(GDExtensionVariantPtr r_dest, GDExtensionConstVariantPtr p_src); - void (*variant_new_nil)(GDExtensionVariantPtr r_dest); - void (*variant_destroy)(GDExtensionVariantPtr p_self); - - /* variant type */ - void (*variant_call)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); - void (*variant_call_static)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); - void (*variant_evaluate)(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionVariantPtr r_return, GDExtensionBool *r_valid); - void (*variant_set)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid); - void (*variant_set_named)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid); - void (*variant_set_keyed)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid); - void (*variant_set_indexed)(GDExtensionVariantPtr p_self, GDExtensionInt p_index, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid, GDExtensionBool *r_oob); - void (*variant_get)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid); - void (*variant_get_named)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid); - void (*variant_get_keyed)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid); - void (*variant_get_indexed)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob); - GDExtensionBool (*variant_iter_init)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid); - GDExtensionBool (*variant_iter_next)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid); - void (*variant_iter_get)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid); - GDExtensionInt (*variant_hash)(GDExtensionConstVariantPtr p_self); - GDExtensionInt (*variant_recursive_hash)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_recursion_count); - GDExtensionBool (*variant_hash_compare)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_other); - GDExtensionBool (*variant_booleanize)(GDExtensionConstVariantPtr p_self); - void (*variant_duplicate)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_ret, GDExtensionBool p_deep); - void (*variant_stringify)(GDExtensionConstVariantPtr p_self, GDExtensionStringPtr r_ret); - - GDExtensionVariantType (*variant_get_type)(GDExtensionConstVariantPtr p_self); - GDExtensionBool (*variant_has_method)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_method); - GDExtensionBool (*variant_has_member)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member); - GDExtensionBool (*variant_has_key)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid); - void (*variant_get_type_name)(GDExtensionVariantType p_type, GDExtensionStringPtr r_name); - GDExtensionBool (*variant_can_convert)(GDExtensionVariantType p_from, GDExtensionVariantType p_to); - GDExtensionBool (*variant_can_convert_strict)(GDExtensionVariantType p_from, GDExtensionVariantType p_to); - - /* ptrcalls */ - GDExtensionVariantFromTypeConstructorFunc (*get_variant_from_type_constructor)(GDExtensionVariantType p_type); - GDExtensionTypeFromVariantConstructorFunc (*get_variant_to_type_constructor)(GDExtensionVariantType p_type); - GDExtensionPtrOperatorEvaluator (*variant_get_ptr_operator_evaluator)(GDExtensionVariantOperator p_operator, GDExtensionVariantType p_type_a, GDExtensionVariantType p_type_b); - GDExtensionPtrBuiltInMethod (*variant_get_ptr_builtin_method)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, GDExtensionInt p_hash); - GDExtensionPtrConstructor (*variant_get_ptr_constructor)(GDExtensionVariantType p_type, int32_t p_constructor); - GDExtensionPtrDestructor (*variant_get_ptr_destructor)(GDExtensionVariantType p_type); - void (*variant_construct)(GDExtensionVariantType p_type, GDExtensionVariantPtr p_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error); - GDExtensionPtrSetter (*variant_get_ptr_setter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member); - GDExtensionPtrGetter (*variant_get_ptr_getter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member); - GDExtensionPtrIndexedSetter (*variant_get_ptr_indexed_setter)(GDExtensionVariantType p_type); - GDExtensionPtrIndexedGetter (*variant_get_ptr_indexed_getter)(GDExtensionVariantType p_type); - GDExtensionPtrKeyedSetter (*variant_get_ptr_keyed_setter)(GDExtensionVariantType p_type); - GDExtensionPtrKeyedGetter (*variant_get_ptr_keyed_getter)(GDExtensionVariantType p_type); - GDExtensionPtrKeyedChecker (*variant_get_ptr_keyed_checker)(GDExtensionVariantType p_type); - void (*variant_get_constant_value)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionVariantPtr r_ret); - GDExtensionPtrUtilityFunction (*variant_get_ptr_utility_function)(GDExtensionConstStringNamePtr p_function, GDExtensionInt p_hash); - - /* extra utilities */ - void (*string_new_with_latin1_chars)(GDExtensionStringPtr r_dest, const char *p_contents); - void (*string_new_with_utf8_chars)(GDExtensionStringPtr r_dest, const char *p_contents); - void (*string_new_with_utf16_chars)(GDExtensionStringPtr r_dest, const char16_t *p_contents); - void (*string_new_with_utf32_chars)(GDExtensionStringPtr r_dest, const char32_t *p_contents); - void (*string_new_with_wide_chars)(GDExtensionStringPtr r_dest, const wchar_t *p_contents); - void (*string_new_with_latin1_chars_and_len)(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size); - void (*string_new_with_utf8_chars_and_len)(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size); - void (*string_new_with_utf16_chars_and_len)(GDExtensionStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size); - void (*string_new_with_utf32_chars_and_len)(GDExtensionStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size); - void (*string_new_with_wide_chars_and_len)(GDExtensionStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size); - /* Information about the following functions: - * - The return value is the resulting encoded string length. - * - The length returned is in characters, not in bytes. It also does not include a trailing zero. - * - These functions also do not write trailing zero, If you need it, write it yourself at the position indicated by the length (and make sure to allocate it). - * - Passing NULL in r_text means only the length is computed (again, without including trailing zero). - * - p_max_write_length argument is in characters, not bytes. It will be ignored if r_text is NULL. - * - p_max_write_length argument does not affect the return value, it's only to cap write length. - */ - GDExtensionInt (*string_to_latin1_chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length); - GDExtensionInt (*string_to_utf8_chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length); - GDExtensionInt (*string_to_utf16_chars)(GDExtensionConstStringPtr p_self, char16_t *r_text, GDExtensionInt p_max_write_length); - GDExtensionInt (*string_to_utf32_chars)(GDExtensionConstStringPtr p_self, char32_t *r_text, GDExtensionInt p_max_write_length); - GDExtensionInt (*string_to_wide_chars)(GDExtensionConstStringPtr p_self, wchar_t *r_text, GDExtensionInt p_max_write_length); - char32_t *(*string_operator_index)(GDExtensionStringPtr p_self, GDExtensionInt p_index); - const char32_t *(*string_operator_index_const)(GDExtensionConstStringPtr p_self, GDExtensionInt p_index); - - void (*string_operator_plus_eq_string)(GDExtensionStringPtr p_self, GDExtensionConstStringPtr p_b); - void (*string_operator_plus_eq_char)(GDExtensionStringPtr p_self, char32_t p_b); - void (*string_operator_plus_eq_cstr)(GDExtensionStringPtr p_self, const char *p_b); - void (*string_operator_plus_eq_wcstr)(GDExtensionStringPtr p_self, const wchar_t *p_b); - void (*string_operator_plus_eq_c32str)(GDExtensionStringPtr p_self, const char32_t *p_b); - - /* XMLParser extra utilities */ - - GDExtensionInt (*xml_parser_open_buffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_buffer, size_t p_size); - - /* FileAccess extra utilities */ - - void (*file_access_store_buffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_src, uint64_t p_length); - uint64_t (*file_access_get_buffer)(GDExtensionConstObjectPtr p_instance, uint8_t *p_dst, uint64_t p_length); - - /* WorkerThreadPool extra utilities */ - - int64_t (*worker_thread_pool_add_native_group_task)(GDExtensionObjectPtr p_instance, void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description); - int64_t (*worker_thread_pool_add_native_task)(GDExtensionObjectPtr p_instance, void (*p_func)(void *), void *p_userdata, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description); - - /* Packed array functions */ - - uint8_t *(*packed_byte_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedByteArray - const uint8_t *(*packed_byte_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedByteArray - - GDExtensionTypePtr (*packed_color_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedColorArray, returns Color ptr - GDExtensionTypePtr (*packed_color_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedColorArray, returns Color ptr - - float *(*packed_float32_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat32Array - const float *(*packed_float32_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat32Array - double *(*packed_float64_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat64Array - const double *(*packed_float64_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat64Array - - int32_t *(*packed_int32_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array - const int32_t *(*packed_int32_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array - int64_t *(*packed_int64_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array - const int64_t *(*packed_int64_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array - - GDExtensionStringPtr (*packed_string_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedStringArray - GDExtensionStringPtr (*packed_string_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedStringArray - - GDExtensionTypePtr (*packed_vector2_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr - GDExtensionTypePtr (*packed_vector2_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr - GDExtensionTypePtr (*packed_vector3_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr - GDExtensionTypePtr (*packed_vector3_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr - - GDExtensionVariantPtr (*array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr - GDExtensionVariantPtr (*array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr - void (*array_ref)(GDExtensionTypePtr p_self, GDExtensionConstTypePtr p_from); // p_self should be an Array ptr - void (*array_set_typed)(GDExtensionTypePtr p_self, GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script); // p_self should be an Array ptr - - /* Dictionary functions */ - - GDExtensionVariantPtr (*dictionary_operator_index)(GDExtensionTypePtr p_self, GDExtensionConstVariantPtr p_key); // p_self should be an Dictionary ptr - GDExtensionVariantPtr (*dictionary_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key); // p_self should be an Dictionary ptr - - /* OBJECT */ - - void (*object_method_bind_call)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionVariantPtr r_ret, GDExtensionCallError *r_error); - void (*object_method_bind_ptrcall)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); - void (*object_destroy)(GDExtensionObjectPtr p_o); - GDExtensionObjectPtr (*global_get_singleton)(GDExtensionConstStringNamePtr p_name); - - void *(*object_get_instance_binding)(GDExtensionObjectPtr p_o, void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks); - void (*object_set_instance_binding)(GDExtensionObjectPtr p_o, void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks); - - void (*object_set_instance)(GDExtensionObjectPtr p_o, GDExtensionConstStringNamePtr p_classname, GDExtensionClassInstancePtr p_instance); /* p_classname should be a registered extension class and should extend the p_o object's class. */ - - GDExtensionObjectPtr (*object_cast_to)(GDExtensionConstObjectPtr p_object, void *p_class_tag); - GDExtensionObjectPtr (*object_get_instance_from_id)(GDObjectInstanceID p_instance_id); - GDObjectInstanceID (*object_get_instance_id)(GDExtensionConstObjectPtr p_object); - - /* REFERENCE */ - - GDExtensionObjectPtr (*ref_get_object)(GDExtensionConstRefPtr p_ref); - void (*ref_set_object)(GDExtensionRefPtr p_ref, GDExtensionObjectPtr p_object); - - /* SCRIPT INSTANCE */ - - GDExtensionScriptInstancePtr (*script_instance_create)(const GDExtensionScriptInstanceInfo *p_info, GDExtensionScriptInstanceDataPtr p_instance_data); - - /* CLASSDB */ - - GDExtensionObjectPtr (*classdb_construct_object)(GDExtensionConstStringNamePtr p_classname); /* The passed class must be a built-in godot class, or an already-registered extension class. In both case, object_set_instance should be called to fully initialize the object. */ - GDExtensionMethodBindPtr (*classdb_get_method_bind)(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash); - void *(*classdb_get_class_tag)(GDExtensionConstStringNamePtr p_classname); - - /* CLASSDB EXTENSION */ - - /* Provided parameters for `classdb_register_extension_*` can be safely freed once the function returns. */ - void (*classdb_register_extension_class)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs); - void (*classdb_register_extension_class_method)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info); - void (*classdb_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); - void (*classdb_register_extension_class_property)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter); - void (*classdb_register_extension_class_property_group)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_group_name, GDExtensionConstStringPtr p_prefix); - void (*classdb_register_extension_class_property_subgroup)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_subgroup_name, GDExtensionConstStringPtr p_prefix); - void (*classdb_register_extension_class_signal)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count); - void (*classdb_unregister_extension_class)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */ - - void (*get_library_path)(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path); - -} GDExtensionInterface; - /* INITIALIZATION */ typedef enum { @@ -629,12 +443,1859 @@ typedef struct { void (*deinitialize)(void *userdata, GDExtensionInitializationLevel p_level); } GDExtensionInitialization; -/* Define a C function prototype that implements the function below and expose it to dlopen() (or similar). - * This is the entry point of the GDExtension library and will be called on initialization. - * It can be used to set up different init levels, which are called during various stages of initialization/shutdown. - * The function name must be a unique one specified in the .gdextension config file. +typedef void (*GDExtensionInterfaceFunctionPtr)(); +typedef GDExtensionInterfaceFunctionPtr (*GDExtensionInterfaceGetProcAddress)(const char *p_function_name); + +/* + * Each GDExtension should define a C function that matches the signature of GDExtensionInitializationFunction, + * and export it so that it can be loaded via dlopen() or equivalent for the given platform. + * + * For example: + * + * GDExtensionBool my_extension_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); + * + * This function's name must be specified as the 'entry_symbol' in the .gdextension file. + * + * This makes it the entry point of the GDExtension and will be called on initialization. + * + * The GDExtension can then modify the r_initialization structure, setting the minimum initialization level, + * and providing pointers to functions that will be called at various stages of initialization/shutdown. + * + * The rest of the GDExtension's interface to Godot consists of function pointers that can be loaded + * by calling p_get_proc_address("...") with the name of the function. + * + * For example: + * + * GDExtensionInterfaceGetGodotVersion *get_godot_version = (GDExtensionInterfaceGetGodotVersion)p_get_proc_address("get_godot_version"); + * + * You can then call it like a normal function: + * + * GDExtensionGodotVersion godot_version; + * get_godot_version(&godot_version); + * printf("Godot v%d.%d.%d\n", godot_version.major, godot_version.minor, godot_version.patch); + * + * All of these interface functions are described below, together with the name that's used to load it, + * and the function pointer typedef that shows its signature. + */ +typedef GDExtensionBool (*GDExtensionInitializationFunction)(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); + +/* INTERFACE */ + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char *string; +} GDExtensionGodotVersion; + +/** + * @name get_godot_version + * @since 4.1 + * + * Gets the Godot version that the GDExtension was loaded into. + * + * @param r_godot_version A pointer to the structure to write the version information into. + */ +typedef void (*GDExtensionInterfaceGetGodotVersion)(GDExtensionGodotVersion *r_godot_version); + +/* INTERFACE: Memory */ + +/** + * @name mem_alloc + * @since 4.1 + * + * Allocates memory. + * + * @param p_bytes The amount of memory to allocate in bytes. + * + * @return A pointer to the allocated memory, or NULL if unsuccessful. + */ +typedef void *(*GDExtensionInterfaceMemAlloc)(size_t p_bytes); + +/** + * @name mem_realloc + * @since 4.1 + * + * Reallocates memory. + * + * @param p_ptr A pointer to the previously allocated memory. + * @param p_bytes The number of bytes to resize the memory block to. + * + * @return A pointer to the allocated memory, or NULL if unsuccessful. + */ +typedef void *(*GDExtensionInterfaceMemRealloc)(void *p_ptr, size_t p_bytes); + +/** + * @name mem_free + * @since 4.1 + * + * Frees memory. + * + * @param p_ptr A pointer to the previously allocated memory. + */ +typedef void (*GDExtensionInterfaceMemFree)(void *p_ptr); + +/* INTERFACE: Godot Core */ + +/** + * @name print_error + * @since 4.1 + * + * Logs an error to Godot's built-in debugger and to the OS terminal. + * + * @param p_description The code trigging the error. + * @param p_function The function name where the error occurred. + * @param p_file The file where the error occurred. + * @param p_line The line where the error occurred. + * @param p_editor_notify Whether or not to notify the editor. + */ +typedef void (*GDExtensionInterfacePrintError)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); + +/** + * @name print_error_with_message + * @since 4.1 + * + * Logs an error with a message to Godot's built-in debugger and to the OS terminal. + * + * @param p_description The code trigging the error. + * @param p_message The message to show along with the error. + * @param p_function The function name where the error occurred. + * @param p_file The file where the error occurred. + * @param p_line The line where the error occurred. + * @param p_editor_notify Whether or not to notify the editor. + */ +typedef void (*GDExtensionInterfacePrintErrorWithMessage)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); + +/** + * @name print_warning + * @since 4.1 + * + * Logs a warning to Godot's built-in debugger and to the OS terminal. + * + * @param p_description The code trigging the warning. + * @param p_function The function name where the warning occurred. + * @param p_file The file where the warning occurred. + * @param p_line The line where the warning occurred. + * @param p_editor_notify Whether or not to notify the editor. + */ +typedef void (*GDExtensionInterfacePrintWarning)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); + +/** + * @name print_warning_with_message + * @since 4.1 + * + * Logs a warning with a message to Godot's built-in debugger and to the OS terminal. + * + * @param p_description The code trigging the warning. + * @param p_message The message to show along with the warning. + * @param p_function The function name where the warning occurred. + * @param p_file The file where the warning occurred. + * @param p_line The line where the warning occurred. + * @param p_editor_notify Whether or not to notify the editor. + */ +typedef void (*GDExtensionInterfacePrintWarningWithMessage)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); + +/** + * @name print_script_error + * @since 4.1 + * + * Logs a script error to Godot's built-in debugger and to the OS terminal. + * + * @param p_description The code trigging the error. + * @param p_function The function name where the error occurred. + * @param p_file The file where the error occurred. + * @param p_line The line where the error occurred. + * @param p_editor_notify Whether or not to notify the editor. + */ +typedef void (*GDExtensionInterfacePrintScriptError)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); + +/** + * @name print_script_error_with_message + * @since 4.1 + * + * Logs a script error with a message to Godot's built-in debugger and to the OS terminal. + * + * @param p_description The code trigging the error. + * @param p_message The message to show along with the error. + * @param p_function The function name where the error occurred. + * @param p_file The file where the error occurred. + * @param p_line The line where the error occurred. + * @param p_editor_notify Whether or not to notify the editor. + */ +typedef void (*GDExtensionInterfacePrintScriptErrorWithMessage)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify); + +/** + * @name get_native_struct_size + * @since 4.1 + * + * Gets the size of a native struct (ex. ObjectID) in bytes. + * + * @param p_name A pointer to a StringName identifying the struct name. + * + * @return The size in bytes. + */ +typedef uint64_t (*GDExtensionInterfaceGetNativeStructSize)(GDExtensionConstStringNamePtr p_name); + +/* INTERFACE: Variant */ + +/** + * @name variant_new_copy + * @since 4.1 + * + * Copies one Variant into a another. + * + * @param r_dest A pointer to the destination Variant. + * @param p_src A pointer to the source Variant. + */ +typedef void (*GDExtensionInterfaceVariantNewCopy)(GDExtensionUninitializedVariantPtr r_dest, GDExtensionConstVariantPtr p_src); + +/** + * @name variant_new_nil + * @since 4.1 + * + * Creates a new Variant containing nil. + * + * @param r_dest A pointer to the destination Variant. + */ +typedef void (*GDExtensionInterfaceVariantNewNil)(GDExtensionUninitializedVariantPtr r_dest); + +/** + * @name variant_destroy + * @since 4.1 + * + * Destroys a Variant. + * + * @param p_self A pointer to the Variant to destroy. + */ +typedef void (*GDExtensionInterfaceVariantDestroy)(GDExtensionVariantPtr p_self); + +/** + * @name variant_call + * @since 4.1 + * + * Calls a method on a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_method A pointer to a StringName identifying the method. + * @param p_args A pointer to a C array of Variant. + * @param p_argument_count The number of arguments. + * @param r_return A pointer a Variant which will be assigned the return value. + * @param r_error A pointer the structure which will hold error information. + * + * @see Variant::callp() + */ +typedef void (*GDExtensionInterfaceVariantCall)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error); + +/** + * @name variant_call_static + * @since 4.1 + * + * Calls a static method on a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_method A pointer to a StringName identifying the method. + * @param p_args A pointer to a C array of Variant. + * @param p_argument_count The number of arguments. + * @param r_return A pointer a Variant which will be assigned the return value. + * @param r_error A pointer the structure which will be updated with error information. + * + * @see Variant::call_static() + */ +typedef void (*GDExtensionInterfaceVariantCallStatic)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error); + +/** + * @name variant_evaluate + * @since 4.1 + * + * Evaluate an operator on two Variants. + * + * @param p_op The operator to evaluate. + * @param p_a The first Variant. + * @param p_b The second Variant. + * @param r_return A pointer a Variant which will be assigned the return value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @see Variant::evaluate() + */ +typedef void (*GDExtensionInterfaceVariantEvaluate)(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionUninitializedVariantPtr r_return, GDExtensionBool *r_valid); + +/** + * @name variant_set + * @since 4.1 + * + * Sets a key on a Variant to a value. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a Variant representing the key. + * @param p_value A pointer to a Variant representing the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @see Variant::set() + */ +typedef void (*GDExtensionInterfaceVariantSet)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid); + +/** + * @name variant_set_named + * @since 4.1 + * + * Sets a named key on a Variant to a value. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a StringName representing the key. + * @param p_value A pointer to a Variant representing the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @see Variant::set_named() + */ +typedef void (*GDExtensionInterfaceVariantSetNamed)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid); + +/** + * @name variant_set_keyed + * @since 4.1 + * + * Sets a keyed property on a Variant to a value. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a Variant representing the key. + * @param p_value A pointer to a Variant representing the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @see Variant::set_keyed() + */ +typedef void (*GDExtensionInterfaceVariantSetKeyed)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid); + +/** + * @name variant_set_indexed + * @since 4.1 + * + * Sets an index on a Variant to a value. + * + * @param p_self A pointer to the Variant. + * @param p_index The index. + * @param p_value A pointer to a Variant representing the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * @param r_oob A pointer to a boolean which will be set to true if the index is out of bounds. + */ +typedef void (*GDExtensionInterfaceVariantSetIndexed)(GDExtensionVariantPtr p_self, GDExtensionInt p_index, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid, GDExtensionBool *r_oob); + +/** + * @name variant_get + * @since 4.1 + * + * Gets the value of a key from a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a Variant representing the key. + * @param r_ret A pointer to a Variant which will be assigned the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + */ +typedef void (*GDExtensionInterfaceVariantGet)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid); + +/** + * @name variant_get_named + * @since 4.1 + * + * Gets the value of a named key from a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a StringName representing the key. + * @param r_ret A pointer to a Variant which will be assigned the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + */ +typedef void (*GDExtensionInterfaceVariantGetNamed)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid); + +/** + * @name variant_get_keyed + * @since 4.1 + * + * Gets the value of a keyed property from a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a Variant representing the key. + * @param r_ret A pointer to a Variant which will be assigned the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + */ +typedef void (*GDExtensionInterfaceVariantGetKeyed)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid); + +/** + * @name variant_get_indexed + * @since 4.1 + * + * Gets the value of an index from a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_index The index. + * @param r_ret A pointer to a Variant which will be assigned the value. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * @param r_oob A pointer to a boolean which will be set to true if the index is out of bounds. + */ +typedef void (*GDExtensionInterfaceVariantGetIndexed)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob); + +/** + * @name variant_iter_init + * @since 4.1 + * + * Initializes an iterator over a Variant. + * + * @param p_self A pointer to the Variant. + * @param r_iter A pointer to a Variant which will be assigned the iterator. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @return true if the operation is valid; otherwise false. + * + * @see Variant::iter_init() + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantIterInit)(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_iter, GDExtensionBool *r_valid); + +/** + * @name variant_iter_next + * @since 4.1 + * + * Gets the next value for an iterator over a Variant. + * + * @param p_self A pointer to the Variant. + * @param r_iter A pointer to a Variant which will be assigned the iterator. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @return true if the operation is valid; otherwise false. + * + * @see Variant::iter_next() + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantIterNext)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid); + +/** + * @name variant_iter_get + * @since 4.1 + * + * Gets the next value for an iterator over a Variant. + * + * @param p_self A pointer to the Variant. + * @param r_iter A pointer to a Variant which will be assigned the iterator. + * @param r_ret A pointer to a Variant which will be assigned false if the operation is invalid. + * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid. + * + * @see Variant::iter_get() + */ +typedef void (*GDExtensionInterfaceVariantIterGet)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid); + +/** + * @name variant_hash + * @since 4.1 + * + * Gets the hash of a Variant. + * + * @param p_self A pointer to the Variant. + * + * @return The hash value. + * + * @see Variant::hash() + */ +typedef GDExtensionInt (*GDExtensionInterfaceVariantHash)(GDExtensionConstVariantPtr p_self); + +/** + * @name variant_recursive_hash + * @since 4.1 + * + * Gets the recursive hash of a Variant. + * + * @param p_self A pointer to the Variant. + * @param p_recursion_count The number of recursive loops so far. + * + * @return The hash value. + * + * @see Variant::recursive_hash() + */ +typedef GDExtensionInt (*GDExtensionInterfaceVariantRecursiveHash)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_recursion_count); + +/** + * @name variant_hash_compare + * @since 4.1 + * + * Compares two Variants by their hash. + * + * @param p_self A pointer to the Variant. + * @param p_other A pointer to the other Variant to compare it to. + * + * @return The hash value. + * + * @see Variant::hash_compare() + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantHashCompare)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_other); + +/** + * @name variant_booleanize + * @since 4.1 + * + * Converts a Variant to a boolean. + * + * @param p_self A pointer to the Variant. + * + * @return The boolean value of the Variant. + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantBooleanize)(GDExtensionConstVariantPtr p_self); + +/** + * @name variant_duplicate + * @since 4.1 + * + * Duplicates a Variant. + * + * @param p_self A pointer to the Variant. + * @param r_ret A pointer to a Variant to store the duplicated value. + * @param p_deep Whether or not to duplicate deeply (when supported by the Variant type). + */ +typedef void (*GDExtensionInterfaceVariantDuplicate)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_ret, GDExtensionBool p_deep); + +/** + * @name variant_stringify + * @since 4.1 + * + * Converts a Variant to a string. + * + * @param p_self A pointer to the Variant. + * @param r_ret A pointer to a String to store the resulting value. + */ +typedef void (*GDExtensionInterfaceVariantStringify)(GDExtensionConstVariantPtr p_self, GDExtensionStringPtr r_ret); + +/** + * @name variant_get_type + * @since 4.1 + * + * Gets the type of a Variant. + * + * @param p_self A pointer to the Variant. + * + * @return The variant type. + */ +typedef GDExtensionVariantType (*GDExtensionInterfaceVariantGetType)(GDExtensionConstVariantPtr p_self); + +/** + * @name variant_has_method + * @since 4.1 + * + * Checks if a Variant has the given method. + * + * @param p_self A pointer to the Variant. + * @param p_method A pointer to a StringName with the method name. + * + * @return + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMethod)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_method); + +/** + * @name variant_has_member + * @since 4.1 + * + * Checks if a type of Variant has the given member. + * + * @param p_type The Variant type. + * @param p_member A pointer to a StringName with the member name. + * + * @return + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member); + +/** + * @name variant_has_key + * @since 4.1 + * + * Checks if a Variant has a key. + * + * @param p_self A pointer to the Variant. + * @param p_key A pointer to a Variant representing the key. + * @param r_valid A pointer to a boolean which will be set to false if the key doesn't exist. + * + * @return true if the key exists; otherwise false. + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid); + +/** + * @name variant_get_type_name + * @since 4.1 + * + * Gets the name of a Variant type. + * + * @param p_type The Variant type. + * @param r_name A pointer to a String to store the Variant type name. + */ +typedef void (*GDExtensionInterfaceVariantGetTypeName)(GDExtensionVariantType p_type, GDExtensionUninitializedStringPtr r_name); + +/** + * @name variant_can_convert + * @since 4.1 + * + * Checks if Variants can be converted from one type to another. + * + * @param p_from The Variant type to convert from. + * @param p_to The Variant type to convert to. + * + * @return true if the conversion is possible; otherwise false. + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantCanConvert)(GDExtensionVariantType p_from, GDExtensionVariantType p_to); + +/** + * @name variant_can_convert_strict + * @since 4.1 + * + * Checks if Variant can be converted from one type to another using stricter rules. + * + * @param p_from The Variant type to convert from. + * @param p_to The Variant type to convert to. + * + * @return true if the conversion is possible; otherwise false. + */ +typedef GDExtensionBool (*GDExtensionInterfaceVariantCanConvertStrict)(GDExtensionVariantType p_from, GDExtensionVariantType p_to); + +/** + * @name get_variant_from_type_constructor + * @since 4.1 + * + * Gets a pointer to a function that can create a Variant of the given type from a raw value. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can create a Variant of the given type from a raw value. + */ +typedef GDExtensionVariantFromTypeConstructorFunc (*GDExtensionInterfaceGetVariantFromTypeConstructor)(GDExtensionVariantType p_type); + +/** + * @name get_variant_to_type_constructor + * @since 4.1 + * + * Gets a pointer to a function that can get the raw value from a Variant of the given type. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can get the raw value from a Variant of the given type. + */ +typedef GDExtensionTypeFromVariantConstructorFunc (*GDExtensionInterfaceGetVariantToTypeConstructor)(GDExtensionVariantType p_type); + +/** + * @name variant_get_ptr_operator_evaluator + * @since 4.1 + * + * Gets a pointer to a function that can evaluate the given Variant operator on the given Variant types. + * + * @param p_operator The variant operator. + * @param p_type_a The type of the first Variant. + * @param p_type_b The type of the second Variant. + * + * @return A pointer to a function that can evaluate the given Variant operator on the given Variant types. + */ +typedef GDExtensionPtrOperatorEvaluator (*GDExtensionInterfaceVariantGetPtrOperatorEvaluator)(GDExtensionVariantOperator p_operator, GDExtensionVariantType p_type_a, GDExtensionVariantType p_type_b); + +/** + * @name variant_get_ptr_builtin_method + * @since 4.1 + * + * Gets a pointer to a function that can call a builtin method on a type of Variant. + * + * @param p_type The Variant type. + * @param p_method A pointer to a StringName with the method name. + * @param p_hash A hash representing the method signature. + * + * @return A pointer to a function that can call a builtin method on a type of Variant. + */ +typedef GDExtensionPtrBuiltInMethod (*GDExtensionInterfaceVariantGetPtrBuiltinMethod)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, GDExtensionInt p_hash); + +/** + * @name variant_get_ptr_constructor + * @since 4.1 + * + * Gets a pointer to a function that can call one of the constructors for a type of Variant. + * + * @param p_type The Variant type. + * @param p_constructor The index of the constructor. + * + * @return A pointer to a function that can call one of the constructors for a type of Variant. + */ +typedef GDExtensionPtrConstructor (*GDExtensionInterfaceVariantGetPtrConstructor)(GDExtensionVariantType p_type, int32_t p_constructor); + +/** + * @name variant_get_ptr_destructor + * @since 4.1 + * + * Gets a pointer to a function than can call the destructor for a type of Variant. + * + * @param p_type The Variant type. + * + * @return A pointer to a function than can call the destructor for a type of Variant. + */ +typedef GDExtensionPtrDestructor (*GDExtensionInterfaceVariantGetPtrDestructor)(GDExtensionVariantType p_type); + +/** + * @name variant_construct + * @since 4.1 + * + * Constructs a Variant of the given type, using the first constructor that matches the given arguments. + * + * @param p_type The Variant type. + * @param p_base A pointer to a Variant to store the constructed value. + * @param p_args A pointer to a C array of Variant pointers representing the arguments for the constructor. + * @param p_argument_count The number of arguments to pass to the constructor. + * @param r_error A pointer the structure which will be updated with error information. + */ +typedef void (*GDExtensionInterfaceVariantConstruct)(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error); + +/** + * @name variant_get_ptr_setter + * @since 4.1 + * + * Gets a pointer to a function that can call a member's setter on the given Variant type. + * + * @param p_type The Variant type. + * @param p_member A pointer to a StringName with the member name. + * + * @return A pointer to a function that can call a member's setter on the given Variant type. + */ +typedef GDExtensionPtrSetter (*GDExtensionInterfaceVariantGetPtrSetter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member); + +/** + * @name variant_get_ptr_getter + * @since 4.1 + * + * Gets a pointer to a function that can call a member's getter on the given Variant type. + * + * @param p_type The Variant type. + * @param p_member A pointer to a StringName with the member name. + * + * @return A pointer to a function that can call a member's getter on the given Variant type. + */ +typedef GDExtensionPtrGetter (*GDExtensionInterfaceVariantGetPtrGetter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member); + +/** + * @name variant_get_ptr_indexed_setter + * @since 4.1 + * + * Gets a pointer to a function that can set an index on the given Variant type. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can set an index on the given Variant type. + */ +typedef GDExtensionPtrIndexedSetter (*GDExtensionInterfaceVariantGetPtrIndexedSetter)(GDExtensionVariantType p_type); + +/** + * @name variant_get_ptr_indexed_getter + * @since 4.1 + * + * Gets a pointer to a function that can get an index on the given Variant type. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can get an index on the given Variant type. + */ +typedef GDExtensionPtrIndexedGetter (*GDExtensionInterfaceVariantGetPtrIndexedGetter)(GDExtensionVariantType p_type); + +/** + * @name variant_get_ptr_keyed_setter + * @since 4.1 + * + * Gets a pointer to a function that can set a key on the given Variant type. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can set a key on the given Variant type. + */ +typedef GDExtensionPtrKeyedSetter (*GDExtensionInterfaceVariantGetPtrKeyedSetter)(GDExtensionVariantType p_type); + +/** + * @name variant_get_ptr_keyed_getter + * @since 4.1 + * + * Gets a pointer to a function that can get a key on the given Variant type. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can get a key on the given Variant type. + */ +typedef GDExtensionPtrKeyedGetter (*GDExtensionInterfaceVariantGetPtrKeyedGetter)(GDExtensionVariantType p_type); + +/** + * @name variant_get_ptr_keyed_checker + * @since 4.1 + * + * Gets a pointer to a function that can check a key on the given Variant type. + * + * @param p_type The Variant type. + * + * @return A pointer to a function that can check a key on the given Variant type. + */ +typedef GDExtensionPtrKeyedChecker (*GDExtensionInterfaceVariantGetPtrKeyedChecker)(GDExtensionVariantType p_type); + +/** + * @name variant_get_constant_value + * @since 4.1 + * + * Gets the value of a constant from the given Variant type. + * + * @param p_type The Variant type. + * @param p_constant A pointer to a StringName with the constant name. + * @param r_ret A pointer to a Variant to store the value. + */ +typedef void (*GDExtensionInterfaceVariantGetConstantValue)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionUninitializedVariantPtr r_ret); + +/** + * @name variant_get_ptr_utility_function + * @since 4.1 + * + * Gets a pointer to a function that can call a Variant utility function. + * + * @param p_function A pointer to a StringName with the function name. + * @param p_hash A hash representing the function signature. + * + * @return A pointer to a function that can call a Variant utility function. + */ +typedef GDExtensionPtrUtilityFunction (*GDExtensionInterfaceVariantGetPtrUtilityFunction)(GDExtensionConstStringNamePtr p_function, GDExtensionInt p_hash); + +/* INTERFACE: String Utilities */ + +/** + * @name string_new_with_latin1_chars + * @since 4.1 + * + * Creates a String from a Latin-1 encoded C string. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a Latin-1 encoded C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringNewWithLatin1Chars)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents); + +/** + * @name string_new_with_utf8_chars + * @since 4.1 + * + * Creates a String from a UTF-8 encoded C string. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-8 encoded C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringNewWithUtf8Chars)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents); + +/** + * @name string_new_with_utf16_chars + * @since 4.1 + * + * Creates a String from a UTF-16 encoded C string. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-16 encoded C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringNewWithUtf16Chars)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents); + +/** + * @name string_new_with_utf32_chars + * @since 4.1 + * + * Creates a String from a UTF-32 encoded C string. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-32 encoded C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringNewWithUtf32Chars)(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents); + +/** + * @name string_new_with_wide_chars + * @since 4.1 + * + * Creates a String from a wide C string. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a wide C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringNewWithWideChars)(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents); + +/** + * @name string_new_with_latin1_chars_and_len + * @since 4.1 + * + * Creates a String from a Latin-1 encoded C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a Latin-1 encoded C string. + * @param p_size The number of characters. + */ +typedef void (*GDExtensionInterfaceStringNewWithLatin1CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size); + +/** + * @name string_new_with_utf8_chars_and_len + * @since 4.1 + * + * Creates a String from a UTF-8 encoded C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-8 encoded C string. + * @param p_size The number of characters. + */ +typedef void (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size); + +/** + * @name string_new_with_utf16_chars_and_len + * @since 4.1 + * + * Creates a String from a UTF-16 encoded C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-16 encoded C string. + * @param p_size The number of characters. + */ +typedef void (*GDExtensionInterfaceStringNewWithUtf16CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size); + +/** + * @name string_new_with_utf32_chars_and_len + * @since 4.1 + * + * Creates a String from a UTF-32 encoded C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-32 encoded C string. + * @param p_size The number of characters. + */ +typedef void (*GDExtensionInterfaceStringNewWithUtf32CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size); + +/** + * @name string_new_with_wide_chars_and_len + * @since 4.1 + * + * Creates a String from a wide C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a wide C string. + * @param p_size The number of characters. + */ +typedef void (*GDExtensionInterfaceStringNewWithWideCharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size); + +/** + * @name string_to_latin1_chars + * @since 4.1 + * + * Converts a String to a Latin-1 encoded C string. + * + * It doesn't write a null terminator. + * + * @param p_self A pointer to the String. + * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed. + * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value. + * + * @return The resulting encoded string length in characters (not bytes), not including a null terminator. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringToLatin1Chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length); + +/** + * @name string_to_utf8_chars + * @since 4.1 + * + * Converts a String to a UTF-8 encoded C string. + * + * It doesn't write a null terminator. + * + * @param p_self A pointer to the String. + * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed. + * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value. + * + * @return The resulting encoded string length in characters (not bytes), not including a null terminator. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringToUtf8Chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length); + +/** + * @name string_to_utf16_chars + * @since 4.1 + * + * Converts a String to a UTF-16 encoded C string. + * + * It doesn't write a null terminator. + * + * @param p_self A pointer to the String. + * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed. + * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value. + * + * @return The resulting encoded string length in characters (not bytes), not including a null terminator. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringToUtf16Chars)(GDExtensionConstStringPtr p_self, char16_t *r_text, GDExtensionInt p_max_write_length); + +/** + * @name string_to_utf32_chars + * @since 4.1 + * + * Converts a String to a UTF-32 encoded C string. + * + * It doesn't write a null terminator. + * + * @param p_self A pointer to the String. + * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed. + * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value. + * + * @return The resulting encoded string length in characters (not bytes), not including a null terminator. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringToUtf32Chars)(GDExtensionConstStringPtr p_self, char32_t *r_text, GDExtensionInt p_max_write_length); + +/** + * @name string_to_wide_chars + * @since 4.1 + * + * Converts a String to a wide C string. + * + * It doesn't write a null terminator. + * + * @param p_self A pointer to the String. + * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed. + * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value. + * + * @return The resulting encoded string length in characters (not bytes), not including a null terminator. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringToWideChars)(GDExtensionConstStringPtr p_self, wchar_t *r_text, GDExtensionInt p_max_write_length); + +/** + * @name string_operator_index + * @since 4.1 + * + * Gets a pointer to the character at the given index from a String. + * + * @param p_self A pointer to the String. + * @param p_index The index. + * + * @return A pointer to the requested character. + */ +typedef char32_t *(*GDExtensionInterfaceStringOperatorIndex)(GDExtensionStringPtr p_self, GDExtensionInt p_index); + +/** + * @name string_operator_index_const + * @since 4.1 + * + * Gets a const pointer to the character at the given index from a String. + * + * @param p_self A pointer to the String. + * @param p_index The index. + * + * @return A const pointer to the requested character. + */ +typedef const char32_t *(*GDExtensionInterfaceStringOperatorIndexConst)(GDExtensionConstStringPtr p_self, GDExtensionInt p_index); + +/** + * @name string_operator_plus_eq_string + * @since 4.1 + * + * Appends another String to a String. + * + * @param p_self A pointer to the String. + * @param p_b A pointer to the other String to append. + */ +typedef void (*GDExtensionInterfaceStringOperatorPlusEqString)(GDExtensionStringPtr p_self, GDExtensionConstStringPtr p_b); + +/** + * @name string_operator_plus_eq_char + * @since 4.1 + * + * Appends a character to a String. + * + * @param p_self A pointer to the String. + * @param p_b A pointer to the character to append. + */ +typedef void (*GDExtensionInterfaceStringOperatorPlusEqChar)(GDExtensionStringPtr p_self, char32_t p_b); + +/** + * @name string_operator_plus_eq_cstr + * @since 4.1 + * + * Appends a Latin-1 encoded C string to a String. + * + * @param p_self A pointer to the String. + * @param p_b A pointer to a Latin-1 encoded C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringOperatorPlusEqCstr)(GDExtensionStringPtr p_self, const char *p_b); + +/** + * @name string_operator_plus_eq_wcstr + * @since 4.1 + * + * Appends a wide C string to a String. + * + * @param p_self A pointer to the String. + * @param p_b A pointer to a wide C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringOperatorPlusEqWcstr)(GDExtensionStringPtr p_self, const wchar_t *p_b); + +/** + * @name string_operator_plus_eq_c32str + * @since 4.1 + * + * Appends a UTF-32 encoded C string to a String. + * + * @param p_self A pointer to the String. + * @param p_b A pointer to a UTF-32 encoded C string (null terminated). + */ +typedef void (*GDExtensionInterfaceStringOperatorPlusEqC32str)(GDExtensionStringPtr p_self, const char32_t *p_b); + +/* INTERFACE: XMLParser Utilities */ + +/** + * @name xml_parser_open_buffer + * @since 4.1 + * + * Opens a raw XML buffer on an XMLParser instance. + * + * @param p_instance A pointer to an XMLParser object. + * @param p_buffer A pointer to the buffer. + * @param p_size The size of the buffer. + * + * @return A Godot error code (ex. OK, ERR_INVALID_DATA, etc). + * + * @see XMLParser::open_buffer() + */ +typedef GDExtensionInt (*GDExtensionInterfaceXmlParserOpenBuffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_buffer, size_t p_size); + +/* INTERFACE: FileAccess Utilities */ + +/** + * @name file_access_store_buffer + * @since 4.1 + * + * Stores the given buffer using an instance of FileAccess. + * + * @param p_instance A pointer to a FileAccess object. + * @param p_src A pointer to the buffer. + * @param p_length The size of the buffer. + * + * @see FileAccess::store_buffer() + */ +typedef void (*GDExtensionInterfaceFileAccessStoreBuffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_src, uint64_t p_length); + +/** + * @name file_access_get_buffer + * @since 4.1 + * + * Reads the next p_length bytes into the given buffer using an instance of FileAccess. + * + * @param p_instance A pointer to a FileAccess object. + * @param p_dst A pointer to the buffer to store the data. + * @param p_length The requested number of bytes to read. + * + * @return The actual number of bytes read (may be less than requested). + */ +typedef uint64_t (*GDExtensionInterfaceFileAccessGetBuffer)(GDExtensionConstObjectPtr p_instance, uint8_t *p_dst, uint64_t p_length); + +/* INTERFACE: WorkerThreadPool Utilities */ + +/** + * @name worker_thread_pool_add_native_group_task + * @since 4.1 + * + * Adds a group task to an instance of WorkerThreadPool. + * + * @param p_instance A pointer to a WorkerThreadPool object. + * @param p_func A pointer to a function to run in the thread pool. + * @param p_userdata A pointer to arbitrary data which will be passed to p_func. + * @param p_tasks The number of tasks needed in the group. + * @param p_high_priority Whether or not this is a high priority task. + * @param p_description A pointer to a String with the task description. + * + * @return The task group ID. + * + * @see WorkerThreadPool::add_group_task() + */ +typedef int64_t (*GDExtensionInterfaceWorkerThreadPoolAddNativeGroupTask)(GDExtensionObjectPtr p_instance, void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description); + +/** + * @name worker_thread_pool_add_native_task + * @since 4.1 + * + * Adds a task to an instance of WorkerThreadPool. + * + * @param p_instance A pointer to a WorkerThreadPool object. + * @param p_func A pointer to a function to run in the thread pool. + * @param p_userdata A pointer to arbitrary data which will be passed to p_func. + * @param p_high_priority Whether or not this is a high priority task. + * @param p_description A pointer to a String with the task description. + * + * @return The task ID. + */ +typedef int64_t (*GDExtensionInterfaceWorkerThreadPoolAddNativeTask)(GDExtensionObjectPtr p_instance, void (*p_func)(void *), void *p_userdata, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description); + +/* INTERFACE: Packed Array */ + +/** + * @name packed_byte_array_operator_index + * @since 4.1 + * + * Gets a pointer to a byte in a PackedByteArray. + * + * @param p_self A pointer to a PackedByteArray object. + * @param p_index The index of the byte to get. + * + * @return A pointer to the requested byte. + */ +typedef uint8_t *(*GDExtensionInterfacePackedByteArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_byte_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a byte in a PackedByteArray. + * + * @param p_self A const pointer to a PackedByteArray object. + * @param p_index The index of the byte to get. + * + * @return A const pointer to the requested byte. + */ +typedef const uint8_t *(*GDExtensionInterfacePackedByteArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_color_array_operator_index + * @since 4.1 + * + * Gets a pointer to a color in a PackedColorArray. + * + * @param p_self A pointer to a PackedColorArray object. + * @param p_index The index of the Color to get. + * + * @return A pointer to the requested Color. + */ +typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_color_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a color in a PackedColorArray. + * + * @param p_self A const pointer to a const PackedColorArray object. + * @param p_index The index of the Color to get. + * + * @return A const pointer to the requested Color. + */ +typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_float32_array_operator_index + * @since 4.1 + * + * Gets a pointer to a 32-bit float in a PackedFloat32Array. + * + * @param p_self A pointer to a PackedFloat32Array object. + * @param p_index The index of the float to get. + * + * @return A pointer to the requested 32-bit float. + */ +typedef float *(*GDExtensionInterfacePackedFloat32ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_float32_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a 32-bit float in a PackedFloat32Array. + * + * @param p_self A const pointer to a PackedFloat32Array object. + * @param p_index The index of the float to get. + * + * @return A const pointer to the requested 32-bit float. + */ +typedef const float *(*GDExtensionInterfacePackedFloat32ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_float64_array_operator_index + * @since 4.1 + * + * Gets a pointer to a 64-bit float in a PackedFloat64Array. + * + * @param p_self A pointer to a PackedFloat64Array object. + * @param p_index The index of the float to get. + * + * @return A pointer to the requested 64-bit float. + */ +typedef double *(*GDExtensionInterfacePackedFloat64ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_float64_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a 64-bit float in a PackedFloat64Array. + * + * @param p_self A const pointer to a PackedFloat64Array object. + * @param p_index The index of the float to get. + * + * @return A const pointer to the requested 64-bit float. + */ +typedef const double *(*GDExtensionInterfacePackedFloat64ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_int32_array_operator_index + * @since 4.1 + * + * Gets a pointer to a 32-bit integer in a PackedInt32Array. + * + * @param p_self A pointer to a PackedInt32Array object. + * @param p_index The index of the integer to get. + * + * @return A pointer to the requested 32-bit integer. + */ +typedef int32_t *(*GDExtensionInterfacePackedInt32ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_int32_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a 32-bit integer in a PackedInt32Array. + * + * @param p_self A const pointer to a PackedInt32Array object. + * @param p_index The index of the integer to get. + * + * @return A const pointer to the requested 32-bit integer. + */ +typedef const int32_t *(*GDExtensionInterfacePackedInt32ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_int64_array_operator_index + * @since 4.1 + * + * Gets a pointer to a 64-bit integer in a PackedInt64Array. + * + * @param p_self A pointer to a PackedInt64Array object. + * @param p_index The index of the integer to get. + * + * @return A pointer to the requested 64-bit integer. + */ +typedef int64_t *(*GDExtensionInterfacePackedInt64ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_int64_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a 64-bit integer in a PackedInt64Array. + * + * @param p_self A const pointer to a PackedInt64Array object. + * @param p_index The index of the integer to get. + * + * @return A const pointer to the requested 64-bit integer. + */ +typedef const int64_t *(*GDExtensionInterfacePackedInt64ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_string_array_operator_index + * @since 4.1 + * + * Gets a pointer to a string in a PackedStringArray. + * + * @param p_self A pointer to a PackedStringArray object. + * @param p_index The index of the String to get. + * + * @return A pointer to the requested String. + */ +typedef GDExtensionStringPtr (*GDExtensionInterfacePackedStringArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_string_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a string in a PackedStringArray. + * + * @param p_self A const pointer to a PackedStringArray object. + * @param p_index The index of the String to get. + * + * @return A const pointer to the requested String. + */ +typedef GDExtensionStringPtr (*GDExtensionInterfacePackedStringArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_vector2_array_operator_index + * @since 4.1 + * + * Gets a pointer to a Vector2 in a PackedVector2Array. + * + * @param p_self A pointer to a PackedVector2Array object. + * @param p_index The index of the Vector2 to get. + * + * @return A pointer to the requested Vector2. + */ +typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector2ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_vector2_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a Vector2 in a PackedVector2Array. + * + * @param p_self A const pointer to a PackedVector2Array object. + * @param p_index The index of the Vector2 to get. + * + * @return A const pointer to the requested Vector2. + */ +typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector2ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_vector3_array_operator_index + * @since 4.1 + * + * Gets a pointer to a Vector3 in a PackedVector3Array. + * + * @param p_self A pointer to a PackedVector3Array object. + * @param p_index The index of the Vector3 to get. + * + * @return A pointer to the requested Vector3. + */ +typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector3ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name packed_vector3_array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a Vector3 in a PackedVector3Array. + * + * @param p_self A const pointer to a PackedVector3Array object. + * @param p_index The index of the Vector3 to get. + * + * @return A const pointer to the requested Vector3. + */ +typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector3ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name array_operator_index + * @since 4.1 + * + * Gets a pointer to a Variant in an Array. + * + * @param p_self A pointer to an Array object. + * @param p_index The index of the Variant to get. + * + * @return A pointer to the requested Variant. + */ +typedef GDExtensionVariantPtr (*GDExtensionInterfaceArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index); + +/** + * @name array_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a Variant in an Array. + * + * @param p_self A const pointer to an Array object. + * @param p_index The index of the Variant to get. + * + * @return A const pointer to the requested Variant. + */ +typedef GDExtensionVariantPtr (*GDExtensionInterfaceArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); + +/** + * @name array_ref + * @since 4.1 + * + * Sets an Array to be a reference to another Array object. + * + * @param p_self A pointer to the Array object to update. + * @param p_from A pointer to the Array object to reference. + */ +typedef void (*GDExtensionInterfaceArrayRef)(GDExtensionTypePtr p_self, GDExtensionConstTypePtr p_from); + +/** + * @name array_set_typed + * @since 4.1 + * + * Makes an Array into a typed Array. + * + * @param p_self A pointer to the Array. + * @param p_type The type of Variant the Array will store. + * @param p_class_name A pointer to a StringName with the name of the object (if p_type is GDEXTENSION_VARIANT_TYPE_OBJECT). + * @param p_script A pointer to a Script object (if p_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script). + */ +typedef void (*GDExtensionInterfaceArraySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script); + +/* INTERFACE: Dictionary */ + +/** + * @name dictionary_operator_index + * @since 4.1 + * + * Gets a pointer to a Variant in a Dictionary with the given key. + * + * @param p_self A pointer to a Dictionary object. + * @param p_key A pointer to a Variant representing the key. + * + * @return A pointer to a Variant representing the value at the given key. + */ +typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionConstVariantPtr p_key); + +/** + * @name dictionary_operator_index_const + * @since 4.1 + * + * Gets a const pointer to a Variant in a Dictionary with the given key. + * + * @param p_self A const pointer to a Dictionary object. + * @param p_key A pointer to a Variant representing the key. + * + * @return A const pointer to a Variant representing the value at the given key. + */ +typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key); + +/* INTERFACE: Object */ + +/** + * @name object_method_bind_call + * @since 4.1 + * + * Calls a method on an Object. + * + * @param p_method_bind A pointer to the MethodBind representing the method on the Object's class. + * @param p_instance A pointer to the Object. + * @param p_args A pointer to a C array of Variants representing the arguments. + * @param p_arg_count The number of arguments. + * @param r_ret A pointer to Variant which will receive the return value. + * @param r_error A pointer to a GDExtensionCallError struct that will receive error information. + */ +typedef void (*GDExtensionInterfaceObjectMethodBindCall)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_ret, GDExtensionCallError *r_error); + +/** + * @name object_method_bind_ptrcall + * @since 4.1 + * + * Calls a method on an Object (using a "ptrcall"). + * + * @param p_method_bind A pointer to the MethodBind representing the method on the Object's class. + * @param p_instance A pointer to the Object. + * @param p_args A pointer to a C array representing the arguments. + * @param r_ret A pointer to the Object that will receive the return value. + */ +typedef void (*GDExtensionInterfaceObjectMethodBindPtrcall)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); + +/** + * @name object_destroy + * @since 4.1 + * + * Destroys an Object. + * + * @param p_o A pointer to the Object. + */ +typedef void (*GDExtensionInterfaceObjectDestroy)(GDExtensionObjectPtr p_o); + +/** + * @name global_get_singleton + * @since 4.1 + * + * Gets a global singleton by name. + * + * @param p_name A pointer to a StringName with the singleton name. + * + * @return A pointer to the singleton Object. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceGlobalGetSingleton)(GDExtensionConstStringNamePtr p_name); + +/** + * @name object_get_instance_binding + * @since 4.1 + * + * Gets a pointer representing an Object's instance binding. + * + * @param p_o A pointer to the Object. + * @param p_library A token the library received by the GDExtension's entry point function. + * @param p_callbacks A pointer to a GDExtensionInstanceBindingCallbacks struct. + * + * @return + */ +typedef void *(*GDExtensionInterfaceObjectGetInstanceBinding)(GDExtensionObjectPtr p_o, void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks); + +/** + * @name object_set_instance_binding + * @since 4.1 + * + * Sets an Object's instance binding. + * + * @param p_o A pointer to the Object. + * @param p_library A token the library received by the GDExtension's entry point function. + * @param p_binding A pointer to the instance binding. + * @param p_callbacks A pointer to a GDExtensionInstanceBindingCallbacks struct. + */ +typedef void (*GDExtensionInterfaceObjectSetInstanceBinding)(GDExtensionObjectPtr p_o, void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks); + +/** + * @name object_set_instance + * @since 4.1 + * + * Sets an extension class instance on a Object. + * + * @param p_o A pointer to the Object. + * @param p_classname A pointer to a StringName with the registered extension class's name. + * @param p_instance A pointer to the extension class instance. + */ +typedef void (*GDExtensionInterfaceObjectSetInstance)(GDExtensionObjectPtr p_o, GDExtensionConstStringNamePtr p_classname, GDExtensionClassInstancePtr p_instance); /* p_classname should be a registered extension class and should extend the p_o object's class. */ + +/** + * @name object_get_class_name + * @since 4.1 + * + * Gets the class name of an Object. + * + * @param p_object A pointer to the Object. + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param r_class_name A pointer to a String to receive the class name. + * + * @return true if successful in getting the class name; otherwise false. + */ +typedef GDExtensionBool (*GDExtensionInterfaceObjectGetClassName)(GDExtensionConstObjectPtr p_object, GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringNamePtr r_class_name); + +/** + * @name object_cast_to + * @since 4.1 + * + * Casts an Object to a different type. + * + * @param p_object A pointer to the Object. + * @param p_class_tag A pointer uniquely identifying a built-in class in the ClassDB. + * + * @return Returns a pointer to the Object, or NULL if it can't be cast to the requested type. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectCastTo)(GDExtensionConstObjectPtr p_object, void *p_class_tag); + +/** + * @name object_get_instance_from_id + * @since 4.1 + * + * Gets an Object by its instance ID. + * + * @param p_instance_id The instance ID. + * + * @return A pointer to the Object. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectGetInstanceFromId)(GDObjectInstanceID p_instance_id); + +/** + * @name object_get_instance_id + * @since 4.1 + * + * Gets the instance ID from an Object. + * + * @param p_object A pointer to the Object. + * + * @return The instance ID. + */ +typedef GDObjectInstanceID (*GDExtensionInterfaceObjectGetInstanceId)(GDExtensionConstObjectPtr p_object); + +/* INTERFACE: Reference */ + +/** + * @name ref_get_object + * @since 4.1 + * + * Gets the Object from a reference. + * + * @param p_ref A pointer to the reference. + * + * @return A pointer to the Object from the reference or NULL. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceRefGetObject)(GDExtensionConstRefPtr p_ref); + +/** + * @name ref_set_object + * @since 4.1 + * + * Sets the Object referred to by a reference. + * + * @param p_ref A pointer to the reference. + * @param p_object A pointer to the Object to refer to. + */ +typedef void (*GDExtensionInterfaceRefSetObject)(GDExtensionRefPtr p_ref, GDExtensionObjectPtr p_object); + +/* INTERFACE: Script Instance */ + +/** + * @name script_instance_create + * @since 4.1 + * + * Creates a script instance that contains the given info and instance data. + * + * @param p_info A pointer to a GDExtensionScriptInstanceInfo struct. + * @param p_instance_data A pointer to a data representing the script instance in the GDExtension. This will be passed to all the function pointers on p_info. + * + * @return A pointer to a ScriptInstanceExtension object. + */ +typedef GDExtensionScriptInstancePtr (*GDExtensionInterfaceScriptInstanceCreate)(const GDExtensionScriptInstanceInfo *p_info, GDExtensionScriptInstanceDataPtr p_instance_data); + +/* INTERFACE: ClassDB */ + +/** + * @name classdb_construct_object + * @since 4.1 + * + * Constructs an Object of the requested class. + * + * The passed class must be a built-in godot class, or an already-registered extension class. In both cases, object_set_instance() should be called to fully initialize the object. + * + * @param p_classname A pointer to a StringName with the class name. + * + * @return A pointer to the newly created Object. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject)(GDExtensionConstStringNamePtr p_classname); + +/** + * @name classdb_get_method_bind + * @since 4.1 + * + * Gets a pointer to the MethodBind in ClassDB for the given class, method and hash. + * + * @param p_classname A pointer to a StringName with the class name. + * @param p_methodname A pointer to a StringName with the method name. + * @param p_hash A hash representing the function signature. + * + * @return A pointer to the MethodBind from ClassDB. + */ +typedef GDExtensionMethodBindPtr (*GDExtensionInterfaceClassdbGetMethodBind)(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash); + +/** + * @name classdb_get_class_tag + * @since 4.1 + * + * Gets a pointer uniquely identifying the given built-in class in the ClassDB. + * + * @param p_classname A pointer to a StringName with the class name. + * + * @return A pointer uniquely identifying the built-in class in the ClassDB. + */ +typedef void *(*GDExtensionInterfaceClassdbGetClassTag)(GDExtensionConstStringNamePtr p_classname); + +/* INTERFACE: ClassDB Extension */ + +/** + * @name classdb_register_extension_class + * @since 4.1 + * + * Registers 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_parent_class_name A pointer to a StringName with the parent class name. + * @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo struct. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs); + +/** + * @name classdb_register_extension_class_method + * @since 4.1 + * + * Registers a method 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_method_info A pointer to a GDExtensionClassMethodInfo struct. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info); + +/** + * @name classdb_register_extension_class_integer_constant + * @since 4.1 + * + * Registers an integer constant on an extension class in the ClassDB. + * + * @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_enum_name A pointer to a StringName with the enum name. + * @param p_constant_name A pointer to a StringName with the constant name. + * @param p_constant_value The constant value. + * @param p_is_bitfield Whether or not this is a bit field. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); + +/** + * @name classdb_register_extension_class_property + * @since 4.1 + * + * Registers a 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. + */ +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_group + * @since 4.1 + * + * Registers a property group on an extension class in the ClassDB. + * + * @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_group_name A pointer to a String with the group name. + * @param p_prefix A pointer to a String with the prefix used by properties in this group. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassPropertyGroup)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_group_name, GDExtensionConstStringPtr p_prefix); + +/** + * @name classdb_register_extension_class_property_subgroup + * @since 4.1 + * + * Registers a property subgroup on an extension class in the ClassDB. + * + * @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_subgroup_name A pointer to a String with the subgroup name. + * @param p_prefix A pointer to a String with the prefix used by properties in this subgroup. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassPropertySubgroup)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_subgroup_name, GDExtensionConstStringPtr p_prefix); + +/** + * @name classdb_register_extension_class_signal + * @since 4.1 + * + * Registers a signal on an extension class in the ClassDB. + * + * Provided structs 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_signal_name A pointer to a StringName with the signal name. + * @param p_argument_info A pointer to a GDExtensionPropertyInfo struct. + * @param p_argument_count The number of arguments the signal receives. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassSignal)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count); + +/** + * @name classdb_unregister_extension_class + * @since 4.1 + * + * Unregisters an extension class in the ClassDB. + * + * @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. + */ +typedef void (*GDExtensionInterfaceClassdbUnregisterExtensionClass)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */ + +/** + * @name get_library_path + * @since 4.1 + * + * Gets the path to the current GDExtension library. + * + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param r_path A pointer to a String which will receive the path. + */ +typedef void (*GDExtensionInterfaceGetLibraryPath)(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path); + +/** + * @name editor_add_plugin + * @since 4.1 + * + * Adds an editor plugin. + * + * It's safe to call during initialization. + * + * @param p_class_name A pointer to a StringName with the name of a class (descending from EditorPlugin) which is already registered with ClassDB. + */ +typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePtr p_class_name); + +/** + * @name editor_remove_plugin + * @since 4.1 + * + * Removes an editor plugin. + * + * @param p_class_name A pointer to a StringName with the name of a class that was previously added as an editor plugin. */ -typedef GDExtensionBool (*GDExtensionInitializationFunction)(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); +typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name); #ifdef __cplusplus } diff --git a/core/extension/make_interface_dumper.py b/core/extension/make_interface_dumper.py index a604112d137e9..a85d62eff3b50 100644 --- a/core/extension/make_interface_dumper.py +++ b/core/extension/make_interface_dumper.py @@ -1,9 +1,19 @@ +import zlib + + def run(target, source, env): src = source[0] dst = target[0] - f = open(src, "r", encoding="utf-8") + f = open(src, "rb") g = open(dst, "w", encoding="utf-8") + buf = f.read() + decomp_size = len(buf) + + # Use maximum zlib compression level to further reduce file size + # (at the cost of initial build times). + buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) + g.write( """/* THIS FILE IS GENERATED DO NOT EDIT */ #ifndef GDEXTENSION_INTERFACE_DUMP_H @@ -11,25 +21,32 @@ def run(target, source, env): #ifdef TOOLS_ENABLED +#include "core/io/compression.h" #include "core/io/file_access.h" #include "core/string/ustring.h" -class GDExtensionInterfaceDump { - private: - static constexpr char const *gdextension_interface_dump =""" +""" ) - for line in f: - g.write('"' + line.rstrip().replace('"', '\\"') + '\\n"\n') - g.write(";\n") + + g.write("static const int _gdextension_interface_data_compressed_size = " + str(len(buf)) + ";\n") + g.write("static const int _gdextension_interface_data_uncompressed_size = " + str(decomp_size) + ";\n") + g.write("static const unsigned char _gdextension_interface_data_compressed[] = {\n") + for i in range(len(buf)): + g.write("\t" + str(buf[i]) + ",\n") + g.write("};\n") g.write( """ +class GDExtensionInterfaceDump { public: static void generate_gdextension_interface_file(const String &p_path) { Ref fa = FileAccess::open(p_path, FileAccess::WRITE); ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path)); - CharString cs(gdextension_interface_dump); - fa->store_buffer((const uint8_t *)cs.ptr(), cs.length()); + Vector data; + data.resize(_gdextension_interface_data_uncompressed_size); + int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE); + ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt."); + fa->store_buffer(data.ptr(), data.size()); }; }; diff --git a/core/input/input.cpp b/core/input/input.cpp index e74523e05989a..cf8d71b9a7440 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -35,6 +35,10 @@ #include "core/input/input_map.h" #include "core/os/os.h" +#ifdef DEV_ENABLED +#include "core/os/thread.h" +#endif + static const char *_joy_buttons[(size_t)JoyButton::SDL_MAX] = { "a", "b", @@ -293,10 +297,13 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con return false; } + // Backward compatibility for legacy behavior, only return true if currently pressed. + bool pressed_requirement = legacy_just_pressed_behavior ? E->value.pressed : true; + if (Engine::get_singleton()->is_in_physics_frame()) { - return E->value.pressed && E->value.physics_frame == Engine::get_singleton()->get_physics_frames(); + return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames(); } else { - return E->value.pressed && E->value.process_frame == Engine::get_singleton()->get_process_frames(); + return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames(); } } @@ -311,10 +318,13 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co return false; } + // Backward compatibility for legacy behavior, only return true if currently released. + bool released_requirement = legacy_just_pressed_behavior ? !E->value.pressed : true; + if (Engine::get_singleton()->is_in_physics_frame()) { - return !E->value.pressed && E->value.physics_frame == Engine::get_singleton()->get_physics_frames(); + return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames(); } else { - return !E->value.pressed && E->value.process_frame == Engine::get_singleton()->get_process_frames(); + return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames(); } } @@ -486,6 +496,10 @@ Vector3 Input::get_gyroscope() const { } void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_emulated) { + // This function does the final delivery of the input event to user land. + // Regardless where the event came from originally, this has to happen on the main thread. + DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id()); + // Notes on mouse-touch emulation: // - Emulated mouse events are parsed, that is, re-routed to this method, so they make the same effects // as true mouse events. The only difference is the situation is flagged as emulated so they are not @@ -534,10 +548,13 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em Ref touch_event; touch_event.instantiate(); touch_event->set_pressed(mb->is_pressed()); + touch_event->set_canceled(mb->is_canceled()); touch_event->set_position(mb->get_position()); touch_event->set_double_tap(mb->is_double_click()); touch_event->set_device(InputEvent::DEVICE_ID_EMULATION); + _THREAD_SAFE_UNLOCK_ event_dispatch_function(touch_event); + _THREAD_SAFE_LOCK_ } } @@ -563,7 +580,9 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em drag_event->set_velocity(get_last_mouse_velocity()); drag_event->set_device(InputEvent::DEVICE_ID_EMULATION); + _THREAD_SAFE_UNLOCK_ event_dispatch_function(drag_event); + _THREAD_SAFE_LOCK_ } } @@ -601,6 +620,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em button_event->set_position(st->get_position()); button_event->set_global_position(st->get_position()); button_event->set_pressed(st->is_pressed()); + button_event->set_canceled(st->is_canceled()); button_event->set_button_index(MouseButton::LEFT); button_event->set_double_click(st->is_double_tap()); @@ -664,30 +684,39 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em if (ge.is_valid()) { if (event_dispatch_function) { + _THREAD_SAFE_UNLOCK_ event_dispatch_function(ge); + _THREAD_SAFE_LOCK_ } } for (const KeyValue &E : InputMap::get_singleton()->get_action_map()) { if (InputMap::get_singleton()->event_is_action(p_event, E.key)) { + Action &action = action_state[E.key]; // If not echo and action pressed state has changed if (!p_event->is_echo() && is_action_pressed(E.key, false) != p_event->is_action_pressed(E.key)) { - Action action; - action.physics_frame = Engine::get_singleton()->get_physics_frames(); - action.process_frame = Engine::get_singleton()->get_process_frames(); - action.pressed = p_event->is_action_pressed(E.key); + if (p_event->is_action_pressed(E.key)) { + action.pressed = true; + action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action.pressed_process_frame = Engine::get_singleton()->get_process_frames(); + } else { + action.pressed = false; + action.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action.released_process_frame = Engine::get_singleton()->get_process_frames(); + } action.strength = 0.0f; action.raw_strength = 0.0f; action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true); - action_state[E.key] = action; } - action_state[E.key].strength = p_event->get_action_strength(E.key); - action_state[E.key].raw_strength = p_event->get_action_raw_strength(E.key); + action.strength = p_event->get_action_strength(E.key); + action.raw_strength = p_event->get_action_raw_strength(E.key); } } if (event_dispatch_function) { + _THREAD_SAFE_UNLOCK_ event_dispatch_function(p_event); + _THREAD_SAFE_LOCK_ } } @@ -795,29 +824,27 @@ Point2i Input::warp_mouse_motion(const Ref &p_motion, con } void Input::action_press(const StringName &p_action, float p_strength) { - Action action; + // Create or retrieve existing action. + Action &action = action_state[p_action]; - action.physics_frame = Engine::get_singleton()->get_physics_frames(); - action.process_frame = Engine::get_singleton()->get_process_frames(); + action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action.pressed_process_frame = Engine::get_singleton()->get_process_frames(); action.pressed = true; action.strength = p_strength; action.raw_strength = p_strength; action.exact = true; - - action_state[p_action] = action; } void Input::action_release(const StringName &p_action) { - Action action; + // Create or retrieve existing action. + Action &action = action_state[p_action]; - action.physics_frame = Engine::get_singleton()->get_physics_frames(); - action.process_frame = Engine::get_singleton()->get_process_frames(); + action.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action.released_process_frame = Engine::get_singleton()->get_process_frames(); action.pressed = false; - action.strength = 0.f; - action.raw_strength = 0.f; + action.strength = 0.0f; + action.raw_strength = 0.0f; action.exact = true; - - action_state[p_action] = action; } void Input::set_emulate_touch_from_mouse(bool p_emulate) { @@ -831,6 +858,7 @@ bool Input::is_emulating_touch_from_mouse() const { // Calling this whenever the game window is focused helps unsticking the "touch mouse" // if the OS or its abstraction class hasn't properly reported that touch pointers raised void Input::ensure_touch_mouse_raised() { + _THREAD_SAFE_METHOD_ if (mouse_from_touch_index != -1) { mouse_from_touch_index = -1; @@ -937,8 +965,15 @@ void Input::flush_buffered_events() { _THREAD_SAFE_METHOD_ while (buffered_events.front()) { - _parse_input_event_impl(buffered_events.front()->get(), false); + // The final delivery of the input event involves releasing the lock. + // While the lock is released, another thread may lock it and add new events to the back. + // Therefore, we get each event and pop it while we still have the lock, + // to ensure the list is in a consistent state. + List>::Element *E = buffered_events.front(); + Ref e = E->get(); buffered_events.pop_front(); + + _parse_input_event_impl(e, false); } } @@ -1330,8 +1365,9 @@ void Input::parse_mapping(String p_mapping) { String output = entry[idx].get_slice(":", 0).replace(" ", ""); String input = entry[idx].get_slice(":", 1).replace(" ", ""); - ERR_CONTINUE_MSG(output.length() < 1 || input.length() < 2, - vformat("Invalid device mapping entry \"%s\" in mapping:\n%s", entry[idx], p_mapping)); + if (output.length() < 1 || input.length() < 2) { + continue; + } if (output == "platform" || output == "hint") { continue; @@ -1505,6 +1541,12 @@ Input::Input() { parse_mapping(entries[i]); } } + + 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. + legacy_just_pressed_behavior = false; + } } Input::~Input() { diff --git a/core/input/input.h b/core/input/input.h index c254650ef887b..9cc596ee903d0 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -96,14 +96,17 @@ class Input : public Object { Vector3 gyroscope; Vector2 mouse_pos; int64_t mouse_window = 0; + bool legacy_just_pressed_behavior = false; struct Action { - uint64_t physics_frame; - uint64_t process_frame; - bool pressed; - bool exact; - float strength; - float raw_strength; + uint64_t pressed_physics_frame = UINT64_MAX; + uint64_t pressed_process_frame = UINT64_MAX; + uint64_t released_physics_frame = UINT64_MAX; + uint64_t released_process_frame = UINT64_MAX; + bool pressed = false; + bool exact = true; + float strength = 0.0f; + float raw_strength = 0.0f; }; HashMap action_state; diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 46f07fe041127..e547b04d0b169 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -52,15 +52,15 @@ bool InputEvent::is_action(const StringName &p_action, bool p_exact_match) const } bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match) const { - bool pressed; - bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, &pressed, nullptr, nullptr); - return valid && pressed && (p_allow_echo || !is_echo()); + bool pressed_state; + bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr); + return valid && pressed_state && (p_allow_echo || !is_echo()); } bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match) const { - bool pressed; - bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, &pressed, nullptr, nullptr); - return valid && !pressed; + bool pressed_state; + bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr); + return valid && !pressed_state; } float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match) const { @@ -75,8 +75,16 @@ float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exa return valid ? raw_strength : 0.0f; } +bool InputEvent::is_canceled() const { + return canceled; +} + bool InputEvent::is_pressed() const { - return false; + return pressed && !canceled; +} + +bool InputEvent::is_released() const { + return !pressed && !canceled; } bool InputEvent::is_echo() const { @@ -108,7 +116,9 @@ void InputEvent::_bind_methods() { ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match"), &InputEvent::is_action_released, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &InputEvent::get_action_strength, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_canceled"), &InputEvent::is_canceled); ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed); + ClassDB::bind_method(D_METHOD("is_released"), &InputEvent::is_released); ClassDB::bind_method(D_METHOD("is_echo"), &InputEvent::is_echo); ClassDB::bind_method(D_METHOD("as_text"), &InputEvent::as_text); @@ -191,7 +201,7 @@ bool InputEventWithModifiers::is_alt_pressed() const { } void InputEventWithModifiers::set_ctrl_pressed(bool p_enabled) { - ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command/Control autoremaping is enabled, cannot set Control directly!"); + ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command or Control autoremapping is enabled, cannot set Control directly!"); ctrl_pressed = p_enabled; emit_changed(); } @@ -201,7 +211,7 @@ bool InputEventWithModifiers::is_ctrl_pressed() const { } void InputEventWithModifiers::set_meta_pressed(bool p_enabled) { - ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command/Control autoremaping is enabled, cannot set Meta directly!"); + ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command or Control autoremapping is enabled, cannot set Meta directly!"); meta_pressed = p_enabled; emit_changed(); } @@ -318,10 +328,6 @@ void InputEventKey::set_pressed(bool p_pressed) { emit_changed(); } -bool InputEventKey::is_pressed() const { - return pressed; -} - void InputEventKey::set_keycode(Key p_keycode) { keycode = p_keycode; emit_changed(); @@ -484,7 +490,10 @@ Ref InputEventKey::create_reference(Key p_keycode, bool p_physica ie->set_keycode(p_keycode & KeyModifierMask::CODE_MASK); } - ie->set_unicode(char32_t(p_keycode & KeyModifierMask::CODE_MASK)); + char32_t ch = char32_t(p_keycode & KeyModifierMask::CODE_MASK); + if (ch < 0xd800 || (ch > 0xdfff && ch <= 0x10ffff)) { + ie->set_unicode(ch); + } if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) { ie->set_shift_pressed(true); @@ -668,8 +677,8 @@ void InputEventMouseButton::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventMouseButton::is_pressed() const { - return pressed; +void InputEventMouseButton::set_canceled(bool p_canceled) { + canceled = p_canceled; } void InputEventMouseButton::set_double_click(bool p_double_click) { @@ -696,6 +705,7 @@ Ref InputEventMouseButton::xformed_by(const Transform2D &p_xform, co mb->set_button_mask(get_button_mask()); mb->set_pressed(pressed); + mb->set_canceled(canceled); mb->set_double_click(double_click); mb->set_factor(factor); mb->set_button_index(button_index); @@ -791,6 +801,7 @@ String InputEventMouseButton::as_text() const { String InputEventMouseButton::to_string() { String p = is_pressed() ? "true" : "false"; + String canceled_state = is_canceled() ? "true" : "false"; String d = double_click ? "true" : "false"; MouseButton idx = get_button_index(); @@ -817,7 +828,7 @@ String InputEventMouseButton::to_string() { // Work around the fact vformat can only take 5 substitutions but 6 need to be passed. String index_and_mods = vformat("button_index=%s, mods=%s", button_index, mods); - return vformat("InputEventMouseButton: %s, pressed=%s, position=(%s), button_mask=%d, double_click=%s", index_and_mods, p, String(get_position()), get_button_mask(), d); + return vformat("InputEventMouseButton: %s, pressed=%s, canceled=%s, position=(%s), button_mask=%d, double_click=%s", index_and_mods, p, canceled_state, String(get_position()), get_button_mask(), d); } void InputEventMouseButton::_bind_methods() { @@ -828,13 +839,14 @@ void InputEventMouseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_button_index"), &InputEventMouseButton::get_button_index); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventMouseButton::set_pressed); - // ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventMouseButton::is_pressed); + ClassDB::bind_method(D_METHOD("set_canceled", "canceled"), &InputEventMouseButton::set_canceled); ClassDB::bind_method(D_METHOD("set_double_click", "double_click"), &InputEventMouseButton::set_double_click); ClassDB::bind_method(D_METHOD("is_double_click"), &InputEventMouseButton::is_double_click); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "factor"), "set_factor", "get_factor"); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "canceled"), "set_canceled", "is_canceled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "double_click"), "set_double_click", "is_double_click"); } @@ -942,6 +954,10 @@ bool InputEventMouseMotion::accumulate(const Ref &p_event) { return false; } + if (is_canceled() != motion->is_canceled()) { + return false; + } + if (is_pressed() != motion->is_pressed()) { return false; } @@ -1012,6 +1028,7 @@ JoyAxis InputEventJoypadMotion::get_axis() const { void InputEventJoypadMotion::set_axis_value(float p_value) { axis_value = p_value; + pressed = Math::abs(axis_value) >= 0.5f; emit_changed(); } @@ -1019,10 +1036,6 @@ float InputEventJoypadMotion::get_axis_value() const { return axis_value; } -bool InputEventJoypadMotion::is_pressed() const { - return Math::abs(axis_value) >= 0.5f; -} - bool InputEventJoypadMotion::action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const { Ref jm = p_event; if (jm.is_null()) { @@ -1037,12 +1050,12 @@ bool InputEventJoypadMotion::action_match(const Ref &p_event, bool p if (match) { float jm_abs_axis_value = Math::abs(jm->get_axis_value()); bool same_direction = (((axis_value < 0) == (jm->axis_value < 0)) || jm->axis_value == 0); - bool pressed = same_direction && jm_abs_axis_value >= p_deadzone; + bool pressed_state = same_direction && jm_abs_axis_value >= p_deadzone; if (r_pressed != nullptr) { - *r_pressed = pressed; + *r_pressed = pressed_state; } if (r_strength != nullptr) { - if (pressed) { + if (pressed_state) { if (p_deadzone == 1.0f) { *r_strength = 1.0f; } else { @@ -1096,6 +1109,15 @@ String InputEventJoypadMotion::to_string() { return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f", axis, axis_value); } +Ref InputEventJoypadMotion::create_reference(JoyAxis p_axis, float p_value) { + Ref ie; + ie.instantiate(); + ie->set_axis(p_axis); + ie->set_axis_value(p_value); + + return ie; +} + void InputEventJoypadMotion::_bind_methods() { ClassDB::bind_method(D_METHOD("set_axis", "axis"), &InputEventJoypadMotion::set_axis); ClassDB::bind_method(D_METHOD("get_axis"), &InputEventJoypadMotion::get_axis); @@ -1122,10 +1144,6 @@ void InputEventJoypadButton::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventJoypadButton::is_pressed() const { - return pressed; -} - void InputEventJoypadButton::set_pressure(float p_pressure) { pressure = p_pressure; } @@ -1206,7 +1224,7 @@ String InputEventJoypadButton::as_text() const { } String InputEventJoypadButton::to_string() { - String p = pressed ? "true" : "false"; + String p = is_pressed() ? "true" : "false"; return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f", button_index, p, pressure); } @@ -1226,7 +1244,6 @@ void InputEventJoypadButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pressure"), &InputEventJoypadButton::get_pressure); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventJoypadButton::set_pressed); - // ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventJoypadButton::is_pressed); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pressure"), "set_pressure", "get_pressure"); @@ -1255,8 +1272,8 @@ void InputEventScreenTouch::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventScreenTouch::is_pressed() const { - return pressed; +void InputEventScreenTouch::set_canceled(bool p_canceled) { + canceled = p_canceled; } void InputEventScreenTouch::set_double_tap(bool p_double_tap) { @@ -1274,21 +1291,23 @@ Ref InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co st->set_index(index); st->set_position(p_xform.xform(pos + p_local_ofs)); st->set_pressed(pressed); + st->set_canceled(canceled); st->set_double_tap(double_tap); return st; } String InputEventScreenTouch::as_text() const { - String status = pressed ? RTR("touched") : RTR("released"); + String status = canceled ? RTR("canceled") : (pressed ? RTR("touched") : RTR("released")); return vformat(RTR("Screen %s at (%s) with %s touch points"), status, String(get_position()), itos(index)); } String InputEventScreenTouch::to_string() { String p = pressed ? "true" : "false"; + String canceled_state = canceled ? "true" : "false"; String double_tap_string = double_tap ? "true" : "false"; - return vformat("InputEventScreenTouch: index=%d, pressed=%s, position=(%s), double_tap=%s", index, p, String(get_position()), double_tap_string); + return vformat("InputEventScreenTouch: index=%d, pressed=%s, canceled=%s, position=(%s), double_tap=%s", index, p, canceled_state, String(get_position()), double_tap_string); } void InputEventScreenTouch::_bind_methods() { @@ -1299,13 +1318,14 @@ void InputEventScreenTouch::_bind_methods() { ClassDB::bind_method(D_METHOD("get_position"), &InputEventScreenTouch::get_position); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventScreenTouch::set_pressed); - //ClassDB::bind_method(D_METHOD("is_pressed"),&InputEventScreenTouch::is_pressed); + ClassDB::bind_method(D_METHOD("set_canceled", "canceled"), &InputEventScreenTouch::set_canceled); ClassDB::bind_method(D_METHOD("set_double_tap", "double_tap"), &InputEventScreenTouch::set_double_tap); ClassDB::bind_method(D_METHOD("is_double_tap"), &InputEventScreenTouch::is_double_tap); ADD_PROPERTY(PropertyInfo(Variant::INT, "index"), "set_index", "get_index"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "canceled"), "set_canceled", "is_canceled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "double_tap"), "set_double_tap", "is_double_tap"); } @@ -1457,10 +1477,6 @@ void InputEventAction::set_pressed(bool p_pressed) { pressed = p_pressed; } -bool InputEventAction::is_pressed() const { - return pressed; -} - void InputEventAction::set_strength(float p_strength) { strength = CLAMP(p_strength, 0.0f, 1.0f); } @@ -1489,7 +1505,7 @@ bool InputEventAction::action_match(const Ref &p_event, bool p_exact bool match = action == act->action; if (match) { - bool act_pressed = act->pressed; + bool act_pressed = act->is_pressed(); if (r_pressed != nullptr) { *r_pressed = act_pressed; } @@ -1520,7 +1536,7 @@ String InputEventAction::as_text() const { } String InputEventAction::to_string() { - String p = pressed ? "true" : "false"; + String p = is_pressed() ? "true" : "false"; return vformat("InputEventAction: action=\"%s\", pressed=%s", action, p); } @@ -1529,13 +1545,10 @@ void InputEventAction::_bind_methods() { ClassDB::bind_method(D_METHOD("get_action"), &InputEventAction::get_action); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventAction::set_pressed); - //ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventAction::is_pressed); ClassDB::bind_method(D_METHOD("set_strength", "strength"), &InputEventAction::set_strength); ClassDB::bind_method(D_METHOD("get_strength"), &InputEventAction::get_strength); - // ClassDB::bind_method(D_METHOD("is_action", "name"), &InputEventAction::is_action); - ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "action"), "set_action", "get_action"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_strength", "get_strength"); @@ -1758,10 +1771,6 @@ void InputEventShortcut::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); } -bool InputEventShortcut::is_pressed() const { - return true; -} - String InputEventShortcut::as_text() const { ERR_FAIL_COND_V(shortcut.is_null(), "None"); diff --git a/core/input/input_event.h b/core/input/input_event.h index 4be42d0bd207c..e9d4fb8325325 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -56,6 +56,9 @@ class InputEvent : public Resource { int device = 0; protected: + bool canceled = false; + bool pressed = false; + static void _bind_methods(); public: @@ -71,8 +74,9 @@ class InputEvent : public Resource { float get_action_strength(const StringName &p_action, bool p_exact_match = false) const; float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false) const; - // To be removed someday, since they do not make sense for all events - virtual bool is_pressed() const; + bool is_canceled() const; + bool is_pressed() const; + bool is_released() const; virtual bool is_echo() const; virtual String as_text() const = 0; @@ -149,8 +153,6 @@ class InputEventWithModifiers : public InputEventFromWindow { class InputEventKey : public InputEventWithModifiers { GDCLASS(InputEventKey, InputEventWithModifiers); - bool pressed = false; /// otherwise release - Key keycode = Key::NONE; // Key enum, without modifier masks. Key physical_keycode = Key::NONE; Key key_label = Key::NONE; @@ -163,7 +165,6 @@ class InputEventKey : public InputEventWithModifiers { public: void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; void set_keycode(Key p_keycode); Key get_keycode() const; @@ -229,7 +230,6 @@ class InputEventMouseButton : public InputEventMouse { float factor = 1; MouseButton button_index = MouseButton::NONE; - bool pressed = false; //otherwise released bool double_click = false; //last even less than double click time protected: @@ -243,7 +243,7 @@ class InputEventMouseButton : public InputEventMouse { MouseButton get_button_index() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; + void set_canceled(bool p_canceled); void set_double_click(bool p_double_click); bool is_double_click() const; @@ -312,8 +312,6 @@ class InputEventJoypadMotion : public InputEvent { void set_axis_value(float p_value); float get_axis_value() const; - virtual bool is_pressed() const override; - virtual bool action_match(const Ref &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override; virtual bool is_match(const Ref &p_event, bool p_exact_match = true) const override; @@ -321,6 +319,8 @@ class InputEventJoypadMotion : public InputEvent { virtual String as_text() const override; virtual String to_string() override; + static Ref create_reference(JoyAxis p_axis, float p_value); + InputEventJoypadMotion() {} }; @@ -328,7 +328,6 @@ class InputEventJoypadButton : public InputEvent { GDCLASS(InputEventJoypadButton, InputEvent); JoyButton button_index = (JoyButton)0; - bool pressed = false; float pressure = 0; //0 to 1 protected: static void _bind_methods(); @@ -338,7 +337,6 @@ class InputEventJoypadButton : public InputEvent { JoyButton get_button_index() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; void set_pressure(float p_pressure); float get_pressure() const; @@ -360,7 +358,6 @@ class InputEventScreenTouch : public InputEventFromWindow { GDCLASS(InputEventScreenTouch, InputEventFromWindow); int index = 0; Vector2 pos; - bool pressed = false; bool double_tap = false; protected: @@ -374,7 +371,7 @@ class InputEventScreenTouch : public InputEventFromWindow { Vector2 get_position() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; + void set_canceled(bool p_canceled); void set_double_tap(bool p_double_tap); bool is_double_tap() const; @@ -434,7 +431,6 @@ class InputEventAction : public InputEvent { GDCLASS(InputEventAction, InputEvent); StringName action; - bool pressed = false; float strength = 1.0f; protected: @@ -445,7 +441,6 @@ class InputEventAction : public InputEvent { StringName get_action() const; void set_pressed(bool p_pressed); - virtual bool is_pressed() const override; void set_strength(float p_strength); float get_strength() const; @@ -569,7 +564,6 @@ class InputEventShortcut : public InputEvent { public: void set_shortcut(Ref p_shortcut); Ref get_shortcut(); - virtual bool is_pressed() const override; virtual String as_text() const override; virtual String to_string() override; diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 910778324ce45..ddfde0e7cdf1a 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -399,21 +399,25 @@ const HashMap>> &InputMap::get_builtins() { inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::LEFT)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_LEFT)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0)); default_builtin_cache.insert("ui_left", inputs); inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::RIGHT)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_RIGHT)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0)); default_builtin_cache.insert("ui_right", inputs); inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::UP)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_UP)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0)); default_builtin_cache.insert("ui_up", inputs); inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::DOWN)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_DOWN)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0)); default_builtin_cache.insert("ui_down", inputs); inputs = List>(); diff --git a/core/io/dir_access.h b/core/io/dir_access.h index 51eb68eaea0c2..52ed688debf3a 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -68,7 +68,7 @@ class DirAccess : public RefCounted { virtual String _get_root_string() const; AccessType get_access_type() const; - String fix_path(String p_path) const; + virtual String fix_path(String p_path) const; template static Ref _create_builtin() { diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index a6a1a224b30ee..b669afdc991a7 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -441,6 +441,11 @@ Vector FileAccess::get_csv_line(const String &p_delim) const { current += c; } } + + if (in_quote) { + WARN_PRINT(vformat("Reached end of file before closing '\"' in CSV file '%s'.", get_path())); + } + strings.push_back(current); return strings; diff --git a/core/io/file_access.h b/core/io/file_access.h index 34c80b3dd9b41..ad1ac665f313f 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -81,7 +81,7 @@ class FileAccess : public RefCounted { static void _bind_methods(); AccessType get_access_type() const; - String fix_path(const String &p_path) const; + virtual String fix_path(const String &p_path) const; virtual Error open_internal(const String &p_path, int p_mode_flags) = 0; ///< open a file virtual uint64_t _get_modified_time(const String &p_file) = 0; virtual void _set_access_type(AccessType p_access); diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp index da59ae8c59030..3e5a1217dd6f3 100644 --- a/core/io/file_access_compressed.cpp +++ b/core/io/file_access_compressed.cpp @@ -34,13 +34,7 @@ void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, uint32_t p_block_size) { magic = p_magic.ascii().get_data(); - if (magic.length() > 4) { - magic = magic.substr(0, 4); - } else { - while (magic.length() < 4) { - magic += " "; - } - } + magic = (magic + " ").substr(0, 4); cmode = p_mode; block_size = p_block_size; diff --git a/core/io/file_access_network.cpp b/core/io/file_access_network.cpp deleted file mode 100644 index 7fabff26ac55a..0000000000000 --- a/core/io/file_access_network.cpp +++ /dev/null @@ -1,498 +0,0 @@ -/**************************************************************************/ -/* file_access_network.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "file_access_network.h" - -#include "core/config/project_settings.h" -#include "core/io/ip.h" -#include "core/io/marshalls.h" -#include "core/os/os.h" - -//#define DEBUG_PRINT(m_p) print_line(m_p) -//#define DEBUG_TIME(m_what) printf("MS: %s - %lli\n",m_what,OS::get_singleton()->get_ticks_usec()); -#define DEBUG_PRINT(m_p) -#define DEBUG_TIME(m_what) - -void FileAccessNetworkClient::lock_mutex() { - mutex.lock(); - lockcount++; -} - -void FileAccessNetworkClient::unlock_mutex() { - lockcount--; - mutex.unlock(); -} - -void FileAccessNetworkClient::put_32(int p_32) { - uint8_t buf[4]; - encode_uint32(p_32, buf); - client->put_data(buf, 4); - DEBUG_PRINT("put32: " + itos(p_32)); -} - -void FileAccessNetworkClient::put_64(int64_t p_64) { - uint8_t buf[8]; - encode_uint64(p_64, buf); - client->put_data(buf, 8); - DEBUG_PRINT("put64: " + itos(p_64)); -} - -int FileAccessNetworkClient::get_32() { - uint8_t buf[4]; - client->get_data(buf, 4); - return decode_uint32(buf); -} - -int64_t FileAccessNetworkClient::get_64() { - uint8_t buf[8]; - client->get_data(buf, 8); - return decode_uint64(buf); -} - -void FileAccessNetworkClient::_thread_func() { - client->set_no_delay(true); - while (!quit) { - DEBUG_PRINT("SEM WAIT - " + itos(sem->get())); - sem.wait(); - DEBUG_TIME("sem_unlock"); - //DEBUG_PRINT("semwait returned "+itos(werr)); - DEBUG_PRINT("MUTEX LOCK " + itos(lockcount)); - lock_mutex(); - DEBUG_PRINT("MUTEX PASS"); - - { - MutexLock lock(blockrequest_mutex); - while (block_requests.size()) { - put_32(block_requests.front()->get().id); - put_32(FileAccessNetwork::COMMAND_READ_BLOCK); - put_64(block_requests.front()->get().offset); - put_32(block_requests.front()->get().size); - block_requests.pop_front(); - } - } - - DEBUG_PRINT("THREAD ITER"); - - DEBUG_TIME("sem_read"); - int id = get_32(); - - int response = get_32(); - DEBUG_PRINT("GET RESPONSE: " + itos(response)); - - FileAccessNetwork *fa = nullptr; - - if (response != FileAccessNetwork::RESPONSE_DATA) { - if (!accesses.has(id)) { - unlock_mutex(); - ERR_FAIL_COND(!accesses.has(id)); - } - } - - if (accesses.has(id)) { - fa = accesses[id]; - } - - switch (response) { - case FileAccessNetwork::RESPONSE_OPEN: { - DEBUG_TIME("sem_open"); - int status = get_32(); - if (status != OK) { - fa->_respond(0, Error(status)); - } else { - int64_t len = get_64(); - fa->_respond(len, Error(status)); - } - - fa->sem.post(); - - } break; - case FileAccessNetwork::RESPONSE_DATA: { - int64_t offset = get_64(); - int32_t len = get_32(); - - Vector resp_block; - resp_block.resize(len); - client->get_data(resp_block.ptrw(), len); - - if (fa) { //may have been queued - fa->_set_block(offset, resp_block); - } - - } break; - case FileAccessNetwork::RESPONSE_FILE_EXISTS: { - int status = get_32(); - fa->exists_modtime = status != 0; - fa->sem.post(); - - } break; - case FileAccessNetwork::RESPONSE_GET_MODTIME: { - uint64_t status = get_64(); - fa->exists_modtime = status; - fa->sem.post(); - - } break; - } - - unlock_mutex(); - } -} - -void FileAccessNetworkClient::_thread_func(void *s) { - FileAccessNetworkClient *self = static_cast(s); - - self->_thread_func(); -} - -Error FileAccessNetworkClient::connect(const String &p_host, int p_port, const String &p_password) { - IPAddress ip; - - if (p_host.is_valid_ip_address()) { - ip = p_host; - } else { - ip = IP::get_singleton()->resolve_hostname(p_host); - } - - DEBUG_PRINT("IP: " + String(ip) + " port " + itos(p_port)); - Error err = client->connect_to_host(ip, p_port); - ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot connect to host with IP: " + String(ip) + " and port: " + itos(p_port)); - while (client->get_status() == StreamPeerTCP::STATUS_CONNECTING) { - //DEBUG_PRINT("trying to connect...."); - OS::get_singleton()->delay_usec(1000); - } - - if (client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - return ERR_CANT_CONNECT; - } - - CharString cs = p_password.utf8(); - put_32(cs.length()); - client->put_data((const uint8_t *)cs.ptr(), cs.length()); - - int e = get_32(); - - if (e != OK) { - return ERR_INVALID_PARAMETER; - } - - thread.start(_thread_func, this); - - return OK; -} - -FileAccessNetworkClient *FileAccessNetworkClient::singleton = nullptr; - -FileAccessNetworkClient::FileAccessNetworkClient() { - singleton = this; - client.instantiate(); -} - -FileAccessNetworkClient::~FileAccessNetworkClient() { - quit = true; - sem.post(); - thread.wait_to_finish(); -} - -void FileAccessNetwork::_set_block(uint64_t p_offset, const Vector &p_block) { - int32_t page = p_offset / page_size; - ERR_FAIL_INDEX(page, pages.size()); - if (page < pages.size() - 1) { - ERR_FAIL_COND(p_block.size() != page_size); - } else { - ERR_FAIL_COND((uint64_t)p_block.size() != total_size % page_size); - } - - { - MutexLock lock(buffer_mutex); - pages.write[page].buffer = p_block; - pages.write[page].queued = false; - } - - if (waiting_on_page == page) { - waiting_on_page = -1; - page_sem.post(); - } -} - -void FileAccessNetwork::_respond(uint64_t p_len, Error p_status) { - DEBUG_PRINT("GOT RESPONSE - len: " + itos(p_len) + " status: " + itos(p_status)); - response = p_status; - if (response != OK) { - return; - } - opened = true; - total_size = p_len; - int32_t pc = ((total_size - 1) / page_size) + 1; - pages.resize(pc); -} - -Error FileAccessNetwork::open_internal(const String &p_path, int p_mode_flags) { - ERR_FAIL_COND_V(p_mode_flags != READ, ERR_UNAVAILABLE); - _close(); - - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - DEBUG_PRINT("open: " + p_path); - - DEBUG_TIME("open_begin"); - - nc->lock_mutex(); - nc->put_32(id); - nc->accesses[id] = this; - nc->put_32(COMMAND_OPEN_FILE); - CharString cs = p_path.utf8(); - nc->put_32(cs.length()); - nc->client->put_data((const uint8_t *)cs.ptr(), cs.length()); - pos = 0; - eof_flag = false; - last_page = -1; - last_page_buff = nullptr; - - //buffers.clear(); - nc->unlock_mutex(); - DEBUG_PRINT("OPEN POST"); - DEBUG_TIME("open_post"); - nc->sem.post(); //awaiting answer - DEBUG_PRINT("WAIT..."); - sem.wait(); - DEBUG_TIME("open_end"); - DEBUG_PRINT("WAIT ENDED..."); - - return response; -} - -void FileAccessNetwork::_close() { - if (!opened) { - return; - } - - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - - DEBUG_PRINT("CLOSE"); - nc->lock_mutex(); - nc->put_32(id); - nc->put_32(COMMAND_CLOSE); - pages.clear(); - opened = false; - nc->unlock_mutex(); -} - -bool FileAccessNetwork::is_open() const { - return opened; -} - -void FileAccessNetwork::seek(uint64_t p_position) { - ERR_FAIL_COND_MSG(!opened, "File must be opened before use."); - - eof_flag = p_position > total_size; - - if (p_position >= total_size) { - p_position = total_size; - } - - pos = p_position; -} - -void FileAccessNetwork::seek_end(int64_t p_position) { - seek(total_size + p_position); -} - -uint64_t FileAccessNetwork::get_position() const { - ERR_FAIL_COND_V_MSG(!opened, 0, "File must be opened before use."); - return pos; -} - -uint64_t FileAccessNetwork::get_length() const { - ERR_FAIL_COND_V_MSG(!opened, 0, "File must be opened before use."); - return total_size; -} - -bool FileAccessNetwork::eof_reached() const { - ERR_FAIL_COND_V_MSG(!opened, false, "File must be opened before use."); - return eof_flag; -} - -uint8_t FileAccessNetwork::get_8() const { - uint8_t v; - get_buffer(&v, 1); - return v; -} - -void FileAccessNetwork::_queue_page(int32_t p_page) const { - if (p_page >= pages.size()) { - return; - } - if (pages[p_page].buffer.is_empty() && !pages[p_page].queued) { - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - { - MutexLock lock(nc->blockrequest_mutex); - - FileAccessNetworkClient::BlockRequest br; - br.id = id; - br.offset = (uint64_t)p_page * page_size; - br.size = page_size; - nc->block_requests.push_back(br); - pages.write[p_page].queued = true; - } - DEBUG_PRINT("QUEUE PAGE POST"); - nc->sem.post(); - DEBUG_PRINT("queued " + itos(p_page)); - } -} - -uint64_t FileAccessNetwork::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - - if (pos + p_length > total_size) { - eof_flag = true; - } - if (pos + p_length >= total_size) { - p_length = total_size - pos; - } - - uint8_t *buff = last_page_buff; - - for (uint64_t i = 0; i < p_length; i++) { - int32_t page = pos / page_size; - - if (page != last_page) { - buffer_mutex.lock(); - if (pages[page].buffer.is_empty()) { - waiting_on_page = page; - for (int32_t j = 0; j < read_ahead; j++) { - _queue_page(page + j); - } - buffer_mutex.unlock(); - DEBUG_PRINT("wait"); - page_sem.wait(); - DEBUG_PRINT("done"); - } else { - for (int32_t j = 0; j < read_ahead; j++) { - _queue_page(page + j); - } - buffer_mutex.unlock(); - } - - buff = pages.write[page].buffer.ptrw(); - last_page_buff = buff; - last_page = page; - } - - p_dst[i] = buff[pos - uint64_t(page) * page_size]; - pos++; - } - - return p_length; -} - -Error FileAccessNetwork::get_error() const { - return pos == total_size ? ERR_FILE_EOF : OK; -} - -void FileAccessNetwork::flush() { - ERR_FAIL(); -} - -void FileAccessNetwork::store_8(uint8_t p_dest) { - ERR_FAIL(); -} - -bool FileAccessNetwork::file_exists(const String &p_path) { - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - nc->lock_mutex(); - nc->put_32(id); - nc->put_32(COMMAND_FILE_EXISTS); - CharString cs = p_path.utf8(); - nc->put_32(cs.length()); - nc->client->put_data((const uint8_t *)cs.ptr(), cs.length()); - nc->unlock_mutex(); - DEBUG_PRINT("FILE EXISTS POST"); - nc->sem.post(); - sem.wait(); - - return exists_modtime != 0; -} - -uint64_t FileAccessNetwork::_get_modified_time(const String &p_file) { - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - nc->lock_mutex(); - nc->put_32(id); - nc->put_32(COMMAND_GET_MODTIME); - CharString cs = p_file.utf8(); - nc->put_32(cs.length()); - nc->client->put_data((const uint8_t *)cs.ptr(), cs.length()); - nc->unlock_mutex(); - DEBUG_PRINT("MODTIME POST"); - nc->sem.post(); - sem.wait(); - - return exists_modtime; -} - -uint32_t FileAccessNetwork::_get_unix_permissions(const String &p_file) { - ERR_PRINT("Getting UNIX permissions from network drives is not implemented yet"); - return 0; -} - -Error FileAccessNetwork::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { - ERR_PRINT("Setting UNIX permissions on network drives is not implemented yet"); - return ERR_UNAVAILABLE; -} - -void FileAccessNetwork::configure() { - GLOBAL_DEF(PropertyInfo(Variant::INT, "network/remote_fs/page_size", PROPERTY_HINT_RANGE, "1,65536,1,or_greater"), 65536); // Is used as denominator and can't be zero - GLOBAL_DEF(PropertyInfo(Variant::INT, "network/remote_fs/page_read_ahead", PROPERTY_HINT_RANGE, "0,8,1,or_greater"), 4); -} - -void FileAccessNetwork::close() { - _close(); - - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - nc->lock_mutex(); - nc->accesses.erase(id); - nc->unlock_mutex(); -} - -FileAccessNetwork::FileAccessNetwork() { - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - nc->lock_mutex(); - id = nc->last_id++; - nc->accesses[id] = this; - nc->unlock_mutex(); - page_size = GLOBAL_GET("network/remote_fs/page_size"); - read_ahead = GLOBAL_GET("network/remote_fs/page_read_ahead"); -} - -FileAccessNetwork::~FileAccessNetwork() { - _close(); - - FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - nc->lock_mutex(); - nc->accesses.erase(id); - nc->unlock_mutex(); -} diff --git a/core/io/file_access_network.h b/core/io/file_access_network.h deleted file mode 100644 index 78c19347ce3b9..0000000000000 --- a/core/io/file_access_network.h +++ /dev/null @@ -1,167 +0,0 @@ -/**************************************************************************/ -/* file_access_network.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef FILE_ACCESS_NETWORK_H -#define FILE_ACCESS_NETWORK_H - -#include "core/io/file_access.h" -#include "core/io/stream_peer_tcp.h" -#include "core/os/semaphore.h" -#include "core/os/thread.h" - -class FileAccessNetwork; - -class FileAccessNetworkClient { - struct BlockRequest { - int32_t id; - uint64_t offset; - int32_t size; - }; - - List block_requests; - - Semaphore sem; - Thread thread; - bool quit = false; - Mutex mutex; - Mutex blockrequest_mutex; - HashMap accesses; - Ref client; - int32_t last_id = 0; - int32_t lockcount = 0; - - Vector block; - - void _thread_func(); - static void _thread_func(void *s); - - void put_32(int32_t p_32); - void put_64(int64_t p_64); - int32_t get_32(); - int64_t get_64(); - void lock_mutex(); - void unlock_mutex(); - - friend class FileAccessNetwork; - static FileAccessNetworkClient *singleton; - -public: - static FileAccessNetworkClient *get_singleton() { return singleton; } - - Error connect(const String &p_host, int p_port, const String &p_password = ""); - - FileAccessNetworkClient(); - ~FileAccessNetworkClient(); -}; - -class FileAccessNetwork : public FileAccess { - Semaphore sem; - Semaphore page_sem; - Mutex buffer_mutex; - bool opened = false; - uint64_t total_size = 0; - mutable uint64_t pos = 0; - int32_t id = -1; - mutable bool eof_flag = false; - mutable int32_t last_page = -1; - mutable uint8_t *last_page_buff = nullptr; - - int32_t page_size = 0; - int32_t read_ahead = 0; - - mutable int waiting_on_page = -1; - - struct Page { - int activity = 0; - bool queued = false; - Vector buffer; - }; - - mutable Vector pages; - - mutable Error response; - - uint64_t exists_modtime = 0; - - friend class FileAccessNetworkClient; - void _queue_page(int32_t p_page) const; - void _respond(uint64_t p_len, Error p_status); - void _set_block(uint64_t p_offset, const Vector &p_block); - void _close(); - -public: - enum Command { - COMMAND_OPEN_FILE, - COMMAND_READ_BLOCK, - COMMAND_CLOSE, - COMMAND_FILE_EXISTS, - COMMAND_GET_MODTIME, - }; - - enum Response { - RESPONSE_OPEN, - RESPONSE_DATA, - RESPONSE_FILE_EXISTS, - RESPONSE_GET_MODTIME, - }; - - virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file - virtual bool is_open() const override; ///< true when file is open - - virtual void seek(uint64_t p_position) override; ///< seek to a given position - virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file - virtual uint64_t get_position() const override; ///< get position in the file - virtual uint64_t get_length() const override; ///< get size of the file - - virtual bool eof_reached() const override; ///< reading passed EOF - - virtual uint8_t get_8() const override; ///< get a byte - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; - - virtual Error get_error() const override; ///< get last error - - virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte - - virtual bool file_exists(const String &p_path) override; ///< return true if a file exists - - virtual uint64_t _get_modified_time(const String &p_file) override; - virtual uint32_t _get_unix_permissions(const String &p_file) override; - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override; - - virtual void close() override; - - static void configure(); - - FileAccessNetwork(); - ~FileAccessNetwork(); -}; - -#endif // FILE_ACCESS_NETWORK_H diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 88a906a38e3e4..74c5c1c19109a 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -48,7 +48,8 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t } void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) { - PathMD5 pmd5(p_path.md5_buffer()); + String simplified_path = p_path.simplify_path(); + PathMD5 pmd5(simplified_path.md5_buffer()); bool exists = files.has(pmd5); @@ -68,7 +69,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64 if (!exists) { //search for dir - String p = p_path.replace_first("res://", ""); + String p = simplified_path.replace_first("res://", ""); PackedDir *cd = root; if (p.contains("/")) { //in a subdir @@ -87,7 +88,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64 } } } - String filename = p_path.get_file(); + String filename = simplified_path.get_file(); // Don't add as a file if the path points to a directory if (!filename.is_empty()) { cd->files.insert(filename); diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 8bfabc9529cc4..1538b302c290b 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -184,7 +184,8 @@ class FileAccessPack : public FileAccess { }; Ref PackedData::try_open_path(const String &p_path) { - PathMD5 pmd5(p_path.md5_buffer()); + String simplified_path = p_path.simplify_path(); + PathMD5 pmd5(simplified_path.md5_buffer()); HashMap::Iterator E = files.find(pmd5); if (!E) { return nullptr; //not found @@ -197,7 +198,7 @@ Ref PackedData::try_open_path(const String &p_path) { } bool PackedData::has_path(const String &p_path) { - return files.has(PathMD5(p_path.md5_buffer())); + return files.has(PathMD5(p_path.simplify_path().md5_buffer())); } bool PackedData::has_directory(const String &p_path) { diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 190edbfb82da3..09505ea05da57 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -63,8 +63,9 @@ Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vecto } Error HTTPClient::_request(Method p_method, const String &p_url, const Vector &p_headers, const String &p_body) { - int size = p_body.length(); - return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)p_body.utf8().get_data() : nullptr, size); + CharString body_utf8 = p_body.utf8(); + int size = body_utf8.length(); + return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)body_utf8.get_data() : nullptr, size); } String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index 3788fa501e904..2f45238951be3 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -60,6 +60,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Refis_server(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(tls_options.is_valid() && !StreamPeerTLS::is_available(), ERR_UNAVAILABLE, "HTTPS is not available in this build."); ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); if (conn_port < 0) { diff --git a/core/io/image.cpp b/core/io/image.cpp index 8111ca447ceb3..9bb987b670c53 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -468,7 +468,7 @@ int Image::get_mipmap_count() const { //using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers template static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) { - uint32_t max_bytes = MAX(read_bytes, write_bytes); + constexpr uint32_t max_bytes = MAX(read_bytes, write_bytes); for (int y = 0; y < p_height; y++) { for (int x = 0; x < p_width; x++) { @@ -492,8 +492,9 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p } if constexpr (write_gray) { - //TODO: not correct grayscale, should use fixed point version of actual weights - wofs[0] = uint8_t((uint16_t(rgba[0]) + uint16_t(rgba[1]) + uint16_t(rgba[2])) / 3); + // REC.709 + const uint8_t luminance = (13938U * rgba[0] + 46869U * rgba[1] + 4729U * rgba[2] + 32768U) >> 16U; + wofs[0] = luminance; } else { for (uint32_t i = 0; i < write_bytes; i++) { wofs[i] = rgba[i]; @@ -3718,9 +3719,9 @@ void Image::premultiply_alpha() { for (int j = 0; j < width; j++) { uint8_t *ptr = &data_ptr[(i * width + j) * 4]; - ptr[0] = (uint16_t(ptr[0]) * uint16_t(ptr[3])) >> 8; - ptr[1] = (uint16_t(ptr[1]) * uint16_t(ptr[3])) >> 8; - ptr[2] = (uint16_t(ptr[2]) * uint16_t(ptr[3])) >> 8; + ptr[0] = (uint16_t(ptr[0]) * uint16_t(ptr[3]) + 255U) >> 8; + ptr[1] = (uint16_t(ptr[1]) * uint16_t(ptr[3]) + 255U) >> 8; + ptr[2] = (uint16_t(ptr[2]) * uint16_t(ptr[3]) + 255U) >> 8; } } } diff --git a/core/io/ip.cpp b/core/io/ip.cpp index 65728f34f6d4b..772f700916c82 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -35,8 +35,6 @@ #include "core/templates/hash_map.h" #include "core/variant/typed_array.h" -VARIANT_ENUM_CAST(IP::ResolverStatus); - /************* RESOLVER ******************/ struct _IP_ResolverPrivate { diff --git a/core/io/ip.h b/core/io/ip.h index b768f0b9d479e..4816d59ac2df7 100644 --- a/core/io/ip.h +++ b/core/io/ip.h @@ -109,5 +109,6 @@ class IP : public Object { }; VARIANT_ENUM_CAST(IP::Type); +VARIANT_ENUM_CAST(IP::ResolverStatus); #endif // IP_H diff --git a/core/io/json.cpp b/core/io/json.cpp index 8d0fe53ed4fa3..a6e054a9fe636 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -47,13 +47,7 @@ const char *JSON::tk_name[TK_MAX] = { }; String JSON::_make_indent(const String &p_indent, int p_size) { - String indent_text = ""; - if (!p_indent.is_empty()) { - for (int i = 0; i < p_size; i++) { - indent_text += p_indent; - } - } - return indent_text; + return p_indent.repeat(p_size); } String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, HashSet &p_markers, bool p_full_precision) { diff --git a/core/io/packed_data_container.cpp b/core/io/packed_data_container.cpp index 6c16401f1799b..ce4edb18fe14a 100644 --- a/core/io/packed_data_container.cpp +++ b/core/io/packed_data_container.cpp @@ -320,6 +320,8 @@ uint32_t PackedDataContainer::_pack(const Variant &p_data, Vector &tmpd } Error PackedDataContainer::pack(const Variant &p_data) { + ERR_FAIL_COND_V_MSG(p_data.get_type() != Variant::ARRAY && p_data.get_type() != Variant::DICTIONARY, ERR_INVALID_DATA, "PackedDataContainer can pack only Array and Dictionary type."); + Vector tmpdata; HashMap string_cache; _pack(p_data, tmpdata, string_cache); @@ -361,7 +363,9 @@ void PackedDataContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("pack", "value"), &PackedDataContainer::pack); ClassDB::bind_method(D_METHOD("size"), &PackedDataContainer::size); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "__data__"), "_set_data", "_get_data"); + BIND_METHOD_ERR_RETURN_DOC("pack", ERR_INVALID_DATA); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "__data__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); } ////////////////// @@ -378,16 +382,11 @@ Variant PackedDataContainerRef::_iter_get(const Variant &p_iter) { return from->_iter_get_ofs(p_iter, offset); } -bool PackedDataContainerRef::_is_dictionary() const { - return from->_type_at_ofs(offset) == PackedDataContainer::TYPE_DICT; -} - void PackedDataContainerRef::_bind_methods() { ClassDB::bind_method(D_METHOD("size"), &PackedDataContainerRef::size); ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainerRef::_iter_init); ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainerRef::_iter_get); ClassDB::bind_method(D_METHOD("_iter_next"), &PackedDataContainerRef::_iter_next); - ClassDB::bind_method(D_METHOD("_is_dictionary"), &PackedDataContainerRef::_is_dictionary); } Variant PackedDataContainerRef::getvar(const Variant &p_key, bool *r_valid) const { diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h index a77970a0bd1da..cc9996101e2a9 100644 --- a/core/io/packed_data_container.h +++ b/core/io/packed_data_container.h @@ -94,7 +94,6 @@ class PackedDataContainerRef : public RefCounted { Variant _iter_init(const Array &p_iter); Variant _iter_next(const Array &p_iter); Variant _iter_get(const Variant &p_iter); - bool _is_dictionary() const; int size() const; virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index e7f4980e94acd..9b49cc3d8c714 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -115,7 +115,9 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr } File pf; - pf.path = p_file; + // Simplify path here and on every 'files' access so that paths that have extra '/' + // symbols in them still match to the MD5 hash for the saved path. + pf.path = p_file.simplify_path(); pf.src_path = p_src; pf.ofs = ofs; pf.size = f->get_length(); diff --git a/core/io/remote_filesystem_client.cpp b/core/io/remote_filesystem_client.cpp new file mode 100644 index 0000000000000..f22e442a3440b --- /dev/null +++ b/core/io/remote_filesystem_client.cpp @@ -0,0 +1,329 @@ +/**************************************************************************/ +/* remote_filesystem_client.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "remote_filesystem_client.h" + +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/stream_peer_tcp.h" +#include "core/string/string_builder.h" + +#define FILESYSTEM_CACHE_VERSION 1 +#define FILESYSTEM_PROTOCOL_VERSION 1 +#define PASSWORD_LENGTH 32 + +#define FILES_SUBFOLDER "remote_filesystem_files" +#define FILES_CACHE_FILE "remote_filesystem.cache" + +Vector RemoteFilesystemClient::_load_cache_file() { + Ref fa = FileAccess::open(cache_path.path_join(FILES_CACHE_FILE), FileAccess::READ); + if (!fa.is_valid()) { + return Vector(); // No cache, return empty + } + + int version = fa->get_line().to_int(); + if (version != FILESYSTEM_CACHE_VERSION) { + return Vector(); // Version mismatch, ignore everything. + } + + String file_path = cache_path.path_join(FILES_SUBFOLDER); + + Vector file_cache; + + while (!fa->eof_reached()) { + String l = fa->get_line(); + Vector fields = l.split("::"); + if (fields.size() != 3) { + break; + } + FileCache fc; + fc.path = fields[0]; + fc.server_modified_time = fields[1].to_int(); + fc.modified_time = fields[2].to_int(); + + String full_path = file_path.path_join(fc.path); + if (!FileAccess::exists(full_path)) { + continue; // File is gone. + } + + if (FileAccess::get_modified_time(full_path) != fc.modified_time) { + DirAccess::remove_absolute(full_path); // Take the chance to remove this file and assume we no longer have it. + continue; + } + + file_cache.push_back(fc); + } + + return file_cache; +} + +Error RemoteFilesystemClient::_store_file(const String &p_path, const LocalVector &p_file, uint64_t &modified_time) { + modified_time = 0; + String full_path = cache_path.path_join(FILES_SUBFOLDER).path_join(p_path); + String base_file_dir = full_path.get_base_dir(); + + if (!validated_directories.has(base_file_dir)) { + // Verify that path exists before writing file, but only verify once for performance. + DirAccess::make_dir_recursive_absolute(base_file_dir); + validated_directories.insert(base_file_dir); + } + + Ref f = FileAccess::open(full_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open file for writing to remote filesystem cache: " + p_path); + f->store_buffer(p_file.ptr(), p_file.size()); + Error err = f->get_error(); + if (err) { + return err; + } + f.unref(); // Unref to ensure file is not locked and modified time can be obtained. + + modified_time = FileAccess::get_modified_time(full_path); + return OK; +} + +Error RemoteFilesystemClient::_remove_file(const String &p_path) { + return DirAccess::remove_absolute(cache_path.path_join(FILES_SUBFOLDER).path_join(p_path)); +} +Error RemoteFilesystemClient::_store_cache_file(const Vector &p_cache) { + String full_path = cache_path.path_join(FILES_CACHE_FILE); + String base_file_dir = full_path.get_base_dir(); + Error err = DirAccess::make_dir_recursive_absolute(base_file_dir); + ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, "Unable to create base directory to store cache file: " + base_file_dir); + + Ref f = FileAccess::open(full_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open the remote cache file for writing: " + full_path); + f->store_line(itos(FILESYSTEM_CACHE_VERSION)); + for (int i = 0; i < p_cache.size(); i++) { + String l = p_cache[i].path + "::" + itos(p_cache[i].server_modified_time) + "::" + itos(p_cache[i].modified_time); + f->store_line(l); + } + return OK; +} + +Error RemoteFilesystemClient::synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path) { + Error err = _synchronize_with_server(p_host, p_port, p_password, r_cache_path); + // Ensure no memory is kept + validated_directories.reset(); + cache_path = String(); + return err; +} + +void RemoteFilesystemClient::_update_cache_path(String &r_cache_path) { + r_cache_path = cache_path.path_join(FILES_SUBFOLDER); +} + +Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path) { + cache_path = r_cache_path; + { + Ref dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + dir->change_dir(cache_path); + cache_path = dir->get_current_dir(); + } + + Ref tcp_client; + tcp_client.instantiate(); + + IPAddress ip = p_host.is_valid_ip_address() ? IPAddress(p_host) : IP::get_singleton()->resolve_hostname(p_host); + ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, "Unable to resolve remote filesystem server hostname: " + p_host); + print_verbose(vformat("Remote Filesystem: Connecting to host %s, port %d.", ip, p_port)); + Error err = tcp_client->connect_to_host(ip, p_port); + ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to open connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed."); + + while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTING) { + tcp_client->poll(); + OS::get_singleton()->delay_usec(100); + } + + if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + ERR_FAIL_V_MSG(ERR_CANT_CONNECT, "Connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed."); + } + + // Connection OK, now send the current file state. + print_verbose("Remote Filesystem: Connection OK."); + + // Header (GRFS) - Godot Remote File System + print_verbose("Remote Filesystem: Sending header"); + tcp_client->put_u8('G'); + tcp_client->put_u8('R'); + tcp_client->put_u8('F'); + tcp_client->put_u8('S'); + // Protocol version + tcp_client->put_32(FILESYSTEM_PROTOCOL_VERSION); + print_verbose("Remote Filesystem: Sending password"); + uint8_t password[PASSWORD_LENGTH]; // Send fixed size password, since it's easier and safe to validate. + for (int i = 0; i < PASSWORD_LENGTH; i++) { + if (i < p_password.length()) { + password[i] = p_password[i]; + } else { + password[i] = 0; + } + } + tcp_client->put_data(password, PASSWORD_LENGTH); + print_verbose("Remote Filesystem: Tags."); + Vector tags; + { + tags.push_back(OS::get_singleton()->get_identifier()); + switch (OS::get_singleton()->get_preferred_texture_format()) { + case OS::PREFERRED_TEXTURE_FORMAT_S3TC_BPTC: { + tags.push_back("bptc"); + tags.push_back("s3tc"); + } break; + case OS::PREFERRED_TEXTURE_FORMAT_ETC2_ASTC: { + tags.push_back("etc2"); + tags.push_back("astc"); + } break; + } + } + + tcp_client->put_32(tags.size()); + for (int i = 0; i < tags.size(); i++) { + tcp_client->put_utf8_string(tags[i]); + } + // Size of compressed list of files + print_verbose("Remote Filesystem: Sending file list"); + + Vector file_cache = _load_cache_file(); + + // Encode file cache to send it via network. + Vector file_cache_buffer; + if (file_cache.size()) { + StringBuilder sbuild; + for (int i = 0; i < file_cache.size(); i++) { + sbuild.append(file_cache[i].path); + sbuild.append("::"); + sbuild.append(itos(file_cache[i].server_modified_time)); + sbuild.append("\n"); + } + String s = sbuild.as_string(); + CharString cs = s.utf8(); + file_cache_buffer.resize(Compression::get_max_compressed_buffer_size(cs.length(), Compression::MODE_ZSTD)); + int res_len = Compression::compress(file_cache_buffer.ptrw(), (const uint8_t *)cs.ptr(), cs.length(), Compression::MODE_ZSTD); + file_cache_buffer.resize(res_len); + + tcp_client->put_32(cs.length()); // Size of buffer uncompressed + tcp_client->put_32(file_cache_buffer.size()); // Size of buffer compressed + tcp_client->put_data(file_cache_buffer.ptr(), file_cache_buffer.size()); // Buffer + } else { + tcp_client->put_32(0); // No file cache buffer + } + + tcp_client->poll(); + ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected after sending header."); + + uint32_t file_count = tcp_client->get_32(); + + ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file list"); + + LocalVector file_buffer; + + Vector temp_file_cache; + + HashSet files_processed; + for (uint32_t i = 0; i < file_count; i++) { + String file = tcp_client->get_utf8_string(); + ERR_FAIL_COND_V_MSG(file == String(), ERR_CONNECTION_ERROR, "Invalid file name received from remote filesystem."); + uint64_t server_modified_time = tcp_client->get_u64(); + ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file info."); + + FileCache fc; + fc.path = file; + fc.server_modified_time = server_modified_time; + temp_file_cache.push_back(fc); + + files_processed.insert(file); + } + + Vector new_file_cache; + + // Get the actual files. As a robustness measure, if the connection is interrupted here, any file not yet received will be considered removed. + // Since the file changed anyway, this makes it the easiest way to keep robustness. + + bool server_disconnected = false; + for (uint32_t i = 0; i < file_count; i++) { + String file = temp_file_cache[i].path; + + if (temp_file_cache[i].server_modified_time == 0 || server_disconnected) { + // File was removed, or server disconnected before tranferring it. Since it's no longer valid, remove anyway. + _remove_file(file); + continue; + } + + uint64_t file_size = tcp_client->get_u64(); + file_buffer.resize(file_size); + + err = tcp_client->get_data(file_buffer.ptr(), file_size); + if (err != OK) { + ERR_PRINT("Error retrieving file from remote filesystem: " + file); + server_disconnected = true; + } + + if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + // Early disconnect, stop accepting files. + server_disconnected = true; + } + + if (server_disconnected) { + // No more server, transfer is invalid, remove this file. + _remove_file(file); + continue; + } + + uint64_t modified_time = 0; + err = _store_file(file, file_buffer, modified_time); + if (err != OK) { + server_disconnected = true; + continue; + } + FileCache fc = temp_file_cache[i]; + fc.modified_time = modified_time; + new_file_cache.push_back(fc); + } + + print_verbose("Remote Filesystem: Updating the cache file."); + + // Go through the list of local files read initially (file_cache) and see which ones are + // unchanged (not sent again from the server). + // These need to be re-saved in the new list (new_file_cache). + + for (int i = 0; i < file_cache.size(); i++) { + if (files_processed.has(file_cache[i].path)) { + continue; // This was either added or removed, so skip. + } + new_file_cache.push_back(file_cache[i]); + } + + err = _store_cache_file(new_file_cache); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, "Error writing the remote filesystem file cache."); + + print_verbose("Remote Filesystem: Update success."); + + _update_cache_path(r_cache_path); + return OK; +} diff --git a/core/io/remote_filesystem_client.h b/core/io/remote_filesystem_client.h new file mode 100644 index 0000000000000..42eba98eb100a --- /dev/null +++ b/core/io/remote_filesystem_client.h @@ -0,0 +1,65 @@ +/**************************************************************************/ +/* remote_filesystem_client.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef REMOTE_FILESYSTEM_CLIENT_H +#define REMOTE_FILESYSTEM_CLIENT_H + +#include "core/io/ip_address.h" +#include "core/string/ustring.h" +#include "core/templates/hash_set.h" +#include "core/templates/local_vector.h" + +class RemoteFilesystemClient { + String cache_path; + HashSet validated_directories; + +protected: + String _get_cache_path() { return cache_path; } + struct FileCache { + String path; // Local path (as in "folder/to/file.png") + uint64_t server_modified_time; // MD5 checksum. + uint64_t modified_time; + }; + virtual bool _is_configured() { return !cache_path.is_empty(); } + // Can be re-implemented per platform. If so, feel free to ignore get_cache_path() + virtual Vector _load_cache_file(); + virtual Error _store_file(const String &p_path, const LocalVector &p_file, uint64_t &modified_time); + virtual Error _remove_file(const String &p_path); + virtual Error _store_cache_file(const Vector &p_cache); + virtual Error _synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path); + + virtual void _update_cache_path(String &r_cache_path); + +public: + Error synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path); + virtual ~RemoteFilesystemClient() {} +}; + +#endif // REMOTE_FILESYSTEM_CLIENT_H diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 38f41d645c55d..2a7a675f2d0d4 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -445,13 +445,12 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { WARN_PRINT("Broken external resource! (index out of size)"); r_v = Variant(); } else { - if (external_resources[erindex].cache.is_null()) { - //cache not here yet, wait for it? - if (use_sub_threads) { - Error err; - external_resources.write[erindex].cache = ResourceLoader::load_threaded_get(external_resources[erindex].path, &err); - - if (err != OK || external_resources[erindex].cache.is_null()) { + Ref &load_token = external_resources.write[erindex].load_token; + if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies. + Error err; + Ref res = ResourceLoader::_load_complete(*load_token.ptr(), &err); + if (res.is_null()) { + if (!ResourceLoader::is_cleaning_tasks()) { if (!ResourceLoader::get_abort_on_missing_resources()) { ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type); } else { @@ -459,12 +458,11 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + "."); } } + } else { + r_v = res; } } - - r_v = external_resources[erindex].cache; } - } break; default: { ERR_FAIL_V(ERR_FILE_CORRUPT); @@ -684,28 +682,13 @@ Error ResourceLoaderBinary::load() { } external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap - - if (!use_sub_threads) { - external_resources.write[i].cache = ResourceLoader::load(path, external_resources[i].type); - - if (external_resources[i].cache.is_null()) { - if (!ResourceLoader::get_abort_on_missing_resources()) { - ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); - } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); - } - } - - } else { - Error err = ResourceLoader::load_threaded_request(path, external_resources[i].type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path); - if (err != OK) { - if (!ResourceLoader::get_abort_on_missing_resources()) { - ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); - } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); - } + external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + if (!external_resources[i].load_token.is_valid()) { + if (!ResourceLoader::get_abort_on_missing_resources()) { + ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); + } else { + error = ERR_FILE_MISSING_DEPENDENCIES; + ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); } } } @@ -937,8 +920,11 @@ void ResourceLoaderBinary::get_dependencies(Ref p_f, List *p for (int i = 0; i < external_resources.size(); i++) { String dep; + String fallback_path; + if (external_resources[i].uid != ResourceUID::INVALID_ID) { dep = ResourceUID::get_singleton()->id_to_text(external_resources[i].uid); + fallback_path = external_resources[i].path; // Used by Dependency Editor, in case uid path fails. } else { dep = external_resources[i].path; } @@ -946,6 +932,12 @@ void ResourceLoaderBinary::get_dependencies(Ref p_f, List *p if (p_add_types && !external_resources[i].type.is_empty()) { dep += "::" + external_resources[i].type; } + if (!fallback_path.is_empty()) { + if (!p_add_types) { + dep += "::"; // Ensure that path comes third, even if there is no type. + } + dep += "::" + fallback_path; + } p_dependencies->push_back(dep); } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index add7cdf297472..30f16649838c1 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -60,7 +60,7 @@ class ResourceLoaderBinary { String path; String type; ResourceUID::ID uid = ResourceUID::INVALID_ID; - Ref cache; + Ref load_token; }; bool using_named_scene_ids = false; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 9af3a7daedcfd..525c41cf872b8 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -202,20 +202,71 @@ void ResourceFormatLoader::_bind_methods() { /////////////////////////////////// +// This should be robust enough to be called redundantly without issues. +void ResourceLoader::LoadToken::clear() { + thread_load_mutex.lock(); + + WorkerThreadPool::TaskID task_to_await = 0; + + if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered. + DEV_ASSERT(thread_load_tasks.has(local_path)); + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + if (!load_task.awaited) { + task_to_await = load_task.task_id; + load_task.awaited = true; + } + thread_load_tasks.erase(local_path); + local_path.clear(); + } + + if (!user_path.is_empty()) { + DEV_ASSERT(user_load_tokens.has(user_path)); + user_load_tokens.erase(user_path); + user_path.clear(); + } + + thread_load_mutex.unlock(); + + // If task is unused, await it here, locally, now the token data is consistent. + if (task_to_await) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await); + } +} + +ResourceLoader::LoadToken::~LoadToken() { + clear(); +} + Ref ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) { - bool found = false; + load_nesting++; + if (load_paths_stack.size()) { + thread_load_mutex.lock(); + HashMap::Iterator E = thread_load_tasks.find(load_paths_stack[load_paths_stack.size() - 1]); + if (E) { + E->value.sub_tasks.insert(p_path); + } + thread_load_mutex.unlock(); + } + load_paths_stack.push_back(p_path); // Try all loaders and pick the first match for the type hint + bool found = false; + Ref res; for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(p_path, p_type_hint)) { continue; } found = true; - Ref res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); - if (res.is_null()) { - continue; + res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); + if (!res.is_null()) { + break; } + } + + load_paths_stack.resize(load_paths_stack.size() - 1); + load_nesting--; + if (!res.is_null()) { return res; } @@ -232,47 +283,64 @@ Ref ResourceLoader::_load(const String &p_path, const String &p_origin void ResourceLoader::_thread_load_function(void *p_userdata) { ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata; - load_task.loader_id = Thread::get_caller_id(); - if (load_task.cond_var) { - //this is an actual thread, so wait for Ok from semaphore - thread_load_semaphore->wait(); //wait until its ok to start loading + thread_load_mutex.lock(); + caller_task_id = load_task.task_id; + if (cleaning_tasks) { + load_task.status = THREAD_LOAD_FAILED; + thread_load_mutex.unlock(); + return; } - load_task.resource = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); + thread_load_mutex.unlock(); - load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 + // Thread-safe either if it's the current thread or a brand new one. + CallQueue *mq_override = nullptr; + if (load_nesting == 0) { + if (!load_task.dependent_path.is_empty()) { + load_paths_stack.push_back(load_task.dependent_path); + } + if (!Thread::is_main_thread()) { + mq_override = memnew(CallQueue); + MessageQueue::set_thread_singleton_override(mq_override); + set_current_thread_safe_for_nodes(true); + } + } else { + DEV_ASSERT(load_task.dependent_path.is_empty()); + } + // -- - thread_load_mutex->lock(); + Ref res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); + if (mq_override) { + mq_override->flush(); + } + + thread_load_mutex.lock(); + + load_task.resource = res; + + load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 if (load_task.error != OK) { load_task.status = THREAD_LOAD_FAILED; } else { load_task.status = THREAD_LOAD_LOADED; } - if (load_task.cond_var) { - if (load_task.start_next && thread_waiting_count > 0) { - thread_waiting_count--; - //thread loading count remains constant, this ends but another one begins - thread_load_semaphore->post(); - } else { - thread_loading_count--; //no threads waiting, just reduce loading count - } - - print_lt("END: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); + if (load_task.cond_var) { load_task.cond_var->notify_all(); memdelete(load_task.cond_var); load_task.cond_var = nullptr; } if (load_task.resource.is_valid()) { - load_task.resource->set_path(load_task.local_path); + if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + load_task.resource->set_path(load_task.local_path); + } if (load_task.xl_remapped) { load_task.resource->set_as_translation_remapped(true); } #ifdef TOOLS_ENABLED - load_task.resource->set_edited(false); if (timestamp_on_load) { uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path); @@ -286,7 +354,11 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { } } - thread_load_mutex->unlock(); + thread_load_mutex.unlock(); + + if (load_nesting == 0 && mq_override) { + memdelete(mq_override); + } } static String _validate_local_path(const String &p_path) { @@ -299,91 +371,127 @@ static String _validate_local_path(const String &p_path) { return ProjectSettings::get_singleton()->localize_path(p_path); } } -Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode, const String &p_source_resource) { - String local_path = _validate_local_path(p_path); - thread_load_mutex->lock(); +Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) { + thread_load_mutex.lock(); + if (user_load_tokens.has(p_path)) { + print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error."); + user_load_tokens[p_path]->reference(); // Additional request. + thread_load_mutex.unlock(); + return OK; + } + user_load_tokens[p_path] = nullptr; + thread_load_mutex.unlock(); + + Ref token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode); + if (token.is_valid()) { + thread_load_mutex.lock(); + token->user_path = p_path; + token->reference(); // First request. + user_load_tokens[p_path] = token.ptr(); + print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); + thread_load_mutex.unlock(); + return OK; + } else { + return FAILED; + } +} - if (!p_source_resource.is_empty()) { - //must be loading from this resource - if (!thread_load_tasks.has(p_source_resource)) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "There is no thread loading source resource '" + p_source_resource + "'."); - } - //must not be already added as s sub tasks - if (thread_load_tasks[p_source_resource].sub_tasks.has(local_path)) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Thread loading source resource '" + p_source_resource + "' already is loading '" + local_path + "'."); - } +Ref ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { + if (r_error) { + *r_error = OK; } - if (thread_load_tasks.has(local_path)) { - thread_load_tasks[local_path].requests++; - if (!p_source_resource.is_empty()) { - thread_load_tasks[p_source_resource].sub_tasks.insert(local_path); + Ref load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode); + if (!load_token.is_valid()) { + if (r_error) { + *r_error = FAILED; } - thread_load_mutex->unlock(); - return OK; + return Ref(); } - { - //create load task - - ThreadLoadTask load_task; + Ref res = _load_complete(*load_token.ptr(), r_error); + return res; +} - load_task.requests = 1; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); - load_task.local_path = local_path; - load_task.type_hint = p_type_hint; - load_task.cache_mode = p_cache_mode; - load_task.use_sub_threads = p_use_sub_threads; +Ref ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) { + String local_path = _validate_local_path(p_path); - { //must check if resource is already loaded before attempting to load it in a thread + Ref load_token; + bool must_not_register = false; + ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load. + ThreadLoadTask *load_task_ptr = nullptr; + bool run_on_current_thread = false; + { + MutexLock thread_load_lock(thread_load_mutex); - if (load_task.loader_id == Thread::get_caller_id()) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?"); + if (thread_load_tasks.has(local_path)) { + load_token = Ref(thread_load_tasks[local_path].load_token); + if (!load_token.is_valid()) { + // The token is dying (reached 0 on another thread). + // Ensure it's killed now so the path can be safely reused right away. + thread_load_tasks[local_path].load_token->clear(); + } else { + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + return load_token; + } } + } - Ref existing = ResourceCache::get_ref(local_path); + load_token.instantiate(); + load_token->local_path = local_path; - if (existing.is_valid()) { - //referencing is fine - load_task.resource = existing; - load_task.status = THREAD_LOAD_LOADED; - load_task.progress = 1.0; + //create load task + { + ThreadLoadTask load_task; + + load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); + load_task.load_token = load_token.ptr(); + load_task.local_path = local_path; + load_task.type_hint = p_type_hint; + load_task.cache_mode = p_cache_mode; + load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE; + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + Ref existing = ResourceCache::get_ref(local_path); + if (existing.is_valid()) { + //referencing is fine + load_task.resource = existing; + load_task.status = THREAD_LOAD_LOADED; + load_task.progress = 1.0; + thread_load_tasks[local_path] = load_task; + return load_token; + } } - } - if (!p_source_resource.is_empty()) { - thread_load_tasks[p_source_resource].sub_tasks.insert(local_path); - } - - thread_load_tasks[local_path] = load_task; - } + // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish unconditionally synchronously. + must_not_register = thread_load_tasks.has(local_path) && p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE; + if (must_not_register) { + load_token->local_path.clear(); + unregistered_load_task = load_task; + } else { + thread_load_tasks[local_path] = load_task; + } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; + load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path]; + } - if (load_task.resource.is_null()) { //needs to be loaded in thread + run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT; - load_task.cond_var = memnew(ConditionVariable); - if (thread_loading_count < thread_load_max) { - thread_loading_count++; - thread_load_semaphore->post(); //we have free threads, so allow one + if (run_on_current_thread) { + load_task_ptr->thread_id = Thread::get_caller_id(); } else { - thread_waiting_count++; + load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr); } - - print_lt("REQUEST: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - - load_task.thread = memnew(Thread); - load_task.thread->start(_thread_load_function, &thread_load_tasks[local_path]); - load_task.loader_id = load_task.thread->get_id(); } - thread_load_mutex->unlock(); + if (run_on_current_thread) { + _thread_load_function(load_task_ptr); + if (must_not_register) { + load_token->res_if_unregistered = load_task_ptr->resource; + } + } - return OK; + return load_token; } float ResourceLoader::_dependency_get_progress(const String &p_path) { @@ -409,13 +517,22 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - String local_path = _validate_local_path(p_path); + MutexLock thread_load_lock(thread_load_mutex); + + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); + return THREAD_LOAD_INVALID_RESOURCE; + } - thread_load_mutex->lock(); + String local_path = _validate_local_path(p_path); if (!thread_load_tasks.has(local_path)) { - thread_load_mutex->unlock(); +#ifdef DEV_ENABLED + CRASH_NOW(); +#endif + // On non-dev, be defensive and at least avoid crashing (at this point at least). return THREAD_LOAD_INVALID_RESOURCE; } + ThreadLoadTask &load_task = thread_load_tasks[local_path]; ThreadLoadStatus status; status = load_task.status; @@ -423,198 +540,139 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const *r_progress = _dependency_get_progress(local_path); } - thread_load_mutex->unlock(); - return status; } Ref ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { - String local_path = _validate_local_path(p_path); - - MutexLock thread_load_lock(*thread_load_mutex); - if (!thread_load_tasks.has(local_path)) { - if (r_error) { - *r_error = ERR_INVALID_PARAMETER; - } - return Ref(); + if (r_error) { + *r_error = OK; } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; + Ref res; + { + MutexLock thread_load_lock(thread_load_mutex); - if (load_task.status == THREAD_LOAD_IN_PROGRESS) { - if (load_task.loader_id == Thread::get_caller_id()) { - // Load is in progress, but it's precisely this thread the one in charge. - // That means this is a cyclic load. + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); if (r_error) { - *r_error = ERR_BUSY; + *r_error = ERR_INVALID_PARAMETER; } return Ref(); - } else if (!load_task.cond_var) { - // Load is in progress, but a condition variable was never created for it. - // That happens when a load has been initiated with subthreads disabled, - // but now another load thread needs to interact with this one (either - // because of subthreads being used this time, or because it's simply a - // threaded load running on a different thread). - // Since we want to be notified when the load ends, we must create the - // condition variable now. - load_task.cond_var = memnew(ConditionVariable); - } - } - - //cond var still exists, meaning it's still loading, request poll - if (load_task.cond_var) { - { - // As we got a cond var, this means we are going to have to wait - // until the sub-resource is done loading - // - // As this thread will become 'blocked' we should "exchange" its - // active status with a waiting one, to ensure load continues. - // - // This ensures loading is never blocked and that is also within - // the maximum number of active threads. - - if (thread_waiting_count > 0) { - thread_waiting_count--; - thread_loading_count++; - thread_load_semaphore->post(); - - load_task.start_next = false; //do not start next since we are doing it here - } - - thread_suspended_count++; - - print_lt("GET: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - } - - bool still_valid = true; - bool was_thread = load_task.thread; - do { - load_task.cond_var->wait(thread_load_lock); - if (!thread_load_tasks.has(local_path)) { //may have been erased during unlock and this was always an invalid call - still_valid = false; - break; - } - } while (load_task.cond_var); // In case of spurious wakeup. - - if (was_thread) { - thread_suspended_count--; } - if (!still_valid) { + LoadToken *load_token = user_load_tokens[p_path]; + if (!load_token) { + // This happens if requested from one thread and rapidly querying from another. if (r_error) { - *r_error = ERR_INVALID_PARAMETER; + *r_error = ERR_BUSY; } return Ref(); } + res = _load_complete_inner(*load_token, r_error, thread_load_lock); + if (load_token->unreference()) { + memdelete(load_token); + } } - Ref resource = load_task.resource; - if (r_error) { - *r_error = load_task.error; - } - - load_task.requests--; + print_lt("GET: user load tokens: " + itos(user_load_tokens.size())); - if (load_task.requests == 0) { - if (load_task.thread) { //thread may not have been used - load_task.thread->wait_to_finish(); - memdelete(load_task.thread); - } - thread_load_tasks.erase(local_path); - } + return res; +} - return resource; +Ref ResourceLoader::_load_complete(LoadToken &p_load_token, Error *r_error) { + MutexLock thread_load_lock(thread_load_mutex); + return _load_complete_inner(p_load_token, r_error, thread_load_lock); } -Ref ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { +Ref ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock> &p_thread_load_lock) { if (r_error) { - *r_error = ERR_CANT_OPEN; + *r_error = OK; } - String local_path = _validate_local_path(p_path); + if (!p_load_token.local_path.is_empty()) { + if (!thread_load_tasks.has(p_load_token.local_path)) { +#ifdef DEV_ENABLED + CRASH_NOW(); +#endif + // On non-dev, be defensive and at least avoid crashing (at this point at least). + if (r_error) { + *r_error = ERR_BUG; + } + return Ref(); + } - if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - thread_load_mutex->lock(); + ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path]; - //Is it already being loaded? poll until done - if (thread_load_tasks.has(local_path)) { - Error err = load_threaded_request(p_path, p_type_hint); - if (err != OK) { + if (load_task.status == THREAD_LOAD_IN_PROGRESS) { + DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0)); + + if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) || + (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) { + // Load is in progress, but it's precisely this thread the one in charge. + // That means this is a cyclic load. if (r_error) { - *r_error = err; + *r_error = ERR_BUSY; } - thread_load_mutex->unlock(); return Ref(); } - thread_load_mutex->unlock(); - - return load_threaded_get(p_path, r_error); - } - //Is it cached? - - Ref existing = ResourceCache::get_ref(local_path); - - if (existing.is_valid()) { - thread_load_mutex->unlock(); - - if (r_error) { - *r_error = OK; + if (load_task.task_id != 0) { + // Loading thread is in the worker pool. + load_task.awaited = true; + thread_load_mutex.unlock(); + Error err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + if (err == ERR_BUSY) { + // The WorkerThreadPool has scheduled tasks in a way that the current load depends on + // another one in a lower stack frame. Restart such load here. When the stack is eventually + // unrolled, the original load will have been notified to go on. +#ifdef DEV_ENABLED + print_verbose("ResourceLoader: Load task happened to wait on another one deep in the call stack. Attempting to avoid deadlock by re-issuing the load now."); +#endif + // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's + // an ongoing load for that resource and wait for it again. This value forces a new load. + Ref token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE); + Ref resource = _load_complete(*token.ptr(), &err); + if (r_error) { + *r_error = err; + } + thread_load_mutex.lock(); + return resource; + } else { + DEV_ASSERT(err == OK); + thread_load_mutex.lock(); + } + } else { + // Loading thread is main or user thread. + if (!load_task.cond_var) { + load_task.cond_var = memnew(ConditionVariable); + } + do { + load_task.cond_var->wait(p_thread_load_lock); + DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); + } while (load_task.cond_var); } - - return existing; //use cached - } - - //load using task (but this thread) - ThreadLoadTask load_task; - - load_task.requests = 1; - load_task.local_path = local_path; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); - load_task.type_hint = p_type_hint; - load_task.cache_mode = p_cache_mode; //ignore - load_task.loader_id = Thread::get_caller_id(); - - thread_load_tasks[local_path] = load_task; - - thread_load_mutex->unlock(); - - _thread_load_function(&thread_load_tasks[local_path]); - - return load_threaded_get(p_path, r_error); - - } else { - bool xl_remapped = false; - String path = _path_remap(local_path, &xl_remapped); - - if (path.is_empty()) { - ERR_FAIL_V_MSG(Ref(), "Remapping '" + local_path + "' failed."); } - print_verbose("Loading resource: " + path); - float p; - Ref res = _load(path, local_path, p_type_hint, p_cache_mode, r_error, false, &p); - - if (res.is_null()) { - print_verbose("Failed loading resource: " + path); - return Ref(); + if (cleaning_tasks) { + load_task.resource = Ref(); + load_task.error = FAILED; } - if (xl_remapped) { - res->set_as_translation_remapped(true); + Ref resource = load_task.resource; + if (r_error) { + *r_error = load_task.error; } - -#ifdef TOOLS_ENABLED - - res->set_edited(false); - if (timestamp_on_load) { - uint64_t mt = FileAccess::get_modified_time(path); - //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt); - res->set_last_modified_time(mt); + return resource; + } else { + // Special case of an unregistered task. + // The resource should have been loaded by now. + Ref resource = p_load_token.res_if_unregistered; + if (!resource.is_valid()) { + if (r_error) { + *r_error = FAILED; + } } -#endif - - return res; + return resource; } } @@ -958,32 +1016,42 @@ void ResourceLoader::clear_translation_remaps() { } void ResourceLoader::clear_thread_load_tasks() { - thread_load_mutex->lock(); - - for (KeyValue &E : thread_load_tasks) { - switch (E.value.status) { - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED: { - E.value.resource = Ref(); - } break; - - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS: { - if (E.value.thread != nullptr) { - E.value.thread->wait_to_finish(); - memdelete(E.value.thread); - E.value.thread = nullptr; + // Bring the thing down as quickly as possible without causing deadlocks or leaks. + + thread_load_mutex.lock(); + cleaning_tasks = true; + + while (true) { + bool none_running = true; + if (thread_load_tasks.size()) { + for (KeyValue &E : thread_load_tasks) { + if (E.value.status == THREAD_LOAD_IN_PROGRESS) { + if (E.value.cond_var) { + E.value.cond_var->notify_all(); + memdelete(E.value.cond_var); + E.value.cond_var = nullptr; + } + none_running = false; } - E.value.resource = Ref(); - } break; - - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED: - default: { - // do nothing } } + if (none_running) { + break; + } + thread_load_mutex.unlock(); + OS::get_singleton()->delay_usec(1000); + thread_load_mutex.lock(); + } + + for (KeyValue &E : user_load_tokens) { + memdelete(E.value); } + user_load_tokens.clear(); + thread_load_tasks.clear(); - thread_load_mutex->unlock(); + cleaning_tasks = false; + thread_load_mutex.unlock(); } void ResourceLoader::load_path_remaps() { @@ -1080,41 +1148,33 @@ void ResourceLoader::remove_custom_loaders() { } } -void ResourceLoader::initialize() { - thread_load_mutex = memnew(SafeBinaryMutex); - thread_load_max = OS::get_singleton()->get_processor_count(); - thread_loading_count = 0; - thread_waiting_count = 0; - thread_suspended_count = 0; - thread_load_semaphore = memnew(Semaphore); +bool ResourceLoader::is_cleaning_tasks() { + MutexLock lock(thread_load_mutex); + return cleaning_tasks; } -void ResourceLoader::finalize() { - clear_thread_load_tasks(); - memdelete(thread_load_mutex); - memdelete(thread_load_semaphore); -} +void ResourceLoader::initialize() {} -ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr; -void *ResourceLoader::err_notify_ud = nullptr; +void ResourceLoader::finalize() {} +ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr; DependencyErrorNotify ResourceLoader::dep_err_notify = nullptr; -void *ResourceLoader::dep_err_notify_ud = nullptr; bool ResourceLoader::create_missing_resources_if_class_unavailable = false; bool ResourceLoader::abort_on_missing_resource = true; bool ResourceLoader::timestamp_on_load = false; +thread_local int ResourceLoader::load_nesting = 0; +thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; +thread_local Vector ResourceLoader::load_paths_stack; + template <> thread_local uint32_t SafeBinaryMutex::count = 0; -SafeBinaryMutex *ResourceLoader::thread_load_mutex = nullptr; +SafeBinaryMutex ResourceLoader::thread_load_mutex; HashMap ResourceLoader::thread_load_tasks; -Semaphore *ResourceLoader::thread_load_semaphore = nullptr; +bool ResourceLoader::cleaning_tasks = false; -int ResourceLoader::thread_loading_count = 0; -int ResourceLoader::thread_waiting_count = 0; -int ResourceLoader::thread_suspended_count = 0; -int ResourceLoader::thread_load_max = 0; +HashMap ResourceLoader::user_load_tokens; SelfList::List ResourceLoader::remapped_list; HashMap> ResourceLoader::translation_remaps; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 72c1f906531cf..592befb6039c3 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" #include "core/object/gdvirtual.gen.inc" #include "core/object/script_language.h" +#include "core/object/worker_thread_pool.h" #include "core/os/semaphore.h" #include "core/os/thread.h" @@ -88,8 +89,8 @@ class ResourceFormatLoader : public RefCounted { VARIANT_ENUM_CAST(ResourceFormatLoader::CacheMode) -typedef void (*ResourceLoadErrorNotify)(void *p_ud, const String &p_text); -typedef void (*DependencyErrorNotify)(void *p_ud, const String &p_loading, const String &p_which, const String &p_type); +typedef void (*ResourceLoadErrorNotify)(const String &p_text); +typedef void (*DependencyErrorNotify)(const String &p_loading, const String &p_which, const String &p_type); typedef Error (*ResourceLoaderImport)(const String &p_path); typedef void (*ResourceLoadedCallback)(Ref p_resource, const String &p_path); @@ -107,9 +108,30 @@ class ResourceLoader { THREAD_LOAD_LOADED }; + enum LoadThreadMode { + LOAD_THREAD_FROM_CURRENT, + LOAD_THREAD_SPAWN_SINGLE, + LOAD_THREAD_DISTRIBUTE, + }; + + struct LoadToken : public RefCounted { + String local_path; + String user_path; + Ref res_if_unregistered; + + void clear(); + + virtual ~LoadToken(); + }; + static const int BINARY_MUTEX_TAG = 1; + static Ref _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode); + static Ref _load_complete(LoadToken &p_load_token, Error *r_error); + private: + static Ref _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock> &p_thread_load_lock); + static Ref loader[MAX_LOADERS]; static int loader_count; static bool timestamp_on_load; @@ -129,8 +151,7 @@ class ResourceLoader { static SelfList::List remapped_list; friend class ResourceFormatImporter; - friend class ResourceInteractiveLoader; - // Internal load function. + static Ref _load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress); static ResourceLoadedCallback _loaded_callback; @@ -138,11 +159,14 @@ class ResourceLoader { static Ref _find_custom_resource_format_loader(String path); struct ThreadLoadTask { - Thread *thread = nullptr; - Thread::ID loader_id = 0; - ConditionVariable *cond_var = nullptr; + WorkerThreadPool::TaskID task_id = 0; // Used if run on a worker thread from the pool. + Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load). + bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread. + ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism. + LoadToken *load_token = nullptr; String local_path; String remapped_path; + String dependent_path; String type_hint; float progress = 0.0; ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; @@ -151,27 +175,29 @@ class ResourceLoader { Ref resource; bool xl_remapped = false; bool use_sub_threads = false; - bool start_next = true; - int requests = 0; HashSet sub_tasks; }; static void _thread_load_function(void *p_userdata); - static SafeBinaryMutex *thread_load_mutex; + + static thread_local int load_nesting; + static thread_local WorkerThreadPool::TaskID caller_task_id; + static thread_local Vector load_paths_stack; + static SafeBinaryMutex thread_load_mutex; static HashMap thread_load_tasks; - static Semaphore *thread_load_semaphore; - static int thread_waiting_count; - static int thread_loading_count; - static int thread_suspended_count; - static int thread_load_max; + static bool cleaning_tasks; + + static HashMap user_load_tokens; static float _dependency_get_progress(const String &p_path); public: - static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, const String &p_source_resource = String()); + static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); static Ref load_threaded_get(const String &p_path, Error *r_error = nullptr); + static bool is_within_load() { return load_nesting > 0; }; + static Ref load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr); static bool exists(const String &p_path, const String &p_type_hint = ""); @@ -192,24 +218,24 @@ class ResourceLoader { static void set_timestamp_on_load(bool p_timestamp) { timestamp_on_load = p_timestamp; } static bool get_timestamp_on_load() { return timestamp_on_load; } + // Loaders can safely use this regardless which thread they are running on. static void notify_load_error(const String &p_err) { if (err_notify) { - err_notify(err_notify_ud, p_err); + callable_mp_static(err_notify).bind(p_err).call_deferred(); } } - static void set_error_notify_func(void *p_ud, ResourceLoadErrorNotify p_err_notify) { + static void set_error_notify_func(ResourceLoadErrorNotify p_err_notify) { err_notify = p_err_notify; - err_notify_ud = p_ud; } + // Loaders can safely use this regardless which thread they are running on. static void notify_dependency_error(const String &p_path, const String &p_dependency, const String &p_type) { if (dep_err_notify) { - dep_err_notify(dep_err_notify_ud, p_path, p_dependency, p_type); + callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type).call_deferred(); } } - static void set_dependency_error_notify_func(void *p_ud, DependencyErrorNotify p_err_notify) { + static void set_dependency_error_notify_func(DependencyErrorNotify p_err_notify) { dep_err_notify = p_err_notify; - dep_err_notify_ud = p_ud; } static void set_abort_on_missing_resources(bool p_abort) { abort_on_missing_resource = p_abort; } @@ -237,6 +263,8 @@ class ResourceLoader { static void set_create_missing_resources_if_class_unavailable(bool p_enable); _FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; } + static bool is_cleaning_tasks(); + static void initialize(); static void finalize(); }; diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp index 5c0a017bfc9b2..958734addf7ef 100644 --- a/core/io/xml_parser.cpp +++ b/core/io/xml_parser.cpp @@ -34,8 +34,6 @@ //#define DEBUG_XML -VARIANT_ENUM_CAST(XMLParser::NodeType); - static inline bool _is_white_space(char c) { return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); } diff --git a/core/io/xml_parser.h b/core/io/xml_parser.h index b96478c7a5799..77df99a881b4f 100644 --- a/core/io/xml_parser.h +++ b/core/io/xml_parser.h @@ -126,4 +126,6 @@ class XMLParser : public RefCounted { ~XMLParser(); }; +VARIANT_ENUM_CAST(XMLParser::NodeType); + #endif // XML_PARSER_H diff --git a/core/io/zip_io.cpp b/core/io/zip_io.cpp index 7f60039578850..a0e6bd62de82d 100644 --- a/core/io/zip_io.cpp +++ b/core/io/zip_io.cpp @@ -30,6 +30,48 @@ #include "zip_io.h" +#include "core/templates/local_vector.h" + +int godot_unzip_get_current_file_info(unzFile p_zip_file, unz_file_info64 &r_file_info, String &r_filepath) { + const uLong short_file_path_buffer_size = 16384ul; + char short_file_path_buffer[short_file_path_buffer_size]; + + int err = unzGetCurrentFileInfo64(p_zip_file, &r_file_info, short_file_path_buffer, short_file_path_buffer_size, nullptr, 0, nullptr, 0); + if (unlikely((err != UNZ_OK) || (r_file_info.size_filename > short_file_path_buffer_size))) { + LocalVector long_file_path_buffer; + long_file_path_buffer.resize(r_file_info.size_filename); + + err = unzGetCurrentFileInfo64(p_zip_file, &r_file_info, long_file_path_buffer.ptr(), long_file_path_buffer.size(), nullptr, 0, nullptr, 0); + if (err != UNZ_OK) { + return err; + } + r_filepath = String::utf8(long_file_path_buffer.ptr(), r_file_info.size_filename); + } else { + r_filepath = String::utf8(short_file_path_buffer, r_file_info.size_filename); + } + + return err; +} + +int godot_unzip_locate_file(unzFile p_zip_file, String p_filepath, bool p_case_sensitive) { + int err = unzGoToFirstFile(p_zip_file); + while (err == UNZ_OK) { + unz_file_info64 current_file_info; + String current_filepath; + err = godot_unzip_get_current_file_info(p_zip_file, current_file_info, current_filepath); + if (err == UNZ_OK) { + bool filepaths_are_equal = p_case_sensitive ? (p_filepath == current_filepath) : (p_filepath.nocasecmp_to(current_filepath) == 0); + if (filepaths_are_equal) { + return UNZ_OK; + } + err = unzGoToNextFile(p_zip_file); + } + } + return err; +} + +// + void *zipio_open(voidpf opaque, const char *p_fname, int mode) { Ref *fa = reinterpret_cast *>(opaque); ERR_FAIL_COND_V(fa == nullptr, nullptr); @@ -38,17 +80,17 @@ void *zipio_open(voidpf opaque, const char *p_fname, int mode) { fname.parse_utf8(p_fname); int file_access_mode = 0; - if (mode & ZLIB_FILEFUNC_MODE_WRITE) { - file_access_mode |= FileAccess::WRITE; - } if (mode & ZLIB_FILEFUNC_MODE_READ) { file_access_mode |= FileAccess::READ; } + if (mode & ZLIB_FILEFUNC_MODE_WRITE) { + file_access_mode |= FileAccess::WRITE; + } if (mode & ZLIB_FILEFUNC_MODE_CREATE) { file_access_mode |= FileAccess::WRITE_READ; } - (*fa) = FileAccess::open(fname, file_access_mode); + (*fa) = FileAccess::open(fname, file_access_mode); if (fa->is_null()) { return nullptr; } diff --git a/core/io/zip_io.h b/core/io/zip_io.h index 094d490bcf656..c59b98137306d 100644 --- a/core/io/zip_io.h +++ b/core/io/zip_io.h @@ -39,6 +39,13 @@ #include "thirdparty/minizip/unzip.h" #include "thirdparty/minizip/zip.h" +// Get the current file info and safely convert the full filepath to a String. +int godot_unzip_get_current_file_info(unzFile p_zip_file, unz_file_info64 &r_file_info, String &r_filepath); +// Try to locate the file in the archive specified by the filepath (works with large paths and Unicode). +int godot_unzip_locate_file(unzFile p_zip_file, String p_filepath, bool p_case_sensitive = true); + +// + void *zipio_open(voidpf opaque, const char *p_fname, int mode); uLong zipio_read(voidpf opaque, voidpf stream, void *buf, uLong size); uLong zipio_write(voidpf opaque, voidpf stream, const void *buf, uLong size); diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 95a4187062f39..6b0ecadc7fdb1 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -397,7 +397,7 @@ void Basis::rotate_to_align(Vector3 p_start_direction, Vector3 p_end_direction) real_t dot = p_start_direction.dot(p_end_direction); dot = CLAMP(dot, -1.0f, 1.0f); const real_t angle_rads = Math::acos(dot); - set_axis_angle(axis, angle_rads); + *this = Basis(axis, angle_rads) * (*this); } } @@ -807,8 +807,8 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const { z = (rows[1][0] - rows[0][1]) / s; r_axis = Vector3(x, y, z); - // CLAMP to avoid NaN if the value passed to acos is not in [0,1]. - r_angle = Math::acos(CLAMP((rows[0][0] + rows[1][1] + rows[2][2] - 1) / 2, (real_t)0.0, (real_t)1.0)); + // acos does clamping. + r_angle = Math::acos((rows[0][0] + rows[1][1] + rows[2][2] - 1) / 2); } void Basis::set_quaternion(const Quaternion &p_quaternion) { @@ -1016,12 +1016,15 @@ void Basis::rotate_sh(real_t *p_values) { p_values[8] = d4 * s_scale_dst4; } -Basis Basis::looking_at(const Vector3 &p_target, const Vector3 &p_up) { +Basis Basis::looking_at(const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) { #ifdef MATH_CHECKS ERR_FAIL_COND_V_MSG(p_target.is_zero_approx(), Basis(), "The target vector can't be zero."); ERR_FAIL_COND_V_MSG(p_up.is_zero_approx(), Basis(), "The up vector can't be zero."); #endif - Vector3 v_z = -p_target.normalized(); + Vector3 v_z = p_target.normalized(); + if (!p_use_model_front) { + v_z = -v_z; + } Vector3 v_x = p_up.cross(v_z); #ifdef MATH_CHECKS ERR_FAIL_COND_V_MSG(v_x.is_zero_approx(), Basis(), "The target vector and up vector can't be parallel to each other."); diff --git a/core/math/basis.h b/core/math/basis.h index bbc1d404697e8..1a68bee6861a3 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -217,7 +217,7 @@ struct _NO_DISCARD_ Basis { operator Quaternion() const { return get_quaternion(); } - static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0)); + static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false); Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); }; Basis(const Quaternion &p_quaternion, const Vector3 &p_scale) { set_quaternion_scale(p_quaternion, p_scale); } diff --git a/core/math/bvh_debug.inc b/core/math/bvh_debug.inc index 2e519ceb3d498..1964f2fa830e7 100644 --- a/core/math/bvh_debug.inc +++ b/core/math/bvh_debug.inc @@ -30,11 +30,7 @@ String _debug_aabb_to_string(const BVHABB_CLASS &aabb) const { void _debug_recursive_print_tree_node(uint32_t p_node_id, int depth = 0) const { const TNode &tnode = _nodes[p_node_id]; - String sz = ""; - for (int n = 0; n < depth; n++) { - sz += "\t"; - } - sz += itos(p_node_id); + String sz = String("\t").repeat(depth) + itos(p_node_id); if (tnode.is_leaf()) { sz += " L"; diff --git a/core/math/color.cpp b/core/math/color.cpp index 0d9325f2362c3..d36306d9687c5 100644 --- a/core/math/color.cpp +++ b/core/math/color.cpp @@ -247,8 +247,7 @@ void Color::set_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { hsl.h = p_h; hsl.s = p_s; hsl.l = p_l; - ok_color new_ok_color; - ok_color::RGB rgb = new_ok_color.okhsl_to_srgb(hsl); + ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl); Color c = Color(rgb.r, rgb.g, rgb.b, p_alpha).clamp(); r = c.r; g = c.g; @@ -595,8 +594,7 @@ float Color::get_ok_hsl_h() const { rgb.r = r; rgb.g = g; rgb.b = b; - ok_color new_ok_color; - ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb); + ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb); if (Math::is_nan(ok_hsl.h)) { return 0.0f; } @@ -608,8 +606,7 @@ float Color::get_ok_hsl_s() const { rgb.r = r; rgb.g = g; rgb.b = b; - ok_color new_ok_color; - ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb); + ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb); if (Math::is_nan(ok_hsl.s)) { return 0.0f; } @@ -621,8 +618,7 @@ float Color::get_ok_hsl_l() const { rgb.r = r; rgb.g = g; rgb.b = b; - ok_color new_ok_color; - ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb); + ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb); if (Math::is_nan(ok_hsl.l)) { return 0.0f; } diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp index a03438a339c8a..f8456ec998048 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -596,9 +596,9 @@ class ConvexHullInternal { } }; - enum Orientation { NONE, - CLOCKWISE, - COUNTER_CLOCKWISE }; + enum Orientation { ORIENTATION_NONE, + ORIENTATION_CLOCKWISE, + ORIENTATION_COUNTER_CLOCKWISE }; Vector3 scaling; Vector3 center; @@ -658,7 +658,7 @@ class ConvexHullInternal { Vector3 get_gd_normal(Face *p_face); - bool shift_face(Face *p_face, real_t p_amount, LocalVector p_stack); + bool shift_face(Face *p_face, real_t p_amount, LocalVector &p_stack); public: ~ConvexHullInternal() { @@ -1140,13 +1140,13 @@ ConvexHullInternal::Orientation ConvexHullInternal::get_orientation(const Edge * CHULL_ASSERT(!m.is_zero()); int64_t dot = n.dot(m); CHULL_ASSERT(dot != 0); - return (dot > 0) ? COUNTER_CLOCKWISE : CLOCKWISE; + return (dot > 0) ? ORIENTATION_COUNTER_CLOCKWISE : ORIENTATION_CLOCKWISE; } - return COUNTER_CLOCKWISE; + return ORIENTATION_COUNTER_CLOCKWISE; } else if (p_prev->prev == p_next) { - return CLOCKWISE; + return ORIENTATION_CLOCKWISE; } else { - return NONE; + return ORIENTATION_NONE; } } @@ -1176,7 +1176,7 @@ ConvexHullInternal::Edge *ConvexHullInternal::find_max_angle(bool p_ccw, const V } else if ((cmp = cot.compare(p_min_cot)) < 0) { p_min_cot = cot; min_edge = e; - } else if ((cmp == 0) && (p_ccw == (get_orientation(min_edge, e, p_s, t) == COUNTER_CLOCKWISE))) { + } else if ((cmp == 0) && (p_ccw == (get_orientation(min_edge, e, p_s, t) == ORIENTATION_COUNTER_CLOCKWISE))) { min_edge = e; } } @@ -1375,7 +1375,7 @@ void ConvexHullInternal::merge(IntermediateHull &p_h0, IntermediateHull &p_h1) { int64_t dot = (*e->target - *c0).dot(normal); CHULL_ASSERT(dot <= 0); if ((dot == 0) && ((*e->target - *c0).dot(t) > 0)) { - if (!start0 || (get_orientation(start0, e, s, Point32(0, 0, -1)) == CLOCKWISE)) { + if (!start0 || (get_orientation(start0, e, s, Point32(0, 0, -1)) == ORIENTATION_CLOCKWISE)) { start0 = e; } } @@ -1390,7 +1390,7 @@ void ConvexHullInternal::merge(IntermediateHull &p_h0, IntermediateHull &p_h1) { int64_t dot = (*e->target - *c1).dot(normal); CHULL_ASSERT(dot <= 0); if ((dot == 0) && ((*e->target - *c1).dot(t) > 0)) { - if (!start1 || (get_orientation(start1, e, s, Point32(0, 0, -1)) == COUNTER_CLOCKWISE)) { + if (!start1 || (get_orientation(start1, e, s, Point32(0, 0, -1)) == ORIENTATION_COUNTER_CLOCKWISE)) { start1 = e; } } @@ -1775,7 +1775,7 @@ real_t ConvexHullInternal::shrink(real_t p_amount, real_t p_clamp_amount) { return p_amount; } -bool ConvexHullInternal::shift_face(Face *p_face, real_t p_amount, LocalVector p_stack) { +bool ConvexHullInternal::shift_face(Face *p_face, real_t p_amount, LocalVector &p_stack) { Vector3 orig_shift = get_gd_normal(p_face) * -p_amount; if (scaling[0] != 0) { orig_shift[0] /= scaling[0]; diff --git a/core/math/delaunay_2d.h b/core/math/delaunay_2d.h index cf7ba8d48ed28..fc70724308ea4 100644 --- a/core/math/delaunay_2d.h +++ b/core/math/delaunay_2d.h @@ -145,7 +145,7 @@ class Delaunay2D { // Filter out the triangles containing vertices of the bounding triangle. int preserved_count = 0; Triangle *triangles_ptrw = triangles.ptrw(); - for (int i = 0; i < triangles.size() - 1; i++) { + for (int i = 0; i < triangles.size(); i++) { if (!(triangles[i].points[0] >= point_count || triangles[i].points[1] >= point_count || triangles[i].points[2] >= point_count)) { triangles_ptrw[preserved_count] = triangles[i]; preserved_count++; diff --git a/core/math/geometry_3d.cpp b/core/math/geometry_3d.cpp index 590483bf20206..9dd88485f909b 100644 --- a/core/math/geometry_3d.cpp +++ b/core/math/geometry_3d.cpp @@ -30,7 +30,6 @@ #include "geometry_3d.h" -#include "thirdparty/misc/clipper.hpp" #include "thirdparty/misc/polypartition.h" void Geometry3D::get_closest_points_between_segments(const Vector3 &p_p0, const Vector3 &p_p1, const Vector3 &p_q0, const Vector3 &p_q1, Vector3 &r_ps, Vector3 &r_qt) { diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 078320d620301..f96d3a909fba8 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -74,11 +74,13 @@ class Math { static _ALWAYS_INLINE_ double tanh(double p_x) { return ::tanh(p_x); } static _ALWAYS_INLINE_ float tanh(float p_x) { return ::tanhf(p_x); } - static _ALWAYS_INLINE_ double asin(double p_x) { return ::asin(p_x); } - static _ALWAYS_INLINE_ float asin(float p_x) { return ::asinf(p_x); } + // Always does clamping so always safe to use. + static _ALWAYS_INLINE_ double asin(double p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asin(p_x)); } + static _ALWAYS_INLINE_ float asin(float p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asinf(p_x)); } - static _ALWAYS_INLINE_ double acos(double p_x) { return ::acos(p_x); } - static _ALWAYS_INLINE_ float acos(float p_x) { return ::acosf(p_x); } + // Always does clamping so always safe to use. + static _ALWAYS_INLINE_ double acos(double p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acos(p_x)); } + static _ALWAYS_INLINE_ float acos(float p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acosf(p_x)); } static _ALWAYS_INLINE_ double atan(double p_x) { return ::atan(p_x); } static _ALWAYS_INLINE_ float atan(float p_x) { return ::atanf(p_x); } diff --git a/core/math/plane.cpp b/core/math/plane.cpp index 99694d787126a..6b9bcea081315 100644 --- a/core/math/plane.cpp +++ b/core/math/plane.cpp @@ -98,13 +98,11 @@ bool Plane::intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, Vector3 Vector3 segment = p_dir; real_t den = normal.dot(segment); - //printf("den is %i\n",den); if (Math::is_zero_approx(den)) { return false; } real_t dist = (normal.dot(p_from) - d) / den; - //printf("dist is %i\n",dist); if (dist > (real_t)CMP_EPSILON) { //this is a ray, before the emitting pos (p_from) doesn't exist @@ -121,13 +119,11 @@ bool Plane::intersects_segment(const Vector3 &p_begin, const Vector3 &p_end, Vec Vector3 segment = p_begin - p_end; real_t den = normal.dot(segment); - //printf("den is %i\n",den); if (Math::is_zero_approx(den)) { return false; } real_t dist = (normal.dot(p_begin) - d) / den; - //printf("dist is %i\n",dist); if (dist < (real_t)-CMP_EPSILON || dist > (1.0f + (real_t)CMP_EPSILON)) { return false; diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index 34e212a5b65b6..e4ad17c8ef89a 100644 --- a/core/math/quaternion.cpp +++ b/core/math/quaternion.cpp @@ -35,7 +35,8 @@ real_t Quaternion::angle_to(const Quaternion &p_to) const { real_t d = dot(p_to); - return Math::acos(CLAMP(d * d * 2 - 1, -1, 1)); + // acos does clamping. + return Math::acos(d * d * 2 - 1); } Vector3 Quaternion::get_euler(EulerOrder p_order) const { diff --git a/core/math/static_raycaster.h b/core/math/static_raycaster.h index 1bafc29c5767d..c53868e12d740 100644 --- a/core/math/static_raycaster.h +++ b/core/math/static_raycaster.h @@ -59,15 +59,15 @@ class StaticRaycaster : public RefCounted { /*! Constructs a ray from origin, direction, and ray segment. Near * has to be smaller than far. */ - _FORCE_INLINE_ Ray(const Vector3 &org, - const Vector3 &dir, - float tnear = 0.0f, - float tfar = INFINITY) : - org(org), - tnear(tnear), - dir(dir), + _FORCE_INLINE_ Ray(const Vector3 &p_org, + const Vector3 &p_dir, + float p_tnear = 0.0f, + float p_tfar = INFINITY) : + org(p_org), + tnear(p_tnear), + dir(p_dir), time(0.0f), - tfar(tfar), + tfar(p_tfar), mask(-1), u(0.0), v(0.0), diff --git a/core/math/transform_3d.cpp b/core/math/transform_3d.cpp index 8d497209f1dfc..cdc94676c9f54 100644 --- a/core/math/transform_3d.cpp +++ b/core/math/transform_3d.cpp @@ -77,20 +77,20 @@ void Transform3D::rotate_basis(const Vector3 &p_axis, real_t p_angle) { basis.rotate(p_axis, p_angle); } -Transform3D Transform3D::looking_at(const Vector3 &p_target, const Vector3 &p_up) const { +Transform3D Transform3D::looking_at(const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) const { #ifdef MATH_CHECKS ERR_FAIL_COND_V_MSG(origin.is_equal_approx(p_target), Transform3D(), "The transform's origin and target can't be equal."); #endif Transform3D t = *this; - t.basis = Basis::looking_at(p_target - origin, p_up); + t.basis = Basis::looking_at(p_target - origin, p_up, p_use_model_front); return t; } -void Transform3D::set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up) { +void Transform3D::set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) { #ifdef MATH_CHECKS ERR_FAIL_COND_MSG(p_eye.is_equal_approx(p_target), "The eye and target vectors can't be equal."); #endif - basis = Basis::looking_at(p_target - p_eye, p_up); + basis = Basis::looking_at(p_target - p_eye, p_up, p_use_model_front); origin = p_eye; } diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index bf1b4cdb6362f..70141a3dbe3f4 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -52,8 +52,8 @@ struct _NO_DISCARD_ Transform3D { void rotate(const Vector3 &p_axis, real_t p_angle); void rotate_basis(const Vector3 &p_axis, real_t p_angle); - void set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0)); - Transform3D looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0)) const; + void set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false); + Transform3D looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false) const; void scale(const Vector3 &p_scale); Transform3D scaled(const Vector3 &p_scale) const; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 760f3bfd0c9bd..cc4a29164d097 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -562,6 +562,60 @@ MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_n return nullptr; } +Vector ClassDB::get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->method_map_compatibility.has(p_name)) { + LocalVector *c = type->method_map_compatibility.getptr(p_name); + Vector ret; + for (uint32_t i = 0; i < c->size(); i++) { + ret.push_back((*c)[i]->get_hash()); + } + return ret; + } + type = type->inherits_ptr; + } + return Vector(); +} + +MethodBind *ClassDB::get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists, bool *r_is_deprecated) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + MethodBind **method = type->method_map.getptr(p_name); + if (method && *method) { + if (r_method_exists) { + *r_method_exists = true; + } + if ((*method)->get_hash() == p_hash) { + return *method; + } + } + + LocalVector *compat = type->method_map_compatibility.getptr(p_name); + if (compat) { + if (r_method_exists) { + *r_method_exists = true; + } + for (uint32_t i = 0; i < compat->size(); i++) { + if ((*compat)[i]->get_hash() == p_hash) { + if (r_is_deprecated) { + *r_is_deprecated = true; + } + return (*compat)[i]; + } + } + } + type = type->inherits_ptr; + } + return nullptr; +} + void ClassDB::bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int64_t p_constant, bool p_is_bitfield) { OBJTYPE_WLOCK; @@ -1274,11 +1328,30 @@ bool ClassDB::has_method(const StringName &p_class, const StringName &p_method, } void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method) { + _bind_method_custom(p_class, p_method, false); +} +void ClassDB::bind_compatibility_method_custom(const StringName &p_class, MethodBind *p_method) { + _bind_method_custom(p_class, p_method, true); +} + +void ClassDB::_bind_compatibility(ClassInfo *type, MethodBind *p_method) { + if (!type->method_map_compatibility.has(p_method->get_name())) { + type->method_map_compatibility.insert(p_method->get_name(), LocalVector()); + } + type->method_map_compatibility[p_method->get_name()].push_back(p_method); +} + +void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility) { ClassInfo *type = classes.getptr(p_class); if (!type) { ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'."); } + if (p_compatibility) { + _bind_compatibility(type, p_method); + return; + } + if (type->method_map.has(p_method->get_name())) { // overloading not supported ERR_FAIL_MSG("Method already bound '" + p_class + "::" + p_method->get_name() + "'."); @@ -1291,11 +1364,44 @@ void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method type->method_map[p_method->get_name()] = p_method; } +MethodBind *ClassDB::_bind_vararg_method(MethodBind *p_bind, const StringName &p_name, const Vector &p_default_args, bool p_compatibility) { + MethodBind *bind = p_bind; + bind->set_name(p_name); + bind->set_default_arguments(p_default_args); + + String instance_type = bind->get_instance_class(); + + ClassInfo *type = classes.getptr(instance_type); + if (!type) { + memdelete(bind); + ERR_FAIL_COND_V(!type, nullptr); + } + + if (p_compatibility) { + _bind_compatibility(type, bind); + return bind; + } + + if (type->method_map.has(p_name)) { + memdelete(bind); + // Overloading not supported + ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + "."); + } + type->method_map[p_name] = bind; +#ifdef DEBUG_METHODS_ENABLED + // FIXME: set_return_type is no longer in MethodBind, so I guess it should be moved to vararg method bind + //bind->set_return_type("Variant"); + type->method_order.push_back(p_name); +#endif + + return bind; +} + #ifdef DEBUG_METHODS_ENABLED -MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount) { +MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount) { StringName mdname = method_name.name; #else -MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const char *method_name, const Variant **p_defs, int p_defcount) { +MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const char *method_name, const Variant **p_defs, int p_defcount) { StringName mdname = StaticCString::create(method_name); #endif @@ -1307,7 +1413,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c #ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + "."); + ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + "."); #endif ClassInfo *type = classes.getptr(instance_type); @@ -1316,7 +1422,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c ERR_FAIL_V_MSG(nullptr, "Couldn't bind method '" + mdname + "' for instance '" + instance_type + "'."); } - if (type->method_map.has(mdname)) { + if (!p_compatibility && type->method_map.has(mdname)) { memdelete(p_bind); // overloading not supported ERR_FAIL_V_MSG(nullptr, "Method already bound '" + instance_type + "::" + mdname + "'."); @@ -1331,10 +1437,16 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c p_bind->set_argument_names(method_name.args); - type->method_order.push_back(mdname); + if (!p_compatibility) { + type->method_order.push_back(mdname); + } #endif - type->method_map[mdname] = p_bind; + if (p_compatibility) { + _bind_compatibility(type, p_bind); + } else { + type->method_map[mdname] = p_bind; + } Vector defvals; @@ -1608,7 +1720,13 @@ void ClassDB::cleanup() { for (KeyValue &F : ti.method_map) { memdelete(F.value); } + for (KeyValue> &F : ti.method_map_compatibility) { + for (uint32_t i = 0; i < F.value.size(); i++) { + memdelete(F.value[i]); + } + } } + classes.clear(); resource_base_extensions.clear(); compat_classes.clear(); diff --git a/core/object/class_db.h b/core/object/class_db.h index 1a5e9235cf19f..ce64336a45c13 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -105,6 +105,7 @@ class ClassDB { ObjectGDExtension *gdextension = nullptr; HashMap method_map; + HashMap> method_map_compatibility; HashMap constant_map; struct EnumInfo { List constants; @@ -148,9 +149,9 @@ class ClassDB { static HashMap compat_classes; #ifdef DEBUG_METHODS_ENABLED - static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount); + static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount); #else - static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const char *method_name, const Variant **p_defs, int p_defcount); + static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const char *method_name, const Variant **p_defs, int p_defcount); #endif static APIType current_api; @@ -172,6 +173,9 @@ class ClassDB { // Non-locking variants of get_parent_class and is_parent_class. static StringName _get_parent_class(const StringName &p_class); static bool _is_parent_class(const StringName &p_class, const StringName &p_inherits); + static void _bind_compatibility(ClassInfo *type, MethodBind *p_method); + static MethodBind *_bind_vararg_method(MethodBind *p_bind, const StringName &p_name, const Vector &p_default_args, bool p_compatibility); + static void _bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility); public: // DO NOT USE THIS!!!!!! NEEDS TO BE PUBLIC BUT DO NOT USE NO MATTER WHAT!!! @@ -273,7 +277,7 @@ class ClassDB { if constexpr (std::is_same::return_type, Object *>::value) { bind->set_return_type_is_raw_object_ptr(true); } - return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } template @@ -288,7 +292,36 @@ class ClassDB { if constexpr (std::is_same::return_type, Object *>::value) { bind->set_return_type_is_raw_object_ptr(true); } - return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + } + + template + static MethodBind *bind_compatibility_method(N p_method_name, M p_method, VarArgs... p_args) { + Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. + const Variant *argptrs[sizeof...(p_args) + 1]; + for (uint32_t i = 0; i < sizeof...(p_args); i++) { + argptrs[i] = &args[i]; + } + MethodBind *bind = create_method_bind(p_method); + if constexpr (std::is_same::return_type, Object *>::value) { + bind->set_return_type_is_raw_object_ptr(true); + } + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + } + + template + static MethodBind *bind_compatibility_static_method(const StringName &p_class, N p_method_name, M p_method, VarArgs... p_args) { + Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. + const Variant *argptrs[sizeof...(p_args) + 1]; + for (uint32_t i = 0; i < sizeof...(p_args); i++) { + argptrs[i] = &args[i]; + } + MethodBind *bind = create_static_method_bind(p_method); + bind->set_instance_class(p_class); + if constexpr (std::is_same::return_type, Object *>::value) { + bind->set_return_type_is_raw_object_ptr(true); + } + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } template @@ -298,36 +331,27 @@ class ClassDB { MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant); ERR_FAIL_COND_V(!bind, nullptr); - bind->set_name(p_name); - bind->set_default_arguments(p_default_args); if constexpr (std::is_same::return_type, Object *>::value) { bind->set_return_type_is_raw_object_ptr(true); } + return _bind_vararg_method(bind, p_name, p_default_args, false); + } - String instance_type = bind->get_instance_class(); + template + static MethodBind *bind_compatibility_vararg_method(uint32_t p_flags, const StringName &p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const Vector &p_default_args = Vector(), bool p_return_nil_is_variant = true) { + GLOBAL_LOCK_FUNCTION; - ClassInfo *type = classes.getptr(instance_type); - if (!type) { - memdelete(bind); - ERR_FAIL_COND_V(!type, nullptr); - } + MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant); + ERR_FAIL_COND_V(!bind, nullptr); - if (type->method_map.has(p_name)) { - memdelete(bind); - // Overloading not supported - ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + "."); + if constexpr (std::is_same::return_type, Object *>::value) { + bind->set_return_type_is_raw_object_ptr(true); } - type->method_map[p_name] = bind; -#ifdef DEBUG_METHODS_ENABLED - // FIXME: set_return_type is no longer in MethodBind, so I guess it should be moved to vararg method bind - //bind->set_return_type("Variant"); - type->method_order.push_back(p_name); -#endif - - return bind; + return _bind_vararg_method(bind, p_name, p_default_args, true); } static void bind_method_custom(const StringName &p_class, MethodBind *p_method); + static void bind_compatibility_method_custom(const StringName &p_class, MethodBind *p_method); static void add_signal(const StringName &p_class, const MethodInfo &p_signal); static bool has_signal(const StringName &p_class, const StringName &p_signal, bool p_no_inheritance = false); @@ -358,6 +382,8 @@ class ClassDB { static void get_method_list(const StringName &p_class, List *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static MethodBind *get_method(const StringName &p_class, const StringName &p_name); + static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr); + static Vector get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name); static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector &p_arg_names = Vector(), bool p_object_core = false); static void get_virtual_methods(const StringName &p_class, List *p_methods, bool p_no_inheritance = false); diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 18f27ae4a4adb..5be9650b32400 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -154,7 +154,6 @@ def generate_version(argcount, const=False, returns=False): def run(target, source, env): - max_versions = 12 txt = """ @@ -165,7 +164,6 @@ def run(target, source, env): """ for i in range(max_versions + 1): - txt += "/* " + str(i) + " Arguments */\n\n" txt += generate_version(i, False, False) txt += generate_version(i, False, True) diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 83e0c4aea169f..18ba5d5b30066 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,14 +35,35 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" +#ifdef DEV_ENABLED +// Includes sanity checks to ensure that a queue set as a thread singleton override +// is only ever called from the thread it was set for. +#define LOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + DEV_ASSERT(!this->is_current_thread_override); \ + mutex.lock(); \ + } else { \ + DEV_ASSERT(this->is_current_thread_override); \ + } +#else +#define LOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + mutex.lock(); \ + } +#endif + +#define UNLOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + mutex.unlock(); \ + } + void CallQueue::_add_page() { - if (pages_used == page_messages.size()) { + if (pages_used == page_bytes.size()) { pages.push_back(allocator->alloc()); - page_messages.push_back(0); + page_bytes.push_back(0); } - page_messages[pages_used] = 0; + page_bytes[pages_used] = 0; pages_used++; - page_offset = 0; } Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { @@ -62,18 +83,19 @@ Error CallQueue::push_set(Object *p_object, const StringName &p_prop, const Vari } Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) { - mutex.lock(); uint32_t room_needed = sizeof(Message) + sizeof(Variant) * p_argcount; ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments."); + LOCK_MUTEX; + _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { - if (room_needed > uint32_t(PAGE_SIZE_BYTES) || pages_used == max_pages) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if (pages_used == max_pages) { ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); @@ -81,7 +103,7 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); msg->args = p_argcount; @@ -103,21 +125,20 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar *v = *p_args[i]; } - page_messages[pages_used - 1]++; - page_offset += room_needed; + page_bytes[pages_used - 1] += room_needed; - mutex.unlock(); + UNLOCK_MUTEX; return OK; } Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) { - mutex.lock(); + LOCK_MUTEX; uint32_t room_needed = sizeof(Message) + sizeof(Variant); _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { String type; if (ObjectDB::get_instance(p_id)) { @@ -126,14 +147,14 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); } Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); msg->args = 1; @@ -145,32 +166,31 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant Variant *v = memnew_placement(buffer_end, Variant); *v = p_value; - page_messages[pages_used - 1]++; - page_offset += room_needed; - mutex.unlock(); + page_bytes[pages_used - 1] += room_needed; + UNLOCK_MUTEX; return OK; } Error CallQueue::push_notification(ObjectID p_id, int p_notification) { ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER); - mutex.lock(); + LOCK_MUTEX; uint32_t room_needed = sizeof(Message); _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); } Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); @@ -179,9 +199,8 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) { //msg->target; msg->notification = p_notification; - page_messages[pages_used - 1]++; - page_offset += room_needed; - mutex.unlock(); + page_bytes[pages_used - 1] += room_needed; + UNLOCK_MUTEX; return OK; } @@ -204,26 +223,78 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args } Error CallQueue::flush() { - mutex.lock(); + 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; + } + + 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. + + 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]; + } + + mq->mutex.unlock(); + + page_bytes[0] = 0; + pages_used = 1; + + return OK; + } if (pages.size() == 0) { // Never allocated - mutex.unlock(); + UNLOCK_MUTEX; return OK; // Do nothing. } if (flushing) { - mutex.unlock(); + UNLOCK_MUTEX; return ERR_BUSY; } flushing = true; uint32_t i = 0; - uint32_t j = 0; uint32_t offset = 0; - while (i < pages_used && j < page_messages[i]) { + while (i < pages_used && offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -240,7 +311,7 @@ Error CallQueue::flush() { Object *target = message->callable.get_object(); - mutex.unlock(); + UNLOCK_MUTEX; switch (message->type & FLAG_MASK) { case TYPE_CALL: { @@ -271,35 +342,32 @@ Error CallQueue::flush() { message->~Message(); - mutex.lock(); - j++; - if (j == page_messages[i]) { - j = 0; + LOCK_MUTEX; + if (offset == page_bytes[i]) { i++; offset = 0; } } - page_messages[0] = 0; - page_offset = 0; + page_bytes[0] = 0; pages_used = 1; flushing = false; - mutex.unlock(); + UNLOCK_MUTEX; return OK; } void CallQueue::clear() { - mutex.lock(); + LOCK_MUTEX; if (pages.size() == 0) { - mutex.unlock(); + UNLOCK_MUTEX; return; // Nothing to clear. } for (uint32_t i = 0; i < pages_used; i++) { uint32_t offset = 0; - for (uint32_t j = 0; j < page_messages[i]; j++) { + while (offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -311,7 +379,6 @@ void CallQueue::clear() { advance += sizeof(Variant) * message->args; } - //pre-advance so this function is reentrant offset += advance; if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -326,14 +393,13 @@ void CallQueue::clear() { } pages_used = 1; - page_offset = 0; - page_messages[0] = 0; + page_bytes[0] = 0; - mutex.unlock(); + UNLOCK_MUTEX; } void CallQueue::statistics() { - mutex.lock(); + LOCK_MUTEX; HashMap set_count; HashMap notify_count; HashMap call_count; @@ -341,7 +407,7 @@ void CallQueue::statistics() { for (uint32_t i = 0; i < pages_used; i++) { uint32_t offset = 0; - for (uint32_t j = 0; j < page_messages[i]; j++) { + while (offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -396,7 +462,6 @@ void CallQueue::statistics() { null_count++; } - //pre-advance so this function is reentrant offset += advance; if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -425,13 +490,24 @@ void CallQueue::statistics() { print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value)); } - mutex.unlock(); + UNLOCK_MUTEX; } bool CallQueue::is_flushing() const { return flushing; } +bool CallQueue::has_messages() const { + if (pages_used == 0) { + return false; + } + if (pages_used == 1 && page_bytes[0] == 0) { + return false; + } + + return true; +} + int CallQueue::get_max_buffer_usage() const { return pages.size() * PAGE_SIZE_BYTES; } @@ -457,20 +533,40 @@ CallQueue::~CallQueue() { if (!allocator_is_custom) { memdelete(allocator); } + // This is done here to avoid a circular dependency between the sanity checks and the thread singleton pointer. + if (this == MessageQueue::thread_singleton) { + MessageQueue::thread_singleton = nullptr; + } } ////////////////////// -MessageQueue *MessageQueue::singleton = nullptr; +CallQueue *MessageQueue::main_singleton = nullptr; +thread_local CallQueue *MessageQueue::thread_singleton = nullptr; + +void MessageQueue::set_thread_singleton_override(CallQueue *p_thread_singleton) { + DEV_ASSERT(p_thread_singleton); // To unset the thread singleton, don't call this with nullptr, but just memfree() it. +#ifdef DEV_ENABLED + if (thread_singleton) { + thread_singleton->is_current_thread_override = false; + } +#endif + thread_singleton = p_thread_singleton; +#ifdef DEV_ENABLED + if (thread_singleton) { + thread_singleton->is_current_thread_override = true; + } +#endif +} MessageQueue::MessageQueue() : CallQueue(nullptr, int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES, "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") { - ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists."); - singleton = this; + ERR_FAIL_COND_MSG(main_singleton != nullptr, "A MessageQueue singleton already exists."); + main_singleton = this; } MessageQueue::~MessageQueue() { - singleton = nullptr; + main_singleton = nullptr; } diff --git a/core/object/message_queue.h b/core/object/message_queue.h index 68969dfd39262..9f567e4dd0457 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -40,11 +40,21 @@ class Object; class CallQueue { + friend class MessageQueue; + public: enum { PAGE_SIZE_BYTES = 4096 }; + struct Page { + uint8_t data[PAGE_SIZE_BYTES]; + }; + + // Needs to be public to be able to define it outside the class. + // Needs to lock because there can be multiple of these allocators in several threads. + typedef PagedAllocator Allocator; + private: enum { TYPE_CALL, @@ -56,23 +66,21 @@ class CallQueue { FLAG_MASK = FLAG_NULL_IS_OK - 1, }; - struct Page { - uint8_t data[PAGE_SIZE_BYTES]; - }; - Mutex mutex; - typedef PagedAllocator Allocator; Allocator *allocator = nullptr; bool allocator_is_custom = false; LocalVector pages; - LocalVector page_messages; + LocalVector page_bytes; uint32_t max_pages = 0; uint32_t pages_used = 0; - uint32_t page_offset = 0; bool flushing = false; +#ifdef DEV_ENABLED + bool is_current_thread_override = false; +#endif + struct Message { Callable callable; int16_t type; @@ -85,7 +93,7 @@ class CallQueue { _FORCE_INLINE_ void _ensure_first_page() { if (unlikely(pages.is_empty())) { pages.push_back(allocator->alloc()); - page_messages.push_back(0); + page_bytes.push_back(0); pages_used = 1; } } @@ -140,6 +148,8 @@ class CallQueue { void clear(); void statistics(); + bool has_messages() const; + bool is_flushing() const; int get_max_buffer_usage() const; @@ -148,10 +158,15 @@ class CallQueue { }; class MessageQueue : public CallQueue { - static MessageQueue *singleton; + static CallQueue *main_singleton; + static thread_local CallQueue *thread_singleton; + friend class CallQueue; public: - _FORCE_INLINE_ static MessageQueue *get_singleton() { return singleton; } + _FORCE_INLINE_ static CallQueue *get_singleton() { return thread_singleton ? thread_singleton : main_singleton; } + + static void set_thread_singleton_override(CallQueue *p_thread_singleton); + MessageQueue(); ~MessageQueue(); }; diff --git a/core/object/method_bind.h b/core/object/method_bind.h index d37479f45ba7d..84f0941b948fd 100644 --- a/core/object/method_bind.h +++ b/core/object/method_bind.h @@ -112,6 +112,8 @@ class MethodBind { _FORCE_INLINE_ int get_argument_count() const { return argument_count; }; virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const = 0; + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const = 0; + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const = 0; StringName get_name() const; @@ -162,8 +164,12 @@ class MethodBindVarArgBase : public MethodBind { } #endif + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { + ERR_FAIL_MSG("Validated call can't be used with vararg methods. This is a bug."); + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { - ERR_FAIL(); // Can't call. + ERR_FAIL_MSG("ptrcall can't be used with vararg methods. This is a bug."); } virtual bool is_const() const { return false; } @@ -253,6 +259,7 @@ class MethodBindVarArgTR : public MethodBindVarArgBase, virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override { return (static_cast(p_object)->*MethodBindVarArgBase, T, R, true>::method)(p_args, p_arg_count, r_error); } + #if defined(SANITIZERS_ENABLED) && defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif @@ -326,6 +333,14 @@ class MethodBindT : public MethodBind { return Variant(); } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { +#ifdef TYPED_METHOD_BIND + call_with_validated_object_instance_args(static_cast(p_object), method, p_args); +#else + call_with_validated_object_instance_args(reinterpret_cast(p_object), method, p_args); +#endif + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { #ifdef TYPED_METHOD_BIND call_with_ptr_args(static_cast(p_object), method, p_args); @@ -393,6 +408,14 @@ class MethodBindTC : public MethodBind { return Variant(); } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { +#ifdef TYPED_METHOD_BIND + call_with_validated_object_instance_argsc(static_cast(p_object), method, p_args); +#else + call_with_validated_object_instance_argsc(reinterpret_cast(p_object), method, p_args); +#endif + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { #ifdef TYPED_METHOD_BIND call_with_ptr_argsc(static_cast(p_object), method, p_args); @@ -471,6 +494,14 @@ class MethodBindTR : public MethodBind { return ret; } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { +#ifdef TYPED_METHOD_BIND + call_with_validated_object_instance_args_ret(static_cast(p_object), method, p_args, r_ret); +#else + call_with_validated_object_instance_args_ret(reinterpret_cast(p_object), method, p_args, r_ret); +#endif + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { #ifdef TYPED_METHOD_BIND call_with_ptr_args_ret(static_cast(p_object), method, p_args, r_ret); @@ -550,6 +581,14 @@ class MethodBindTRC : public MethodBind { return ret; } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { +#ifdef TYPED_METHOD_BIND + call_with_validated_object_instance_args_retc(static_cast(p_object), method, p_args, r_ret); +#else + call_with_validated_object_instance_args_retc(reinterpret_cast(p_object), method, p_args, r_ret); +#endif + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { #ifdef TYPED_METHOD_BIND call_with_ptr_args_retc(static_cast(p_object), method, p_args, r_ret); @@ -614,6 +653,10 @@ class MethodBindTS : public MethodBind { return Variant(); } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { + call_with_validated_variant_args_static_method(function, p_args); + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { (void)p_object; (void)r_ret; @@ -677,6 +720,10 @@ class MethodBindTRS : public MethodBind { return ret; } + virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { + call_with_validated_variant_args_static_method_ret(function, p_args, r_ret); + } + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { (void)p_object; call_with_ptr_args_static_method_ret(function, p_args, r_ret); diff --git a/core/object/object.cpp b/core/object/object.cpp index d96f4eb9fc51e..c76188a2cd321 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -201,6 +201,10 @@ bool Object::_predelete() { return _predelete_ok; } +void Object::cancel_free() { + _predelete_ok = false; +} + void Object::_postinitialize() { _class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after postinitialize. _initialize_classv(); @@ -884,8 +888,13 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (p_value.get_type() == Variant::NIL) { if (metadata.has(p_name)) { metadata.erase(p_name); - metadata_properties.erase("metadata/" + p_name.operator String()); - notify_property_list_changed(); + + const String &sname = p_name; + metadata_properties.erase("metadata/" + sname); + if (!sname.begins_with("_")) { + // Metadata starting with _ don't show up in the inspector, so no need to update. + notify_property_list_changed(); + } } return; } @@ -894,10 +903,14 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (E) { E->value = p_value; } else { - ERR_FAIL_COND(!p_name.operator String().is_valid_identifier()); + ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_identifier(), "Invalid metadata identifier: '" + p_name + "'."); Variant *V = &metadata.insert(p_name, p_value)->value; - metadata_properties["metadata/" + p_name.operator String()] = V; - notify_property_list_changed(); + + const String &sname = p_name; + metadata_properties["metadata/" + sname] = V; + if (!sname.begins_with("_")) { + notify_property_list_changed(); + } } } @@ -936,8 +949,8 @@ TypedArray Object::_get_method_list_bind() const { return ret; } -Vector Object::_get_meta_list_bind() const { - Vector _metaret; +TypedArray Object::_get_meta_list_bind() const { + TypedArray _metaret; for (const KeyValue &K : metadata) { _metaret.push_back(K.key); @@ -1323,28 +1336,28 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable) _disconnect(p_signal, p_callable); } -void Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) { - ERR_FAIL_COND_MSG(p_callable.is_null(), "Cannot disconnect from '" + p_signal + "': the provided callable is null."); +bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) { + ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null."); Object *target_object = p_callable.get_object(); - ERR_FAIL_COND_MSG(!target_object, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null."); + ERR_FAIL_COND_V_MSG(!target_object, false, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null."); SignalData *s = signal_map.getptr(p_signal); if (!s) { bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) || (!script.is_null() && Ref