Compare commits

...

41 Commits

Author SHA1 Message Date
David Snopek
4d5d707d60 Merge pull request #1572 from dsnopek/4.1-cherrypicks-13
Cherry-picks for the godot-cpp 4.1 branch - 13th batch
2024-09-11 17:15:09 -05:00
George L. Albany
fad74366cd Fix GCC 14 -Wtemplate-id-cdtor warning
As was fixed with godotengine/godot#91208

(cherry picked from commit 7b31f39bea)
2024-09-04 10:16:26 -05:00
Fabio Alessandrelli
d421c98461 [Web/SCons] Use CCFLAGS for SIDE_MODULE option
Was using CPPFLAGS, but should use the explicit scons CCFLAGS which
makes it clear they are applied to both the C and C++ compiler.

CPPFLAGS was also fine (they are preprocessor flags, also applied to
both C and C++), but we should try to stay consistent with what we do
in Godot.

(cherry picked from commit f36acd8e31)
2024-09-04 10:16:17 -05:00
Aaron Franke
592a7762cd [4.2] Fix missing MAKE_TYPED_ARRAY_INFO for Packed*Arrays
(cherry picked from commit a0d56336c3)
2024-09-04 10:16:01 -05:00
Mikael Hermansson
76c0a4b4ae Fix incorrect generation of some C++ operators
(cherry picked from commit 9949d09f3e)
2024-09-04 10:13:33 -05:00
Raul Santos
67145b323b Avoid hardcoded type conversion for metadata
The engine uses the names `int` and `float` to refer to the 64-bit types, so in the bindings generator we have a hardcoded conversion for those types.

But this type conversion should not be used for metadata. Even though the underlying type should still be 64-bit for interop, metadata is meant to specify the correct type to expose. So if metadata says `float` it means the type is really meant to be a 32-bit `float` and not `double`. Other hardcoded type conversions (`int` and `Nil`) won't ever be metadata.

This change corrects the `float` type, to use the right type in the generated C++ code. Before we were always using `double` due to this type conversion.

(cherry picked from commit 4829199081)
2024-09-04 10:13:23 -05:00
Klaim (Joël Lamotte)
d529fc5b69 removes warnings generated by GDCLASS usage
This change removes the warnings (unused parameters) coming from code injected by the GDCLASS macro.
Contrary to warnings coming from the normal source code which can be suppressed with most compiles by specifying the include directories of this library as external or system,
when the code is injected through a macro it is considered in the context of the user, which is the source code of user of the library.
That forces the users to modify their code to hide the warnings coming from the mandatory `GDCLASS` here.
That's why it's important to remove these warning from that specific macro and ideally any other macro that the user must use.

(cherry picked from commit 738859f49b)
2024-09-04 10:10:04 -05:00
Chris Cranford
9a4ba00b0e Make sure _get and _set dispatch up the class hierarchy
(cherry picked from commit c77d44f3f6)
2024-09-04 10:09:27 -05:00
Rémi Verschelde
bc6cfd70d8 SCons: Remove old Python 2 compat code
(cherry picked from commit 958776dfc3)
2024-09-04 10:09:15 -05:00
A Thousand Ships
09b735ac97 [CI] Upload build cache before running tests
(cherry picked from commit 76b38de01a)
2024-09-04 10:03:23 -05:00
Fabio Alessandrelli
9e860a84a0 [Web] Force emcc to use "wasm" longjmp mode
SUPPORT_LONGJMP have changed since emscripten 3.1.32 to default to
"wasm" mode when exceptions are enabled, and "emscripten" mode when
disabled.

While we generally doesn't use exception in core, linked libraries may
need them, and emscripten don't plan to support WASM EH + Emscripten
SjLj in the long term.

(cherry picked from commit 1bb543b6f4)
2024-09-04 10:03:13 -05:00
Fabio Alessandrelli
9b4c5f0562 [SCons] Add option to build without threads
This is relevant for the Web platform, where builds with and without
threads are incompatible.

(cherry picked from commit b0296bb562)
2024-09-04 09:58:58 -05:00
David Snopek
a4f9d22ac5 Merge pull request #1529 from dsnopek/4.1-cherrypicks-12
Cherry-picks for the godot-cpp 4.1 branch - 12th batch
2024-07-17 12:28:25 -05:00
A Thousand Ships
7dd2d24805 Fix sharing of typed arrays from constructor
(cherry picked from commit 41aa71f8c3)
2024-07-17 09:58:09 -05:00
Yuri Sizov
18b81efbf3 Fix argument metadata when binding methods
While there doesn't seem to be any runtime issues,
this triggers the address sanitizer in a few ways,
depending on what kind of method you're
binding.

(cherry picked from commit 2b34bd0d8b)
2024-07-17 09:57:10 -05:00
Thaddeus Crews
430637bf1a SCons: Add silence_msvc option
(cherry picked from commit 1989b1bf57)
2024-07-17 09:57:01 -05:00
Thaddeus Crews
ca26922a19 Integrate .pre-commit-config.yaml
(cherry picked from commit e0d363aad8)
2024-07-17 09:56:32 -05:00
Thaddeus Crews
edce0b7178 Fix #include formatting
(cherry picked from commit 999018e7d1)
2024-07-17 09:53:29 -05:00
Thaddeus Crews
23325b9ab0 Add .editorconfig, consolidate .gitattributes
(cherry picked from commit 7a96d0314e)
2024-07-17 09:53:19 -05:00
Fabio Alessandrelli
208b70dbef [CI] Update macOS workers to macos-latest
GitHub actions no longer allow `macos-11` runners

(cherry picked from commit 2dd8917508)
2024-07-17 09:53:07 -05:00
A Thousand Ships
1f57a8d76e Fix some style details in generation
(cherry picked from commit e7a13e3bf4)
2024-07-17 09:48:31 -05:00
A Thousand Ships
f16814ee05 Enforce p_ prefixes for arguments in binds
(cherry picked from commit 9e2771f918)
2024-07-17 09:37:59 -05:00
Fabio Alessandrelli
22beef8cbc Add support for build profiles.
Allow enabling or disabling specific classes (which will not be built).

(cherry picked from commit 1186c488bd)
2024-07-17 09:34:07 -05:00
David Snopek
2e7f5512d1 Merge pull request #1491 from dsnopek/4.1-cherrypicks-11
Cherry-picks for the godot-cpp 4.1 branch - 11th batch
2024-06-14 13:32:54 -05:00
David Snopek
5880be034c [4.1] Allow selecting Godot version to run the tests with
(cherry picked from commit f88b6a2f00)
2024-06-14 11:44:39 -05:00
A Thousand Ships
a648650c5d Add default argument processing for NodePath
(cherry picked from commit 37e7a6da05)
2024-06-14 10:40:04 -05:00
A Thousand Ships
65058828a6 Fix generating default values for StringName
Cases other than `&""` were not processed correctly

(cherry picked from commit 6cd6c8923a)
2024-06-14 10:38:34 -05:00
David Snopek
f067a9bff0 Fix vararg methods forwarded to the ClassDB singleton
(cherry picked from commit e04a26b2bc)
2024-06-14 10:37:44 -05:00
Richard Hozák
6ba31a1800 Fix warnings emitted with -Wall
(cherry picked from commit 8c6cc1ec15)
2024-06-14 10:37:27 -05:00
David Snopek
8269c0102a Explicitly refer to godot namespace in GDREGISTER_*_CLASS() macros
(cherry picked from commit 246a803954)
2024-06-14 10:31:04 -05:00
DmitriySalnikov
5f864cd8de [Scons] Added the ability to change the visibility of symbols
(cherry picked from commit f5e4f95cde)
2024-06-14 10:28:30 -05:00
David Snopek
32becf6a13 Merge pull request #1466 from dsnopek/4.1-cherrypicks-10
Cherry-picks for the godot-cpp 4.1 branch - 10th batch
2024-05-28 08:35:20 -05:00
David Snopek
7b93607779 Fix NOTIFICATION_POSTINITIALIZE sent twice to native parent class
(cherry picked from commit 06373ce1cf)
2024-05-17 17:11:27 -05:00
Daylily-Zeleen
1ab42ec6c5 mark return value of get_class_static and get_parent_class_static as const
(cherry picked from commit 3db8549e19)
2024-05-17 16:58:32 -05:00
David Snopek
6328728dc2 Allow forwarding from ClassDB to ClassDBSingleton to support enumerations
(cherry picked from commit e1b3b32db5)
2024-05-17 16:57:33 -05:00
David Snopek
6123c86f06 Correctly handle Object * arguments that were encoded as nullptr
(cherry picked from commit 37542dc2ec)
2024-05-17 16:56:37 -05:00
David Snopek
fff665e0f5 Give compile-time error if registering a class without its own _bind_methods() function
(cherry picked from commit ca46ef4d25)
2024-05-17 16:55:41 -05:00
pupil1337
82ea3f74a4 Add static_assert() for register_class
(cherry picked from commit 1fa7a9cb19)
2024-05-17 16:54:44 -05:00
A Thousand Ships
11cd9a0727 [Math] Add is_finite methods
(cherry picked from commit d389171905)
2024-05-17 16:54:00 -05:00
Chris Cranford
a75e33c333 Implement to/from dict helpers for PropertyInfo/MethodInfo
(cherry picked from commit 2a041b5240)
2024-05-17 16:53:43 -05:00
David Snopek
d24983d682 Backport miscellaneous changes that can't be cherry-picked
(cherry picked from commit 9afbdb9cf6)
2024-05-17 16:51:42 -05:00
84 changed files with 1210 additions and 1190 deletions

16
.editorconfig Normal file
View File

@@ -0,0 +1,16 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[{*.py,SConstruct}]
indent_style = space
[*.{yml,yaml}]
indent_size = 2
indent_style = space

8
.gitattributes vendored
View File

@@ -1,6 +1,2 @@
*.c eol=lf
*.cpp eol=lf
*.gd eol=lf
*.tscn eol=lf
*.cfg eol=lf
*.godot eol=lf
# Normalize EOL for all files that Git considers text files
* text=auto eol=lf

View File

@@ -1,5 +1,5 @@
name: Setup Godot build cache
description: Setup Godot build cache.
name: Restore Godot build cache
description: Restore Godot build cache.
inputs:
cache-name:
description: The cache base name (job name by default).
@@ -10,9 +10,8 @@ inputs:
runs:
using: "composite"
steps:
# Upload cache on completion and check it out now
- name: Load .scons_cache directory
uses: actions/cache@v3
- name: Restore .scons_cache directory
uses: actions/cache/restore@v3
with:
path: ${{inputs.scons-cache}}
key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}

View File

@@ -0,0 +1,17 @@
name: Save Godot build cache
description: Save Godot build cache.
inputs:
cache-name:
description: The cache base name (job name by default).
default: "${{github.job}}"
scons-cache:
description: The SCons cache path.
default: "${{github.workspace}}/.scons-cache/"
runs:
using: "composite"
steps:
- name: Save SCons cache directory
uses: actions/cache/save@v4
with:
path: ${{inputs.scons-cache}}
key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}

View File

@@ -4,6 +4,8 @@ on: [push, pull_request]
env:
# Only used for the cache key. Increment version to force clean build.
GODOT_BASE_BRANCH: master
# Used to select the version of Godot to run the tests with.
GODOT_TEST_VERSION: 4.1.4-stable
concurrency:
group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}
@@ -52,7 +54,7 @@ jobs:
cache-name: windows-x86_64-mingw
- name: 🍎 macOS (universal)
os: macos-11
os: macos-latest
platform: macos
artifact-name: godot-cpp-macos-universal-release
artifact-path: bin/libgodot-cpp.macos.template_release.universal.a
@@ -70,7 +72,7 @@ jobs:
cache-name: android-arm64
- name: 🍏 iOS (arm64)
os: macos-11
os: macos-latest
platform: ios
artifact-name: godot-cpp-ios-arm64-release
artifact-path: bin/libgodot-cpp.ios.template_release.arm64.a
@@ -97,8 +99,8 @@ jobs:
with:
submodules: recursive
- name: Setup Godot build cache
uses: ./.github/actions/godot-cache
- name: Restore Godot build cache
uses: ./.github/actions/godot-cache-restore
with:
cache-name: ${{ matrix.cache-name }}
continue-on-error: true
@@ -151,9 +153,15 @@ jobs:
cd test
scons platform=${{ matrix.platform }} verbose=yes target=template_release ${{ matrix.flags }}
- name: Save Godot build cache
uses: ./.github/actions/godot-cache-save
with:
cache-name: ${{ matrix.cache-name }}
continue-on-error: true
- name: Download latest Godot artifacts
uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9
if: ${{ matrix.run-tests }}
if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }}
with:
repo: godotengine/godot
branch: master
@@ -166,15 +174,28 @@ jobs:
ensure_latest: true
path: godot-artifacts
- name: Prepare Godot artifacts for testing
if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }}
run: |
chmod +x ./godot-artifacts/godot.linuxbsd.editor.x86_64.mono
echo "GODOT=$(pwd)/godot-artifacts/godot.linuxbsd.editor.x86_64.mono" >> $GITHUB_ENV
- name: Download requested Godot version for testing
if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION != 'master' }}
run: |
wget "https://github.com/godotengine/godot-builds/releases/download/${GODOT_TEST_VERSION}/Godot_v${GODOT_TEST_VERSION}_linux.x86_64.zip" -O Godot.zip
unzip -a Godot.zip
chmod +x "Godot_v${GODOT_TEST_VERSION}_linux.x86_64"
echo "GODOT=$(pwd)/Godot_v${GODOT_TEST_VERSION}_linux.x86_64" >> $GITHUB_ENV
- name: Run tests
if: ${{ matrix.run-tests }}
run: |
chmod +x ./godot-artifacts/godot.linuxbsd.editor.x86_64.mono
./godot-artifacts/godot.linuxbsd.editor.x86_64.mono --headless --version
$GODOT --headless --version
cd test
# Need to run the editor so .godot is generated... but it crashes! Ignore that :-)
(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
(cd project && (timeout 30 $GODOT --editor --headless --quit >/dev/null 2>&1 || true))
./run-tests.sh
- name: Upload artifact
uses: actions/upload-artifact@v3

View File

@@ -7,48 +7,27 @@ concurrency:
jobs:
static-checks:
name: Format (clang-format, black format, file format)
runs-on: ubuntu-20.04
name: Format (clang-format, ruff format, file format)
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
# Azure repositories are not reliable, we need to prevent Azure giving us packages.
- name: Make apt sources.list use the default Ubuntu repositories
- name: Get changed files
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sudo rm -f /etc/apt/sources.list.d/*
sudo cp -f misc/ci/sources.list /etc/apt/sources.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main"
sudo apt-get update
if [ "${{ github.event_name }}" == "pull_request" ]; then
files=$(git diff-tree --no-commit-id --name-only -r HEAD^1..HEAD 2> /dev/null || true)
elif [ "${{ github.event_name }}" == "push" -a "${{ github.event.forced }}" == "false" -a "${{ github.event.created }}" == "false" ]; then
files=$(git diff-tree --no-commit-id --name-only -r ${{ github.event.before }}..${{ github.event.after }} 2> /dev/null || true)
fi
files=$(echo "$files" | grep -v 'thirdparty' | xargs -I {} sh -c 'echo "\"./{}\""' | tr '\n' ' ')
echo "CHANGED_FILES=$files" >> $GITHUB_ENV
- name: Install dependencies
run: |
sudo apt-get install -qq dos2unix recode clang-format-15 libxml2-utils python3-pip moreutils
sudo update-alternatives --remove-all clang-format || true
sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-15 100
sudo pip3 install black==22.3.0 pygments pytest==7.1.2 mypy==0.971
- name: File formatting checks (file_format.sh)
run: |
bash ./misc/scripts/file_format.sh
- name: Header guards formatting checks (header_guards.sh)
run: |
bash ./misc/scripts/header_guards.sh
- name: Python style checks via black (black_format.sh)
run: |
bash ./misc/scripts/black_format.sh
- name: Python scripts static analysis (mypy_check.sh)
run: |
bash ./misc/scripts/mypy_check.sh
- name: Bindings generation checks (ensures get_file_list returns all generated files)
run: |
python ./misc/scripts/check_get_file_list.py
- name: Style checks via clang-format (clang_format.sh)
run: |
bash ./misc/scripts/clang_format.sh
- name: Style checks via pre-commit
uses: pre-commit/action@v3.0.1
with:
extra_args: --verbose --hook-stage manual --files ${{ env.CHANGED_FILES }}

2
.gitignore vendored
View File

@@ -8,7 +8,7 @@
include/gen
src/gen
# Build configuarion.
# Build configuration.
/custom.py
# Misc

64
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,64 @@
default_language_version:
python: python3
exclude: |
(?x)^(
gdextension/extension_api\.json|
gdextension/gdextension_interface\.h
)$
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v17.0.6
hooks:
- id: clang-format
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.971
hooks:
- id: mypy
files: \.py$
types_or: [text]
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
additional_dependencies: [tomli]
- repo: local
hooks:
- id: copyright-headers
name: copyright-headers
language: python
entry: python misc/scripts/copyright_headers.py
files: \.(c|h)pp$
exclude: ^test/
- id: header-guards
name: header-guards
language: python
entry: python misc/scripts/header_guards.py
files: \.hpp$
exclude: ^test/
- id: file-format
name: file-format
language: python
entry: python misc/scripts/file_format.py
types_or: [text]
- id: check-get-file-list
name: check-get-file-list
language: python
entry: python misc/scripts/check_get_file_list.py
pass_filenames: false
always_run: true
stages: [manual]

View File

@@ -3,7 +3,7 @@
#
# godot-cpp cmake arguments
# GODOT_GDEXTENSION_DIR: Path to the directory containing GDExtension interface header and API JSON file
# GODOT_CPP_SYSTEM_HEADERS Mark the header files as SYSTEM. This may be useful to supress warnings in projects including this one.
# GODOT_CPP_SYSTEM_HEADERS Mark the header files as SYSTEM. This may be useful to suppress warnings in projects including this one.
# GODOT_CPP_WARNING_AS_ERROR Treat any warnings as errors
# GODOT_CUSTOM_API_FILE: Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)
# FLOAT_PRECISION: Floating-point precision level ("single", "double")

View File

@@ -1,18 +1,13 @@
#!/usr/bin/env python
import os
import platform
import sys
import subprocess
from binding_generator import scons_generate_bindings, scons_emit_files
EnsureSConsVersion(4, 0)
try:
Import("env")
except:
except Exception:
# Default tools with no platform defaults to gnu toolchain.
# We apply platform specific toolchains via our custom tools.
env = Environment(tools=["default"], PLATFORM="")
@@ -23,7 +18,7 @@ env.PrependENVPath("PATH", os.getenv("PATH"))
customs = ["custom.py"]
try:
customs += Import("customs")
except:
except Exception:
pass
profile = ARGUMENTS.get("profile", "")
if profile:

View File

@@ -70,12 +70,14 @@ def generate_wrappers(target):
f.write(txt)
def get_file_list(api_filepath, output_dir, headers=False, sources=False):
def get_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
api = {}
files = []
with open(api_filepath, encoding="utf-8") as api_file:
api = json.load(api_file)
build_profile = parse_build_profile(profile_filepath, api)
core_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp" / "core"
include_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp"
source_gen_folder = Path(output_dir) / "gen" / "src"
@@ -105,7 +107,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
if headers:
files.append(str(header_filename.as_posix()))
if sources:
if sources and is_class_included(engine_class["name"], build_profile):
files.append(str(source_filename.as_posix()))
for native_struct in api["native_structures"]:
@@ -134,12 +136,105 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
return files
def print_file_list(api_filepath, output_dir, headers=False, sources=False):
print(*get_file_list(api_filepath, output_dir, headers, sources), sep=";", end=None)
def print_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
print(*get_file_list(api_filepath, output_dir, headers, sources, profile_filepath), sep=";", end=None)
def parse_build_profile(profile_filepath, api):
if profile_filepath == "":
return {}
print("Using feature build profile: " + profile_filepath)
with open(profile_filepath, encoding="utf-8") as profile_file:
profile = json.load(profile_file)
api_dict = {}
parents = {}
children = {}
for engine_class in api["classes"]:
api_dict[engine_class["name"]] = engine_class
parent = engine_class.get("inherits", "")
child = engine_class["name"]
parents[child] = parent
if parent == "":
continue
children[parent] = children.get(parent, [])
children[parent].append(child)
# Parse methods dependencies
deps = {}
reverse_deps = {}
for name, engine_class in api_dict.items():
ref_cls = set()
for method in engine_class.get("methods", []):
rtype = method.get("return_value", {}).get("type", "")
args = [a["type"] for a in method.get("arguments", [])]
if rtype in api_dict:
ref_cls.add(rtype)
elif is_enum(rtype) and get_enum_class(rtype) in api_dict:
ref_cls.add(get_enum_class(rtype))
for arg in args:
if arg in api_dict:
ref_cls.add(arg)
elif is_enum(arg) and get_enum_class(arg) in api_dict:
ref_cls.add(get_enum_class(arg))
deps[engine_class["name"]] = set(filter(lambda x: x != name, ref_cls))
for acls in ref_cls:
if acls == name:
continue
reverse_deps[acls] = reverse_deps.get(acls, set())
reverse_deps[acls].add(name)
included = []
front = list(profile.get("enabled_classes", []))
if front:
# These must always be included
front.append("WorkerThreadPool")
front.append("ClassDB")
front.append("ClassDBSingleton")
while front:
cls = front.pop()
if cls in included:
continue
included.append(cls)
parent = parents.get(cls, "")
if parent:
front.append(parent)
for rcls in deps.get(cls, set()):
if rcls in included or rcls in front:
continue
front.append(rcls)
excluded = []
front = list(profile.get("disabled_classes", []))
while front:
cls = front.pop()
if cls in excluded:
continue
excluded.append(cls)
front += children.get(cls, [])
for rcls in reverse_deps.get(cls, set()):
if rcls in excluded or rcls in front:
continue
front.append(rcls)
if included and excluded:
print(
"WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored."
)
return {
"enabled_classes": included,
"disabled_classes": excluded,
}
def scons_emit_files(target, source, env):
files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True)]
profile_filepath = env.get("build_profile", "")
if profile_filepath and not Path(profile_filepath).is_absolute():
profile_filepath = str((Path(env.Dir("#").abspath) / profile_filepath).as_posix())
files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True, profile_filepath)]
env.Clean(target, files)
env["godot_cpp_gen_dir"] = target[0].abspath
return files, source
@@ -178,6 +273,10 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision=
generate_utility_functions(api, target_dir)
CLASS_ALIASES = {
"ClassDB": "ClassDBSingleton",
}
builtin_classes = []
# Key is class name, value is boolean where True means the class is refcounted.
@@ -485,14 +584,14 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
result.append(method_signature)
# Move constructor.
result.append(f"\t{class_name}({class_name} &&other);")
result.append(f"\t{class_name}({class_name} &&p_other);")
# Special cases.
if class_name == "String" or class_name == "StringName" or class_name == "NodePath":
result.append(f"\t{class_name}(const char *from);")
result.append(f"\t{class_name}(const wchar_t *from);")
result.append(f"\t{class_name}(const char16_t *from);")
result.append(f"\t{class_name}(const char32_t *from);")
result.append(f"\t{class_name}(const char *p_from);")
result.append(f"\t{class_name}(const wchar_t *p_from);")
result.append(f"\t{class_name}(const char16_t *p_from);")
result.append(f"\t{class_name}(const char32_t *p_from);")
if "constants" in builtin_api:
axis_constants_count = 0
@@ -520,14 +619,16 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
vararg = method["is_vararg"]
if vararg:
result.append("\ttemplate<typename... Args>")
result.append("\ttemplate <typename... Args>")
method_signature = "\t"
if "is_static" in method and method["is_static"]:
method_signature += "static "
if "return_type" in method:
method_signature += f'{correct_type(method["return_type"])} '
method_signature += f'{correct_type(method["return_type"])}'
if not method_signature.endswith("*"):
method_signature += " "
else:
method_signature += "void "
@@ -550,10 +651,10 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
# Special cases.
if class_name == "String":
result.append("\tstatic String utf8(const char *from, int64_t len = -1);")
result.append("\tvoid parse_utf8(const char *from, int64_t len = -1);")
result.append("\tstatic String utf16(const char16_t *from, int64_t len = -1);")
result.append("\tvoid parse_utf16(const char16_t *from, int64_t len = -1);")
result.append("\tstatic String utf8(const char *p_from, int64_t p_len = -1);")
result.append("\tvoid parse_utf8(const char *p_from, int64_t p_len = -1);")
result.append("\tstatic String utf16(const char16_t *p_from, int64_t p_len = -1);")
result.append("\tvoid parse_utf16(const char16_t *p_from, int64_t p_len = -1);")
result.append("\tCharString utf8() const;")
result.append("\tCharString ascii() const;")
result.append("\tChar16String utf16() const;")
@@ -570,22 +671,22 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
if "operators" in builtin_api:
for operator in builtin_api["operators"]:
if operator["name"] not in ["in", "xor"]:
if is_valid_cpp_operator(operator["name"]):
if "right_type" in operator:
result.append(
f'\t{correct_type(operator["return_type"])} operator{operator["name"]}({type_for_parameter(operator["right_type"])}other) const;'
f'\t{correct_type(operator["return_type"])} operator{get_operator_cpp_name(operator["name"])}({type_for_parameter(operator["right_type"])}p_other) const;'
)
else:
result.append(
f'\t{correct_type(operator["return_type"])} operator{operator["name"].replace("unary", "")}() const;'
f'\t{correct_type(operator["return_type"])} operator{get_operator_cpp_name(operator["name"])}() const;'
)
# Copy assignment.
if copy_constructor_index >= 0:
result.append(f"\t{class_name} &operator=(const {class_name} &other);")
result.append(f"\t{class_name} &operator=(const {class_name} &p_other);")
# Move assignment.
result.append(f"\t{class_name} &operator=({class_name} &&other);")
result.append(f"\t{class_name} &operator=({class_name} &&p_other);")
# Special cases.
if class_name == "String":
@@ -619,8 +720,8 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
if class_name == "Array":
result.append("\ttemplate <typename... Args>")
result.append("\tstatic Array make(Args... args) {")
result.append("\t\treturn helpers::append_all(Array(), args...);")
result.append("\tstatic Array make(Args... p_args) {")
result.append("\t\treturn helpers::append_all(Array(), p_args...);")
result.append("\t}")
if is_packed_array(class_name):
@@ -915,13 +1016,13 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
result.append("")
# Move constructor.
result.append(f"{class_name}::{class_name}({class_name} &&other) {{")
result.append(f"{class_name}::{class_name}({class_name} &&p_other) {{")
if needs_copy_instead_of_move(class_name) and copy_constructor_index >= 0:
result.append(
f"\tinternal::_call_builtin_constructor(_method_bindings.constructor_{copy_constructor_index}, &opaque, &other);"
f"\tinternal::_call_builtin_constructor(_method_bindings.constructor_{copy_constructor_index}, &opaque, &p_other);"
)
else:
result.append("\tstd::swap(opaque, other.opaque);")
result.append("\tstd::swap(opaque, p_other.opaque);")
result.append("}")
result.append("")
@@ -942,7 +1043,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
continue
method_signature = make_signature(class_name, method, for_builtin=True)
result.append(method_signature + "{")
result.append(method_signature + " {")
method_call = "\t"
is_ref = False
@@ -1009,10 +1110,10 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
if "operators" in builtin_api:
for operator in builtin_api["operators"]:
if operator["name"] not in ["in", "xor"]:
if is_valid_cpp_operator(operator["name"]):
if "right_type" in operator:
result.append(
f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"]}({type_for_parameter(operator["right_type"])}other) const {{'
f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}({type_for_parameter(operator["right_type"])}p_other) const {{'
)
(encode, arg_name) = get_encoded_arg("other", operator["right_type"], None)
result += encode
@@ -1022,7 +1123,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
result.append("}")
else:
result.append(
f'{correct_type(operator["return_type"])} {class_name}::operator{operator["name"].replace("unary", "")}() const {{'
f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}() const {{'
)
result.append(
f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr)nullptr);'
@@ -1032,7 +1133,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
# Copy assignment.
if copy_constructor_index >= 0:
result.append(f"{class_name} &{class_name}::operator=(const {class_name} &other) {{")
result.append(f"{class_name} &{class_name}::operator=(const {class_name} &p_other) {{")
if builtin_api["has_destructor"]:
result.append("\t_method_bindings.destructor(&opaque);")
(encode, arg_name) = get_encoded_arg(
@@ -1049,13 +1150,13 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
result.append("")
# Move assignment.
result.append(f"{class_name} &{class_name}::operator=({class_name} &&other) {{")
result.append(f"{class_name} &{class_name}::operator=({class_name} &&p_other) {{")
if needs_copy_instead_of_move(class_name) and copy_constructor_index >= 0:
result.append(
f"\tinternal::_call_builtin_constructor(_method_bindings.constructor_{copy_constructor_index}, &opaque, &other);"
f"\tinternal::_call_builtin_constructor(_method_bindings.constructor_{copy_constructor_index}, &opaque, &p_other);"
)
else:
result.append("\tstd::swap(opaque, other.opaque);")
result.append("\tstd::swap(opaque, p_other.opaque);")
result.append("\treturn *this;")
result.append("}")
@@ -1079,9 +1180,9 @@ 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"]:
# Generate code for the ClassDB singleton under a different name.
if class_api["name"] == "ClassDB":
class_api["name"] = "ClassDBSingleton"
class_api["alias_for"] = "ClassDB"
if class_api["name"] in CLASS_ALIASES:
class_api["alias_for"] = class_api["name"]
class_api["name"] = CLASS_ALIASES[class_api["alias_for"]]
engine_classes[class_api["name"]] = class_api["is_refcounted"]
for native_struct in api["native_structures"]:
engine_classes[native_struct["name"]] = False
@@ -1089,9 +1190,9 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
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"
if singleton["name"] in CLASS_ALIASES:
singleton["alias_for"] = singleton["name"]
singleton["name"] = CLASS_ALIASES[singleton["name"]]
singletons.append(singleton["name"])
for class_api in api["classes"]:
@@ -1227,7 +1328,7 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
for field in expanded_format.split(";"):
field_type = field.strip().split(" ")[0].split("::")[0]
if field_type != "" and not is_included_type(field_type) and not is_pod_type(field_type):
if not field_type in used_classes:
if field_type not in used_classes:
used_classes.append(field_type)
result.append("")
@@ -1294,6 +1395,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append("#include <type_traits>")
result.append("")
if class_name == "ClassDBSingleton":
result.append("#include <godot_cpp/core/binder_common.hpp>")
result.append("")
result.append("namespace godot {")
result.append("")
@@ -1346,8 +1451,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
vararg = "is_vararg" in method and method["is_vararg"]
method_signature = "\t"
if vararg:
method_signature += "private: "
method_signature += make_signature(
class_name, method, for_header=True, use_template_get_node=use_template_get_node
)
@@ -1417,16 +1520,16 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
if class_name == "Object":
result.append("")
result.append("\ttemplate<typename T>")
result.append("\ttemplate <typename T>")
result.append("\tstatic T *cast_to(Object *p_object);")
result.append("\ttemplate<typename T>")
result.append("\ttemplate <typename T>")
result.append("\tstatic const T *cast_to(const Object *p_object);")
result.append("\tvirtual ~Object() = default;")
elif use_template_get_node and class_name == "Node":
result.append("\ttemplate<typename T>")
result.append("\ttemplate <typename T>")
result.append(
"\tT *get_node(const NodePath &p_path) const { return Object::cast_to<T>(get_node_internal(p_path)); }"
)
@@ -1448,20 +1551,43 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
if class_name == "ClassDBSingleton":
result.append("#define CLASSDB_SINGLETON_FORWARD_METHODS \\")
if "enums" in class_api:
for enum_api in class_api["enums"]:
if enum_api["is_bitfield"]:
result.append(f'\tenum {enum_api["name"]} : uint64_t {{ \\')
else:
result.append(f'\tenum {enum_api["name"]} {{ \\')
for value in enum_api["values"]:
result.append(f'\t\t{value["name"]} = {value["value"]}, \\')
result.append("\t}; \\")
result.append("\t \\")
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
# ClassDBSingleton shouldn't have any static methods, but if some appear later, lets skip them.
if "is_static" in method and method["is_static"]:
continue
method_signature = "\tstatic "
vararg = "is_vararg" in method and method["is_vararg"]
if vararg:
method_signature = "\ttemplate <typename... Args> static "
else:
method_signature = "\tstatic "
return_type = None
if "return_type" in method:
method_signature += f'{correct_type(method["return_type"])} '
return_type = correct_type(method["return_type"].replace("ClassDBSingleton", "ClassDB"), None, False)
elif "return_value" in method:
method_signature += (
correct_type(method["return_value"]["type"], method["return_value"].get("meta", None)) + " "
return_type = correct_type(
method["return_value"]["type"].replace("ClassDBSingleton", "ClassDB"),
method["return_value"].get("meta", None),
False,
)
if return_type is not None:
method_signature += return_type
if not method_signature.endswith("*"):
method_signature += " "
else:
method_signature += "void "
@@ -1472,7 +1598,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
method_arguments = method["arguments"]
method_signature += make_function_parameters(
method_arguments, include_default=True, for_builtin=True, is_vararg=False
method_arguments, include_default=True, for_builtin=True, is_vararg=vararg
)
method_signature += ") { \\"
@@ -1480,10 +1606,14 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append(method_signature)
method_body = "\t\t"
if "return_type" in method or "return_value" in method:
if return_type is not None:
method_body += "return "
if "alias_for" in class_api and return_type.startswith(class_api["alias_for"] + "::"):
method_body += f"({return_type})"
method_body += f'ClassDBSingleton::get_singleton()->{method["name"]}('
method_body += ", ".join(map(lambda x: escape_identifier(x["name"]), method_arguments))
method_body += ", ".join(map(lambda x: escape_argument(x["name"]), method_arguments))
if vararg:
method_body += ", p_args..."
method_body += "); \\"
result.append(method_body)
@@ -1491,6 +1621,18 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append("\t;")
result.append("")
result.append("#define CLASSDB_SINGLETON_VARIANT_CAST \\")
if "enums" in class_api:
for enum_api in class_api["enums"]:
if enum_api["is_bitfield"]:
result.append(f'\tVARIANT_BITFIELD_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\')
else:
result.append(f'\tVARIANT_ENUM_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\')
result.append("\t;")
result.append("")
result.append(f"#endif // ! {header_guard}")
return "\n".join(result)
@@ -1620,7 +1762,7 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
else: # vararg.
result.append("\tGDExtensionCallError error;")
result.append("\tVariant ret;")
method_call += "internal::gdextension_interface_object_method_bind_call(_gde_method_bind, _owner, reinterpret_cast<GDExtensionConstVariantPtr *>(args), arg_count, &ret, &error"
method_call += "internal::gdextension_interface_object_method_bind_call(_gde_method_bind, _owner, reinterpret_cast<GDExtensionConstVariantPtr *>(p_args), p_arg_count, &ret, &error"
if is_ref:
method_call += ")" # Close Ref<> constructor.
@@ -1764,7 +1906,7 @@ def generate_global_constant_binds(api, output_dir):
header.append(f'VARIANT_ENUM_CAST({enum_def["name"]});')
# Variant::Type is not a global enum, but only one line, it is worth to place in this file instead of creating new file.
header.append(f"VARIANT_ENUM_CAST(godot::Variant::Type);")
header.append("VARIANT_ENUM_CAST(godot::Variant::Type);")
header.append("")
@@ -1883,7 +2025,7 @@ def generate_utility_functions(api, output_dir):
source.append(f'\t{get_gdextension_type(correct_type(function["return_type"]))} ret;')
else:
source.append("\tVariant ret;")
function_call += "_gde_function(&ret, reinterpret_cast<GDExtensionConstVariantPtr *>(args), arg_count"
function_call += "_gde_function(&ret, reinterpret_cast<GDExtensionConstVariantPtr *>(p_args), p_arg_count"
function_call += ");"
source.append(function_call)
@@ -1914,9 +2056,9 @@ def make_function_parameters(parameters, include_default=False, for_builtin=Fals
for index, par in enumerate(parameters):
parameter = type_for_parameter(par["type"], par["meta"] if "meta" in par else None)
parameter_name = escape_identifier(par["name"])
parameter_name = escape_argument(par["name"])
if len(parameter_name) == 0:
parameter_name = "arg_" + str(index + 1)
parameter_name = "p_arg_" + str(index + 1)
parameter += parameter_name
if include_default and "default_value" in par and (not for_builtin or par["type"] != "Variant"):
@@ -1930,7 +2072,7 @@ def make_function_parameters(parameters, include_default=False, for_builtin=Fals
signature.append(parameter)
if is_vararg:
signature.append("const Args&... args")
signature.append("const Args&... p_args")
return ", ".join(signature)
@@ -1961,7 +2103,7 @@ def get_include_path(type_name):
def get_encoded_arg(arg_name, type_name, type_meta):
result = []
name = escape_identifier(arg_name)
name = escape_argument(arg_name)
arg_type = correct_type(type_name)
if is_pod_type(arg_type):
result.append(f"\t{get_gdextension_type(arg_type)} {name}_encoded;")
@@ -2027,7 +2169,7 @@ def make_signature(
if not is_vararg:
function_signature += make_function_parameters(arguments, for_header, for_builtin, is_vararg)
else:
function_signature += "const Variant **args, GDExtensionInt arg_count"
function_signature += "const Variant **p_args, GDExtensionInt p_arg_count"
function_signature += ")"
@@ -2042,7 +2184,7 @@ def make_signature(
def make_varargs_template(function_data, static=False):
result = []
function_signature = "\tpublic: template<typename... Args> "
function_signature = "\tpublic: template <typename... Args> "
if static:
function_signature += "static "
@@ -2086,12 +2228,12 @@ def make_varargs_template(function_data, static=False):
args_array = f"\t\tstd::array<Variant, {len(method_arguments)} + sizeof...(Args)> variant_args {{ "
for argument in method_arguments:
if argument["type"] == "Variant":
args_array += argument["name"]
args_array += escape_argument(argument["name"])
else:
args_array += f'Variant({argument["name"]})'
args_array += f'Variant({escape_argument(argument["name"])})'
args_array += ", "
args_array += "Variant(args)... };"
args_array += "Variant(p_args)... };"
result.append(args_array)
result.append(f"\t\tstd::array<const Variant *, {len(method_arguments)} + sizeof...(Args)> call_args;")
result.append("\t\tfor(size_t i = 0; i < variant_args.size(); i++) {")
@@ -2182,7 +2324,7 @@ def is_packed_array(type_name):
def needs_copy_instead_of_move(type_name):
"""
Those are types which need initialised data or we'll get warning spam so need a copy instead of move.
Those are types which need initialized data or we'll get warning spam so need a copy instead of move.
"""
return type_name in [
"Dictionary",
@@ -2245,6 +2387,20 @@ def is_refcounted(type_name):
return type_name in engine_classes and engine_classes[type_name]
def is_class_included(class_name, build_profile):
"""
Check if an engine class should be included.
This removes classes according to a build profile of enabled or disabled classes.
"""
included = build_profile.get("enabled_classes", [])
excluded = build_profile.get("disabled_classes", [])
if included:
return class_name in included
if excluded:
return class_name not in excluded
return True
def is_included(type_name, current_type):
"""
Check if a builtin type should be included.
@@ -2265,6 +2421,7 @@ def correct_default_value(value, type_name):
"null": "nullptr",
'""': "String()",
'&""': "StringName()",
'^""': "NodePath()",
"[]": "Array()",
"{}": "Dictionary()",
"Transform2D(1, 0, 0, 1, 0, 0)": "Transform2D()", # Default transform.
@@ -2275,7 +2432,11 @@ def correct_default_value(value, type_name):
if value == "":
return f"{type_name}()"
if value.startswith("Array["):
return f"{{}}"
return "{}"
if value.startswith("&"):
return value[1::]
if value.startswith("^"):
return value[1::]
return value
@@ -2285,13 +2446,11 @@ def correct_typed_array(type_name):
return type_name
def correct_type(type_name, meta=None):
def correct_type(type_name, meta=None, use_alias=True):
type_conversion = {"float": "double", "int": "int64_t", "Nil": "Variant"}
if meta != None:
if meta is not None:
if "int" in meta:
return f"{meta}_t"
elif meta in type_conversion:
return type_conversion[type_name]
else:
return meta
if type_name in type_conversion:
@@ -2301,11 +2460,15 @@ def correct_type(type_name, meta=None):
if is_enum(type_name):
if is_bitfield(type_name):
base_class = get_enum_class(type_name)
if use_alias and base_class in CLASS_ALIASES:
base_class = CLASS_ALIASES[base_class]
if base_class == "GlobalConstants":
return f"BitField<{get_enum_name(type_name)}>"
return f"BitField<{base_class}::{get_enum_name(type_name)}>"
else:
base_class = get_enum_class(type_name)
if use_alias and base_class in CLASS_ALIASES:
base_class = CLASS_ALIASES[base_class]
if base_class == "GlobalConstants":
return f"{get_enum_name(type_name)}"
return f"{base_class}::{get_enum_name(type_name)}"
@@ -2313,7 +2476,7 @@ def correct_type(type_name, meta=None):
return f"Ref<{type_name}>"
if type_name == "Object" or is_engine_class(type_name):
return f"{type_name} *"
if type_name.endswith("*"):
if type_name.endswith("*") and not type_name.endswith("**") and not type_name.endswith(" *"):
return f"{type_name[:-1]} *"
return type_name
@@ -2362,6 +2525,12 @@ def escape_identifier(id):
return id
def escape_argument(id):
if id.startswith("p_") or id.startswith("r_"):
return id
return "p_" + id
def get_operator_id_name(op):
op_id_map = {
"==": "equal",
@@ -2388,12 +2557,43 @@ def get_operator_id_name(op):
"or": "or",
"xor": "xor",
"not": "not",
"and": "and",
"in": "in",
}
return op_id_map[op]
def get_operator_cpp_name(op):
op_cpp_map = {
"==": "==",
"!=": "!=",
"<": "<",
"<=": "<=",
">": ">",
">=": ">=",
"+": "+",
"-": "-",
"*": "*",
"/": "/",
"unary-": "-",
"unary+": "+",
"%": "%",
"<<": "<<",
">>": ">>",
"&": "&",
"|": "|",
"^": "^",
"~": "~",
"and": "&&",
"or": "||",
"not": "!",
}
return op_cpp_map[op]
def is_valid_cpp_operator(op):
return op not in ["**", "xor", "in"]
def get_default_value_for_type(type_name):
if type_name == "int":
return "0"

View File

@@ -76,14 +76,15 @@ protected:
uint32_t plist_size = 0;
void _postinitialize();
virtual void _notificationv(int32_t p_what) {}
Wrapped(const StringName p_godot_class);
Wrapped(GodotObject *p_godot_object);
virtual ~Wrapped() {}
public:
static StringName &get_class_static() {
static StringName string_name = StringName("Wrapped");
static const StringName &get_class_static() {
static const StringName string_name = StringName("Wrapped");
return string_name;
}
@@ -119,7 +120,7 @@ struct EngineClassRegistration {
#define GDCLASS(m_class, m_inherits) \
private: \
void operator=(const m_class &p_rval) {} \
void operator=(const m_class & /*p_rval*/) {} \
friend class ::godot::ClassDB; \
\
protected: \
@@ -171,6 +172,7 @@ protected:
\
public: \
typedef m_class self_type; \
typedef m_inherits parent_type; \
\
static void initialize_class() { \
static bool initialized = false; \
@@ -185,12 +187,12 @@ public:
initialized = true; \
} \
\
static ::godot::StringName &get_class_static() { \
static ::godot::StringName string_name = ::godot::StringName(#m_class); \
static const ::godot::StringName &get_class_static() { \
static const ::godot::StringName string_name = ::godot::StringName(#m_class); \
return string_name; \
} \
\
static ::godot::StringName &get_parent_class_static() { \
static const ::godot::StringName &get_parent_class_static() { \
return m_inherits::get_class_static(); \
} \
\
@@ -210,23 +212,27 @@ public:
} \
\
static GDExtensionBool set_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionConstVariantPtr p_value) { \
if (p_instance && m_class::_get_set()) { \
if (p_instance) { \
if (m_inherits::set_bind(p_instance, p_name, p_value)) { \
return true; \
} \
if (m_class::_get_set() != m_inherits::_get_set()) { \
m_class *cls = reinterpret_cast<m_class *>(p_instance); \
return cls->_set(*reinterpret_cast<const ::godot::StringName *>(p_name), *reinterpret_cast<const ::godot::Variant *>(p_value)); \
} \
return m_inherits::set_bind(p_instance, p_name, p_value); \
} \
return false; \
} \
\
static GDExtensionBool get_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { \
if (p_instance && m_class::_get_get()) { \
if (p_instance) { \
if (m_inherits::get_bind(p_instance, p_name, r_ret)) { \
return true; \
} \
if (m_class::_get_get() != m_inherits::_get_get()) { \
m_class *cls = reinterpret_cast<m_class *>(p_instance); \
return cls->_get(*reinterpret_cast<const ::godot::StringName *>(p_name), *reinterpret_cast<::godot::Variant *>(r_ret)); \
} \
return m_inherits::get_bind(p_instance, p_name, r_ret); \
} \
return false; \
} \
@@ -302,7 +308,7 @@ public:
} \
} \
\
static void free(void *data, GDExtensionClassInstancePtr ptr) { \
static void free(void * /*data*/, GDExtensionClassInstancePtr ptr) { \
if (ptr) { \
m_class *cls = reinterpret_cast<m_class *>(ptr); \
cls->~m_class(); \
@@ -310,14 +316,14 @@ public:
} \
} \
\
static void *_gde_binding_create_callback(void *p_token, void *p_instance) { \
static void *_gde_binding_create_callback(void * /*p_token*/, void * /*p_instance*/) { \
return nullptr; \
} \
\
static void _gde_binding_free_callback(void *p_token, void *p_instance, void *p_binding) { \
static void _gde_binding_free_callback(void * /*p_token*/, void * /*p_instance*/, void * /*p_binding*/) { \
} \
\
static GDExtensionBool _gde_binding_reference_callback(void *p_token, void *p_instance, GDExtensionBool p_reference) { \
static GDExtensionBool _gde_binding_reference_callback(void * /*p_token*/, void * /*p_instance*/, GDExtensionBool /*p_reference*/) { \
return true; \
} \
\
@@ -327,6 +333,11 @@ public:
_gde_binding_reference_callback, \
}; \
\
protected: \
virtual void _notificationv(int32_t p_what) override { \
m_class::notification_bind(this, p_what); \
} \
\
private:
// Don't use this for your classes, use GDCLASS() instead.
@@ -334,6 +345,7 @@ private:
private: \
inline static ::godot::internal::EngineClassRegistration<m_class> _gde_engine_class_registration_helper; \
void operator=(const m_class &p_rval) {} \
friend class ::godot::ClassDB; \
\
protected: \
virtual const GDExtensionInstanceBindingCallbacks *_get_bindings_callbacks() const override { \
@@ -343,6 +355,8 @@ protected:
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 _bind_methods() {} \
\
static void (*_get_bind_methods())() { \
return nullptr; \
} \
@@ -385,15 +399,16 @@ protected:
\
public: \
typedef m_class self_type; \
typedef m_inherits parent_type; \
\
static void initialize_class() {} \
\
static ::godot::StringName &get_class_static() { \
static ::godot::StringName string_name = ::godot::StringName(#m_alias_for); \
static const ::godot::StringName &get_class_static() { \
static const ::godot::StringName string_name = ::godot::StringName(#m_alias_for); \
return string_name; \
} \
\
static ::godot::StringName &get_parent_class_static() { \
static const ::godot::StringName &get_parent_class_static() { \
return m_inherits::get_class_static(); \
} \
\

View File

@@ -174,6 +174,8 @@ public:
template <typename 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.");
static_assert(!FunctionsAreSame<T::self_type::_bind_methods, T::parent_type::_bind_methods>::value, "Class must declare 'static void _bind_methods'.");
static_assert(!std::is_abstract_v<T> || is_abstract, "Class is abstract, please use GDREGISTER_ABSTRACT_CLASS.");
instance_binding_callbacks[T::get_class_static()] = &T::_gde_binding_callbacks;
// Register this class within our plugin
@@ -284,10 +286,12 @@ MethodBind *ClassDB::bind_vararg_method(uint32_t p_flags, StringName p_name, M p
return bind;
}
#define GDREGISTER_CLASS(m_class) ClassDB::register_class<m_class>();
#define GDREGISTER_VIRTUAL_CLASS(m_class) ClassDB::register_class<m_class>(true);
#define GDREGISTER_ABSTRACT_CLASS(m_class) ClassDB::register_abstract_class<m_class>();
#define GDREGISTER_CLASS(m_class) ::godot::ClassDB::register_class<m_class>();
#define GDREGISTER_VIRTUAL_CLASS(m_class) ::godot::ClassDB::register_class<m_class>(true);
#define GDREGISTER_ABSTRACT_CLASS(m_class) ::godot::ClassDB::register_abstract_class<m_class>();
} // namespace godot
CLASSDB_SINGLETON_VARIANT_CAST;
#endif // GODOT_CLASS_DB_HPP

View File

@@ -613,6 +613,14 @@ inline bool is_inf(double p_val) {
return std::isinf(p_val);
}
inline bool is_finite(float p_val) {
return std::isfinite(p_val);
}
inline bool is_finite(double p_val) {
return std::isfinite(p_val);
}
inline bool is_equal_approx(float a, float b) {
// Check for exact equality first, required to handle "infinity" values.
if (a == b) {

View File

@@ -132,7 +132,7 @@ public:
std::vector<GDExtensionClassMethodArgumentMetadata> vec;
// First element is return value
vec.reserve(argument_count + 1);
for (int i = 0; i < argument_count; i++) {
for (int i = 0; i < argument_count + 1; i++) {
vec.push_back(get_argument_metadata(i - 1));
}
return vec;
@@ -412,6 +412,7 @@ public:
method = p_method;
generate_argument_types(sizeof...(P));
set_argument_count(sizeof...(P));
set_const(true);
}
};
@@ -578,6 +579,7 @@ public:
generate_argument_types(sizeof...(P));
set_argument_count(sizeof...(P));
set_return(true);
set_const(true);
}
};

View File

@@ -170,11 +170,11 @@ template <typename 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))));
return likely(p_ptr) ? reinterpret_cast<T *>(godot::internal::get_object_instance_binding(*reinterpret_cast<GDExtensionObjectPtr *>(const_cast<void *>(p_ptr)))) : nullptr;
}
typedef Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
*reinterpret_cast<const void **>(p_ptr) = p_var ? p_var->_owner : nullptr;
*reinterpret_cast<const void **>(p_ptr) = likely(p_var) ? p_var->_owner : nullptr;
}
};
@@ -182,11 +182,11 @@ template <typename 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))));
return likely(p_ptr) ? reinterpret_cast<const T *>(godot::internal::get_object_instance_binding(*reinterpret_cast<GDExtensionObjectPtr *>(const_cast<void *>(p_ptr)))) : nullptr;
}
typedef const Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
*reinterpret_cast<const void **>(p_ptr) = p_var ? p_var->_owner : nullptr;
*reinterpret_cast<const void **>(p_ptr) = likely(p_var) ? p_var->_owner : nullptr;
}
};

View File

@@ -68,6 +68,63 @@ struct PropertyInfo {
PropertyInfo(GDExtensionVariantType p_type, const StringName &p_name, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = "", uint32_t p_usage = PROPERTY_USAGE_DEFAULT, const StringName &p_class_name = "") :
PropertyInfo((Variant::Type)p_type, p_name, p_hint, p_hint_string, p_usage, p_class_name) {}
PropertyInfo(const GDExtensionPropertyInfo *p_info) :
PropertyInfo(p_info->type, *reinterpret_cast<StringName *>(p_info->name), (PropertyHint)p_info->hint, *reinterpret_cast<String *>(p_info->hint_string), p_info->usage, *reinterpret_cast<StringName *>(p_info->class_name)) {}
operator Dictionary() const {
Dictionary dict;
dict["name"] = name;
dict["class_name"] = class_name;
dict["type"] = type;
dict["hint"] = hint;
dict["hint_string"] = hint_string;
dict["usage"] = usage;
return dict;
}
static PropertyInfo from_dict(const Dictionary &p_dict) {
PropertyInfo pi;
if (p_dict.has("type")) {
pi.type = Variant::Type(int(p_dict["type"]));
}
if (p_dict.has("name")) {
pi.name = p_dict["name"];
}
if (p_dict.has("class_name")) {
pi.class_name = p_dict["class_name"];
}
if (p_dict.has("hint")) {
pi.hint = PropertyHint(int(p_dict["hint"]));
}
if (p_dict.has("hint_string")) {
pi.hint_string = p_dict["hint_string"];
}
if (p_dict.has("usage")) {
pi.usage = p_dict["usage"];
}
return pi;
}
void _update(GDExtensionPropertyInfo *p_info) {
p_info->type = (GDExtensionVariantType)type;
*(reinterpret_cast<StringName *>(p_info->name)) = name;
p_info->hint = hint;
*(reinterpret_cast<String *>(p_info->hint_string)) = hint_string;
p_info->usage = usage;
*(reinterpret_cast<StringName *>(p_info->class_name)) = class_name;
}
GDExtensionPropertyInfo _to_gdextension() const {
return {
(GDExtensionVariantType)type,
name._native_ptr(),
class_name._native_ptr(),
hint,
hint_string._native_ptr(),
usage,
};
}
};
} // namespace godot

View File

@@ -58,6 +58,16 @@ struct TypesAreSame<A, A> {
static bool const value = true;
};
template <auto A, auto B>
struct FunctionsAreSame {
static bool const value = false;
};
template <auto A>
struct FunctionsAreSame<A, A> {
static bool const value = true;
};
template <typename B, typename D>
struct TypeInherits {
static D *get_d();
@@ -383,17 +393,15 @@ MAKE_TYPED_ARRAY_INFO(Callable, Variant::CALLABLE)
MAKE_TYPED_ARRAY_INFO(Signal, Variant::SIGNAL)
MAKE_TYPED_ARRAY_INFO(Dictionary, Variant::DICTIONARY)
MAKE_TYPED_ARRAY_INFO(Array, Variant::ARRAY)
/*
MAKE_TYPED_ARRAY_INFO(Vector<uint8_t>, Variant::PACKED_BYTE_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<int32_t>, Variant::PACKED_INT32_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<int64_t>, Variant::PACKED_INT64_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<float>, Variant::PACKED_FLOAT32_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<double>, Variant::PACKED_FLOAT64_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<String>, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<Vector2>, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<Vector3>, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<Color>, Variant::PACKED_COLOR_ARRAY)
*/
MAKE_TYPED_ARRAY_INFO(PackedByteArray, Variant::PACKED_BYTE_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedInt32Array, Variant::PACKED_INT32_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedInt64Array, Variant::PACKED_INT64_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedStringArray, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY_INFO(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
#define CLASS_INFO(m_type) (GetTypeInfo<m_type *>::get_class_info())

View File

@@ -31,10 +31,10 @@
#ifndef GODOT_LOCAL_VECTOR_HPP
#define GODOT_LOCAL_VECTOR_HPP
#include "godot_cpp/core/error_macros.hpp"
#include "godot_cpp/core/memory.hpp"
#include "godot_cpp/templates/sort_array.hpp"
#include "godot_cpp/templates/vector.hpp"
#include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/templates/sort_array.hpp>
#include <godot_cpp/templates/vector.hpp>
#include <initializer_list>
#include <type_traits>

View File

@@ -132,7 +132,7 @@ public:
}
}
_ALWAYS_INLINE_ explicit SafeNumeric<T>(T p_value = static_cast<T>(0)) {
_ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast<T>(0)) {
set(p_value);
}
};

View File

@@ -65,6 +65,7 @@ struct _NO_DISCARD_ AABB {
bool operator!=(const AABB &p_rval) const;
bool is_equal_approx(const AABB &p_aabb) const;
bool is_finite() const;
_FORCE_INLINE_ bool intersects(const AABB &p_aabb) const; /// Both AABBs overlap
_FORCE_INLINE_ bool intersects_inclusive(const AABB &p_aabb) const; /// Both AABBs (or their faces) overlap
_FORCE_INLINE_ bool encloses(const AABB &p_aabb) const; /// p_aabb is completely inside this

View File

@@ -28,6 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GODOT_ARRAY_HELPERS_HPP
#define GODOT_ARRAY_HELPERS_HPP
namespace godot {
namespace helpers {
template <typename T, typename ValueT>
@@ -48,3 +51,5 @@ T append_all(T appendable) {
}
} // namespace helpers
} // namespace godot
#endif // GODOT_ARRAY_HELPERS_HPP

View File

@@ -128,6 +128,7 @@ struct _NO_DISCARD_ Basis {
}
bool is_equal_approx(const Basis &p_basis) const;
bool is_finite() const;
bool operator==(const Basis &p_matrix) const;
bool operator!=(const Basis &p_matrix) const;

View File

@@ -28,6 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GODOT_COLOR_NAMES_INC_HPP
#define GODOT_COLOR_NAMES_INC_HPP
namespace godot {
// Names from https://en.wikipedia.org/wiki/X11_color_names
@@ -189,3 +192,5 @@ static NamedColor named_colors[] = {
};
} // namespace godot
#endif // GODOT_COLOR_NAMES_INC_HPP

View File

@@ -77,6 +77,7 @@ struct _NO_DISCARD_ Plane {
Plane operator-() const { return Plane(-normal, -d); }
bool is_equal_approx(const Plane &p_plane) const;
bool is_equal_approx_any_side(const Plane &p_plane) const;
bool is_finite() const;
_FORCE_INLINE_ bool operator==(const Plane &p_plane) const;
_FORCE_INLINE_ bool operator!=(const Plane &p_plane) const;

View File

@@ -55,6 +55,7 @@ struct _NO_DISCARD_ Quaternion {
}
_FORCE_INLINE_ real_t length_squared() const;
bool is_equal_approx(const Quaternion &p_quaternion) const;
bool is_finite() const;
real_t length() const;
void normalize();
Quaternion normalized() const;

View File

@@ -146,7 +146,7 @@ struct _NO_DISCARD_ Rect2 {
return size.x > 0.0f && size.y > 0.0f;
}
// Returns the instersection between two Rect2s or an empty Rect2 if there is no intersection
// Returns the intersection between two Rect2s or an empty Rect2 if there is no intersection
inline Rect2 intersection(const Rect2 &p_rect) const {
Rect2 new_rect = p_rect;
@@ -209,6 +209,7 @@ struct _NO_DISCARD_ Rect2 {
}
bool is_equal_approx(const Rect2 &p_rect) const;
bool is_finite() const;
bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; }
bool operator!=(const Rect2 &p_rect) const { return position != p_rect.position || size != p_rect.size; }

View File

@@ -89,7 +89,7 @@ struct _NO_DISCARD_ Rect2i {
return size.x > 0 && size.y > 0;
}
// Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection
// Returns the intersection between two Rect2is or an empty Rect2i if there is no intersection
inline Rect2i intersection(const Rect2i &p_rect) const {
Rect2i new_rect = p_rect;

View File

@@ -99,6 +99,7 @@ struct _NO_DISCARD_ Transform2D {
void orthonormalize();
Transform2D orthonormalized() const;
bool is_equal_approx(const Transform2D &p_transform) const;
bool is_finite() const;
Transform2D looking_at(const Vector2 &p_target) const;

View File

@@ -78,6 +78,7 @@ struct _NO_DISCARD_ Transform3D {
void orthogonalize();
Transform3D orthogonalized() const;
bool is_equal_approx(const Transform3D &p_transform) const;
bool is_finite() const;
bool operator==(const Transform3D &p_transform) const;
bool operator!=(const Transform3D &p_transform) const;

View File

@@ -44,10 +44,15 @@ public:
_ref(p_array);
}
_FORCE_INLINE_ TypedArray(const Variant &p_variant) :
Array(p_variant.operator Array(), Variant::OBJECT, T::get_class_static(), Variant()) {
TypedArray(Array(p_variant)) {
}
_FORCE_INLINE_ TypedArray(const Array &p_array) :
Array(p_array, Variant::OBJECT, T::get_class_static(), Variant()) {
_FORCE_INLINE_ TypedArray(const Array &p_array) {
set_typed(Variant::OBJECT, T::get_class_static(), Variant());
if (is_same_typed(p_array)) {
_ref(p_array);
} else {
assign(p_array);
}
}
_FORCE_INLINE_ TypedArray() {
set_typed(Variant::OBJECT, T::get_class_static(), Variant());
@@ -65,10 +70,15 @@ public:
_ref(p_array); \
} \
_FORCE_INLINE_ TypedArray(const Variant &p_variant) : \
Array(p_variant.operator Array(), m_variant_type, StringName(), Variant()) { \
TypedArray(Array(p_variant)) { \
} \
_FORCE_INLINE_ TypedArray(const Array &p_array) : \
Array(p_array, m_variant_type, StringName(), Variant()) { \
_FORCE_INLINE_ TypedArray(const Array &p_array) { \
set_typed(m_variant_type, StringName(), Variant()); \
if (is_same_typed(p_array)) { \
_ref(p_array); \
} else { \
assign(p_array); \
} \
} \
_FORCE_INLINE_ TypedArray() { \
set_typed(m_variant_type, StringName(), Variant()); \

View File

@@ -123,6 +123,7 @@ struct _NO_DISCARD_ Vector2 {
bool is_equal_approx(const Vector2 &p_v) const;
bool is_zero_approx() const;
bool is_finite() const;
Vector2 operator+(const Vector2 &p_v) const;
void operator+=(const Vector2 &p_v);

View File

@@ -146,6 +146,7 @@ struct _NO_DISCARD_ Vector3 {
bool is_equal_approx(const Vector3 &p_v) const;
bool is_zero_approx() const;
bool is_finite() const;
/* Operators */

View File

@@ -81,6 +81,7 @@ struct _NO_DISCARD_ Vector4 {
_FORCE_INLINE_ real_t length_squared() const;
bool is_equal_approx(const Vector4 &p_vec4) const;
bool is_zero_approx() const;
bool is_finite() const;
real_t length() const;
void normalize();
Vector4 normalized() const;

View File

@@ -1,37 +0,0 @@
# Git hooks for Godot Engine
This folder contains Git hooks meant to be installed locally by Godot Engine
contributors to make sure they comply with our requirements.
## List of hooks
- Pre-commit hook for `clang-format`: Applies `clang-format` to the staged
files before accepting a commit; blocks the commit and generates a patch if
the style is not respected.
You may need to edit the file if your `clang-format` binary is not in the
`PATH`, or if you want to enable colored output with `pygmentize`.
- Pre-commit hook for `black`: Applies `black` to the staged Python files
before accepting a commit.
- Pre-commit hook for `make_rst`: Checks the class reference syntax using
`make_rst.py`.
## Installation
Copy all the files from this folder into your `.git/hooks` folder, and make
sure the hooks and helper scripts are executable.
#### Linux/MacOS
The hooks rely on bash scripts and tools which should be in the system `PATH`,
so they should work out of the box on Linux/macOS.
#### Windows
##### clang-format
- Download LLVM for Windows (version 13 or later) from
<https://releases.llvm.org/download.html>
- Make sure LLVM is added to the `PATH` during installation
##### black
- Python installation: make sure Python is added to the `PATH`
- Install `black` - in any console: `pip3 install black`

View File

@@ -1,48 +0,0 @@
#!/bin/sh
# Provide the canonicalize filename (physical filename with out any symlinks)
# like the GNU version readlink with the -f option regardless of the version of
# readlink (GNU or BSD).
# This file is part of a set of unofficial pre-commit hooks available
# at github.
# Link: https://github.com/githubbrowser/Pre-commit-hooks
# Contact: David Martin, david.martin.mailbox@googlemail.com
###########################################################
# There should be no need to change anything below this line.
# Canonicalize by recursively following every symlink in every component of the
# specified filename. This should reproduce the results of the GNU version of
# readlink with the -f option.
#
# Reference: https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac
canonicalize_filename () {
local target_file="$1"
local physical_directory=""
local result=""
# Need to restore the working directory after work.
local working_dir="`pwd`"
cd -- "$(dirname -- "$target_file")"
target_file="$(basename -- "$target_file")"
# Iterate down a (possible) chain of symlinks
while [ -L "$target_file" ]
do
target_file="$(readlink -- "$target_file")"
cd -- "$(dirname -- "$target_file")"
target_file="$(basename -- "$target_file")"
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
physical_directory="`pwd -P`"
result="$physical_directory/$target_file"
# restore the working directory after work.
cd -- "$working_dir"
echo "$result"
}

View File

@@ -1,50 +0,0 @@
#!/bin/sh
# Git pre-commit hook that runs multiple hooks specified in $HOOKS.
# Make sure this script is executable. Bypass hooks with git commit --no-verify.
# This file is part of a set of unofficial pre-commit hooks available
# at github.
# Link: https://github.com/githubbrowser/Pre-commit-hooks
# Contact: David Martin, david.martin.mailbox@googlemail.com
###########################################################
# CONFIGURATION:
# pre-commit hooks to be executed. They should be in the same .git/hooks/ folder
# as this script. Hooks should return 0 if successful and nonzero to cancel the
# commit. They are executed in the order in which they are listed.
#HOOKS="pre-commit-compile pre-commit-uncrustify"
HOOKS="pre-commit-clang-format pre-commit-black"
###########################################################
# There should be no need to change anything below this line.
. "$(dirname -- "$0")/canonicalize_filename.sh"
# exit on error
set -e
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT="$(canonicalize_filename "$0")"
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH="$(dirname -- "$SCRIPT")"
for hook in $HOOKS
do
echo "Running hook: $hook"
# run hook if it exists
# if it returns with nonzero exit with 1 and thus abort the commit
if [ -f "$SCRIPTPATH/$hook" ]; then
"$SCRIPTPATH/$hook"
if [ $? != 0 ]; then
exit 1
fi
else
echo "Error: file $hook not found."
echo "Aborting commit. Make sure the hook is in $SCRIPTPATH and executable."
echo "You can disable it by removing it from the list in $SCRIPT."
echo "You can skip all pre-commit hooks with --no-verify (not recommended)."
exit 1
fi
done

View File

@@ -1,202 +0,0 @@
#!/usr/bin/env bash
# git pre-commit hook that runs a black stylecheck.
# Based on pre-commit-clang-format.
##################################################################
# SETTINGS
# Set path to black binary.
BLACK=`which black 2>/dev/null`
BLACK_OPTIONS="-l 120"
# Remove any older patches from previous commits. Set to true or false.
DELETE_OLD_PATCHES=false
# File types to parse.
FILE_NAMES="SConstruct SCsub"
FILE_EXTS=".py"
# Use pygmentize instead of cat to parse diff with highlighting.
# Install it with `pip install pygments` (Linux) or `easy_install Pygments` (Mac)
PYGMENTIZE=`which pygmentize 2>/dev/null`
if [ ! -z "$PYGMENTIZE" ]; then
READER="pygmentize -l diff"
else
READER=cat
fi
# Path to zenity
ZENITY=`which zenity 2>/dev/null`
# Path to xmessage
XMSG=`which xmessage 2>/dev/null`
# Path to powershell (Windows only)
PWSH=`which powershell 2>/dev/null`
##################################################################
# There should be no need to change anything below this line.
. "$(dirname -- "$0")/canonicalize_filename.sh"
# exit on error
set -e
# check whether the given file matches any of the set extensions
matches_name_or_extension() {
local filename=$(basename "$1")
local extension=".${filename##*.}"
for name in $FILE_NAMES; do [[ "$name" == "$filename" ]] && return 0; done
for ext in $FILE_EXTS; do [[ "$ext" == "$extension" ]] && return 0; done
return 1
}
# necessary check for initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1 ; then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
if [ ! -x "$BLACK" ] ; then
if [ ! -t 1 ] ; then
if [ -x "$ZENITY" ] ; then
$ZENITY --error --title="Error" --text="Error: black executable not found."
exit 1
elif [ -x "$XMSG" ] ; then
$XMSG -center -title "Error" "Error: black executable not found."
exit 1
elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then
winmessage="$(canonicalize_filename "./.git/hooks/winmessage.ps1")"
$PWSH -noprofile -executionpolicy bypass -file "$winmessage" -center -title "Error" --text "Error: black executable not found."
exit 1
fi
fi
printf "Error: black executable not found.\n"
printf "Set the correct path in $(canonicalize_filename "$0").\n"
exit 1
fi
# create a random filename to store our generated patch
prefix="pre-commit-black"
suffix="$(date +%s)"
patch="/tmp/$prefix-$suffix.patch"
# clean up any older black patches
$DELETE_OLD_PATCHES && rm -f /tmp/$prefix*.patch
# create one patch containing all changes to the files
git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file;
do
# ignore thirdparty files
if grep -q "thirdparty" <<< $file; then
continue;
fi
# ignore file if not one of the names or extensions we handle
if ! matches_name_or_extension "$file"; then
continue;
fi
# format our file with black, create a patch with diff and append it to our $patch
# The sed call is necessary to transform the patch from
# --- $file timestamp
# +++ $file timestamp
# to both lines working on the same file and having a/ and b/ prefix.
# Else it can not be applied with 'git apply'.
"$BLACK" "$BLACK_OPTIONS" --diff "$file" | \
sed -e "1s|--- |--- a/|" -e "2s|+++ |+++ b/|" >> "$patch"
done
# if no patch has been generated all is ok, clean up the file stub and exit
if [ ! -s "$patch" ] ; then
printf "Files in this commit comply with the black formatter rules.\n"
rm -f "$patch"
exit 0
fi
# a patch has been created, notify the user and exit
printf "\nThe following differences were found between the code to commit "
printf "and the black formatter rules:\n\n"
if [ -t 1 ] ; then
$READER "$patch"
printf "\n"
# Allows us to read user input below, assigns stdin to keyboard
exec < /dev/tty
terminal="1"
else
cat "$patch"
printf "\n"
# Allows non zero zenity/powershell output
set +e
terminal="0"
fi
while true; do
if [ $terminal = "0" ] ; then
if [ -x "$ZENITY" ] ; then
choice=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage")
if [ "$?" = "0" ] ; then
yn="Y"
else
if [ "$choice" = "Apply and stage" ] ; then
yn="S"
else
yn="N"
fi
fi
elif [ -x "$XMSG" ] ; then
$XMSG -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?"
choice=$?
if [ "$choice" = "100" ] ; then
yn="Y"
elif [ "$choice" = "200" ] ; then
yn="S"
else
yn="N"
fi
elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then
winmessage="$(canonicalize_filename "./.git/hooks/winmessage.ps1")"
$PWSH -noprofile -executionpolicy bypass -file "$winmessage" -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?"
choice=$?
if [ "$choice" = "100" ] ; then
yn="Y"
elif [ "$choice" = "200" ] ; then
yn="S"
else
yn="N"
fi
else
printf "Error: zenity, xmessage, or powershell executable not found.\n"
exit 1
fi
else
read -p "Do you want to apply that patch (Y - Apply, N - Do not apply, S - Apply and stage files)? [Y/N/S] " yn
fi
case $yn in
[Yy] ) git apply $patch;
printf "The patch was applied. You can now stage the changes and commit again.\n\n";
break
;;
[Nn] ) printf "\nYou can apply these changes with:\n git apply $patch\n";
printf "(may need to be called from the root directory of your repository)\n";
printf "Aborting commit. Apply changes and commit again or skip checking with";
printf " --no-verify (not recommended).\n\n";
break
;;
[Ss] ) git apply $patch;
git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file;
do git add $file;
done
printf "The patch was applied and the changed files staged. You can now commit.\n\n";
break
;;
* ) echo "Please answer yes or no."
;;
esac
done
exit 1 # we don't commit in any case

View File

@@ -1,242 +0,0 @@
#!/usr/bin/env bash
# git pre-commit hook that runs a clang-format stylecheck.
# Features:
# - abort commit when commit does not comply with the style guidelines
# - create a patch of the proposed style changes
# Modifications for clang-format by rene.milk@wwu.de
# This file is part of a set of unofficial pre-commit hooks available
# at github.
# Link: https://github.com/githubbrowser/Pre-commit-hooks
# Contact: David Martin, david.martin.mailbox@googlemail.com
# Some quality of life modifications made for Godot Engine.
##################################################################
# SETTINGS
# Set path to clang-format binary.
CLANG_FORMAT=`which clang-format 2>/dev/null`
# Remove any older patches from previous commits. Set to true or false.
DELETE_OLD_PATCHES=false
# Only parse files with the extensions in FILE_EXTS. Set to true or false.
# If false every changed file in the commit will be parsed with clang-format.
# If true only files matching one of the extensions are parsed with clang-format.
PARSE_EXTS=true
# File types to parse. Only effective when PARSE_EXTS is true.
FILE_EXTS=".c .h .cpp .hpp .cc .hh .cxx .m .mm .inc .java .glsl"
# Use pygmentize instead of cat to parse diff with highlighting.
# Install it with `pip install pygments` (Linux) or `easy_install Pygments` (Mac)
PYGMENTIZE=`which pygmentize 2>/dev/null`
if [ ! -z "$PYGMENTIZE" ]; then
READER="pygmentize -l diff"
else
READER=cat
fi
# Path to zenity
ZENITY=`which zenity 2>/dev/null`
# Path to xmessage
XMSG=`which xmessage 2>/dev/null`
# Path to powershell (Windows only)
PWSH=`which powershell 2>/dev/null`
##################################################################
# There should be no need to change anything below this line.
. "$(dirname -- "$0")/canonicalize_filename.sh"
# exit on error
set -e
# check whether the given file matches any of the set extensions
matches_extension() {
local filename=$(basename "$1")
local extension=".${filename##*.}"
local ext
for ext in $FILE_EXTS; do [[ "$ext" == "$extension" ]] && return 0; done
return 1
}
# necessary check for initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1 ; then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# To get consistent formatting, we recommend contributors to use the same
# clang-format version as CI.
RECOMMENDED_CLANG_FORMAT_MAJOR_MIN="12"
RECOMMENDED_CLANG_FORMAT_MAJOR_MAX="13"
if [ ! -x "$CLANG_FORMAT" ] ; then
message="Error: clang-format executable not found. Please install clang-format $RECOMMENDED_CLANG_FORMAT_MAJOR_MAX."
if [ ! -t 1 ] ; then
if [ -x "$ZENITY" ] ; then
$ZENITY --error --title="Error" --text="$message"
exit 1
elif [ -x "$XMSG" ] ; then
$XMSG -center -title "Error" "$message"
exit 1
elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then
winmessage="$(canonicalize_filename "./.git/hooks/winmessage.ps1")"
$PWSH -noprofile -executionpolicy bypass -file "$winmessage" -center -title "Error" --text "$message"
exit 1
fi
fi
printf "$message\n"
printf "Set the correct path in $(canonicalize_filename "$0").\n"
exit 1
fi
# The returned string can be inconsistent depending on where clang-format comes from.
# Example output strings reported by `clang-format --version`:
# - Ubuntu: "Ubuntu clang-format version 11.0.0-2"
# - Fedora: "clang-format version 11.0.0 (Fedora 11.0.0-2.fc33)"
CLANG_FORMAT_VERSION="$(clang-format --version | sed "s/[^0-9\.]*\([0-9\.]*\).*/\1/")"
CLANG_FORMAT_MAJOR="$(echo "$CLANG_FORMAT_VERSION" | cut -d. -f1)"
if [[ "$CLANG_FORMAT_MAJOR" -lt "$RECOMMENDED_CLANG_FORMAT_MAJOR_MIN" || "$CLANG_FORMAT_MAJOR" -gt "$RECOMMENDED_CLANG_FORMAT_MAJOR_MAX" ]]; then
echo "Warning: Your clang-format binary is the wrong version ($CLANG_FORMAT_VERSION, expected between $RECOMMENDED_CLANG_FORMAT_MAJOR_MIN and $RECOMMENDED_CLANG_FORMAT_MAJOR_MAX)."
echo " Consider upgrading or downgrading clang-format as formatting may not be applied correctly."
fi
# create a random filename to store our generated patch
prefix="pre-commit-clang-format"
suffix="$(date +%s)"
patch="/tmp/$prefix-$suffix.patch"
# clean up any older clang-format patches
$DELETE_OLD_PATCHES && rm -f /tmp/$prefix*.patch
# create one patch containing all changes to the files
git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file;
do
# ignore thirdparty files
if grep -q "thirdparty" <<< $file; then
continue;
fi
if grep -q "platform/android/java/lib/src/com" <<< $file; then
continue;
fi
if grep -q "\-so_wrap." <<< $file; then
continue;
fi
# ignore file if we do check for file extensions and the file
# does not match any of the extensions specified in $FILE_EXTS
if $PARSE_EXTS && ! matches_extension "$file"; then
continue;
fi
# clang-format our sourcefile, create a patch with diff and append it to our $patch
# The sed call is necessary to transform the patch from
# --- $file timestamp
# +++ - timestamp
# to both lines working on the same file and having a/ and b/ prefix.
# Else it can not be applied with 'git apply'.
"$CLANG_FORMAT" -style=file "$file" --Wno-error=unknown | \
diff -u "$file" - | \
sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patch"
done
# if no patch has been generated all is ok, clean up the file stub and exit
if [ ! -s "$patch" ] ; then
printf "Files in this commit comply with the clang-format rules.\n"
rm -f "$patch"
exit 0
fi
# a patch has been created, notify the user and exit
printf "\nThe following differences were found between the code to commit "
printf "and the clang-format rules:\n\n"
if [ -t 1 ] ; then
$READER "$patch"
printf "\n"
# Allows us to read user input below, assigns stdin to keyboard
exec < /dev/tty
terminal="1"
else
cat "$patch"
printf "\n"
# Allows non zero zenity/powershell output
set +e
terminal="0"
fi
while true; do
if [ $terminal = "0" ] ; then
if [ -x "$ZENITY" ] ; then
choice=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage")
if [ "$?" = "0" ] ; then
yn="Y"
else
if [ "$choice" = "Apply and stage" ] ; then
yn="S"
else
yn="N"
fi
fi
elif [ -x "$XMSG" ] ; then
$XMSG -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?"
choice=$?
if [ "$choice" = "100" ] ; then
yn="Y"
elif [ "$choice" = "200" ] ; then
yn="S"
else
yn="N"
fi
elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then
winmessage="$(canonicalize_filename "./.git/hooks/winmessage.ps1")"
$PWSH -noprofile -executionpolicy bypass -file "$winmessage" -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?"
choice=$?
if [ "$choice" = "100" ] ; then
yn="Y"
elif [ "$choice" = "200" ] ; then
yn="S"
else
yn="N"
fi
else
printf "Error: zenity, xmessage, or powershell executable not found.\n"
exit 1
fi
else
read -p "Do you want to apply that patch (Y - Apply, N - Do not apply, S - Apply and stage files)? [Y/N/S] " yn
fi
case $yn in
[Yy] ) git apply $patch;
printf "The patch was applied. You can now stage the changes and commit again.\n\n";
break
;;
[Nn] ) printf "\nYou can apply these changes with:\n git apply $patch\n";
printf "(may need to be called from the root directory of your repository)\n";
printf "Aborting commit. Apply changes and commit again or skip checking with";
printf " --no-verify (not recommended).\n\n";
break
;;
[Ss] ) git apply $patch;
git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file;
do git add $file;
done
printf "The patch was applied and the changed files staged. You can now commit.\n\n";
break
;;
* ) echo "Please answer yes or no."
;;
esac
done
exit 1 # we don't commit in any case

View File

@@ -1,103 +0,0 @@
Param (
[string]$file = "",
[string]$text = "",
[string]$buttons = "OK:0",
[string]$default = "",
[switch]$nearmouse = $false,
[switch]$center = $false,
[string]$geometry = "",
[int32]$timeout = 0,
[string]$title = "Message"
)
Add-Type -assembly System.Windows.Forms
$global:Result = 0
$main_form = New-Object System.Windows.Forms.Form
$main_form.Text = $title
$geometry_data = $geometry.Split("+")
if ($geometry_data.Length -ge 1) {
$size_data = $geometry_data[0].Split("x")
if ($size_data.Length -eq 2) {
$main_form.Width = $size_data[0]
$main_form.Height = $size_data[1]
}
}
if ($geometry_data.Length -eq 3) {
$main_form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
$main_form.Location = New-Object System.Drawing.Point($geometry_data[1], $geometry_data[2])
}
if ($nearmouse) {
$main_form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
$main_form.Location = System.Windows.Forms.Cursor.Position
}
if ($center) {
$main_form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
}
$main_form.SuspendLayout()
$button_panel = New-Object System.Windows.Forms.FlowLayoutPanel
$button_panel.SuspendLayout()
$button_panel.FlowDirection = [System.Windows.Forms.FlowDirection]::RightToLeft
$button_panel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$button_panel.Autosize = $true
if ($file -ne "") {
$text = [IO.File]::ReadAllText($file).replace("`n", "`r`n")
}
if ($text -ne "") {
$text_box = New-Object System.Windows.Forms.TextBox
$text_box.Multiline = $true
$text_box.ReadOnly = $true
$text_box.Autosize = $true
$text_box.Text = $text
$text_box.Select(0,0)
$text_box.Dock = [System.Windows.Forms.DockStyle]::Fill
$main_form.Controls.Add($text_box)
}
$buttons_array = $buttons.Split(",")
foreach ($button in $buttons_array) {
$button_data = $button.Split(":")
$button_ctl = New-Object System.Windows.Forms.Button
if ($button_data.Length -eq 2) {
$button_ctl.Tag = $button_data[1]
} else {
$button_ctl.Tag = 100 + $buttons_array.IndexOf($button)
}
if ($default -eq $button_data[0]) {
$main_form.AcceptButton = $button_ctl
}
$button_ctl.Autosize = $true
$button_ctl.Text = $button_data[0]
$button_ctl.Add_Click(
{
Param($sender)
$global:Result = $sender.Tag
$main_form.Close()
}
)
$button_panel.Controls.Add($button_ctl)
}
$main_form.Controls.Add($button_panel)
$button_panel.ResumeLayout($false)
$main_form.ResumeLayout($false)
if ($timeout -gt 0) {
$timer = New-Object System.Windows.Forms.Timer
$timer.Add_Tick(
{
$global:Result = 0
$main_form.Close()
}
)
$timer.Interval = $timeout
$timer.Start()
}
$dlg_res = $main_form.ShowDialog()
[Environment]::Exit($global:Result)

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env bash
# This script runs black on all Python files in the repo.
set -uo pipefail
# Apply black.
echo -e "Formatting Python files..."
PY_FILES=$(git ls-files -- '*SConstruct' '*SCsub' '*.py' ':!:.git/*' ':!:thirdparty/*')
black -l 120 $PY_FILES
diff=$(git diff --color)
# If no patch has been generated all is OK, clean up, and exit.
if [ -z "$diff" ] ; then
printf "Files in this commit comply with the black style rules.\n"
exit 0
fi
# A patch has been created, notify the user, clean up, and exit.
printf "\n*** The following differences were found between the code "
printf "and the formatting rules:\n\n"
echo "$diff"
printf "\n*** Aborting, please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\n"
exit 1

View File

@@ -9,8 +9,8 @@ if len(sys.argv) < 2:
fname = sys.argv[1]
fileread = open(fname.strip(), "r")
file_contents = fileread.read()
with open(fname.strip(), "r", encoding="utf-8") as fileread:
file_contents = fileread.read()
# If find "ERROR: AddressSanitizer:", then happens invalid read or write
# This is critical bug, so we need to fix this as fast as possible
@@ -25,6 +25,8 @@ if (
file_contents.find("Program crashed with signal") != -1
or file_contents.find("Dumping the backtrace") != -1
or file_contents.find("Segmentation fault (core dumped)") != -1
or file_contents.find("Aborted (core dumped)") != -1
or file_contents.find("terminate called without an active exception") != -1
):
print("FATAL ERROR: Godot has been crashed.")
sys.exit(52)

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env python
import os, sys
import os
import sys
from pathlib import Path
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", ".."))
from binding_generator import get_file_list, generate_bindings
from binding_generator import generate_bindings, get_file_list
api_filepath = "gdextension/extension_api.json"
bits = "64"

View File

@@ -1,38 +0,0 @@
#!/usr/bin/env bash
# This script runs clang-format and fixes copyright headers on all relevant files in the repo.
# This is the primary script responsible for fixing style violations.
set -uo pipefail
# Loops through all code files tracked by Git.
git ls-files -- '*.c' '*.h' '*.cpp' '*.hpp' '*.cc' '*.hh' '*.cxx' '*.m' '*.mm' '*.inc' |
while read -r f; do
# Run clang-format.
clang-format --Wno-error=unknown -i "$f"
# Fix copyright headers, but not all files get them.
if [[ "$f" == *"inc" ]]; then
continue
elif [[ "$f" == *"glsl" ]]; then
continue
elif [[ "$f" == "test/"* ]]; then
continue
fi
python misc/scripts/copyright_headers.py "$f"
done
diff=$(git diff --color)
# If no patch has been generated all is OK, clean up, and exit.
if [ -z "$diff" ] ; then
printf "Files in this commit comply with the clang-tidy style rules.\n"
exit 0
fi
# A patch has been created, notify the user, clean up, and exit.
printf "\n*** The following changes have been made to comply with the formatting rules:\n\n"
echo "$diff"
printf "\n*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\n"
exit 1

View File

@@ -1,5 +0,0 @@
#!/bin/sh
SKIP_LIST="./thirdparty,*.gen.*,*.po,*.pot,package-lock.json,./core/string/locales.h,./DONORS.md,./misc/scripts/codespell.sh"
IGNORE_LIST="ba,childs,curvelinear,expct,fave,findn,gird,inout,lod,nd,numer,ois,ro,statics,te,varn"
codespell -w -q 3 -S "${SKIP_LIST}" -L "${IGNORE_LIST}"

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
header = """\
@@ -35,61 +36,61 @@ header = """\
/**************************************************************************/
"""
fname = sys.argv[1]
if len(sys.argv) < 2:
print("Invalid usage of copyright_headers.py, it should be called with a path to one or multiple files.")
sys.exit(1)
# Handle replacing $filename with actual filename and keep alignment
fsingle = fname.strip()
if fsingle.find("/") != -1:
fsingle = fsingle[fsingle.rfind("/") + 1 :]
rep_fl = "$filename"
rep_fi = fsingle
len_fl = len(rep_fl)
len_fi = len(rep_fi)
# Pad with spaces to keep alignment
if len_fi < len_fl:
for x in range(len_fl - len_fi):
rep_fi += " "
elif len_fl < len_fi:
for x in range(len_fi - len_fl):
rep_fl += " "
if header.find(rep_fl) != -1:
text = header.replace(rep_fl, rep_fi)
else:
text = header.replace("$filename", fsingle)
text += "\n"
for f in sys.argv[1:]:
fname = f
# We now have the proper header, so we want to ignore the one in the original file
# and potentially empty lines and badly formatted lines, while keeping comments that
# come after the header, and then keep everything non-header unchanged.
# To do so, we skip empty lines that may be at the top in a first pass.
# In a second pass, we skip all consecutive comment lines starting with "/*",
# then we can append the rest (step 2).
# Handle replacing $filename with actual filename and keep alignment
fsingle = os.path.basename(fname.strip())
rep_fl = "$filename"
rep_fi = fsingle
len_fl = len(rep_fl)
len_fi = len(rep_fi)
# Pad with spaces to keep alignment
if len_fi < len_fl:
for x in range(len_fl - len_fi):
rep_fi += " "
elif len_fl < len_fi:
for x in range(len_fi - len_fl):
rep_fl += " "
if header.find(rep_fl) != -1:
text = header.replace(rep_fl, rep_fi)
else:
text = header.replace("$filename", fsingle)
text += "\n"
fileread = open(fname.strip(), "r")
line = fileread.readline()
header_done = False
# We now have the proper header, so we want to ignore the one in the original file
# and potentially empty lines and badly formatted lines, while keeping comments that
# come after the header, and then keep everything non-header unchanged.
# To do so, we skip empty lines that may be at the top in a first pass.
# In a second pass, we skip all consecutive comment lines starting with "/*",
# then we can append the rest (step 2).
while line.strip() == "": # Skip empty lines at the top
line = fileread.readline()
with open(fname.strip(), "r", encoding="utf-8") as fileread:
line = fileread.readline()
header_done = False
if line.find("/**********") == -1: # Godot header starts this way
# Maybe starting with a non-Godot comment, abort header magic
header_done = True
while line.strip() == "" and line != "": # Skip empty lines at the top
line = fileread.readline()
while not header_done: # Handle header now
if line.find("/*") != 0: # No more starting with a comment
header_done = True
if line.strip() != "":
if line.find("/**********") == -1: # Godot header starts this way
# Maybe starting with a non-Godot comment, abort header magic
header_done = True
while not header_done: # Handle header now
if line.find("/*") != 0: # No more starting with a comment
header_done = True
if line.strip() != "":
text += line
line = fileread.readline()
while line != "": # Dump everything until EOF
text += line
line = fileread.readline()
line = fileread.readline()
while line != "": # Dump everything until EOF
text += line
line = fileread.readline()
fileread.close()
# Write
filewrite = open(fname.strip(), "w")
filewrite.write(text)
filewrite.close()
# Write
with open(fname.strip(), "w", encoding="utf-8", newline="\n") as filewrite:
filewrite.write(text)

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
if len(sys.argv) < 2:
print("Invalid usage of file_format.py, it should be called with a path to one or multiple files.")
sys.exit(1)
BOM = b"\xef\xbb\xbf"
changed = []
invalid = []
for file in sys.argv[1:]:
try:
with open(file, "rt", encoding="utf-8") as f:
original = f.read()
except UnicodeDecodeError:
invalid.append(file)
continue
if original == "":
continue
revamp = "\n".join([line.rstrip("\n\r\t ") for line in original.splitlines(True)]).rstrip("\n") + "\n"
new_raw = revamp.encode(encoding="utf-8")
if new_raw.startswith(BOM):
new_raw = new_raw[len(BOM) :]
with open(file, "rb") as f:
old_raw = f.read()
if old_raw != new_raw:
changed.append(file)
with open(file, "wb") as f:
f.write(new_raw)
if changed:
for file in changed:
print(f"FIXED: {file}")
if invalid:
for file in invalid:
print(f"REQUIRES MANUAL CHANGES: {file}")
sys.exit(1)

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env bash
# This script ensures proper POSIX text file formatting and a few other things.
# This is supplementary to clang_format.sh and black_format.sh, but should be
# run before them.
# We need dos2unix and recode.
if [ ! -x "$(command -v dos2unix)" -o ! -x "$(command -v recode)" ]; then
printf "Install 'dos2unix' and 'recode' to use this script.\n"
fi
set -uo pipefail
IFS=$'\n\t'
# Loops through all text files tracked by Git.
git grep -zIl '' |
while IFS= read -rd '' f; do
# Ensure that files are UTF-8 formatted.
recode UTF-8 "$f" 2> /dev/null
# Ensure that files have LF line endings and do not contain a BOM.
dos2unix "$f" 2> /dev/null
# Remove trailing space characters and ensures that files end
# with newline characters. -l option handles newlines conveniently.
perl -i -ple 's/\s*$//g' "$f"
done
diff=$(git diff --color)
# If no patch has been generated all is OK, clean up, and exit.
if [ -z "$diff" ] ; then
printf "Files in this commit comply with the formatting rules.\n"
rm -f patch.patch
exit 0
fi
# A patch has been created, notify the user, clean up, and exit.
printf "\n*** The following differences were found between the code "
printf "and the formatting rules:\n\n"
echo "$diff"
printf "\n*** Aborting, please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\n"
exit 1

View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from pathlib import Path
if len(sys.argv) < 2:
print("Invalid usage of header_guards.py, it should be called with a path to one or multiple files.")
sys.exit(1)
HEADER_CHECK_OFFSET = 30
HEADER_BEGIN_OFFSET = 31
HEADER_END_OFFSET = -1
changed = []
invalid = []
for file in sys.argv[1:]:
with open(file, "rt", encoding="utf-8", newline="\n") as f:
lines = f.readlines()
if len(lines) <= HEADER_BEGIN_OFFSET:
continue # Most likely a dummy file.
if lines[HEADER_CHECK_OFFSET].startswith("#import"):
continue # Early catch obj-c file.
name = f"GODOT_{Path(file).name}".upper().replace(".", "_").replace("-", "_").replace(" ", "_")
HEADER_CHECK = f"#ifndef {name}\n"
HEADER_BEGIN = f"#define {name}\n"
HEADER_END = f"#endif // {name}\n"
if (
lines[HEADER_CHECK_OFFSET] == HEADER_CHECK
and lines[HEADER_BEGIN_OFFSET] == HEADER_BEGIN
and lines[HEADER_END_OFFSET] == HEADER_END
):
continue
# Guards might exist but with the wrong names.
if (
lines[HEADER_CHECK_OFFSET].startswith("#ifndef")
and lines[HEADER_BEGIN_OFFSET].startswith("#define")
and lines[HEADER_END_OFFSET].startswith("#endif")
):
lines[HEADER_CHECK_OFFSET] = HEADER_CHECK
lines[HEADER_BEGIN_OFFSET] = HEADER_BEGIN
lines[HEADER_END_OFFSET] = HEADER_END
with open(file, "wt", encoding="utf-8", newline="\n") as f:
f.writelines(lines)
changed.append(file)
continue
header_check = -1
header_begin = -1
header_end = -1
pragma_once = -1
objc = False
for idx, line in enumerate(lines):
if not line.startswith("#"):
continue
elif line.startswith("#ifndef") and header_check == -1:
header_check = idx
elif line.startswith("#define") and header_begin == -1:
header_begin = idx
elif line.startswith("#endif") and header_end == -1:
header_end = idx
elif line.startswith("#pragma once"):
pragma_once = idx
break
elif line.startswith("#import"):
objc = True
break
if objc:
continue
if pragma_once != -1:
lines.pop(pragma_once)
lines.insert(HEADER_CHECK_OFFSET, HEADER_CHECK)
lines.insert(HEADER_BEGIN_OFFSET, HEADER_BEGIN)
lines.append("\n")
lines.append(HEADER_END)
with open(file, "wt", encoding="utf-8", newline="\n") as f:
f.writelines(lines)
changed.append(file)
continue
if header_check == -1 and header_begin == -1 and header_end == -1:
# Guards simply didn't exist
lines.insert(HEADER_CHECK_OFFSET, HEADER_CHECK)
lines.insert(HEADER_BEGIN_OFFSET, HEADER_BEGIN)
lines.append("\n")
lines.append(HEADER_END)
with open(file, "wt", encoding="utf-8", newline="\n") as f:
f.writelines(lines)
changed.append(file)
continue
if header_check != -1 and header_begin != -1 and header_end != -1:
# All prepends "found", see if we can salvage this.
if header_check == header_begin - 1 and header_begin < header_end:
lines.pop(header_check)
lines.pop(header_begin - 1)
lines.pop(header_end - 2)
if lines[header_end - 3] == "\n":
lines.pop(header_end - 3)
lines.insert(HEADER_CHECK_OFFSET, HEADER_CHECK)
lines.insert(HEADER_BEGIN_OFFSET, HEADER_BEGIN)
lines.append("\n")
lines.append(HEADER_END)
with open(file, "wt", encoding="utf-8", newline="\n") as f:
f.writelines(lines)
changed.append(file)
continue
invalid.append(file)
if changed:
for file in changed:
print(f"FIXED: {file}")
if invalid:
for file in invalid:
print(f"REQUIRES MANUAL CHANGES: {file}")
sys.exit(1)

View File

@@ -1,60 +0,0 @@
#!/bin/bash
if [ ! -f "SConstruct" ]; then
echo "Warning: This script is intended to be run from the root of the Godot repository."
echo "Some of the paths checks may not work as intended from a different folder."
fi
files_invalid_guard=""
for file in $(find . -name "*.hpp" -print); do
# Skip generated files.
if [[ "$file" == "./gen/"* || "$file" == "./include/gen/"* ]]; then continue; fi
# Skip the test project.
if [[ "$file" == "./test/"* ]]; then continue; fi
bname=$(basename $file .hpp)
# NOTE: The "GODOT_CPP_" prefix is already used by the generated
# bindings, so we can't use that. We'll use "GODOT_" instead.
prefix="GODOT_"
# ^^ is bash builtin for UPPERCASE.
guard="${prefix}${bname^^}_HPP"
# Replaces guards to use computed name.
# We also add some \n to make sure there's a proper separation.
sed -i $file -e "0,/ifndef/s/#ifndef.*/\n#ifndef $guard/"
sed -i $file -e "0,/define/s/#define.*/#define $guard\n/"
sed -i $file -e "$ s/#endif.*/\n#endif \/\/ $guard/"
# Removes redundant \n added before, if they weren't needed.
sed -i $file -e "/^$/N;/^\n$/D"
# Check that first ifndef (should be header guard) is at the expected position.
# If not it can mean we have some code before the guard that should be after.
# "31" is the expected line with the copyright header.
first_ifndef=$(grep -n -m 1 "ifndef" $file | sed 's/\([0-9]*\).*/\1/')
if [[ "$first_ifndef" != "31" ]]; then
files_invalid_guard+="$file\n"
fi
done
if [[ ! -z "$files_invalid_guard" ]]; then
echo -e "The following files were found to have potentially invalid header guard:\n"
echo -e "$files_invalid_guard"
fi
diff=$(git diff --color)
# If no diff has been generated all is OK, clean up, and exit.
if [ -z "$diff" ] ; then
printf "Files in this commit comply with the header guards formatting rules.\n"
exit 0
fi
# A diff has been created, notify the user, clean up, and exit.
printf "\n*** The following differences were found between the code "
printf "and the header guards formatting rules:\n\n"
echo "$diff"
printf "\n*** Aborting, please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\n"
exit 1

View File

@@ -1,11 +0,0 @@
[mypy]
ignore_missing_imports = true
disallow_any_generics = True
pretty = True
show_column_numbers = True
warn_redundant_casts = True
warn_return_any = True
warn_unreachable = True
namespace_packages = True
explicit_package_bases = True

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -uo pipefail
echo -e "Python: mypy static analysis..."
mypy --config-file=./misc/scripts/mypy.ini .

58
pyproject.toml Normal file
View File

@@ -0,0 +1,58 @@
[tool.mypy]
disallow_any_generics = true
explicit_package_bases = true
ignore_missing_imports = true
namespace_packages = true
no_implicit_optional = true
pretty = true
scripts_are_modules = true
show_column_numbers = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
[tool.ruff]
extend-include = ["SConstruct"]
line-length = 120
target-version = "py37"
[tool.ruff.lint]
extend-select = [
"I", # isort
]
[tool.ruff.lint.per-file-ignores]
"SConstruct" = [
"F821", # Undefined name
]
[tool.codespell]
enable-colors = ""
write-changes = ""
check-hidden = ""
quiet-level = 3
builtin = "clear,rare,en-GB_to_en-US"
ignore-words-list = """\
breaked,
cancelled,
checkin,
curvelinear,
doubleclick,
expct,
findn,
gird,
hel,
inout,
labelin,
lod,
mis,
nd,
numer,
ot,
outin,
requestor,
te,
textin,
thirdparty,
vai
"""

View File

@@ -51,10 +51,7 @@ void Wrapped::_postinitialize() {
}
godot::internal::gdextension_interface_object_set_instance_binding(_owner, godot::internal::token, this, _get_bindings_callbacks());
if (extension_class) {
Object *obj = dynamic_cast<Object *>(this);
if (obj) {
obj->notification(Object::NOTIFICATION_POSTINITIALIZE);
}
_notificationv(Object::NOTIFICATION_POSTINITIALIZE);
}
}

View File

@@ -360,6 +360,7 @@ void ClassDB::initialize(GDExtensionInitializationLevel p_level) {
}
void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) {
std::set<StringName> to_erase;
for (std::vector<StringName>::reverse_iterator i = class_register_order.rbegin(); i != class_register_order.rend(); ++i) {
const StringName &name = *i;
const ClassInfo &cl = classes[name];
@@ -370,9 +371,20 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) {
internal::gdextension_interface_classdb_unregister_extension_class(internal::library, name._native_ptr());
for (auto method : cl.method_map) {
for (const std::pair<const StringName, MethodBind *> &method : cl.method_map) {
memdelete(method.second);
}
classes.erase(name);
to_erase.insert(name);
}
{
// The following is equivalent to c++20 `std::erase_if(class_register_order, [&](const StringName& name){ return to_erase.contains(name); });`
std::vector<StringName>::iterator it = std::remove_if(class_register_order.begin(), class_register_order.end(), [&](const StringName &p_name) {
return to_erase.count(p_name) > 0;
});
class_register_order.erase(it, class_register_order.end());
}
}

View File

@@ -36,7 +36,7 @@ namespace godot {
void *Memory::alloc_static(size_t p_bytes, bool p_pad_align) {
#ifdef DEBUG_ENABLED
bool prepad = false; // Alredy pre paded in the engine.
bool prepad = false; // Already pre paded in the engine.
#else
bool prepad = p_pad_align;
#endif
@@ -63,7 +63,7 @@ void *Memory::realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align) {
uint8_t *mem = (uint8_t *)p_memory;
#ifdef DEBUG_ENABLED
bool prepad = false; // Alredy pre paded in the engine.
bool prepad = false; // Already pre paded in the engine.
#else
bool prepad = p_pad_align;
#endif
@@ -82,7 +82,7 @@ void Memory::free_static(void *p_ptr, bool p_pad_align) {
uint8_t *mem = (uint8_t *)p_ptr;
#ifdef DEBUG_ENABLED
bool prepad = false; // Alredy pre paded in the engine.
bool prepad = false; // Already pre paded in the engine.
#else
bool prepad = p_pad_align;
#endif

View File

@@ -60,8 +60,66 @@ Object *get_object_instance_binding(GodotObject *p_engine_object) {
return reinterpret_cast<Object *>(gdextension_interface_object_get_instance_binding(p_engine_object, token, binding_callbacks));
}
TypedArray<Dictionary> convert_property_list(const std::vector<PropertyInfo> &p_list) {
TypedArray<Dictionary> va;
for (const PropertyInfo &pi : p_list) {
va.push_back(Dictionary(pi));
}
return va;
}
} // namespace internal
MethodInfo::operator Dictionary() const {
Dictionary dict;
dict["name"] = name;
dict["args"] = internal::convert_property_list(arguments);
Array da;
for (size_t i = 0; i < default_arguments.size(); i++) {
da.push_back(default_arguments[i]);
}
dict["default_args"] = da;
dict["flags"] = flags;
dict["id"] = id;
Dictionary r = return_val;
dict["return"] = r;
return dict;
}
MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) {
MethodInfo mi;
if (p_dict.has("name")) {
mi.name = p_dict["name"];
}
Array args;
if (p_dict.has("args")) {
args = p_dict["args"];
}
for (int i = 0; i < args.size(); i++) {
Dictionary d = args[i];
mi.arguments.push_back(PropertyInfo::from_dict(d));
}
Array defargs;
if (p_dict.has("default_args")) {
defargs = p_dict["default_args"];
}
for (int i = 0; i < defargs.size(); i++) {
mi.default_arguments.push_back(defargs[i]);
}
if (p_dict.has("return")) {
mi.return_val = PropertyInfo::from_dict(p_dict["return"]);
}
if (p_dict.has("flags")) {
mi.flags = p_dict["flags"];
}
return mi;
}
MethodInfo::MethodInfo() :
flags(GDEXTENSION_METHOD_FLAG_NORMAL) {}

View File

@@ -78,6 +78,10 @@ bool AABB::is_equal_approx(const AABB &p_aabb) const {
return position.is_equal_approx(p_aabb.position) && size.is_equal_approx(p_aabb.size);
}
bool AABB::is_finite() const {
return position.is_finite() && size.is_finite();
}
AABB AABB::intersection(const AABB &p_aabb) const {
#ifdef MATH_CHECKS
if (unlikely(size.x < 0 || size.y < 0 || size.z < 0 || p_aabb.size.x < 0 || p_aabb.size.y < 0 || p_aabb.size.z < 0)) {

View File

@@ -692,6 +692,10 @@ bool Basis::is_equal_approx(const Basis &p_basis) const {
return rows[0].is_equal_approx(p_basis.rows[0]) && rows[1].is_equal_approx(p_basis.rows[1]) && rows[2].is_equal_approx(p_basis.rows[2]);
}
bool Basis::is_finite() const {
return rows[0].is_finite() && rows[1].is_finite() && rows[2].is_finite();
}
bool Basis::operator==(const Basis &p_matrix) const {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
@@ -812,7 +816,7 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
return;
}
// As we have reached here there are no singularities so we can handle normally.
double s = Math::sqrt((rows[2][1] - rows[1][2]) * (rows[2][1] - rows[1][2]) + (rows[0][2] - rows[2][0]) * (rows[0][2] - rows[2][0]) + (rows[1][0] - rows[0][1]) * (rows[1][0] - rows[0][1])); // Used to normalise.
double s = Math::sqrt((rows[2][1] - rows[1][2]) * (rows[2][1] - rows[1][2]) + (rows[0][2] - rows[2][0]) * (rows[0][2] - rows[2][0]) + (rows[1][0] - rows[0][1]) * (rows[1][0] - rows[0][1])); // Used to normalize.
if (Math::abs(s) < CMP_EPSILON) {
// Prevent divide by zero, should not happen if matrix is orthogonal and should be caught by singularity test above.

View File

@@ -192,7 +192,7 @@ void Color::set_hsv(float p_h, float p_s, float p_v, float p_alpha) {
a = p_alpha;
if (p_s == 0.0f) {
// Achromatic (grey)
// Achromatic (gray)
r = g = b = p_v;
return;
}

View File

@@ -178,6 +178,10 @@ bool Plane::is_equal_approx(const Plane &p_plane) const {
return normal.is_equal_approx(p_plane.normal) && Math::is_equal_approx(d, p_plane.d);
}
bool Plane::is_finite() const {
return normal.is_finite() && Math::is_finite(d);
}
Plane::operator String() const {
return "[N: " + normal.operator String() + ", D: " + String::num_real(d, false) + "]";
}

View File

@@ -81,6 +81,10 @@ bool Quaternion::is_equal_approx(const Quaternion &p_quaternion) const {
return Math::is_equal_approx(x, p_quaternion.x) && Math::is_equal_approx(y, p_quaternion.y) && Math::is_equal_approx(z, p_quaternion.z) && Math::is_equal_approx(w, p_quaternion.w);
}
bool Quaternion::is_finite() const {
return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z) && Math::is_finite(w);
}
real_t Quaternion::length() const {
return Math::sqrt(length_squared());
}

View File

@@ -40,6 +40,10 @@ bool Rect2::is_equal_approx(const Rect2 &p_rect) const {
return position.is_equal_approx(p_rect.position) && size.is_equal_approx(p_rect.size);
}
bool Rect2::is_finite() const {
return position.is_finite() && size.is_finite();
}
bool Rect2::intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos, Point2 *r_normal) const {
#ifdef MATH_CHECKS
if (unlikely(size.x < 0 || size.y < 0)) {

View File

@@ -170,6 +170,10 @@ bool Transform2D::is_equal_approx(const Transform2D &p_transform) const {
return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]);
}
bool Transform2D::is_finite() const {
return columns[0].is_finite() && columns[1].is_finite() && columns[2].is_finite();
}
Transform2D Transform2D::looking_at(const Vector2 &p_target) const {
Transform2D return_trans = Transform2D(get_rotation(), get_origin());
Vector2 target_position = affine_inverse().xform(p_target);

View File

@@ -175,6 +175,10 @@ bool Transform3D::is_equal_approx(const Transform3D &p_transform) const {
return basis.is_equal_approx(p_transform.basis) && origin.is_equal_approx(p_transform.origin);
}
bool Transform3D::is_finite() const {
return basis.is_finite() && origin.is_finite();
}
bool Transform3D::operator==(const Transform3D &p_transform) const {
return (basis == p_transform.basis && origin == p_transform.origin);
}

View File

@@ -188,6 +188,10 @@ bool Vector2::is_zero_approx() const {
return Math::is_zero_approx(x) && Math::is_zero_approx(y);
}
bool Vector2::is_finite() const {
return Math::is_finite(x) && Math::is_finite(y);
}
Vector2::operator String() const {
return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")";
}

View File

@@ -141,6 +141,10 @@ bool Vector3::is_zero_approx() const {
return Math::is_zero_approx(x) && Math::is_zero_approx(y) && Math::is_zero_approx(z);
}
bool Vector3::is_finite() const {
return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z);
}
Vector3::operator String() const {
return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ")";
}

View File

@@ -67,6 +67,10 @@ bool Vector4::is_zero_approx() const {
return Math::is_zero_approx(x) && Math::is_zero_approx(y) && Math::is_zero_approx(z) && Math::is_zero_approx(w);
}
bool Vector4::is_finite() const {
return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z) && Math::is_finite(w);
}
real_t Vector4::length() const {
return Math::sqrt(length_squared());
}

View File

@@ -1,6 +1,4 @@
#!/usr/bin/env python
import os
import sys
env = SConscript("../SConstruct")

9
test/build_profile.json Normal file
View File

@@ -0,0 +1,9 @@
{
"enabled_classes": [
"Control",
"Label",
"OS",
"TileMap",
"InputEventKey"
]
}

View File

@@ -21,5 +21,17 @@ 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"
ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework"
ios.release = "res://bin/libgdexample.ios.template_release.xcframework"
web.debug.threads.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm"
web.release.threads.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm"
web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.nothreads.wasm"
web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.nothreads.wasm"
[dependencies]
ios.debug = {
"res://bin/libgodot-cpp.ios.template_debug.xcframework": ""
}
ios.release = {
"res://bin/libgodot-cpp.ios.template_release.xcframework": ""
}

View File

@@ -86,8 +86,8 @@ func _ready():
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)
assert_equal(example.test_string_is_forty_two("blah"), false)
assert_equal(example.test_string_is_forty_two("forty two"), true)
# PackedArray iterators
assert_equal(example.test_vector_ops(), 105)
@@ -122,6 +122,10 @@ func _ready():
control.queue_free()
sprite.queue_free()
# Test that passing null for objects works as expected too.
var example_null : Example = null
assert_equal(example.test_object_cast_to_node(example_null), false)
# 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))

View File

@@ -146,7 +146,8 @@ 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_string_is_forty_two"), &Example::test_string_is_forty_two);
ClassDB::bind_method(D_METHOD("test_typed_array_of_packed"), &Example::test_typed_array_of_packed);
ClassDB::bind_method(D_METHOD("test_vector_ops"), &Example::test_vector_ops);
ClassDB::bind_method(D_METHOD("test_vector_init_list"), &Example::test_vector_init_list);
@@ -278,7 +279,7 @@ ExampleRef *Example::return_extended_ref() const {
}
Ref<ExampleRef> Example::extended_ref_checks(Ref<ExampleRef> p_ref) const {
// This is therefor the prefered way of instancing and returning a refcounted object:
// This is therefore the preferred way of instancing and returning a refcounted object:
Ref<ExampleRef> ref;
ref.instantiate();
return ref;
@@ -323,8 +324,21 @@ 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;
bool Example::test_string_is_forty_two(const String &p_string) const {
return strcmp(p_string.utf8().ptr(), "forty two") == 0;
}
TypedArray<PackedInt32Array> Example::test_typed_array_of_packed() const {
TypedArray<PackedInt32Array> arr;
PackedInt32Array packed_arr1;
packed_arr1.push_back(1);
packed_arr1.push_back(2);
arr.push_back(packed_arr1);
PackedInt32Array packed_arr2;
packed_arr2.push_back(3);
packed_arr2.push_back(4);
arr.push_back(packed_arr2);
return arr;
}
int Example::test_vector_ops() const {

View File

@@ -125,7 +125,8 @@ 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;
bool test_string_is_forty_two(const String &p_str) const;
TypedArray<PackedInt32Array> test_typed_array_of_packed() const;
int test_vector_ops() const;
int test_vector_init_list() const;

View File

@@ -6,23 +6,24 @@
#ifndef TESTS_H
#define TESTS_H
#include "godot_cpp/templates/cowdata.hpp"
#include "godot_cpp/templates/hash_map.hpp"
#include "godot_cpp/templates/hash_set.hpp"
#include "godot_cpp/templates/hashfuncs.hpp"
#include "godot_cpp/templates/list.hpp"
#include "godot_cpp/templates/pair.hpp"
#include "godot_cpp/templates/rb_map.hpp"
#include "godot_cpp/templates/rb_set.hpp"
#include "godot_cpp/templates/rid_owner.hpp"
#include "godot_cpp/templates/safe_refcount.hpp"
#include "godot_cpp/templates/search_array.hpp"
#include "godot_cpp/templates/self_list.hpp"
#include "godot_cpp/templates/sort_array.hpp"
#include "godot_cpp/templates/spin_lock.hpp"
#include "godot_cpp/templates/thread_work_pool.hpp"
#include "godot_cpp/templates/vector.hpp"
#include "godot_cpp/templates/vmap.hpp"
#include "godot_cpp/templates/vset.hpp"
#include <godot_cpp/templates/cowdata.hpp>
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/hashfuncs.hpp>
#include <godot_cpp/templates/list.hpp>
#include <godot_cpp/templates/local_vector.hpp>
#include <godot_cpp/templates/pair.hpp>
#include <godot_cpp/templates/rb_map.hpp>
#include <godot_cpp/templates/rb_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/search_array.hpp>
#include <godot_cpp/templates/self_list.hpp>
#include <godot_cpp/templates/sort_array.hpp>
#include <godot_cpp/templates/spin_lock.hpp>
#include <godot_cpp/templates/thread_work_pool.hpp>
#include <godot_cpp/templates/vector.hpp>
#include <godot_cpp/templates/vmap.hpp>
#include <godot_cpp/templates/vset.hpp>
#endif // TESTS_H

View File

@@ -1,8 +1,8 @@
import os
import sys
import my_spawn
import common_compiler_flags
from SCons.Script import ARGUMENTS
import my_spawn
def options(opts):

View File

@@ -1,6 +1,5 @@
import os
import subprocess
import sys
def using_clang(env):
@@ -39,6 +38,14 @@ def generate(env):
elif env.get("is_msvc", False):
env.Append(CXXFLAGS=["/EHsc"])
if not env.get("is_msvc", False):
if env["symbols_visibility"] == "visible":
env.Append(CCFLAGS=["-fvisibility=default"])
env.Append(LINKFLAGS=["-fvisibility=default"])
elif env["symbols_visibility"] == "hidden":
env.Append(CCFLAGS=["-fvisibility=hidden"])
env.Append(LINKFLAGS=["-fvisibility=hidden"])
# Set optimize and debug_symbols flags.
# "custom" means do nothing and let users set their own optimization flags.
if env.get("is_msvc", False):

View File

@@ -1,15 +1,16 @@
import os, sys, platform
import os
import platform
import sys
from SCons.Variables import EnumVariable, PathVariable, BoolVariable
from SCons.Variables.BoolVariable import _text2bool
from SCons.Tool import Tool
from SCons.Action import Action
from SCons.Builder import Builder
from SCons.Errors import UserError
from SCons.Script import ARGUMENTS
from SCons.Tool import Tool
from SCons.Variables import BoolVariable, EnumVariable, PathVariable
from SCons.Variables.BoolVariable import _text2bool
from binding_generator import scons_generate_bindings, scons_emit_files
from binding_generator import scons_emit_files, scons_generate_bindings
def add_sources(sources, dir, extension):
@@ -266,6 +267,8 @@ def options(opts, env):
)
)
opts.Add(BoolVariable(key="threads", help="Enable threading support", default=env.get("threads", True)))
# compiledb
opts.Add(
BoolVariable(
@@ -283,6 +286,15 @@ def options(opts, env):
)
)
opts.Add(
PathVariable(
"build_profile",
"Path to a file containing a feature build profile",
default=env.get("build_profile", None),
validator=validate_file,
)
)
opts.Add(
BoolVariable(
"disable_exceptions",
@@ -291,6 +303,15 @@ def options(opts, env):
)
)
opts.Add(
EnumVariable(
key="symbols_visibility",
help="Symbols visibility on GNU platforms. Use 'auto' to apply the default value.",
default=env.get("symbols_visibility", "hidden"),
allowed_values=["auto", "visible", "hidden"],
)
)
opts.Add(
EnumVariable(
"optimize",
@@ -377,6 +398,9 @@ def generate(env):
tool.generate(env)
if env["threads"]:
env.Append(CPPDEFINES=["THREADS_ENABLED"])
if env.editor_build:
env.Append(CPPDEFINES=["TOOLS_ENABLED"])
@@ -417,6 +441,8 @@ def generate(env):
suffix += "." + env["arch"]
if env["ios_simulator"]:
suffix += ".simulator"
if not env["threads"]:
suffix += ".nothreads"
env["suffix"] = suffix # Exposed when included from another project
env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]

View File

@@ -1,19 +1,10 @@
import codecs
import os
import sys
import subprocess
import sys
import common_compiler_flags
from SCons.Variables import *
if sys.version_info < (3,):
def decode_utf8(x):
return x
else:
import codecs
def decode_utf8(x):
return codecs.utf_8_decode(x)[0]
from SCons.Variables import BoolVariable
def has_ios_osxcross():
@@ -52,9 +43,9 @@ def generate(env):
if sys.platform == "darwin":
if env["IOS_SDK_PATH"] == "":
try:
env["IOS_SDK_PATH"] = decode_utf8(
env["IOS_SDK_PATH"] = codecs.utf_8_decode(
subprocess.check_output(["xcrun", "--sdk", sdk_name, "--show-sdk-path"]).strip()
)
)[0]
except (subprocess.CalledProcessError, OSError):
raise ValueError(
"Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name)

View File

@@ -1,6 +1,6 @@
import common_compiler_flags
from SCons.Variables import *
from SCons.Tool import clang, clangxx
from SCons.Variables import BoolVariable
def options(opts):

View File

@@ -1,5 +1,6 @@
import os
import sys
import common_compiler_flags

View File

@@ -32,7 +32,6 @@ def configure(env):
return rv
def mySpawn(sh, escape, cmd, args, env):
newargs = " ".join(args[1:])
cmdline = cmd + " " + newargs

View File

@@ -1,4 +1,3 @@
import os
import common_compiler_flags
from SCons.Util import WhereIs
@@ -35,12 +34,17 @@ def generate(env):
env["SHLIBSUFFIX"] = ".wasm"
# Thread support (via SharedArrayBuffer).
env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
if env["threads"]:
env.Append(CCFLAGS=["-sUSE_PTHREADS=1"])
env.Append(LINKFLAGS=["-sUSE_PTHREADS=1"])
# Build as side module (shared library).
env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
env.Append(CCFLAGS=["-sSIDE_MODULE=1"])
env.Append(LINKFLAGS=["-sSIDE_MODULE=1"])
# Force wasm longjmp mode.
env.Append(CCFLAGS=["-sSUPPORT_LONGJMP='wasm'"])
env.Append(LINKFLAGS=["-sSUPPORT_LONGJMP='wasm'"])
env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])

View File

@@ -1,14 +1,81 @@
import sys
import my_spawn
import common_compiler_flags
from SCons.Tool import msvc, mingw
from SCons.Variables import *
import my_spawn
from SCons.Tool import mingw, msvc
from SCons.Variables import BoolVariable
def silence_msvc(env):
import os
import re
import tempfile
# Ensure we have a location to write captured output to, in case of false positives.
capture_path = os.path.join(os.path.dirname(__file__), "..", "msvc_capture.log")
with open(capture_path, "wt", encoding="utf-8"):
pass
old_spawn = env["SPAWN"]
re_redirect_stream = re.compile(r"^[12]?>")
re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE)
re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)')
def spawn_capture(sh, escape, cmd, args, env):
# We only care about cl/link, process everything else as normal.
if args[0] not in ["cl", "link"]:
return old_spawn(sh, escape, cmd, args, env)
# Process as normal if the user is manually rerouting output.
for arg in args:
if re_redirect_stream.match(arg):
return old_spawn(sh, escape, cmd, args, env)
tmp_stdout, tmp_stdout_name = tempfile.mkstemp()
os.close(tmp_stdout)
args.append(f">{tmp_stdout_name}")
ret = old_spawn(sh, escape, cmd, args, env)
try:
with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout:
lines = tmp_stdout.read().splitlines()
os.remove(tmp_stdout_name)
except OSError:
pass
# Early process no lines (OSError)
if not lines:
return ret
is_cl = args[0] == "cl"
content = ""
caught = False
for line in lines:
# These conditions are far from all-encompassing, but are specialized
# for what can be reasonably expected to show up in the repository.
if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)):
caught = True
try:
with open(capture_path, "a", encoding=sys.stdout.encoding) as log:
log.write(line + "\n")
except OSError:
print(f'WARNING: Failed to log captured line: "{line}".')
continue
content += line + "\n"
# Content remaining assumed to be an error/warning.
if content:
sys.stderr.write(content)
return ret
env["SPAWN"] = spawn_capture
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))
opts.Add(BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting errors to stderr.", True))
def exists(env):
@@ -16,7 +83,6 @@ def exists(env):
def generate(env):
base = None
if not env["use_mingw"] and msvc.exists(env):
if env["arch"] == "x86_64":
env["TARGET_ARCH"] = "amd64"
@@ -42,6 +108,9 @@ def generate(env):
else:
env.Append(CCFLAGS=["/MD"])
if env["silence_msvc"] and not env.GetOption("clean"):
silence_msvc(env)
elif sys.platform == "win32" or sys.platform == "msys":
env["use_mingw"] = True
mingw.generate(env)