Compare commits

...

52 Commits

Author SHA1 Message Date
Rémi Verschelde
631cd5fe37 Merge pull request #1306 from dsnopek/4.1-cherrypicks-6
Cherry-picks for the godot-cpp 4.1 branch - 6th batch
2023-11-13 20:28:26 +01:00
Alex Drozd
731a10a4ea ignoring venv in .gitignore
(cherry picked from commit 92dd34ae96)
2023-11-13 13:00:24 -06:00
Thaddeus Crews
a1ae58448c fix is_msvc and use_hot_reload variables
(cherry picked from commit 648b8c4489)
2023-11-13 13:00:24 -06:00
Thaddeus Crews
805cdde0b7 GDCLASS synced by ending with "private:"
• Matches implementation used by modules and godot itself
• Apply same to GDEXTENSION_CLASS, setup with same diff-friendly spacers as GDCLASS

(cherry picked from commit 6eb5d450bd)
2023-11-13 13:00:24 -06:00
Fredia Huya-Kouadio
29335d8f5c Update the environment variables used to access the Android NDK toolchain
(cherry picked from commit 86dbd5fa0d)
2023-11-13 13:00:24 -06:00
Rémi Verschelde
c5f47b2a4e CI: Workaround upstream issue with .NET editor build not exiting
We force closing the process after 10 s, which should be ample time to generate
the .godot folder.

(cherry picked from commit 306774b5a4)
2023-11-13 19:15:07 +01:00
Rémi Verschelde
df5b1a9a69 gdextension: Sync with upstream commit fc79201851a16215f9554884aa242ed957801b10 (4.1.3-stable) 2023-11-09 13:25:22 +01:00
Rémi Verschelde
04b34077d8 Merge pull request #1281 from dsnopek/4.1-cherrypicks-5
Cherry-picks for the godot-cpp 4.1 branch - 5th batch
2023-10-24 11:38:55 +02:00
David Snopek
9d813310bb Add protections against registering classes that didn't use GDCLASS()
(cherry picked from commit a61cdc8860)
2023-10-23 10:11:04 -05:00
Rémi Verschelde
ef8a499eac SCons: Disable C++ exception handling by default
Counterpart to https://github.com/godotengine/godot/pull/80612.

(cherry picked from commit bf1c03ab5f)
2023-10-23 10:10:59 -05:00
gilzoide
698da13d66 Fix return value and r_valid value in Variant::iter_init and iter_next
(cherry picked from commit 60dfa3445a)
2023-10-22 14:47:10 -05:00
Adam Scott
8295486fdb Refactor compiledb implementation
This comment enables the possibility to build the "compile_commands.json"
file by only using `scons -Q compiledb`. No need to use the argument
`compiledb=yes`.

And when using the `compiledb=yes`, it will create a
"compiled_commands.json" automatically.

(cherry picked from commit 2d5024ac8e)
2023-10-22 14:46:58 -05:00
Thaddeus Crews
7704a9d054 Let gdextension_dir function as only argument
(cherry picked from commit 7a5cbcac21)
2023-10-22 14:46:48 -05:00
David Snopek
f7ffc4fe4d Automatically register only engine classes whose header has been included
(cherry picked from commit b507b3e591)
2023-10-22 14:46:26 -05:00
Fabio Alessandrelli
62cb5eac47 [SCons] Rename javascript tool to web
And clean it up a bit.

(cherry picked from commit 18bfa133ab)
2023-10-22 14:03:20 -05:00
Mikael Hermansson
03ea717742 Declare explicit specializations for CharStringT
(cherry picked from commit 6e05b978b8)
2023-10-22 14:03:10 -05:00
Rémi Verschelde
e389f7a50c Merge pull request #1261 from dsnopek/4.1-cherrypicks-4
Cherry-picks for the godot-cpp 4.1 branch - 4th batch
2023-10-12 18:08:12 +02:00
Nick Maltbie
0b1c8bcac3 Added fix for javascript build for godot 4.x
Added changes to tools/javascript.py to add PFlags to fix SharedArrayBuffer memory error.
Corrected some small errors in tools/javascript.py to support new target names.
Also updated ci to include validation for web build.

(cherry picked from commit 2b4bcbb0ce)
2023-10-09 08:43:36 -05:00
Matthew Murphy
857d8e3a56 Fix variant call compiler error
Co-authored-by: David Snopek <dsnopek@gmail.com>
(cherry picked from commit ca3e25de04)
2023-10-09 08:43:23 -05:00
David Snopek
ec6e51b3a4 Handle missing instance binding callbacks by finding the closest parent
(cherry picked from commit 52ca3ef547)
2023-10-09 08:42:55 -05:00
Adam Scott
f8054cca80 Add support to import custom variables from parent SConstruct (redux)
(cherry picked from commit 982e01ec7f)
2023-10-09 08:42:43 -05:00
A Thousand Ships
59ebcfd744 Fix allocation size overflow check in CowData
(cherry picked from commit 06ffc7e952)
2023-10-09 08:42:31 -05:00
A Thousand Ships
205beacc5b Replace ERR_FAIL_COND with ERR_FAIL_NULL where applicable
(cherry picked from commit 1e5767693e)
2023-10-09 08:42:16 -05:00
Rémi Verschelde
3b3f357de9 CI: Fix MinGW install error by pinning to earlier version
Works around https://github.com/egor-tensin/setup-mingw/issues/14.

(cherry picked from commit 0369f6fea0)
2023-10-04 15:21:01 +02:00
Rémi Verschelde
48b92acf8c gdextension: Sync with upstream commit 399c9dc393f6f84c0b4e4d4117906c70c048ecf2 (4.1.2-stable) 2023-10-04 12:02:54 +02:00
Rémi Verschelde
4eed2d7be0 Merge pull request #1244 from dsnopek/4.1-cherrypicks-3
Cherry-picks for the godot-cpp 4.1 branch - 3rd batch
2023-09-20 23:48:26 +02:00
David Snopek
bc82ae8b0b Add static methods to ClassDB for the methods bound to the ClassDB singleton
(cherry picked from commit 6f913563d8)
2023-09-19 21:30:03 -05:00
David Snopek
590e267902 Load 'print_error_with_message' function
(cherry picked from commit 634ed09ec0)
2023-09-19 21:29:29 -05:00
David Snopek
3be7ec4162 Check that GDExtension is opened by compatible Godot version
(cherry picked from commit fecb2959b4)
2023-09-19 21:27:40 -05:00
DmitriySalnikov
dd8e1def67 [SCons] Fixed crashes in several scripts
(cherry picked from commit 0e5975dd26)
2023-09-19 21:23:20 -05:00
David Snopek
dcd7a69512 Ensure that PtrToArg specializations for native structs are used
(cherry picked from commit 3cd3f24150)
2023-09-19 21:22:54 -05:00
dependabot[bot]
354ed1e79d Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 5d4ff63930)
2023-09-19 21:22:20 -05:00
A Thousand Ships
014132d4c0 Ensure const correctness for wrappers
(cherry picked from commit f651df5e7a)
2023-09-19 21:21:20 -05:00
David Snopek
bc980b59ff Merge pull request #1227 from dsnopek/4.1-cherrypicks-2
Cherry-picks for the godot-cpp 4.1 branch - 2nd batch
2023-09-02 12:38:20 -05:00
A Thousand Ships
c3771fb065 Fix formatting of compatibility_minimum examples
Without quotes the values is parsed as a float, breaking in various
cases.

(cherry picked from commit b3596a18e1)
2023-09-01 17:08:03 -05:00
Fabio Alessandrelli
63755b2a32 [SCons] Move the GodotCPP build to its own tool.
(cherry picked from commit f8b4f60cb9)
2023-09-01 17:07:53 -05:00
David Snopek
ce5dd378d9 Clarify versions and examples in the README
(cherry picked from commit 1588dc8437)
2023-09-01 17:07:41 -05:00
A Thousand Ships
c6fe6533f9 Fix link to test project in readme
Also updated format for library paths

(cherry picked from commit e586e11637)
2023-09-01 17:07:30 -05:00
A Thousand Ships
170a691a7e Add remaining component-wise min/max functions to Vector*
(cherry picked from commit 52eb77efd4)
2023-09-01 17:07:18 -05:00
Rémi Verschelde
738ef9baf8 SCons: Sync targets.py fully with upstream Godot
- Reorders existing code to match Godot.
- Adds `NDEBUG` for non-dev builds.
- Adds `-gdwarf-4` for Clang debug symbols.
- Adds strip link flag for GCC/Clang builds without debug symbols.

(cherry picked from commit 600e749d9b)
2023-09-01 17:07:03 -05:00
Adam Scott
c7afd0f89a Fix forgotten not operator
(cherry picked from commit f5c8e5190f)
2023-09-01 17:06:51 -05:00
Adam Scott
6789b29b72 Fix Clang deprecated builtins
It seems that Clang and GCC have different interpretations of certain
builtins. So this PR uses std <type_traits> functions just as cowdata.h
does in the godot project.

(cherry picked from commit 5c262844ad)
2023-09-01 17:06:39 -05:00
David Snopek
960c906da1 Add automated tests to verify some previous fixes
(cherry picked from commit d5fab0b9f8)
2023-09-01 17:06:20 -05:00
Marc Gilleron
0f2d3652e5 Added generated version header
(cherry picked from commit c6b2c82570)
2023-09-01 17:06:03 -05:00
David Snopek
28494f0bd5 Merge pull request #1205 from dsnopek/4.1-cherrypicks-1
Cherry-picks for the godot-cpp 4.1 branch - 1st batch
2023-08-11 10:35:12 -05:00
Feiyun Wang
4fb9af7fb2 Statically link mingw/msvc runtime libraries on Windows
Co-authored-by: David Snopek <dsnopek@gmail.com>
(cherry picked from commit a745c2ac47)
2023-08-10 09:10:04 -05:00
Fabio Alessandrelli
6fa6b8b178 [SCons] Merge OSXCross tools into platofrm ones
(cherry picked from commit 6d195137fe)
2023-08-10 09:09:49 -05:00
Fabio Alessandrelli
784c3dc012 [SCons] Add option to generate a compilation database.
(cherry picked from commit 2586ad016e)
2023-08-10 09:09:30 -05:00
Adam Scott
7a9b323931 Add platform macros
(cherry picked from commit 9d9f4279ed)
2023-08-10 09:09:14 -05:00
Marc Gilleron
e75ec636db Don't cache null forever if a singleton isn't available yet
# Conflicts:
#	binding_generator.py

(cherry picked from commit 548c758677)
2023-08-10 09:08:56 -05:00
David Snopek
5dda0212f6 In generated methods, only construct the method StringName the first time
(cherry picked from commit efc16b49d9)
2023-08-10 09:06:33 -05:00
David Snopek
011965d864 Attempt to fully implement CharString
(cherry picked from commit 4df112cd95)
2023-08-10 09:06:09 -05:00
47 changed files with 1625 additions and 812 deletions

View File

@@ -78,12 +78,22 @@ jobs:
run-tests: false
cache-name: ios-arm64
- name: 🌐 Web (wasm32)
os: ubuntu-20.04
platform: web
artifact-name: godot-cpp-web-wasm32-release
artifact-path: bin/libgodot-cpp.web.template_release.wasm32.a
run-tests: false
cache-name: web-wasm32
env:
SCONS_CACHE: ${{ github.workspace }}/.scons-cache/
EM_VERSION: 3.1.45
EM_CACHE_FOLDER: "emsdk-cache"
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
@@ -104,6 +114,13 @@ jobs:
sudo apt-get update -qq
sudo apt-get install -qqq build-essential pkg-config
- name: Web dependencies
if: ${{ matrix.platform == 'web' }}
uses: mymindstorm/setup-emsdk@v12
with:
version: ${{env.EM_VERSION}}
actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
- name: Install scons
run: |
python -m pip install scons==4.0.0
@@ -111,6 +128,8 @@ jobs:
- name: Setup MinGW for Windows/MinGW build
if: ${{ matrix.platform == 'windows' && matrix.flags == 'use_mingw=yes' }}
uses: egor-tensin/setup-mingw@v2
with:
version: 12.2.0
- name: Generate godot-cpp sources only
run: |
@@ -153,7 +172,7 @@ jobs:
./godot-artifacts/godot.linuxbsd.editor.x86_64.mono --headless --version
cd test
# Need to run the editor so .godot is generated... but it crashes! Ignore that :-)
(cd project && (../../godot-artifacts/godot.linuxbsd.editor.x86_64.mono --editor --headless --quit >/dev/null 2>&1 || true))
(cd project && (timeout 10 ../../godot-artifacts/godot.linuxbsd.editor.x86_64.mono --editor --headless --quit >/dev/null 2>&1 || true))
GODOT=../godot-artifacts/godot.linuxbsd.editor.x86_64.mono ./run-tests.sh
- name: Upload artifact
@@ -168,7 +187,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
@@ -192,7 +211,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
@@ -216,7 +235,7 @@ jobs:
runs-on: windows-2019
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Azure repositories are not reliable, we need to prevent Azure giving us packages.
- name: Make apt sources.list use the default Ubuntu repositories

4
.gitignore vendored
View File

@@ -191,3 +191,7 @@ godot.creator.*
# compile commands (https://clang.llvm.org/docs/JSONCompilationDatabase.html)
compile_commands.json
# Python development
.venv
venv

View File

@@ -72,21 +72,22 @@ endif()
# Input from user for GDExtension interface header and the API JSON file
set(GODOT_GDEXTENSION_DIR "gdextension" CACHE STRING "")
set(GODOT_CUSTOM_API_FILE "" CACHE STRING "")
set(FLOAT_PRECISION "single" CACHE STRING "")
if ("${FLOAT_PRECISION}" STREQUAL "double")
add_definitions(-DREAL_T_IS_DOUBLE)
endif()
set(GODOT_GDEXTENSION_API_FILE "${GODOT_GDEXTENSION_DIR}/extension_api.json")
if (NOT "${GODOT_CUSTOM_API_FILE}" STREQUAL "") # User-defined override.
set(GODOT_GDEXTENSION_API_FILE "${GODOT_CUSTOM_API_FILE}")
endif()
set(FLOAT_PRECISION "single" CACHE STRING "")
if ("${FLOAT_PRECISION}" STREQUAL "double")
add_definitions(-DREAL_T_IS_DOUBLE)
endif()
set(GODOT_COMPILE_FLAGS )
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# using Visual Studio C++
set(GODOT_COMPILE_FLAGS "/EHsc /utf-8") # /GF /MP
set(GODOT_COMPILE_FLAGS "/utf-8") # /GF /MP
if(CMAKE_BUILD_TYPE MATCHES Debug)
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /MDd") # /Od /RTC1 /Zi
@@ -107,6 +108,21 @@ else() # GCC/Clang
endif(CMAKE_BUILD_TYPE MATCHES Debug)
endif()
# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time (GH-80513).
option(GODOT_DISABLE_EXCEPTIONS OFF "Force disabling exception handling code")
if (GODOT_DISABLE_EXCEPTIONS)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -D_HAS_EXCEPTIONS=0")
else()
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -fno-exceptions")
endif()
else()
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /EHsc")
endif()
endif()
# Generate source from the bindings file
find_package(Python3 3.4 REQUIRED) # pathlib should be present
if(GENERATE_TEMPLATE_GET_NODE)

View File

@@ -2,11 +2,15 @@
> **Warning**
>
> This repository's `master` branch is only usable with the latest version of
> Godot's ([GDExtension](https://godotengine.org/article/introducing-gd-extensions))
> API (Godot 4.1 and later).
> This repository's `master` branch is only usable with
> [GDExtension](https://godotengine.org/article/introducing-gd-extensions)
> from Godot's `master` branch.
>
> For users of Godot 4.0.x, switch to the [`4.0`](https://github.com/godotengine/godot-cpp/tree/4.0) branch.
> For users of stable branches, switch to the branch matching your target Godot version:
> - [`4.0`](https://github.com/godotengine/godot-cpp/tree/4.0)
> - [`4.1`](https://github.com/godotengine/godot-cpp/tree/4.1)
>
> Or check out the Git tag matching your Godot version (e.g. `godot-4.1.1-stable`).
>
> For GDNative users (Godot 3.x), switch to the [`3.x`](https://github.com/godotengine/godot-cpp/tree/3.x)
> or the [`3.5`](https://github.com/godotengine/godot-cpp/tree/3.5) branch.
@@ -52,9 +56,10 @@ first-party `godot-cpp` extension.
Some compatibility breakage is to be expected as GDExtension and `godot-cpp`
get more used, documented, and critical issues get resolved. See the
[issue tracker](https://github.com/godotengine/godot/issues) for a list of known
issues, and be sure to provide feedback on issues and PRs which affect your use
of this extension.
[Godot issue tracker](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aopen+label%3Atopic%3Agdextension)
and the [godot-cpp issue tracker](https://github.com/godotengine/godot/issues)
for a list of known issues, and be sure to provide feedback on issues and PRs
which affect your use of this extension.
## Contributing
@@ -76,22 +81,22 @@ just like before.
To use the shared lib in your Godot project you'll need a `.gdextension`
file, which replaces what was the `.gdnlib` before.
Follow [the example](test/demo/example.gdextension):
See [example.gdextension](test/project/example.gdextension) used in the test project:
```ini
[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = 4.1
compatibility_minimum = "4.1"
[libraries]
macos.debug = "bin/libgdexample.macos.debug.framework"
macos.release = "bin/libgdexample.macos.release.framework"
windows.debug.x86_64 = "bin/libgdexample.windows.debug.x86_64.dll"
windows.release.x86_64 = "bin/libgdexample.windows.release.x86_64.dll"
linux.debug.x86_64 = "bin/libgdexample.linux.debug.x86_64.so"
linux.release.x86_64 = "bin/libgdexample.linux.release.x86_64.so"
macos.debug = "res://bin/libgdexample.macos.debug.framework"
macos.release = "res://bin/libgdexample.macos.release.framework"
windows.debug.x86_64 = "res://bin/libgdexample.windows.debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libgdexample.linux.debug.x86_64.so"
linux.release.x86_64 = "res://bin/libgdexample.linux.release.x86_64.so"
# Repeat for other architectures to support arm64, rv64, etc.
```
@@ -129,6 +134,10 @@ void initialize_example_module(ModuleInitializationLevel p_level) {
Any node and resource you register will be available in the corresponding `Create...` dialog. Any class will be available to scripting as well.
## Included example
## Examples and templates
Check the project in the `test` folder for an example on how to use and register different things.
See the [godot-cpp-template](https://github.com/godotengine/godot-cpp-template) project for a
generic reusable template.
Or checkout the code for the [Summator example](https://github.com/paddy-exe/GDExtensionSummator)
as shown in the [official documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdextension/gdextension_cpp_example.html).

View File

@@ -5,52 +5,11 @@ import platform
import sys
import subprocess
from binding_generator import scons_generate_bindings, scons_emit_files
from SCons.Errors import UserError
EnsureSConsVersion(4, 0)
def add_sources(sources, dir, extension):
for f in os.listdir(dir):
if f.endswith("." + extension):
sources.append(dir + "/" + f)
def normalize_path(val):
return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val)
def validate_api_file(key, val, env):
if not os.path.isfile(normalize_path(val)):
raise UserError("GDExtension API file ('%s') does not exist: %s" % (key, val))
def validate_gdextension_dir(key, val, env):
if not os.path.isdir(normalize_path(val)):
raise UserError("GDExtension directory ('%s') does not exist: %s" % (key, val))
def get_gdextension_dir(env):
return normalize_path(env.get("gdextension_dir", env.Dir("gdextension").abspath))
def get_api_file(env):
return normalize_path(env.get("custom_api_file", os.path.join(get_gdextension_dir(env), "extension_api.json")))
# Try to detect the host platform automatically.
# This is used if no `platform` argument is passed
if sys.platform.startswith("linux"):
default_platform = "linux"
elif sys.platform == "darwin":
default_platform = "macos"
elif sys.platform == "win32" or sys.platform == "msys":
default_platform = "windows"
elif ARGUMENTS.get("platform", ""):
default_platform = ARGUMENTS.get("platform")
else:
raise ValueError("Could not detect platform automatically, please specify with platform=<platform>")
try:
Import("env")
except:
@@ -60,26 +19,12 @@ except:
env.PrependENVPath("PATH", os.getenv("PATH"))
# Default num_jobs to local cpu count if not user specified.
# SCons has a peculiarity where user-specified options won't be overridden
# by SetOption, so we can rely on this to know if we should use our default.
initial_num_jobs = env.GetOption("num_jobs")
altered_num_jobs = initial_num_jobs + 1
env.SetOption("num_jobs", altered_num_jobs)
if env.GetOption("num_jobs") == altered_num_jobs:
cpu_count = os.cpu_count()
if cpu_count is None:
print("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.")
else:
safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
print(
"Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument."
% (cpu_count, safer_cpu_count)
)
env.SetOption("num_jobs", safer_cpu_count)
# Custom options and profile flags.
customs = ["custom.py"]
try:
customs += Import("customs")
except:
pass
profile = ARGUMENTS.get("profile", "")
if profile:
if os.path.isfile(profile):
@@ -87,143 +32,12 @@ if profile:
elif os.path.isfile(profile + ".py"):
customs.append(profile + ".py")
opts = Variables(customs, ARGUMENTS)
platforms = ("linux", "macos", "windows", "android", "ios", "javascript")
opts.Add(
EnumVariable(
key="platform",
help="Target platform",
default=env.get("platform", default_platform),
allowed_values=platforms,
ignorecase=2,
)
)
# Editor and template_debug are compatible (i.e. you can use the same binary for Godot editor builds and Godot debug templates).
# Godot release templates are only compatible with "template_release" builds.
# For this reason, we default to template_debug builds, unlike Godot which defaults to editor builds.
opts.Add(
EnumVariable(
key="target",
help="Compilation target",
default=env.get("target", "template_debug"),
allowed_values=("editor", "template_release", "template_debug"),
)
)
opts.Add(
PathVariable(
key="gdextension_dir",
help="Path to a custom directory containing GDExtension interface header and API JSON file",
default=env.get("gdextension_dir", None),
validator=validate_gdextension_dir,
)
)
opts.Add(
PathVariable(
key="custom_api_file",
help="Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)",
default=env.get("custom_api_file", None),
validator=validate_api_file,
)
)
opts.Add(
BoolVariable(
key="generate_bindings",
help="Force GDExtension API bindings generation. Auto-detected by default.",
default=env.get("generate_bindings", False),
)
)
opts.Add(
BoolVariable(
key="generate_template_get_node",
help="Generate a template version of the Node class's get_node.",
default=env.get("generate_template_get_node", True),
)
)
opts.Add(BoolVariable(key="build_library", help="Build the godot-cpp library.", default=env.get("build_library", True)))
opts.Add(
EnumVariable(
key="precision",
help="Set the floating-point precision level",
default=env.get("precision", "single"),
allowed_values=("single", "double"),
)
)
# Add platform options
tools = {}
for pl in platforms:
tool = Tool(pl, toolpath=["tools"])
if hasattr(tool, "options"):
tool.options(opts)
tools[pl] = tool
# CPU architecture options.
architecture_array = ["", "universal", "x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"]
architecture_aliases = {
"x64": "x86_64",
"amd64": "x86_64",
"armv7": "arm32",
"armv8": "arm64",
"arm64v8": "arm64",
"aarch64": "arm64",
"rv": "rv64",
"riscv": "rv64",
"riscv64": "rv64",
"ppcle": "ppc32",
"ppc": "ppc32",
"ppc64le": "ppc64",
}
opts.Add(
EnumVariable(
key="arch",
help="CPU architecture",
default=env.get("arch", ""),
allowed_values=architecture_array,
map=architecture_aliases,
)
)
# Targets flags tool (optimizations, debug symbols)
target_tool = Tool("targets", toolpath=["tools"])
target_tool.options(opts)
cpp_tool = Tool("godotcpp", toolpath=["tools"])
cpp_tool.options(opts, env)
opts.Update(env)
Help(opts.GenerateHelpText(env))
# Process CPU architecture argument.
if env["arch"] == "":
# No architecture specified. Default to arm64 if building for Android,
# universal if building for macOS or iOS, wasm32 if building for web,
# otherwise default to the host architecture.
if env["platform"] in ["macos", "ios"]:
env["arch"] = "universal"
elif env["platform"] == "android":
env["arch"] = "arm64"
elif env["platform"] == "javascript":
env["arch"] = "wasm32"
else:
host_machine = platform.machine().lower()
if host_machine in architecture_array:
env["arch"] = host_machine
elif host_machine in architecture_aliases.keys():
env["arch"] = architecture_aliases[host_machine]
elif "86" in host_machine:
# Catches x86, i386, i486, i586, i686, etc.
env["arch"] = "x86_32"
else:
print("Unsupported CPU architecture: " + host_machine)
Exit()
tool = Tool(env["platform"], toolpath=["tools"])
if tool is None or not tool.exists(env):
raise ValueError("Required toolchain not found for platform " + env["platform"])
tool.generate(env)
target_tool.generate(env)
# Detect and print a warning listing unknown SCons variables to ease troubleshooting.
unknown = opts.UnknownVariables()
if unknown:
@@ -231,66 +45,12 @@ if unknown:
for item in unknown.items():
print(" " + item[0] + "=" + item[1])
print("Building for architecture " + env["arch"] + " on platform " + env["platform"])
# Require C++17
if env.get("is_msvc", False):
env.Append(CXXFLAGS=["/std:c++17"])
else:
env.Append(CXXFLAGS=["-std=c++17"])
if env["precision"] == "double":
env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"])
# Generate bindings
env.Append(BUILDERS={"GenerateBindings": Builder(action=scons_generate_bindings, emitter=scons_emit_files)})
bindings = env.GenerateBindings(
env.Dir("."),
[get_api_file(env), os.path.join(get_gdextension_dir(env), "gdextension_interface.h"), "binding_generator.py"],
)
scons_cache_path = os.environ.get("SCONS_CACHE")
if scons_cache_path is not None:
CacheDir(scons_cache_path)
Decider("MD5")
# Forces bindings regeneration.
if env["generate_bindings"]:
AlwaysBuild(bindings)
NoCache(bindings)
cpp_tool.generate(env)
library = env.GodotCPP()
# Includes
env.Append(CPPPATH=[[env.Dir(d) for d in [get_gdextension_dir(env), "include", os.path.join("gen", "include")]]])
# Sources to compile
sources = []
add_sources(sources, "src", "cpp")
add_sources(sources, "src/classes", "cpp")
add_sources(sources, "src/core", "cpp")
add_sources(sources, "src/variant", "cpp")
sources.extend([f for f in bindings if str(f).endswith(".cpp")])
suffix = ".{}.{}".format(env["platform"], env["target"])
if env.dev_build:
suffix += ".dev"
if env["precision"] == "double":
suffix += ".double"
suffix += "." + env["arch"]
if env["ios_simulator"]:
suffix += ".simulator"
# Expose it when included from another project
env["suffix"] = suffix
library = None
env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]
library_name = "libgodot-cpp{}{}".format(suffix, env["LIBSUFFIX"])
if env["build_library"]:
library = env.StaticLibrary(target=env.File("bin/%s" % library_name), source=sources)
Default(library)
env.Append(LIBPATH=[env.Dir("bin")])
env.Append(LIBS=library_name)
Return("env")

View File

@@ -97,9 +97,10 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
files.append(str(source_filename.as_posix()))
for engine_class in api["classes"]:
# TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
# Generate code for the ClassDB singleton under a different name.
if engine_class["name"] == "ClassDB":
continue
engine_class["name"] = "ClassDBSingleton"
engine_class["alias_for"] = "ClassDB"
header_filename = include_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".hpp")
source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
if headers:
@@ -123,13 +124,12 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
include_gen_folder / "variant" / "variant_size.hpp",
include_gen_folder / "classes" / "global_constants.hpp",
include_gen_folder / "classes" / "global_constants_binds.hpp",
include_gen_folder / "core" / "version.hpp",
]:
files.append(str(path.as_posix()))
if sources:
utility_functions_source_path = source_gen_folder / "variant" / "utility_functions.cpp"
files.append(str(utility_functions_source_path.as_posix()))
register_engine_classes_source_path = source_gen_folder / "register_engine_classes.cpp"
files.append(str(register_engine_classes_source_path.as_posix()))
return files
@@ -173,6 +173,7 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision=
print("Built-in type config: " + real_t + "_" + bits)
generate_global_constants(api, target_dir)
generate_version_header(api, target_dir)
generate_global_constant_binds(api, target_dir)
generate_builtin_bindings(api, target_dir, real_t + "_" + bits)
generate_engine_classes_bindings(api, target_dir, use_template_get_node)
@@ -1036,21 +1037,23 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
# First create map of classes and singletons.
for class_api in api["classes"]:
# TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
# Generate code for the ClassDB singleton under a different name.
if class_api["name"] == "ClassDB":
continue
class_api["name"] = "ClassDBSingleton"
class_api["alias_for"] = "ClassDB"
engine_classes[class_api["name"]] = class_api["is_refcounted"]
for native_struct in api["native_structures"]:
engine_classes[native_struct["name"]] = False
native_structures.append(native_struct["name"])
for singleton in api["singletons"]:
# Generate code for the ClassDB singleton under a different name.
if singleton["name"] == "ClassDB":
singleton["name"] = "ClassDBSingleton"
singleton["alias_for"] = "ClassDB"
singletons.append(singleton["name"])
for class_api in api["classes"]:
# TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
if class_api["name"] == "ClassDB":
continue
# Check used classes for header include.
used_classes = set()
fully_used_classes = set()
@@ -1140,6 +1143,12 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
else:
fully_used_classes.add("Wrapped")
# In order to ensure that PtrToArg specializations for native structs are
# always used, let's move any of them into 'fully_used_classes'.
for type_name in used_classes:
if is_struct_type(type_name) and not is_included_struct_type(type_name):
fully_used_classes.add(type_name)
for type_name in fully_used_classes:
if type_name in used_classes:
used_classes.remove(type_name)
@@ -1159,10 +1168,6 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node)
)
register_engine_classes_filename = Path(output_dir) / "src" / "register_engine_classes.cpp"
with register_engine_classes_filename.open("w+", encoding="utf-8") as source_file:
source_file.write(generate_register_engine_classes_source(api))
for native_struct in api["native_structures"]:
struct_name = native_struct["name"]
snake_struct_name = camel_to_snake(struct_name)
@@ -1237,12 +1242,12 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append(f"#include <godot_cpp/{get_include_path(included)}>")
if class_name == "EditorPlugin":
result.append("#include <godot_cpp/templates/vector.hpp>")
result.append("#include <godot_cpp/classes/editor_plugin_registration.hpp>")
if len(fully_used_classes) > 0:
result.append("")
if class_name != "Object":
if class_name != "Object" and class_name != "ClassDBSingleton":
result.append("#include <godot_cpp/core/class_db.hpp>")
result.append("")
result.append("#include <type_traits>")
@@ -1263,7 +1268,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
inherits = class_api["inherits"] if "inherits" in class_api else "Wrapped"
result.append(f"class {class_name} : public {inherits} {{")
result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
if "alias_for" in class_api:
result.append(f"\tGDEXTENSION_CLASS_ALIAS({class_name}, {class_api['alias_for']}, {inherits})")
else:
result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
result.append("")
result.append("public:")
@@ -1386,30 +1394,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append("};")
result.append("")
if class_name == "EditorPlugin":
result.append("class EditorPlugins {")
result.append("private:")
result.append("\tstatic Vector<StringName> plugin_classes;")
result.append("")
result.append("public:")
result.append("\tstatic void add_plugin_class(const StringName &p_class_name);")
result.append("\tstatic void remove_plugin_class(const StringName &p_class_name);")
result.append("\tstatic void deinitialize(GDExtensionInitializationLevel p_level);")
result.append("")
result.append("\ttemplate <class T>")
result.append("\tstatic void add_by_type() {")
result.append("\t\tadd_plugin_class(T::get_class_static());")
result.append("\t}")
result.append("\ttemplate <class T>")
result.append("\tstatic void remove_by_type() {")
result.append("\t\tremove_plugin_class(T::get_class_static());")
result.append("\t}")
result.append("};")
result.append("")
result.append("} // namespace godot")
result.append("")
@@ -1421,6 +1405,51 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append(f'VARIANT_ENUM_CAST({class_name}::{enum_api["name"]});')
result.append("")
if class_name == "ClassDBSingleton":
result.append("#define CLASSDB_SINGLETON_FORWARD_METHODS \\")
for method in class_api["methods"]:
# ClassDBSingleton shouldn't have any static or vararg methods, but if some appear later, lets skip them.
if vararg:
continue
if "is_static" in method and method["is_static"]:
continue
method_signature = "\tstatic "
if "return_type" in method:
method_signature += f'{correct_type(method["return_type"])} '
elif "return_value" in method:
method_signature += (
correct_type(method["return_value"]["type"], method["return_value"].get("meta", None)) + " "
)
else:
method_signature += "void "
method_signature += f'{method["name"]}('
method_arguments = []
if "arguments" in method:
method_arguments = method["arguments"]
method_signature += make_function_parameters(
method_arguments, include_default=True, for_builtin=True, is_vararg=False
)
method_signature += ") { \\"
result.append(method_signature)
method_body = "\t\t"
if "return_type" in method or "return_value" in method:
method_body += "return "
method_body += f'ClassDBSingleton::get_singleton()->{method["name"]}('
method_body += ", ".join(map(lambda x: escape_identifier(x["name"]), method_arguments))
method_body += "); \\"
result.append(method_body)
result.append("\t} \\")
result.append("\t;")
result.append("")
result.append(f"#endif // ! {header_guard}")
return "\n".join(result)
@@ -1453,16 +1482,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
if is_singleton:
result.append(f"{class_name} *{class_name}::get_singleton() {{")
result.append(f"\tconst StringName _gde_class_name = {class_name}::get_class_static();")
# We assume multi-threaded access is OK because each assignment will assign the same value every time
result.append(f"\tstatic {class_name} *singleton = nullptr;")
result.append("\tif (unlikely(singleton == nullptr)) {")
result.append(
"\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(_gde_class_name._native_ptr());"
f"\t\tGDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());"
)
result.append("#ifdef DEBUG_ENABLED")
result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);")
result.append("\t\tERR_FAIL_NULL_V(singleton_obj, nullptr);")
result.append("#endif // DEBUG_ENABLED")
result.append(
f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::_gde_binding_callbacks));"
f"\t\tsingleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::_gde_binding_callbacks));"
)
result.append("#ifdef DEBUG_ENABLED")
result.append("\t\tERR_FAIL_NULL_V(singleton, nullptr);")
result.append("#endif // DEBUG_ENABLED")
result.append("\t}")
result.append("\treturn singleton;")
result.append("}")
result.append("")
@@ -1480,10 +1515,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
result.append(method_signature + " {")
# Method body.
result.append(f"\tconst StringName _gde_class_name = {class_name}::get_class_static();")
result.append(f'\tconst StringName _gde_method_name = "{method["name"]}";')
result.append(
f'\tstatic GDExtensionMethodBindPtr _gde_method_bind = internal::gdextension_interface_classdb_get_method_bind(_gde_class_name._native_ptr(), _gde_method_name._native_ptr(), {method["hash"]});'
f'\tstatic GDExtensionMethodBindPtr _gde_method_bind = internal::gdextension_interface_classdb_get_method_bind({class_name}::get_class_static()._native_ptr(), StringName("{method["name"]}")._native_ptr(), {method["hash"]});'
)
method_call = "\t"
has_return = "return_value" in method and method["return_value"]["type"] != "void"
@@ -1585,38 +1618,6 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
return "\n".join(result)
def generate_register_engine_classes_source(api):
includes = []
registrations = []
for class_api in api["classes"]:
if class_api["name"] == "ClassDB":
continue
class_name = class_api["name"]
snake_class_name = camel_to_snake(class_name)
includes.append(f"#include <godot_cpp/classes/{snake_class_name}.hpp>")
registrations.append(f"\tClassDB::register_engine_class<{class_name}>();")
result = []
add_header(f"register_engine_classes.cpp", result)
result.append("#include <godot_cpp/godot.hpp>")
result.append("")
result = result + includes
result.append("")
result.append("namespace godot {")
result.append("")
result.append("void GDExtensionBinding::register_engine_classes() {")
result = result + registrations
result.append("}")
result.append("")
result.append("} // namespace godot ")
return "\n".join(result)
def generate_global_constants(api, output_dir):
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "classes"
source_gen_folder = Path(output_dir) / "src" / "classes"
@@ -1662,6 +1663,35 @@ def generate_global_constants(api, output_dir):
header_file.write("\n".join(header))
def generate_version_header(api, output_dir):
header = []
header_filename = "version.hpp"
add_header(header_filename, header)
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "core"
include_gen_folder.mkdir(parents=True, exist_ok=True)
header_file_path = include_gen_folder / header_filename
header_guard = "GODOT_CPP_VERSION_HPP"
header.append(f"#ifndef {header_guard}")
header.append(f"#define {header_guard}")
header.append("")
header.append(f"#define GODOT_VERSION_MAJOR {api['header']['version_major']}")
header.append(f"#define GODOT_VERSION_MINOR {api['header']['version_minor']}")
header.append(f"#define GODOT_VERSION_PATCH {api['header']['version_patch']}")
header.append(f"#define GODOT_VERSION_STATUS \"{api['header']['version_status']}\"")
header.append(f"#define GODOT_VERSION_BUILD \"{api['header']['version_build']}\"")
header.append("")
header.append(f"#endif // {header_guard}")
header.append("")
with header_file_path.open("w+", encoding="utf-8") as header_file:
header_file.write("\n".join(header))
def generate_global_constant_binds(api, output_dir):
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "classes"
source_gen_folder = Path(output_dir) / "src" / "classes"
@@ -1773,9 +1803,8 @@ def generate_utility_functions(api, output_dir):
# Function body.
source.append(f'\tconst StringName _gde_function_name = "{function["name"]}";')
source.append(
f'\tstatic GDExtensionPtrUtilityFunction _gde_function = internal::gdextension_interface_variant_get_ptr_utility_function(_gde_function_name._native_ptr(), {function["hash"]});'
f'\tstatic GDExtensionPtrUtilityFunction _gde_function = internal::gdextension_interface_variant_get_ptr_utility_function(StringName("{function["name"]}")._native_ptr(), {function["hash"]});'
)
has_return = "return_type" in function and function["return_type"] != "void"
if has_return:
@@ -2285,6 +2314,7 @@ def escape_identifier(id):
"operator": "_operator",
"typeof": "type_of",
"typename": "type_name",
"enum": "_enum",
}
if id in cpp_keywords_map:
return cpp_keywords_map[id]

View File

@@ -2,10 +2,10 @@
"header": {
"version_major": 4,
"version_minor": 1,
"version_patch": 1,
"version_patch": 3,
"version_status": "stable",
"version_build": "official",
"version_full_name": "Godot Engine v4.1.1.stable.official"
"version_full_name": "Godot Engine v4.1.3.stable.official"
},
"builtin_class_sizes": [
{
@@ -2593,6 +2593,22 @@
"name": "KEY_LAUNCHF",
"value": 4194415
},
{
"name": "KEY_GLOBE",
"value": 4194416
},
{
"name": "KEY_KEYBOARD",
"value": 4194417
},
{
"name": "KEY_JIS_EISU",
"value": 4194418
},
{
"name": "KEY_JIS_KANA",
"value": 4194419
},
{
"name": "KEY_UNKNOWN",
"value": 8388607
@@ -2880,22 +2896,6 @@
{
"name": "KEY_SECTION",
"value": 167
},
{
"name": "KEY_GLOBE",
"value": 4194416
},
{
"name": "KEY_KEYBOARD",
"value": 4194417
},
{
"name": "KEY_JIS_EISU",
"value": 4194418
},
{
"name": "KEY_JIS_KANA",
"value": 4194419
}
]
},
@@ -113089,6 +113089,29 @@
}
]
},
{
"name": "should_ignore_device",
"is_const": true,
"is_vararg": false,
"is_static": false,
"is_virtual": false,
"hash": 2522259332,
"return_value": {
"type": "bool"
},
"arguments": [
{
"name": "vendor_id",
"type": "int",
"meta": "int32"
},
{
"name": "product_id",
"type": "int",
"meta": "int32"
}
]
},
{
"name": "get_connected_joypads",
"is_const": false,
@@ -122009,6 +122032,10 @@
{
"name": "BAKE_ERROR_USER_ABORTED",
"value": 8
},
{
"name": "BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL",
"value": 9
}
]
},
@@ -149258,6 +149285,93 @@
}
]
},
{
"name": "OpenXRInteractionProfileMetadata",
"is_refcounted": false,
"is_instantiable": true,
"inherits": "Object",
"api_type": "core",
"methods": [
{
"name": "register_top_level_path",
"is_const": false,
"is_vararg": false,
"is_static": false,
"is_virtual": false,
"hash": 254767734,
"arguments": [
{
"name": "display_name",
"type": "String"
},
{
"name": "openxr_path",
"type": "String"
},
{
"name": "openxr_extension_name",
"type": "String"
}
]
},
{
"name": "register_interaction_profile",
"is_const": false,
"is_vararg": false,
"is_static": false,
"is_virtual": false,
"hash": 254767734,
"arguments": [
{
"name": "display_name",
"type": "String"
},
{
"name": "openxr_path",
"type": "String"
},
{
"name": "openxr_extension_name",
"type": "String"
}
]
},
{
"name": "register_io_path",
"is_const": false,
"is_vararg": false,
"is_static": false,
"is_virtual": false,
"hash": 3443511926,
"arguments": [
{
"name": "interaction_profile",
"type": "String"
},
{
"name": "display_name",
"type": "String"
},
{
"name": "toplevel_path",
"type": "String"
},
{
"name": "openxr_path",
"type": "String"
},
{
"name": "openxr_extension_name",
"type": "String"
},
{
"name": "action_type",
"type": "enum::OpenXRAction.ActionType"
}
]
}
]
},
{
"name": "OpenXRInterface",
"is_refcounted": true,
@@ -251426,6 +251540,43 @@
}
]
},
{
"name": "set_text_overrun_behavior",
"is_const": false,
"is_vararg": false,
"is_static": false,
"is_virtual": false,
"hash": 1940772195,
"arguments": [
{
"name": "column",
"type": "int",
"meta": "int32"
},
{
"name": "overrun_behavior",
"type": "enum::TextServer.OverrunBehavior"
}
]
},
{
"name": "get_text_overrun_behavior",
"is_const": true,
"is_vararg": false,
"is_static": false,
"is_virtual": false,
"hash": 3782727860,
"return_value": {
"type": "enum::TextServer.OverrunBehavior"
},
"arguments": [
{
"name": "column",
"type": "int",
"meta": "int32"
}
]
},
{
"name": "set_structured_text_bidi_override",
"is_const": false,

View File

@@ -0,0 +1,62 @@
/**************************************************************************/
/* editor_plugin_registration.hpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
#define GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
#include <godot_cpp/templates/vector.hpp>
namespace godot {
class EditorPlugin;
class StringName;
class EditorPlugins {
private:
static Vector<StringName> plugin_classes;
public:
static void add_plugin_class(const StringName &p_class_name);
static void remove_plugin_class(const StringName &p_class_name);
static void deinitialize(GDExtensionInitializationLevel p_level);
template <class T>
static void add_by_type() {
add_plugin_class(T::get_class_static());
}
template <class T>
static void remove_by_type() {
remove_plugin_class(T::get_class_static());
}
};
} // namespace godot
#endif // GODOT_EDITOR_PLUGIN_REGISTRATION_HPP

View File

@@ -63,7 +63,7 @@ class Ref {
}
void ref_pointer(T *p_ref) {
ERR_FAIL_COND(!p_ref);
ERR_FAIL_NULL(p_ref);
if (p_ref->init_ref()) {
reference = p_ref;

View File

@@ -95,6 +95,26 @@ public:
GodotObject *_owner = nullptr;
};
namespace internal {
typedef void (*EngineClassRegistrationCallback)();
void add_engine_class_registration_callback(EngineClassRegistrationCallback p_callback);
void register_engine_class(const StringName &p_name, const GDExtensionInstanceBindingCallbacks *p_callbacks);
void register_engine_classes();
template <class T>
struct EngineClassRegistration {
EngineClassRegistration() {
add_engine_class_registration_callback(&EngineClassRegistration<T>::callback);
}
static void callback() {
register_engine_class(T::get_class_static(), &T::_gde_binding_callbacks);
}
};
} // namespace internal
} // namespace godot
#define GDCLASS(m_class, m_inherits) \
@@ -132,16 +152,16 @@ protected:
return (void(::godot::Wrapped::*)(::godot::List<::godot::PropertyInfo> * p_list) const) & m_class::_get_property_list; \
} \
\
static bool (::godot::Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) { \
return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name)) & m_class::_property_can_revert; \
static bool (::godot::Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) const { \
return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name) const) & m_class::_property_can_revert; \
} \
\
static bool (::godot::Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, ::godot::Variant &) { \
return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name, ::godot::Variant &)) & m_class::_property_get_revert; \
static bool (::godot::Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, ::godot::Variant &) const { \
return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name, ::godot::Variant &) const) & m_class::_property_get_revert; \
} \
\
static ::godot::String (::godot::Wrapped::*_get_to_string())() { \
return (::godot::String(::godot::Wrapped::*)()) & m_class::_to_string; \
static ::godot::String (::godot::Wrapped::*_get_to_string())() const { \
return (::godot::String(::godot::Wrapped::*)() const) & m_class::_to_string; \
} \
\
template <class T, class B> \
@@ -150,6 +170,8 @@ protected:
} \
\
public: \
typedef m_class self_type; \
\
static void initialize_class() { \
static bool initialized = false; \
if (initialized) { \
@@ -303,82 +325,107 @@ public:
_gde_binding_create_callback, \
_gde_binding_free_callback, \
_gde_binding_reference_callback, \
};
}; \
\
private:
// Don't use this for your classes, use GDCLASS() instead.
#define GDEXTENSION_CLASS(m_class, m_inherits) \
private: \
void operator=(const m_class &p_rval) {} \
\
protected: \
virtual const GDExtensionInstanceBindingCallbacks *_get_bindings_callbacks() const override { \
return &_gde_binding_callbacks; \
} \
\
m_class(const char *p_godot_class) : m_inherits(p_godot_class) {} \
m_class(GodotObject *p_godot_object) : m_inherits(p_godot_object) {} \
\
static void (*_get_bind_methods())() { \
return nullptr; \
} \
\
static void (Wrapped::*_get_notification())(int) { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_set())(const ::godot::StringName &p_name, const Variant &p_property) { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_get())(const ::godot::StringName &p_name, Variant &r_ret) const { \
return nullptr; \
} \
\
static void (Wrapped::*_get_get_property_list())(List<PropertyInfo> * p_list) const { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, Variant &) { \
return nullptr; \
} \
\
static String (Wrapped::*_get_to_string())() { \
return nullptr; \
} \
\
public: \
static void initialize_class() {} \
\
static ::godot::StringName &get_class_static() { \
static ::godot::StringName string_name = ::godot::StringName(#m_class); \
return string_name; \
} \
\
static ::godot::StringName &get_parent_class_static() { \
return m_inherits::get_class_static(); \
} \
\
static void *_gde_binding_create_callback(void *p_token, void *p_instance) { \
/* Do not call memnew here, we don't want the post-initializer to be called */ \
return new ("") m_class((GodotObject *)p_instance); \
} \
static void _gde_binding_free_callback(void *p_token, void *p_instance, void *p_binding) { \
/* Explicitly call the deconstructor to ensure proper lifecycle for non-trivial members */ \
reinterpret_cast<m_class *>(p_binding)->~m_class(); \
Memory::free_static(reinterpret_cast<m_class *>(p_binding)); \
} \
static GDExtensionBool _gde_binding_reference_callback(void *p_token, void *p_instance, GDExtensionBool p_reference) { \
return true; \
} \
static constexpr GDExtensionInstanceBindingCallbacks _gde_binding_callbacks = { \
_gde_binding_create_callback, \
_gde_binding_free_callback, \
_gde_binding_reference_callback, \
}; \
m_class() : m_class(#m_class) {}
#define GDEXTENSION_CLASS_ALIAS(m_class, m_alias_for, m_inherits) /******************************************************************************************************************/ \
private: \
inline static ::godot::internal::EngineClassRegistration<m_class> _gde_engine_class_registration_helper; \
void operator=(const m_class &p_rval) {} \
\
protected: \
virtual const GDExtensionInstanceBindingCallbacks *_get_bindings_callbacks() const override { \
return &_gde_binding_callbacks; \
} \
\
m_class(const char *p_godot_class) : m_inherits(p_godot_class) {} \
m_class(GodotObject *p_godot_object) : m_inherits(p_godot_object) {} \
\
static void (*_get_bind_methods())() { \
return nullptr; \
} \
\
static void (Wrapped::*_get_notification())(int) { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_set())(const ::godot::StringName &p_name, const Variant &p_property) { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_get())(const ::godot::StringName &p_name, Variant &r_ret) const { \
return nullptr; \
} \
\
static inline bool has_get_property_list() { \
return false; \
} \
\
static void (Wrapped::*_get_get_property_list())(List<PropertyInfo> * p_list) const { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_property_can_revert())(const ::godot::StringName &p_name) const { \
return nullptr; \
} \
\
static bool (Wrapped::*_get_property_get_revert())(const ::godot::StringName &p_name, Variant &) const { \
return nullptr; \
} \
\
static void (Wrapped::*_get_validate_property())(::godot::PropertyInfo & p_property) const { \
return nullptr; \
} \
\
static String (Wrapped::*_get_to_string())() const { \
return nullptr; \
} \
\
public: \
typedef m_class self_type; \
\
static void initialize_class() {} \
\
static ::godot::StringName &get_class_static() { \
static ::godot::StringName string_name = ::godot::StringName(#m_alias_for); \
return string_name; \
} \
\
static ::godot::StringName &get_parent_class_static() { \
return m_inherits::get_class_static(); \
} \
\
static GDExtensionObjectPtr create(void *data) { \
return nullptr; \
} \
\
static void free(void *data, GDExtensionClassInstancePtr ptr) { \
} \
\
static void *_gde_binding_create_callback(void *p_token, void *p_instance) { \
/* Do not call memnew here, we don't want the post-initializer to be called */ \
return new ("") m_class((GodotObject *)p_instance); \
} \
static void _gde_binding_free_callback(void *p_token, void *p_instance, void *p_binding) { \
/* Explicitly call the deconstructor to ensure proper lifecycle for non-trivial members */ \
reinterpret_cast<m_class *>(p_binding)->~m_class(); \
Memory::free_static(reinterpret_cast<m_class *>(p_binding)); \
} \
static GDExtensionBool _gde_binding_reference_callback(void *p_token, void *p_instance, GDExtensionBool p_reference) { \
return true; \
} \
static constexpr GDExtensionInstanceBindingCallbacks _gde_binding_callbacks = { \
_gde_binding_create_callback, \
_gde_binding_free_callback, \
_gde_binding_reference_callback, \
}; \
m_class() : m_class(#m_alias_for) {} \
\
private:
// Don't use this for your classes, use GDCLASS() instead.
#define GDEXTENSION_CLASS(m_class, m_inherits) GDEXTENSION_CLASS_ALIAS(m_class, m_class, m_inherits)
#endif // GODOT_WRAPPED_HPP

View File

@@ -38,6 +38,8 @@
#include <godot_cpp/core/method_bind.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/classes/class_db_singleton.hpp>
#include <list>
#include <set>
#include <string>
@@ -120,8 +122,10 @@ public:
static void register_class(bool p_virtual = false);
template <class T>
static void register_abstract_class();
template <class T>
static void register_engine_class();
_FORCE_INLINE_ static void _register_engine_class(const StringName &p_name, const GDExtensionInstanceBindingCallbacks *p_callbacks) {
instance_binding_callbacks[p_name] = p_callbacks;
}
template <class N, class M, typename... VarArgs>
static MethodBind *bind_method(N p_method_name, M p_method, VarArgs... p_args);
@@ -146,6 +150,8 @@ public:
static void initialize(GDExtensionInitializationLevel p_level);
static void deinitialize(GDExtensionInitializationLevel p_level);
CLASSDB_SINGLETON_FORWARD_METHODS;
};
#define BIND_CONSTANT(m_constant) \
@@ -167,6 +173,7 @@ public:
template <class T, bool is_abstract>
void ClassDB::_register_class(bool p_virtual) {
static_assert(TypesAreSame<typename T::self_type, T>::value, "Class not declared properly, please use GDCLASS.");
instance_binding_callbacks[T::get_class_static()] = &T::_gde_binding_callbacks;
// Register this class within our plugin
@@ -222,11 +229,6 @@ void ClassDB::register_abstract_class() {
ClassDB::_register_class<T, true>();
}
template <class T>
void ClassDB::register_engine_class() {
instance_binding_callbacks[T::get_class_static()] = &T::_gde_binding_callbacks;
}
template <class N, class M, typename... VarArgs>
MethodBind *ClassDB::bind_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.
@@ -253,7 +255,7 @@ MethodBind *ClassDB::bind_static_method(StringName p_class, N p_method_name, M p
template <class M>
MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p_method, const MethodInfo &p_info, const std::vector<Variant> &p_default_args, bool p_return_nil_is_variant) {
MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant);
ERR_FAIL_COND_V(!bind, nullptr);
ERR_FAIL_NULL_V(bind, nullptr);
bind->set_name(p_name);
bind->set_default_arguments(p_default_args);

View File

@@ -146,7 +146,7 @@ T *memnew_arr_template(size_t p_elements, const char *p_descr = "") {
size_t len = sizeof(T) * p_elements;
uint64_t *mem = (uint64_t *)Memory::alloc_static(len, true);
T *failptr = nullptr; // Get rid of a warning.
ERR_FAIL_COND_V(!mem, failptr);
ERR_FAIL_NULL_V(mem, failptr);
*(mem - 1) = p_elements;
if (!std::is_trivially_destructible<T>::value) {

View File

@@ -168,6 +168,7 @@ MAKE_PTRARG_BY_REFERENCE(Variant);
template <class T>
struct PtrToArg<T *> {
static_assert(std::is_base_of<Object, T>::value, "Cannot encode non-Object value as an Object");
_FORCE_INLINE_ static T *convert(const void *p_ptr) {
return reinterpret_cast<T *>(godot::internal::get_object_instance_binding(*reinterpret_cast<GDExtensionObjectPtr *>(const_cast<void *>(p_ptr))));
}
@@ -179,6 +180,7 @@ struct PtrToArg<T *> {
template <class T>
struct PtrToArg<const T *> {
static_assert(std::is_base_of<Object, T>::value, "Cannot encode non-Object value as an Object");
_FORCE_INLINE_ static const T *convert(const void *p_ptr) {
return reinterpret_cast<const T *>(godot::internal::get_object_instance_binding(*reinterpret_cast<GDExtensionObjectPtr *>(const_cast<void *>(p_ptr))));
}

View File

@@ -191,9 +191,6 @@ enum ModuleInitializationLevel {
};
class GDExtensionBinding {
private:
static void register_engine_classes();
public:
using Callback = void (*)(ModuleInitializationLevel p_level);

View File

@@ -32,13 +32,14 @@
#define GODOT_COWDATA_HPP
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/core/math.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/templates/safe_refcount.hpp>
#include <cstring>
#include <new>
#include <type_traits>
namespace godot {
@@ -48,6 +49,9 @@ class Vector;
template <class T, class V>
class VMap;
template <class T>
class CharStringT;
// Silence a false positive warning (see GH-52119).
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
@@ -62,6 +66,9 @@ class CowData {
template <class TV, class VV>
friend class VMap;
template <class TS>
friend class CharStringT;
private:
mutable T *_ptr = nullptr;
@@ -95,6 +102,10 @@ private:
}
_FORCE_INLINE_ bool _get_alloc_size_checked(size_t p_elements, size_t *out) const {
if (unlikely(p_elements == 0)) {
*out = 0;
return true;
}
#if defined(__GNUC__)
size_t o;
size_t p;
@@ -106,13 +117,12 @@ private:
if (__builtin_add_overflow(o, static_cast<size_t>(32), &p)) {
return false; // No longer allocated here.
}
return true;
#else
// Speed is more important than correctness here, do the operations unchecked
// and hope for the best.
*out = _get_alloc_size(p_elements);
return true;
#endif
return *out;
}
void _unref(void *p_data);
@@ -204,9 +214,9 @@ void CowData<T>::_unref(void *p_data) {
if (refc->decrement() > 0) {
return; // still in use
}
// clean up
if (!__has_trivial_destructor(T)) {
// clean up
if (!std::is_trivially_destructible<T>::value) {
uint32_t *count = _get_size();
T *data = (T *)(count + 1);
@@ -241,7 +251,7 @@ uint32_t CowData<T>::_copy_on_write() {
T *_data = (T *)(mem_new);
// initialize new elements
if (__has_trivial_copy(T)) {
if (std::is_trivially_copyable<T>::value) {
memcpy(mem_new, _ptr, current_size * sizeof(T));
} else {
@@ -287,7 +297,7 @@ Error CowData<T>::resize(int p_size) {
if (current_size == 0) {
// alloc from scratch
uint32_t *ptr = (uint32_t *)Memory::alloc_static(alloc_size, true);
ERR_FAIL_COND_V(!ptr, ERR_OUT_OF_MEMORY);
ERR_FAIL_NULL_V(ptr, ERR_OUT_OF_MEMORY);
*(ptr - 1) = 0; // size, currently none
new (ptr - 2) SafeNumeric<uint32_t>(1); // refcount
@@ -295,7 +305,7 @@ Error CowData<T>::resize(int p_size) {
} else {
uint32_t *_ptrnew = (uint32_t *)Memory::realloc_static(_ptr, alloc_size, true);
ERR_FAIL_COND_V(!_ptrnew, ERR_OUT_OF_MEMORY);
ERR_FAIL_NULL_V(_ptrnew, ERR_OUT_OF_MEMORY);
new (_ptrnew - 2) SafeNumeric<uint32_t>(rc); // refcount
_ptr = (T *)(_ptrnew);
@@ -304,7 +314,7 @@ Error CowData<T>::resize(int p_size) {
// construct the newly created elements
if (!__has_trivial_constructor(T)) {
if (!std::is_trivially_constructible<T>::value) {
T *elems = _get_data();
for (int i = *_get_size(); i < p_size; i++) {
@@ -315,7 +325,7 @@ Error CowData<T>::resize(int p_size) {
*_get_size() = p_size;
} else if (p_size < current_size) {
if (!__has_trivial_destructor(T)) {
if (!std::is_trivially_destructible<T>::value) {
// deinitialize no longer needed elements
for (uint32_t i = p_size; i < *_get_size(); i++) {
T *t = &_get_data()[i];
@@ -325,7 +335,7 @@ Error CowData<T>::resize(int p_size) {
if (alloc_size != current_alloc_size) {
uint32_t *_ptrnew = (uint32_t *)Memory::realloc_static(_ptr, alloc_size, true);
ERR_FAIL_COND_V(!_ptrnew, ERR_OUT_OF_MEMORY);
ERR_FAIL_NULL_V(_ptrnew, ERR_OUT_OF_MEMORY);
new (_ptrnew - 2) SafeNumeric<uint32_t>(rc); // refcount
_ptr = (T *)(_ptrnew);

View File

@@ -221,7 +221,7 @@ private:
int size_cache = 0;
bool erase(const Element *p_I) {
ERR_FAIL_COND_V(!p_I, false);
ERR_FAIL_NULL_V(p_I, false);
ERR_FAIL_COND_V(p_I->data != this, false);
if (first == p_I) {

View File

@@ -186,12 +186,12 @@ public:
}
void initialize_rid(RID p_rid) {
T *mem = get_or_null(p_rid, true);
ERR_FAIL_COND(!mem);
ERR_FAIL_NULL(mem);
memnew_placement(mem, T);
}
void initialize_rid(RID p_rid, const T &p_value) {
T *mem = get_or_null(p_rid, true);
ERR_FAIL_COND(!mem);
ERR_FAIL_NULL(mem);
memnew_placement(mem, T(p_value));
}
@@ -374,7 +374,7 @@ public:
_FORCE_INLINE_ void replace(const RID &p_rid, T *p_new_ptr) {
T **ptr = alloc.get_or_null(p_rid);
ERR_FAIL_COND(!ptr);
ERR_FAIL_NULL(ptr);
*ptr = p_new_ptr;
}

View File

@@ -96,7 +96,7 @@ class ThreadWorkPool {
public:
template <class C, class M, class U>
void begin_work(uint32_t p_elements, C *p_instance, M p_method, U p_userdata) {
ERR_FAIL_COND(!threads); // never initialized
ERR_FAIL_NULL(threads); // Never initialized.
ERR_FAIL_COND(current_work != nullptr);
index.store(0, std::memory_order_release);
@@ -123,18 +123,18 @@ public:
}
bool is_done_dispatching() const {
ERR_FAIL_COND_V(current_work == nullptr, true);
ERR_FAIL_NULL_V(current_work, true);
return index.load(std::memory_order_acquire) >= current_work->max_elements;
}
uint32_t get_work_index() const {
ERR_FAIL_COND_V(current_work == nullptr, 0);
ERR_FAIL_NULL_V(current_work, 0);
uint32_t idx = index.load(std::memory_order_acquire);
return Math::min(idx, current_work->max_elements);
}
void end_work() {
ERR_FAIL_COND(current_work == nullptr);
ERR_FAIL_NULL(current_work);
for (uint32_t i = 0; i < threads_working; i++) {
threads[i].completed.wait();
threads[i].work = nullptr;

View File

@@ -31,82 +31,111 @@
#ifndef GODOT_CHAR_STRING_HPP
#define GODOT_CHAR_STRING_HPP
#include <godot_cpp/templates/cowdata.hpp>
#include <cstddef>
#include <cstdint>
namespace godot {
class CharString {
friend class String;
template <class T>
class CharStringT;
const char *_data = nullptr;
int _length = 0;
template <class T>
class CharProxy {
template <class TS>
friend class CharStringT;
CharString(const char *str, int length);
const int _index;
CowData<T> &_cowdata;
static inline const T _null = 0;
_FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &p_cowdata) :
_index(p_index),
_cowdata(p_cowdata) {}
public:
int length() const;
const char *get_data() const;
_FORCE_INLINE_ CharProxy(const CharProxy<T> &p_other) :
_index(p_other._index),
_cowdata(p_other._cowdata) {}
CharString(CharString &&p_str);
void operator=(CharString &&p_str);
CharString() {}
~CharString();
_FORCE_INLINE_ operator T() const {
if (unlikely(_index == _cowdata.size())) {
return _null;
}
return _cowdata.get(_index);
}
_FORCE_INLINE_ const T *operator&() const {
return _cowdata.ptr() + _index;
}
_FORCE_INLINE_ void operator=(const T &p_other) const {
_cowdata.set(_index, p_other);
}
_FORCE_INLINE_ void operator=(const CharProxy<T> &p_other) const {
_cowdata.set(_index, p_other.operator T());
}
};
class Char16String {
template <class T>
class CharStringT {
friend class String;
const char16_t *_data = nullptr;
int _length = 0;
Char16String(const char16_t *str, int length);
CowData<T> _cowdata;
static inline const T _null = 0;
public:
int length() const;
const char16_t *get_data() const;
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
_FORCE_INLINE_ int size() const { return _cowdata.size(); }
Error resize(int p_size) { return _cowdata.resize(p_size); }
Char16String(Char16String &&p_str);
void operator=(Char16String &&p_str);
Char16String() {}
~Char16String();
_FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); }
_FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
_FORCE_INLINE_ const T &operator[](int p_index) const {
if (unlikely(p_index == _cowdata.size())) {
return _null;
}
return _cowdata.get(p_index);
}
_FORCE_INLINE_ CharProxy<T> operator[](int p_index) { return CharProxy<T>(p_index, _cowdata); }
_FORCE_INLINE_ CharStringT() {}
_FORCE_INLINE_ CharStringT(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ void operator=(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); }
void operator=(const T *p_cstr);
bool operator<(const CharStringT<T> &p_right) const;
CharStringT<T> &operator+=(T p_char);
int length() const { return size() ? size() - 1 : 0; }
const T *get_data() const;
operator const T *() const { return get_data(); };
protected:
void copy_from(const T *p_cstr);
};
class Char32String {
friend class String;
template <>
const char *CharStringT<char>::get_data() const;
const char32_t *_data = nullptr;
int _length = 0;
template <>
const char16_t *CharStringT<char16_t>::get_data() const;
Char32String(const char32_t *str, int length);
template <>
const char32_t *CharStringT<char32_t>::get_data() const;
public:
int length() const;
const char32_t *get_data() const;
template <>
const wchar_t *CharStringT<wchar_t>::get_data() const;
Char32String(Char32String &&p_str);
void operator=(Char32String &&p_str);
Char32String() {}
~Char32String();
};
class CharWideString {
friend class String;
const wchar_t *_data = nullptr;
int _length = 0;
CharWideString(const wchar_t *str, int length);
public:
int length() const;
const wchar_t *get_data() const;
CharWideString(CharWideString &&p_str);
void operator=(CharWideString &&p_str);
CharWideString() {}
~CharWideString();
};
typedef CharStringT<char> CharString;
typedef CharStringT<char16_t> Char16String;
typedef CharStringT<char32_t> Char32String;
typedef CharStringT<wchar_t> CharWideString;
} // namespace godot

View File

@@ -255,25 +255,33 @@ public:
bool operator!=(const Variant &other) const;
bool operator<(const Variant &other) const;
void call(const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error);
void callp(const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error);
template <class... Args>
Variant call(const StringName &method, Args... args) {
std::array<Variant, sizeof...(args)> vargs = { args... };
std::array<const Variant *, sizeof...(args)> argptrs;
for (size_t i = 0; i < vargs.size(); i++) {
argptrs[i] = &vargs[i];
}
Variant result;
GDExtensionCallError error;
std::array<GDExtensionConstVariantPtr, sizeof...(Args)> call_args = { Variant(args)... };
call(method, call_args.data(), call_args.size(), result, error);
callp(method, argptrs.data(), argptrs.size(), result, error);
return result;
}
static void call_static(Variant::Type type, const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error);
static void callp_static(Variant::Type type, const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error);
template <class... Args>
static Variant call_static(Variant::Type type, const StringName &method, Args... args) {
std::array<Variant, sizeof...(args)> vargs = { args... };
std::array<const Variant *, sizeof...(args)> argptrs;
for (size_t i = 0; i < vargs.size(); i++) {
argptrs[i] = &vargs[i];
}
Variant result;
GDExtensionCallError error;
std::array<GDExtensionConstVariantPtr, sizeof...(Args)> call_args = { Variant(args)... };
call_static(type, method, call_args.data(), call_args.size(), result, error);
callp_static(type, method, argptrs.data(), argptrs.size(), sizeof...(args), result, error);
return result;
}

View File

@@ -78,6 +78,14 @@ struct _NO_DISCARD_ Vector3 {
return x < y ? (y < z ? Vector3::AXIS_Z : Vector3::AXIS_Y) : (x < z ? Vector3::AXIS_Z : Vector3::AXIS_X);
}
Vector3 min(const Vector3 &p_vector3) const {
return Vector3(MIN(x, p_vector3.x), MIN(y, p_vector3.y), MIN(z, p_vector3.z));
}
Vector3 max(const Vector3 &p_vector3) const {
return Vector3(MAX(x, p_vector3.x), MAX(y, p_vector3.y), MAX(z, p_vector3.z));
}
_FORCE_INLINE_ real_t length() const;
_FORCE_INLINE_ real_t length_squared() const;

View File

@@ -71,6 +71,14 @@ struct _NO_DISCARD_ Vector3i {
Vector3i::Axis min_axis_index() const;
Vector3i::Axis max_axis_index() const;
Vector3i min(const Vector3i &p_vector3i) const {
return Vector3i(MIN(x, p_vector3i.x), MIN(y, p_vector3i.y), MIN(z, p_vector3i.z));
}
Vector3i max(const Vector3i &p_vector3i) const {
return Vector3i(MAX(x, p_vector3i.x), MAX(y, p_vector3i.y), MAX(z, p_vector3i.z));
}
_FORCE_INLINE_ int64_t length_squared() const;
_FORCE_INLINE_ double length() const;

View File

@@ -70,6 +70,14 @@ struct _NO_DISCARD_ Vector4 {
Vector4::Axis min_axis_index() const;
Vector4::Axis max_axis_index() const;
Vector4 min(const Vector4 &p_vector4) const {
return Vector4(MIN(x, p_vector4.x), MIN(y, p_vector4.y), MIN(z, p_vector4.z), MIN(w, p_vector4.w));
}
Vector4 max(const Vector4 &p_vector4) const {
return Vector4(MAX(x, p_vector4.x), MAX(y, p_vector4.y), MAX(z, p_vector4.z), MAX(w, p_vector4.w));
}
_FORCE_INLINE_ real_t length_squared() const;
bool is_equal_approx(const Vector4 &p_vec4) const;
bool is_zero_approx() const;

View File

@@ -73,6 +73,14 @@ struct _NO_DISCARD_ Vector4i {
Vector4i::Axis min_axis_index() const;
Vector4i::Axis max_axis_index() const;
Vector4i min(const Vector4i &p_vector4i) const {
return Vector4i(MIN(x, p_vector4i.x), MIN(y, p_vector4i.y), MIN(z, p_vector4i.z), MIN(w, p_vector4i.w));
}
Vector4i max(const Vector4i &p_vector4i) const {
return Vector4i(MAX(x, p_vector4i.x), MAX(y, p_vector4i.y), MAX(z, p_vector4i.z), MAX(w, p_vector4i.w));
}
_FORCE_INLINE_ int64_t length_squared() const;
_FORCE_INLINE_ double length() const;

View File

@@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_plugin.cpp */
/* editor_plugin_registration.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,9 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include <godot_cpp/classes/editor_plugin.hpp>
#include <godot_cpp/classes/editor_plugin_registration.hpp>
#include <godot_cpp/variant/string_name.hpp>
#include <godot_cpp/variant/variant.hpp>
namespace godot {

View File

@@ -28,12 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include <vector>
#include <godot_cpp/classes/wrapped.hpp>
#include <godot_cpp/variant/builtin_types.hpp>
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/core/class_db.hpp>
namespace godot {
const StringName *Wrapped::_get_extension_class_name() const {
@@ -60,4 +64,29 @@ void postinitialize_handler(Wrapped *p_wrapped) {
p_wrapped->_postinitialize();
}
namespace internal {
std::vector<EngineClassRegistrationCallback> &get_engine_class_registration_callbacks() {
static std::vector<EngineClassRegistrationCallback> engine_class_registration_callbacks;
return engine_class_registration_callbacks;
}
void add_engine_class_registration_callback(EngineClassRegistrationCallback p_callback) {
get_engine_class_registration_callbacks().push_back(p_callback);
}
void register_engine_class(const StringName &p_name, const GDExtensionInstanceBindingCallbacks *p_callbacks) {
ClassDB::_register_engine_class(p_name, p_callbacks);
}
void register_engine_classes() {
std::vector<EngineClassRegistrationCallback> &engine_class_registration_callbacks = get_engine_class_registration_callbacks();
for (EngineClassRegistrationCallback cb : engine_class_registration_callbacks) {
cb();
}
engine_class_registration_callbacks.clear();
}
} // namespace internal
} // namespace godot

View File

@@ -77,7 +77,7 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
if (p_setter != String("")) {
setter = get_method(p_class, p_setter);
ERR_FAIL_COND_MSG(!setter, String("Setter method '{0}::{1}()' not found for property '{2}::{3}'.").format(Array::make(p_class, p_setter, p_class, p_pinfo.name)));
ERR_FAIL_NULL_MSG(setter, String("Setter method '{0}::{1}()' not found for property '{2}::{3}'.").format(Array::make(p_class, p_setter, p_class, p_pinfo.name)));
size_t exp_args = 1 + (p_index >= 0 ? 1 : 0);
ERR_FAIL_COND_MSG((int)exp_args != setter->get_argument_count(), String("Setter method '{0}::{1}()' must take a single argument.").format(Array::make(p_class, p_setter)));
@@ -86,7 +86,7 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
ERR_FAIL_COND_MSG(p_getter == String(""), String("Getter method must be specified for '{0}::{1}'.").format(Array::make(p_class, p_pinfo.name)));
MethodBind *getter = get_method(p_class, p_getter);
ERR_FAIL_COND_MSG(!getter, String("Getter method '{0}::{1}()' not found for property '{2}::{3}'.").format(Array::make(p_class, p_getter, p_class, p_pinfo.name)));
ERR_FAIL_NULL_MSG(getter, String("Getter method '{0}::{1}()' not found for property '{2}::{3}'.").format(Array::make(p_class, p_getter, p_class, p_pinfo.name)));
{
size_t exp_args = 0 + (p_index >= 0 ? 1 : 0);
ERR_FAIL_COND_MSG((int)exp_args != getter->get_argument_count(), String("Getter method '{0}::{1}()' must not take any argument.").format(Array::make(p_class, p_getter)));
@@ -318,7 +318,18 @@ GDExtensionClassCallVirtual ClassDB::get_virtual_func(void *p_userdata, GDExtens
const GDExtensionInstanceBindingCallbacks *ClassDB::get_instance_binding_callbacks(const StringName &p_class) {
std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *>::iterator callbacks_it = instance_binding_callbacks.find(p_class);
ERR_FAIL_COND_V_MSG(callbacks_it == instance_binding_callbacks.end(), nullptr, String("Cannot find instance binding callbacks for class '{0}'.").format(Array::make(p_class)));
if (likely(callbacks_it != instance_binding_callbacks.end())) {
return callbacks_it->second;
}
// If we don't have an instance binding callback for the given class, find the closest parent where we do.
StringName class_name = p_class;
do {
class_name = get_parent_class(class_name);
ERR_FAIL_COND_V_MSG(class_name == StringName(), nullptr, String("Cannot find instance binding callbacks for class '{0}'.").format(Array::make(p_class)));
callbacks_it = instance_binding_callbacks.find(class_name);
} while (callbacks_it == instance_binding_callbacks.end());
return callbacks_it->second;
}

View File

@@ -42,7 +42,7 @@ void *Memory::alloc_static(size_t p_bytes, bool p_pad_align) {
#endif
void *mem = internal::gdextension_interface_mem_alloc(p_bytes + (prepad ? PAD_ALIGN : 0));
ERR_FAIL_COND_V(!mem, nullptr);
ERR_FAIL_NULL_V(mem, nullptr);
if (prepad) {
uint8_t *s8 = (uint8_t *)mem;
@@ -71,7 +71,7 @@ void *Memory::realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align) {
if (prepad) {
mem -= PAD_ALIGN;
mem = (uint8_t *)internal::gdextension_interface_mem_realloc(mem, p_bytes + PAD_ALIGN);
ERR_FAIL_COND_V(!mem, nullptr);
ERR_FAIL_NULL_V(mem, nullptr);
return mem + PAD_ALIGN;
} else {
return (uint8_t *)internal::gdextension_interface_mem_realloc(mem, p_bytes);

View File

@@ -30,10 +30,11 @@
#include <godot_cpp/godot.hpp>
#include <godot_cpp/classes/editor_plugin.hpp>
#include <godot_cpp/classes/editor_plugin_registration.hpp>
#include <godot_cpp/classes/wrapped.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/core/version.hpp>
#include <godot_cpp/variant/variant.hpp>
#include <godot_cpp/core/error_macros.hpp>
@@ -192,9 +193,15 @@ GDExtensionBinding::Callback GDExtensionBinding::init_callback = nullptr;
GDExtensionBinding::Callback GDExtensionBinding::terminate_callback = nullptr;
GDExtensionInitializationLevel GDExtensionBinding::minimum_initialization_level = GDEXTENSION_INITIALIZATION_CORE;
#define LOAD_PROC_ADDRESS(m_name, m_type) \
internal::gdextension_interface_##m_name = (m_type)p_get_proc_address(#m_name); \
ERR_FAIL_NULL_V_MSG(internal::gdextension_interface_##m_name, false, "Unable to load GDExtension interface function " #m_name "()")
#define ERR_PRINT_EARLY(m_msg) \
internal::gdextension_interface_print_error(m_msg, FUNCTION_STR, __FILE__, __LINE__, false)
#define LOAD_PROC_ADDRESS(m_name, m_type) \
internal::gdextension_interface_##m_name = (m_type)p_get_proc_address(#m_name); \
if (!internal::gdextension_interface_##m_name) { \
ERR_PRINT_EARLY("Unable to load GDExtension interface function " #m_name "()"); \
return false; \
}
// Partial definition of the legacy interface so we can detect it and show an error.
typedef struct {
@@ -217,14 +224,15 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
if (raw_interface[0] == 4 && raw_interface[1] == 0) {
// Use the legacy interface only to give a nice error.
LegacyGDExtensionInterface *legacy_interface = (LegacyGDExtensionInterface *)p_get_proc_address;
internal::gdextension_interface_print_error_with_message = (GDExtensionInterfacePrintErrorWithMessage)legacy_interface->print_error_with_message;
ERR_FAIL_V_MSG(false, "Cannot load a GDExtension built for Godot 4.1+ in Godot 4.0.");
internal::gdextension_interface_print_error = (GDExtensionInterfacePrintError)legacy_interface->print_error;
ERR_PRINT_EARLY("Cannot load a GDExtension built for Godot 4.1+ in Godot 4.0.");
return false;
}
// Load the "print_error_with_message" function first (needed by the ERR_FAIL_NULL_V_MSG() macro).
internal::gdextension_interface_print_error_with_message = (GDExtensionInterfacePrintErrorWithMessage)p_get_proc_address("print_error_with_message");
if (!internal::gdextension_interface_print_error_with_message) {
printf("ERROR: Unable to load GDExtension interface function print_error_with_message().\n");
// Load the "print_error" function first (needed by the ERR_PRINT_EARLY() macro).
internal::gdextension_interface_print_error = (GDExtensionInterfacePrintError)p_get_proc_address("print_error");
if (!internal::gdextension_interface_print_error) {
printf("ERROR: Unable to load GDExtension interface function print_error().\n");
return false;
}
@@ -233,10 +241,33 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
internal::token = p_library;
LOAD_PROC_ADDRESS(get_godot_version, GDExtensionInterfaceGetGodotVersion);
internal::gdextension_interface_get_godot_version(&internal::godot_version);
// Check that godot-cpp was compiled using an extension_api.json older or at the
// same version as the Godot that is loading it.
bool compatible;
if (internal::godot_version.major != GODOT_VERSION_MAJOR) {
compatible = internal::godot_version.major > GODOT_VERSION_MAJOR;
} else if (internal::godot_version.minor != GODOT_VERSION_MINOR) {
compatible = internal::godot_version.minor > GODOT_VERSION_MINOR;
} else {
compatible = internal::godot_version.patch >= GODOT_VERSION_PATCH;
}
if (!compatible) {
// We need to use snprintf() here because vformat() uses Variant, and we haven't loaded
// the GDExtension interface far enough to use Variants yet.
char msg[128];
snprintf(msg, 128, "Cannot load a GDExtension built for Godot %d.%d.%d using an older version of Godot (%d.%d.%d).",
GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR, GODOT_VERSION_PATCH,
internal::godot_version.major, internal::godot_version.minor, internal::godot_version.patch);
ERR_PRINT_EARLY(msg);
return false;
}
LOAD_PROC_ADDRESS(mem_alloc, GDExtensionInterfaceMemAlloc);
LOAD_PROC_ADDRESS(mem_realloc, GDExtensionInterfaceMemRealloc);
LOAD_PROC_ADDRESS(mem_free, GDExtensionInterfaceMemFree);
LOAD_PROC_ADDRESS(print_error, GDExtensionInterfacePrintError);
LOAD_PROC_ADDRESS(print_error_with_message, GDExtensionInterfacePrintErrorWithMessage);
LOAD_PROC_ADDRESS(print_warning, GDExtensionInterfacePrintWarning);
LOAD_PROC_ADDRESS(print_warning_with_message, GDExtensionInterfacePrintWarningWithMessage);
LOAD_PROC_ADDRESS(print_script_error, GDExtensionInterfacePrintScriptError);
@@ -368,22 +399,20 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
LOAD_PROC_ADDRESS(editor_add_plugin, GDExtensionInterfaceEditorAddPlugin);
LOAD_PROC_ADDRESS(editor_remove_plugin, GDExtensionInterfaceEditorRemovePlugin);
// Load the Godot version.
internal::gdextension_interface_get_godot_version(&internal::godot_version);
r_initialization->initialize = initialize_level;
r_initialization->deinitialize = deinitialize_level;
r_initialization->minimum_initialization_level = minimum_initialization_level;
ERR_FAIL_COND_V_MSG(init_callback == nullptr, false, "Initialization callback must be defined.");
ERR_FAIL_NULL_V_MSG(init_callback, false, "Initialization callback must be defined.");
Variant::init_bindings();
register_engine_classes();
godot::internal::register_engine_classes();
return true;
}
#undef LOAD_PROC_ADDRESS
#undef ERR_PRINT_EARLY
void GDExtensionBinding::initialize_level(void *userdata, GDExtensionInitializationLevel p_level) {
ClassDB::current_level = p_level;

View File

@@ -38,117 +38,121 @@
#include <godot_cpp/godot.hpp>
#include <cmath>
#include <string>
namespace godot {
int CharString::length() const {
return _length;
}
template <typename L, typename R>
_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) {
while (true) {
const char32_t l = *l_ptr;
const char32_t r = *r_ptr;
const char *CharString::get_data() const {
return _data;
}
if (l == 0 && r == 0) {
return false;
} else if (l == 0) {
return true;
} else if (r == 0) {
return false;
} else if (l < r) {
return true;
} else if (l > r) {
return false;
}
CharString::CharString(CharString &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
void CharString::operator=(CharString &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
CharString::CharString(const char *str, int length) :
_data(str), _length(length) {}
CharString::~CharString() {
if (_data != nullptr) {
memdelete_arr(_data);
l_ptr++;
r_ptr++;
}
}
int Char16String::length() const {
return _length;
template <class T>
bool CharStringT<T>::operator<(const CharStringT<T> &p_right) const {
if (length() == 0) {
return p_right.length() != 0;
}
return is_str_less(get_data(), p_right.get_data());
}
const char16_t *Char16String::get_data() const {
return _data;
template <class T>
CharStringT<T> &CharStringT<T>::operator+=(T p_char) {
const int lhs_len = length();
resize(lhs_len + 2);
T *dst = ptrw();
dst[lhs_len] = p_char;
dst[lhs_len + 1] = 0;
return *this;
}
Char16String::Char16String(Char16String &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
template <class T>
void CharStringT<T>::operator=(const T *p_cstr) {
copy_from(p_cstr);
}
void Char16String::operator=(Char16String &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
Char16String::Char16String(const char16_t *str, int length) :
_data(str), _length(length) {}
Char16String::~Char16String() {
if (_data != nullptr) {
memdelete_arr(_data);
template <>
const char *CharStringT<char>::get_data() const {
if (size()) {
return &operator[](0);
} else {
return "";
}
}
int Char32String::length() const {
return _length;
}
const char32_t *Char32String::get_data() const {
return _data;
}
Char32String::Char32String(Char32String &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
void Char32String::operator=(Char32String &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
Char32String::Char32String(const char32_t *str, int length) :
_data(str), _length(length) {}
Char32String::~Char32String() {
if (_data != nullptr) {
memdelete_arr(_data);
template <>
const char16_t *CharStringT<char16_t>::get_data() const {
if (size()) {
return &operator[](0);
} else {
return u"";
}
}
int CharWideString::length() const {
return _length;
}
const wchar_t *CharWideString::get_data() const {
return _data;
}
CharWideString::CharWideString(CharWideString &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
void CharWideString::operator=(CharWideString &&p_str) {
SWAP(_length, p_str._length);
SWAP(_data, p_str._data);
}
CharWideString::CharWideString(const wchar_t *str, int length) :
_data(str), _length(length) {}
CharWideString::~CharWideString() {
if (_data != nullptr) {
memdelete_arr(_data);
template <>
const char32_t *CharStringT<char32_t>::get_data() const {
if (size()) {
return &operator[](0);
} else {
return U"";
}
}
template <>
const wchar_t *CharStringT<wchar_t>::get_data() const {
if (size()) {
return &operator[](0);
} else {
return L"";
}
}
template <class T>
void CharStringT<T>::copy_from(const T *p_cstr) {
if (!p_cstr) {
resize(0);
return;
}
size_t len = std::char_traits<T>::length(p_cstr);
if (len == 0) {
resize(0);
return;
}
Error err = resize(++len); // include terminating null char
ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string.");
memcpy(ptrw(), p_cstr, len);
}
template class CharStringT<char>;
template class CharStringT<char16_t>;
template class CharStringT<char32_t>;
template class CharStringT<wchar_t>;
// Custom String functions that are not part of bound API.
// It's easier to have them written in C++ directly than in a Python script that generates them.
@@ -228,56 +232,61 @@ String rtoss(double p_val) {
CharString String::utf8() const {
int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0);
int size = length + 1;
char *cstr = memnew_arr(char, size);
internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), cstr, length);
CharString str;
str.resize(size);
internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), str.ptrw(), length);
cstr[length] = '\0';
str[length] = '\0';
return CharString(cstr, length);
return str;
}
CharString String::ascii() const {
int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0);
int size = length + 1;
char *cstr = memnew_arr(char, size);
internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), cstr, length);
CharString str;
str.resize(size);
internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), str.ptrw(), length);
cstr[length] = '\0';
str[length] = '\0';
return CharString(cstr, length);
return str;
}
Char16String String::utf16() const {
int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0);
int size = length + 1;
char16_t *cstr = memnew_arr(char16_t, size);
internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), cstr, length);
Char16String str;
str.resize(size);
internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), str.ptrw(), length);
cstr[length] = '\0';
str[length] = '\0';
return Char16String(cstr, length);
return str;
}
Char32String String::utf32() const {
int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0);
int size = length + 1;
char32_t *cstr = memnew_arr(char32_t, size);
internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), cstr, length);
Char32String str;
str.resize(size);
internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), str.ptrw(), length);
cstr[length] = '\0';
str[length] = '\0';
return Char32String(cstr, length);
return str;
}
CharWideString String::wide_string() const {
int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0);
int size = length + 1;
wchar_t *cstr = memnew_arr(wchar_t, size);
internal::gdextension_interface_string_to_wide_chars(_native_ptr(), cstr, length);
CharWideString str;
str.resize(size);
internal::gdextension_interface_string_to_wide_chars(_native_ptr(), str.ptrw(), length);
cstr[length] = '\0';
str[length] = '\0';
return CharWideString(cstr, length);
return str;
}
String &String::operator=(const char *p_str) {

View File

@@ -549,11 +549,11 @@ bool Variant::operator<(const Variant &other) const {
return result.operator bool();
}
void Variant::call(const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error) {
void Variant::callp(const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error) {
internal::gdextension_interface_variant_call(_native_ptr(), method._native_ptr(), reinterpret_cast<GDExtensionConstVariantPtr *>(args), argcount, r_ret._native_ptr(), &r_error);
}
void Variant::call_static(Variant::Type type, const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error) {
void Variant::callp_static(Variant::Type type, const StringName &method, const Variant **args, int argcount, Variant &r_ret, GDExtensionCallError &r_error) {
internal::gdextension_interface_variant_call_static(static_cast<GDExtensionVariantType>(type), method._native_ptr(), reinterpret_cast<GDExtensionConstVariantPtr *>(args), argcount, r_ret._native_ptr(), &r_error);
}
@@ -638,14 +638,16 @@ bool Variant::in(const Variant &index, bool *r_valid) const {
bool Variant::iter_init(Variant &r_iter, bool &r_valid) const {
GDExtensionBool valid;
internal::gdextension_interface_variant_iter_init(_native_ptr(), r_iter._native_ptr(), &valid);
return PtrToArg<bool>::convert(&valid);
GDExtensionBool result = internal::gdextension_interface_variant_iter_init(_native_ptr(), r_iter._native_ptr(), &valid);
r_valid = PtrToArg<bool>::convert(&valid);
return PtrToArg<bool>::convert(&result);
}
bool Variant::iter_next(Variant &r_iter, bool &r_valid) const {
GDExtensionBool valid;
internal::gdextension_interface_variant_iter_next(_native_ptr(), r_iter._native_ptr(), &valid);
return PtrToArg<bool>::convert(&valid);
GDExtensionBool result = internal::gdextension_interface_variant_iter_next(_native_ptr(), r_iter._native_ptr(), &valid);
r_valid = PtrToArg<bool>::convert(&valid);
return PtrToArg<bool>::convert(&result);
}
Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const {

View File

@@ -36,7 +36,7 @@ set(GODOT_LINKER_FLAGS )
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# using Visual Studio C++
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /EHsc /WX") # /GF /MP
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /WX") # /GF /MP
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /DTYPED_METHOD_BIND")
if(CMAKE_BUILD_TYPE MATCHES Debug)
@@ -92,6 +92,21 @@ else()
endif(CMAKE_BUILD_TYPE MATCHES Debug)
endif()
# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time (GH-80513).
option(GODOT_DISABLE_EXCEPTIONS OFF "Force disabling exception handling code")
if (GODOT_DISABLE_EXCEPTIONS)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -D_HAS_EXCEPTIONS=0")
else()
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -fno-exceptions")
endif()
else()
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /EHsc")
endif()
endif()
# Get Sources
file(GLOB_RECURSE SOURCES src/*.c**)
file(GLOB_RECURSE HEADERS include/*.h**)

View File

@@ -1,7 +1,7 @@
[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = 4.1
compatibility_minimum = "4.1"
[libraries]
@@ -21,3 +21,5 @@ android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so"
android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"
web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm"
web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm"

View File

@@ -2,6 +2,9 @@ extends "res://test_base.gd"
var custom_signal_emitted = null
class TestClass:
func test(p_msg: String) -> String:
return p_msg + " world"
func _ready():
var example: Example = $Example
@@ -82,6 +85,10 @@ func _ready():
# UtilityFunctions::str()
assert_equal(example.test_str_utility(), "Hello, World! The answer is 42")
# Test converting string to char* and doing comparison.
assert_equal(example.test_string_is_fourty_two("blah"), false)
assert_equal(example.test_string_is_fourty_two("fourty two"), true)
# PackedArray iterators
assert_equal(example.test_vector_ops(), 105)
@@ -90,6 +97,53 @@ func _ready():
example.group_subgroup_custom_position = Vector2(50, 50)
assert_equal(example.group_subgroup_custom_position, Vector2(50, 50))
# Test Object::cast_to<>() and that correct wrappers are being used.
var control = Control.new()
var sprite = Sprite2D.new()
var example_ref = ExampleRef.new()
assert_equal(example.test_object_cast_to_node(control), true)
assert_equal(example.test_object_cast_to_control(control), true)
assert_equal(example.test_object_cast_to_example(control), false)
assert_equal(example.test_object_cast_to_node(example), true)
assert_equal(example.test_object_cast_to_control(example), true)
assert_equal(example.test_object_cast_to_example(example), true)
assert_equal(example.test_object_cast_to_node(sprite), true)
assert_equal(example.test_object_cast_to_control(sprite), false)
assert_equal(example.test_object_cast_to_example(sprite), false)
assert_equal(example.test_object_cast_to_node(example_ref), false)
assert_equal(example.test_object_cast_to_control(example_ref), false)
assert_equal(example.test_object_cast_to_example(example_ref), false)
control.queue_free()
sprite.queue_free()
# Test conversions to and from Variant.
assert_equal(example.test_variant_vector2i_conversion(Vector2i(1, 1)), Vector2i(1, 1))
assert_equal(example.test_variant_vector2i_conversion(Vector2(1.0, 1.0)), Vector2i(1, 1))
assert_equal(example.test_variant_int_conversion(10), 10)
assert_equal(example.test_variant_int_conversion(10.0), 10)
assert_equal(example.test_variant_float_conversion(10.0), 10.0)
assert_equal(example.test_variant_float_conversion(10), 10.0)
# Test that ptrcalls from GDExtension to the engine are correctly encoding Object and RefCounted.
var new_node = Node.new()
example.test_add_child(new_node)
assert_equal(new_node.get_parent(), example)
var new_tileset = TileSet.new()
var new_tilemap = TileMap.new()
example.test_set_tileset(new_tilemap, new_tileset)
assert_equal(new_tilemap.tile_set, new_tileset)
new_tilemap.queue_free()
# Test variant call.
var test_obj = TestClass.new()
assert_equal(example.test_variant_call(test_obj), "hello world")
# Constants.
assert_equal(Example.FIRST, 0)
assert_equal(Example.ANSWER_TO_EVERYTHING, 42)

View File

@@ -138,8 +138,22 @@ void Example::_bind_methods() {
ClassDB::bind_method(D_METHOD("test_node_argument"), &Example::test_node_argument);
ClassDB::bind_method(D_METHOD("test_string_ops"), &Example::test_string_ops);
ClassDB::bind_method(D_METHOD("test_str_utility"), &Example::test_str_utility);
ClassDB::bind_method(D_METHOD("test_string_is_fourty_two"), &Example::test_string_is_fourty_two);
ClassDB::bind_method(D_METHOD("test_vector_ops"), &Example::test_vector_ops);
ClassDB::bind_method(D_METHOD("test_object_cast_to_node", "object"), &Example::test_object_cast_to_node);
ClassDB::bind_method(D_METHOD("test_object_cast_to_control", "object"), &Example::test_object_cast_to_control);
ClassDB::bind_method(D_METHOD("test_object_cast_to_example", "object"), &Example::test_object_cast_to_example);
ClassDB::bind_method(D_METHOD("test_variant_vector2i_conversion", "variant"), &Example::test_variant_vector2i_conversion);
ClassDB::bind_method(D_METHOD("test_variant_int_conversion", "variant"), &Example::test_variant_int_conversion);
ClassDB::bind_method(D_METHOD("test_variant_float_conversion", "variant"), &Example::test_variant_float_conversion);
ClassDB::bind_method(D_METHOD("test_add_child", "node"), &Example::test_add_child);
ClassDB::bind_method(D_METHOD("test_set_tileset", "tilemap", "tileset"), &Example::test_set_tileset);
ClassDB::bind_method(D_METHOD("test_variant_call", "variant"), &Example::test_variant_call);
ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield);
ClassDB::bind_method(D_METHOD("test_rpc", "value"), &Example::test_rpc);
@@ -299,6 +313,10 @@ String Example::test_str_utility() const {
return UtilityFunctions::str("Hello, ", "World", "! The answer is ", 42);
}
bool Example::test_string_is_fourty_two(const String &p_string) const {
return strcmp(p_string.utf8().ptr(), "fourty two") == 0;
}
int Example::test_vector_ops() const {
PackedInt32Array arr;
arr.push_back(10);
@@ -343,6 +361,42 @@ Example *Example::test_node_argument(Example *p_node) const {
return p_node;
}
bool Example::test_object_cast_to_node(Object *p_object) const {
return Object::cast_to<Node>(p_object) != nullptr;
}
bool Example::test_object_cast_to_control(Object *p_object) const {
return Object::cast_to<Control>(p_object) != nullptr;
}
bool Example::test_object_cast_to_example(Object *p_object) const {
return Object::cast_to<Example>(p_object) != nullptr;
}
Vector2i Example::test_variant_vector2i_conversion(const Variant &p_variant) const {
return p_variant;
}
int Example::test_variant_int_conversion(const Variant &p_variant) const {
return p_variant;
}
float Example::test_variant_float_conversion(const Variant &p_variant) const {
return p_variant;
}
void Example::test_add_child(Node *p_node) {
add_child(p_node);
}
void Example::test_set_tileset(TileMap *p_tilemap, const Ref<TileSet> &p_tileset) const {
p_tilemap->set_tileset(p_tileset);
}
Variant Example::test_variant_call(Variant p_variant) {
return p_variant.call("test", "hello");
}
BitField<Example::Flags> Example::test_bitfield(BitField<Flags> flags) {
return flags;
}

View File

@@ -18,7 +18,10 @@
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/classes/image.hpp>
#include <godot_cpp/classes/input_event_key.hpp>
#include <godot_cpp/classes/tile_map.hpp>
#include <godot_cpp/classes/tile_set.hpp>
#include <godot_cpp/classes/viewport.hpp>
#include <godot_cpp/variant/variant.hpp>
#include <godot_cpp/core/binder_common.hpp>
@@ -117,8 +120,22 @@ public:
Example *test_node_argument(Example *p_node) const;
String test_string_ops() const;
String test_str_utility() const;
bool test_string_is_fourty_two(const String &p_str) const;
int test_vector_ops() const;
bool test_object_cast_to_node(Object *p_object) const;
bool test_object_cast_to_control(Object *p_object) const;
bool test_object_cast_to_example(Object *p_object) const;
Vector2i test_variant_vector2i_conversion(const Variant &p_variant) const;
int test_variant_int_conversion(const Variant &p_variant) const;
float test_variant_float_conversion(const Variant &p_variant) const;
void test_add_child(Node *p_node);
void test_set_tileset(TileMap *p_tilemap, const Ref<TileSet> &p_tileset) const;
Variant test_variant_call(Variant p_variant);
BitField<Flags> test_bitfield(BitField<Flags> flags);
// RPC

View File

@@ -11,25 +11,37 @@ def options(opts):
"18" if "32" in ARGUMENTS.get("arch", "arm64") else "21",
)
opts.Add(
"ANDROID_NDK_ROOT",
"Path to your Android NDK installation. By default, uses ANDROID_NDK_ROOT from your defined environment variables.",
os.environ.get("ANDROID_NDK_ROOT", None),
"ANDROID_HOME",
"Path to your Android SDK installation. By default, uses ANDROID_HOME from your defined environment variables.",
os.environ.get("ANDROID_HOME", os.environ.get("ANDROID_SDK_ROOT")),
)
def exists(env):
return "ANDROID_NDK_ROOT" in os.environ or "ANDROID_NDK_ROOT" in ARGUMENTS
return get_android_ndk_root(env) is not None
# This must be kept in sync with the value in https://github.com/godotengine/godot/blob/master/platform/android/detect.py#L58.
def get_ndk_version():
return "23.2.8568313"
def get_android_ndk_root(env):
if env["ANDROID_HOME"]:
return env["ANDROID_HOME"] + "/ndk/" + get_ndk_version()
else:
return os.environ.get("ANDROID_NDK_ROOT")
def generate(env):
if "ANDROID_NDK_ROOT" not in env:
if get_android_ndk_root(env) is None:
raise ValueError(
"To build for Android, ANDROID_NDK_ROOT must be defined. Please set ANDROID_NDK_ROOT to the root folder of your Android NDK installation."
"To build for Android, the path to the NDK must be defined. Please set ANDROID_HOME to the root folder of your Android SDK installation."
)
if env["arch"] not in ("arm64", "x86_64", "arm32", "x86_32"):
print("Only arm64, x86_64, arm32, and x86_32 are supported on Android. Exiting.")
Exit()
env.Exit(1)
if sys.platform == "win32" or sys.platform == "msys":
my_spawn.configure(env)
@@ -42,7 +54,7 @@ def generate(env):
api_level = 21
# Setup toolchain
toolchain = env["ANDROID_NDK_ROOT"] + "/toolchains/llvm/prebuilt/"
toolchain = get_android_ndk_root(env) + "/toolchains/llvm/prebuilt/"
if sys.platform == "win32" or sys.platform == "msys":
toolchain += "windows"
import platform as pltfm
@@ -100,3 +112,5 @@ def generate(env):
)
env.Append(CCFLAGS=arch_info["ccflags"])
env.Append(LINKFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"]])
env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"])

333
tools/godotcpp.py Normal file
View File

@@ -0,0 +1,333 @@
import os, sys, platform
from SCons.Variables import EnumVariable, PathVariable, BoolVariable
from SCons.Tool import Tool
from SCons.Builder import Builder
from SCons.Errors import UserError
from binding_generator import scons_generate_bindings, scons_emit_files
def add_sources(sources, dir, extension):
for f in os.listdir(dir):
if f.endswith("." + extension):
sources.append(dir + "/" + f)
def normalize_path(val, env):
return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val)
def validate_file(key, val, env):
if not os.path.isfile(normalize_path(val, env)):
raise UserError("'%s' is not a file: %s" % (key, val))
def validate_dir(key, val, env):
if not os.path.isdir(normalize_path(val, env)):
raise UserError("'%s' is not a directory: %s" % (key, val))
def validate_parent_dir(key, val, env):
if not os.path.isdir(normalize_path(os.path.dirname(val), env)):
raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val)))
platforms = ("linux", "macos", "windows", "android", "ios", "web")
# CPU architecture options.
architecture_array = [
"",
"universal",
"x86_32",
"x86_64",
"arm32",
"arm64",
"rv64",
"ppc32",
"ppc64",
"wasm32",
]
architecture_aliases = {
"x64": "x86_64",
"amd64": "x86_64",
"armv7": "arm32",
"armv8": "arm64",
"arm64v8": "arm64",
"aarch64": "arm64",
"rv": "rv64",
"riscv": "rv64",
"riscv64": "rv64",
"ppcle": "ppc32",
"ppc": "ppc32",
"ppc64le": "ppc64",
}
def exists(env):
return True
def options(opts, env):
# Try to detect the host platform automatically.
# This is used if no `platform` argument is passed
if sys.platform.startswith("linux"):
default_platform = "linux"
elif sys.platform == "darwin":
default_platform = "macos"
elif sys.platform == "win32" or sys.platform == "msys":
default_platform = "windows"
elif ARGUMENTS.get("platform", ""):
default_platform = ARGUMENTS.get("platform")
else:
raise ValueError("Could not detect platform automatically, please specify with platform=<platform>")
opts.Add(
EnumVariable(
key="platform",
help="Target platform",
default=env.get("platform", default_platform),
allowed_values=platforms,
ignorecase=2,
)
)
# Editor and template_debug are compatible (i.e. you can use the same binary for Godot editor builds and Godot debug templates).
# Godot release templates are only compatible with "template_release" builds.
# For this reason, we default to template_debug builds, unlike Godot which defaults to editor builds.
opts.Add(
EnumVariable(
key="target",
help="Compilation target",
default=env.get("target", "template_debug"),
allowed_values=("editor", "template_release", "template_debug"),
)
)
opts.Add(
PathVariable(
key="gdextension_dir",
help="Path to a custom directory containing GDExtension interface header and API JSON file",
default=env.get("gdextension_dir", None),
validator=validate_dir,
)
)
opts.Add(
PathVariable(
key="custom_api_file",
help="Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)",
default=env.get("custom_api_file", None),
validator=validate_file,
)
)
opts.Add(
BoolVariable(
key="generate_bindings",
help="Force GDExtension API bindings generation. Auto-detected by default.",
default=env.get("generate_bindings", False),
)
)
opts.Add(
BoolVariable(
key="generate_template_get_node",
help="Generate a template version of the Node class's get_node.",
default=env.get("generate_template_get_node", True),
)
)
opts.Add(
BoolVariable(
key="build_library",
help="Build the godot-cpp library.",
default=env.get("build_library", True),
)
)
opts.Add(
EnumVariable(
key="precision",
help="Set the floating-point precision level",
default=env.get("precision", "single"),
allowed_values=("single", "double"),
)
)
opts.Add(
EnumVariable(
key="arch",
help="CPU architecture",
default=env.get("arch", ""),
allowed_values=architecture_array,
map=architecture_aliases,
)
)
# compiledb
opts.Add(
BoolVariable(
key="compiledb",
help="Generate compilation DB (`compile_commands.json`) for external tools",
default=env.get("compiledb", False),
)
)
opts.Add(
PathVariable(
key="compiledb_file",
help="Path to a custom `compile_commands.json` file",
default=env.get("compiledb_file", "compile_commands.json"),
validator=validate_parent_dir,
)
)
opts.Add(
BoolVariable(
"disable_exceptions",
"Force disabling exception handling code",
default=env.get("disable_exceptions", False),
)
)
# Add platform options
for pl in platforms:
tool = Tool(pl, toolpath=["tools"])
if hasattr(tool, "options"):
tool.options(opts)
# Targets flags tool (optimizations, debug symbols)
target_tool = Tool("targets", toolpath=["tools"])
target_tool.options(opts)
def generate(env):
# Default num_jobs to local cpu count if not user specified.
# SCons has a peculiarity where user-specified options won't be overridden
# by SetOption, so we can rely on this to know if we should use our default.
initial_num_jobs = env.GetOption("num_jobs")
altered_num_jobs = initial_num_jobs + 1
env.SetOption("num_jobs", altered_num_jobs)
if env.GetOption("num_jobs") == altered_num_jobs:
cpu_count = os.cpu_count()
if cpu_count is None:
print("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.")
else:
safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
print(
"Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument."
% (cpu_count, safer_cpu_count)
)
env.SetOption("num_jobs", safer_cpu_count)
# Process CPU architecture argument.
if env["arch"] == "":
# No architecture specified. Default to arm64 if building for Android,
# universal if building for macOS or iOS, wasm32 if building for web,
# otherwise default to the host architecture.
if env["platform"] in ["macos", "ios"]:
env["arch"] = "universal"
elif env["platform"] == "android":
env["arch"] = "arm64"
elif env["platform"] == "web":
env["arch"] = "wasm32"
else:
host_machine = platform.machine().lower()
if host_machine in architecture_array:
env["arch"] = host_machine
elif host_machine in architecture_aliases.keys():
env["arch"] = architecture_aliases[host_machine]
elif "86" in host_machine:
# Catches x86, i386, i486, i586, i686, etc.
env["arch"] = "x86_32"
else:
print("Unsupported CPU architecture: " + host_machine)
env.Exit(1)
print("Building for architecture " + env["arch"] + " on platform " + env["platform"])
tool = Tool(env["platform"], toolpath=["tools"])
if tool is None or not tool.exists(env):
raise ValueError("Required toolchain not found for platform " + env["platform"])
tool.generate(env)
target_tool = Tool("targets", toolpath=["tools"])
target_tool.generate(env)
# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time.
if env["disable_exceptions"]:
if env.get("is_msvc", False):
env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)])
else:
env.Append(CXXFLAGS=["-fno-exceptions"])
elif env.get("is_msvc", False):
env.Append(CXXFLAGS=["/EHsc"])
# Require C++17
if env.get("is_msvc", False):
env.Append(CXXFLAGS=["/std:c++17"])
else:
env.Append(CXXFLAGS=["-std=c++17"])
if env["precision"] == "double":
env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"])
# Suffix
suffix = ".{}.{}".format(env["platform"], env["target"])
if env.dev_build:
suffix += ".dev"
if env["precision"] == "double":
suffix += ".double"
suffix += "." + env["arch"]
if env["ios_simulator"]:
suffix += ".simulator"
env["suffix"] = suffix # Exposed when included from another project
env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]
# compile_commands.json
env.Tool("compilation_db")
env.Alias("compiledb", env.CompilationDatabase(normalize_path(env["compiledb_file"], env)))
# Builders
env.Append(BUILDERS={"GodotCPPBindings": Builder(action=scons_generate_bindings, emitter=scons_emit_files)})
env.AddMethod(_godot_cpp, "GodotCPP")
def _godot_cpp(env):
extension_dir = normalize_path(env.get("gdextension_dir", env.Dir("gdextension").abspath), env)
api_file = normalize_path(env.get("custom_api_file", env.File(extension_dir + "/extension_api.json").abspath), env)
bindings = env.GodotCPPBindings(
env.Dir("."),
[
api_file,
os.path.join(extension_dir, "gdextension_interface.h"),
"binding_generator.py",
],
)
# Forces bindings regeneration.
if env["generate_bindings"]:
env.AlwaysBuild(bindings)
env.NoCache(bindings)
# Sources to compile
sources = []
add_sources(sources, "src", "cpp")
add_sources(sources, "src/classes", "cpp")
add_sources(sources, "src/core", "cpp")
add_sources(sources, "src/variant", "cpp")
sources.extend([f for f in bindings if str(f).endswith(".cpp")])
# Includes
env.AppendUnique(CPPPATH=[env.Dir(d) for d in [extension_dir, "include", "gen/include"]])
library = None
library_name = "libgodot-cpp" + env["suffix"] + env["LIBSUFFIX"]
if env["build_library"]:
library = env.StaticLibrary(target=env.File("bin/%s" % library_name), source=sources)
default_args = [library]
# Add compiledb if the option is set
if env.get("compiledb", False):
default_args += ["compiledb"]
env.Default(*default_args)
env.AppendUnique(LIBS=[env.File("bin/%s" % library_name)])
return library

View File

@@ -1,7 +1,6 @@
import os
import sys
import subprocess
import ios_osxcross
from SCons.Variables import *
if sys.version_info < (3,):
@@ -16,6 +15,10 @@ else:
return codecs.utf_8_decode(x)[0]
def has_ios_osxcross():
return "OSXCROSS_IOS" in os.environ
def options(opts):
opts.Add(BoolVariable("ios_simulator", "Target iOS Simulator", False))
opts.Add("ios_min_version", "Target minimum iphoneos/iphonesimulator version", "10.0")
@@ -25,17 +28,18 @@ def options(opts):
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
)
opts.Add("IOS_SDK_PATH", "Path to the iOS SDK", "")
ios_osxcross.options(opts)
if has_ios_osxcross():
opts.Add("ios_triple", "Triple for ios toolchain", "")
def exists(env):
return sys.platform == "darwin" or ios_osxcross.exists(env)
return sys.platform == "darwin" or has_ios_osxcross()
def generate(env):
if env["arch"] not in ("universal", "arm64", "x86_64"):
print("Only universal, arm64, and x86_64 are supported on iOS. Exiting.")
Exit()
raise ValueError("Only universal, arm64, and x86_64 are supported on iOS. Exiting.")
if env["ios_simulator"]:
sdk_name = "iphonesimulator"
@@ -64,7 +68,26 @@ def generate(env):
env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
else:
ios_osxcross.generate(env)
# OSXCross
compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
env["CC"] = compiler_path + "clang"
env["CXX"] = compiler_path + "clang++"
env["AR"] = compiler_path + "ar"
env["RANLIB"] = compiler_path + "ranlib"
env["SHLIBSUFFIX"] = ".dylib"
env.Prepend(
CPPPATH=[
"$IOS_SDK_PATH/usr/include",
"$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers",
]
)
env.Append(CCFLAGS=["-stdlib=libc++"])
binpath = os.path.join(env["IOS_TOOLCHAIN_PATH"], "usr", "bin")
if binpath not in env["ENV"]["PATH"]:
env.PrependENVPath("PATH", binpath)
if env["arch"] == "universal":
if env["ios_simulator"]:
@@ -79,3 +102,5 @@ def generate(env):
env.Append(CCFLAGS=["-isysroot", env["IOS_SDK_PATH"]])
env.Append(LINKFLAGS=["-isysroot", env["IOS_SDK_PATH"], "-F" + env["IOS_SDK_PATH"]])
env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED"])

View File

@@ -1,26 +0,0 @@
import os
def options(opts):
opts.Add("ios_triple", "Triple for ios toolchain", "")
def exists(env):
return "OSXCROSS_IOS" in os.environ
def generate(env):
compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
env["CC"] = compiler_path + "clang"
env["CXX"] = compiler_path + "clang++"
env["AR"] = compiler_path + "ar"
env["RANLIB"] = compiler_path + "ranlib"
env["SHLIBSUFFIX"] = ".dylib"
env.Prepend(
CPPPATH=[
"$IOS_SDK_PATH/usr/include",
"$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers",
]
)
env.Append(CCFLAGS=["-stdlib=libc++"])

View File

@@ -32,3 +32,5 @@ def generate(env):
elif env["arch"] == "rv64":
env.Append(CCFLAGS=["-march=rv64gc"])
env.Append(LINKFLAGS=["-march=rv64gc"])
env.Append(CPPDEFINES=["LINUX_ENABLED", "UNIX_ENABLED"])

View File

@@ -1,31 +1,51 @@
import os
import sys
import macos_osxcross
def has_osxcross():
return "OSXCROSS_ROOT" in os.environ
def options(opts):
opts.Add("macos_deployment_target", "macOS deployment target", "default")
opts.Add("macos_sdk_path", "macOS SDK path", "")
macos_osxcross.options(opts)
if has_osxcross():
opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16")
def exists(env):
return sys.platform == "darwin" or macos_osxcross.exists(env)
return sys.platform == "darwin" or has_osxcross()
def generate(env):
if env["arch"] not in ("universal", "arm64", "x86_64"):
print("Only universal, arm64, and x86_64 are supported on macOS. Exiting.")
Exit()
env.Exit(1)
if sys.platform == "darwin":
# Use clang on macOS by default
env["CXX"] = "clang++"
env["CC"] = "clang"
else:
# Use osxcross
macos_osxcross.generate(env)
# OSXCross
root = os.environ.get("OSXCROSS_ROOT", "")
if env["arch"] == "arm64":
basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-"
else:
basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
env["CC"] = basecmd + "clang"
env["CXX"] = basecmd + "clang++"
env["AR"] = basecmd + "ar"
env["RANLIB"] = basecmd + "ranlib"
env["AS"] = basecmd + "as"
binpath = os.path.join(root, "target", "bin")
if binpath not in env["ENV"]["PATH"]:
# Add OSXCROSS bin folder to PATH (required for linking).
env.PrependENVPath("PATH", binpath)
# Common flags
if env["arch"] == "universal":
env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"])
env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"])
@@ -48,3 +68,5 @@ def generate(env):
"-Wl,-undefined,dynamic_lookup",
]
)
env.Append(CPPDEFINES=["MACOS_ENABLED", "UNIX_ENABLED"])

View File

@@ -1,28 +0,0 @@
import os
def options(opts):
opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16")
def exists(env):
return "OSXCROSS_ROOT" in os.environ
def generate(env):
root = os.environ.get("OSXCROSS_ROOT", "")
if env["arch"] == "arm64":
basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-"
else:
basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
env["CC"] = basecmd + "clang"
env["CXX"] = basecmd + "clang++"
env["AR"] = basecmd + "ar"
env["RANLIB"] = basecmd + "ranlib"
env["AS"] = basecmd + "as"
binpath = os.path.join(root, "target", "bin")
if binpath not in env["ENV"]["PATH"]:
# Add OSXCROSS bin folder to PATH (required for linking).
env["ENV"]["PATH"] = "%s:%s" % (binpath, env["ENV"]["PATH"])

View File

@@ -1,10 +1,14 @@
import os
import subprocess
import sys
from SCons.Script import ARGUMENTS
from SCons.Variables import *
from SCons.Variables.BoolVariable import _text2bool
# Helper methods
def get_cmdline_bool(option, default):
"""We use `ARGUMENTS.get()` to check if options were manually overridden on the command line,
and SCons' _text2bool helper to convert them to booleans, otherwise they're handled as strings.
@@ -16,6 +20,24 @@ def get_cmdline_bool(option, default):
return default
def using_clang(env):
return "clang" in os.path.basename(env["CC"])
def is_vanilla_clang(env):
if not using_clang(env):
return False
try:
version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8")
except (subprocess.CalledProcessError, OSError):
print("Couldn't parse CXX environment variable to infer compiler version.")
return False
return not version.startswith("Apple")
# Main tool definition
def options(opts):
opts.Add(
EnumVariable(
@@ -34,19 +56,21 @@ def exists(env):
def generate(env):
# Configuration of build targets:
# - Editor or template
# - Debug features (DEBUG_ENABLED code)
# - Dev only code (DEV_ENABLED code)
# - Optimization level
# - Debug symbols for crash traces / debuggers
# Keep this configuration in sync with SConstruct in upstream Godot.
env.editor_build = env["target"] == "editor"
env.dev_build = env["dev_build"]
env.debug_features = env["target"] in ["editor", "template_debug"]
env.editor_build = env["target"] == "editor"
if env.editor_build:
env.AppendUnique(CPPDEFINES=["TOOLS_ENABLED"])
if env.debug_features:
env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_METHODS_ENABLED"])
if env.dev_build:
opt_level = "none"
env.AppendUnique(CPPDEFINES=["DEV_ENABLED"])
elif env.debug_features:
opt_level = "speed_trace"
else: # Release
@@ -55,29 +79,57 @@ def generate(env):
env["optimize"] = ARGUMENTS.get("optimize", opt_level)
env["debug_symbols"] = get_cmdline_bool("debug_symbols", env.dev_build)
if env.editor_build:
env.Append(CPPDEFINES=["TOOLS_ENABLED"])
if env.debug_features:
# DEBUG_ENABLED enables debugging *features* and debug-only code, which is intended
# to give *users* extra debugging information for their game development.
env.Append(CPPDEFINES=["DEBUG_ENABLED"])
# In upstream Godot this is added in typedefs.h when DEBUG_ENABLED is set.
env.Append(CPPDEFINES=["DEBUG_METHODS_ENABLED"])
if env.dev_build:
# DEV_ENABLED enables *engine developer* code which should only be compiled for those
# working on the engine itself.
env.Append(CPPDEFINES=["DEV_ENABLED"])
else:
# Disable assert() for production targets (only used in thirdparty code).
env.Append(CPPDEFINES=["NDEBUG"])
# Set optimize and debug_symbols flags.
# "custom" means do nothing and let users set their own optimization flags.
if env.get("is_msvc", False):
if env["debug_symbols"]:
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"])
if env["optimize"] == "debug" or env["optimize"] == "none":
env.Append(CCFLAGS=["/MDd", "/Od"])
else:
env.Append(CCFLAGS=["/MD"])
elif env["optimize"] == "debug" or env["optimize"] == "none":
env.Append(CCFLAGS=["/Od"])
else:
if env["debug_symbols"]:
# Adding dwarf-4 explicitly makes stacktraces work with clang builds,
# otherwise addr2line doesn't understand them.
env.Append(CCFLAGS=["-gdwarf-4"])
if env.dev_build:
env.Append(CCFLAGS=["-g3"])
else:
env.Append(CCFLAGS=["-g2"])
else:
if using_clang(env) and not is_vanilla_clang(env):
# Apple Clang, its linker doesn't like -s.
env.Append(LINKFLAGS=["-Wl,-S", "-Wl,-x", "-Wl,-dead_strip"])
else:
env.Append(LINKFLAGS=["-s"])
if env["optimize"] == "speed":
env.Append(CCFLAGS=["-O3"])

View File

@@ -1,45 +1,44 @@
import os
from SCons.Util import WhereIs
def exists(env):
return "EM_CONFIG" in os.environ
return WhereIs("emcc") is not None
def generate(env):
if env["arch"] not in ("wasm32"):
print("Only wasm32 supported on web. Exiting.")
Exit()
if "EM_CONFIG" in os.environ:
env["ENV"] = os.environ
env.Exit(1)
# Emscripten toolchain
env["CC"] = "emcc"
env["CXX"] = "em++"
env["AR"] = "emar"
env["RANLIB"] = "emranlib"
env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
env["SHOBJSUFFIX"] = ".bc"
env["SHLIBSUFFIX"] = ".wasm"
# Use TempFileMunge since some AR invocations are too long for cmd.exe.
# Use POSIX-style paths, required with TempFileMunge.
env["ARCOM_POSIX"] = env["ARCOM"].replace("$TARGET", "$TARGET.posix").replace("$SOURCES", "$SOURCES.posix")
env["ARCOM"] = "${TEMPFILE(ARCOM_POSIX)}"
# All intermediate files are just LLVM bitcode.
env["OBJPREFIX"] = ""
env["OBJSUFFIX"] = ".bc"
env["PROGPREFIX"] = ""
# Program() output consists of multiple files, so specify suffixes manually at builder.
env["PROGSUFFIX"] = ""
# All intermediate files are just object files.
env["OBJSUFFIX"] = ".o"
env["SHOBJSUFFIX"] = ".o"
# Static libraries clang-style.
env["LIBPREFIX"] = "lib"
env["LIBSUFFIX"] = ".a"
env["LIBPREFIXES"] = ["$LIBPREFIX"]
env["LIBSUFFIXES"] = ["$LIBSUFFIX"]
env.Replace(SHLINKFLAGS="$LINKFLAGS")
env.Replace(SHLINKFLAGS="$LINKFLAGS")
if env["target"] == "debug":
env.Append(CCFLAGS=["-O0", "-g"])
elif env["target"] == "release":
env.Append(CCFLAGS=["-O3"])
# Shared library as wasm.
env["SHLIBSUFFIX"] = ".wasm"
# Thread support (via SharedArrayBuffer).
env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
# Build as side module (shared library).
env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])

View File

@@ -9,6 +9,7 @@ from SCons.Variables import *
def options(opts):
opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False))
opts.Add(BoolVariable("use_clang_cl", "Use the clang driver instead of MSVC - only effective on Windows", False))
opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True))
def exists(env):
@@ -30,13 +31,18 @@ def generate(env):
env.Tool("mslink")
env.Append(CPPDEFINES=["TYPED_METHOD_BIND", "NOMINMAX"])
env.Append(CCFLAGS=["/EHsc", "/utf-8"])
env.Append(CCFLAGS=["/utf-8"])
env.Append(LINKFLAGS=["/WX"])
if env["use_clang_cl"]:
env["CC"] = "clang-cl"
env["CXX"] = "clang-cl"
if env["use_static_cpp"]:
env.Append(CCFLAGS=["/MT"])
else:
env.Append(CCFLAGS=["/MD"])
elif sys.platform == "win32" or sys.platform == "msys":
env["use_mingw"] = True
mingw.generate(env)
@@ -45,6 +51,18 @@ def generate(env):
env["SHLIBPREFIX"] = ""
# Want dll suffix
env["SHLIBSUFFIX"] = ".dll"
env.Append(CCFLAGS=["-Wwrite-strings"])
env.Append(LINKFLAGS=["-Wl,--no-undefined"])
if env["use_static_cpp"]:
env.Append(
LINKFLAGS=[
"-static",
"-static-libgcc",
"-static-libstdc++",
]
)
# Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
my_spawn.configure(env)
@@ -60,13 +78,15 @@ def generate(env):
# Want dll suffix
env["SHLIBSUFFIX"] = ".dll"
# These options are for a release build even using target=debug
env.Append(CCFLAGS=["-O3", "-Wwrite-strings"])
env.Append(
LINKFLAGS=[
"--static",
"-Wl,--no-undefined",
"-static-libgcc",
"-static-libstdc++",
]
)
env.Append(CCFLAGS=["-Wwrite-strings"])
env.Append(LINKFLAGS=["-Wl,--no-undefined"])
if env["use_static_cpp"]:
env.Append(
LINKFLAGS=[
"-static",
"-static-libgcc",
"-static-libstdc++",
]
)
env.Append(CPPDEFINES=["WINDOWS_ENABLED"])