feat: updated engine

This commit is contained in:
Sara Gerretsen 2026-07-10 17:04:34 +02:00
parent cbe99774ff
commit f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# Script used to dump char ranges for specific properties from
# the Unicode Character Database to the `char_range.inc` file.
# the Unicode Character Database to the `char_range.cpp` file.
# NOTE: This script is deliberately not integrated into the build system;
# you should run it manually whenever you want to update the data.
from __future__ import annotations
@ -16,7 +16,7 @@ if __name__ == "__main__":
from methods import generate_copyright_header
URL: Final[str] = "https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt"
URL: Final[str] = "https://www.unicode.org/Public/17.0.0/ucd/DerivedCoreProperties.txt"
xid_start: list[tuple[int, int]] = []
@ -89,7 +89,8 @@ def parse_unicode_data() -> None:
def make_array(array_name: str, range_list: list[tuple[int, int]]) -> str:
result: str = f"\n\nconstexpr inline CharRange {array_name}[] = {{\n"
result: str = f"\n\nconst int {array_name}_size = {len(range_list)};\n"
result += f"const CharRange {array_name}[{array_name}_size] = {{\n"
for start, end in range_list:
result += f"\t{{ 0x{start:x}, 0x{end:x} }},\n"
@ -102,22 +103,16 @@ def make_array(array_name: str, range_list: list[tuple[int, int]]) -> str:
def generate_char_range_inc() -> None:
parse_unicode_data()
source: str = generate_copyright_header("char_range.inc")
source: str = generate_copyright_header("char_range.cpp")
source += f"""
// This file was generated using the `misc/scripts/char_range_fetch.py` script.
#pragma once
#include "core/typedefs.h"
#include "core/string/char_utils.h"
// Unicode Derived Core Properties
// Source: {URL}
struct CharRange {{
\tchar32_t start;
\tchar32_t end;
}};"""
// Source: {URL}\
"""
source += make_array("xid_start", xid_start)
source += make_array("xid_continue", xid_continue)
@ -127,11 +122,11 @@ struct CharRange {{
source += "\n"
char_range_path: str = os.path.join(os.path.dirname(__file__), "../../core/string/char_range.inc")
char_range_path: str = os.path.join(os.path.dirname(__file__), "../../core/string/char_range.cpp")
with open(char_range_path, "w", newline="\n") as f:
f.write(source)
print("`char_range.inc` generated successfully.")
print("`char_range.cpp` generated successfully.")
if __name__ == "__main__":

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3
import os
import sys
if len(sys.argv) < 2:
@ -45,7 +46,10 @@ if file_contents.find("ERROR: LeakSanitizer:") != -1:
# this possibility should also be handled as a potential error, even if
# LeakSanitizer doesn't report anything
if file_contents.find("ObjectDB instances leaked at exit") != -1:
if (
file_contents.find("ObjectDB instance was leaked at exit") != -1
or file_contents.find("ObjectDB instances were leaked at exit") != -1
):
print("ERROR: Memory leak was found")
sys.exit(54)
@ -57,6 +61,12 @@ if file_contents.find("Assertion failed") != -1:
print("ERROR: Assertion failed in project, check execution log for more info")
sys.exit(55)
if os.environ.get("GODOT_CHECK_CI_LOG_ALL_ERRORS"):
# If any occurrence of "ERROR:" is found in the log, we consider it a failure.
if file_contents.find("ERROR:") != -1:
print("ERROR: 'ERROR:' found in log and GODOT_CHECK_CI_LOG_ALL_ERRORS is set.")
sys.exit(56)
# For now Godot leaks a lot of rendering stuff so for now we just show info
# about it and this needs to be re-enabled after fixing this memory leaks.

View file

@ -2,6 +2,7 @@
import glob
import os
import subprocess
import sys
if len(sys.argv) < 2:
@ -30,4 +31,4 @@ projects = {
# Run dotnet format on all projects with more than 0 modified files.
for path, files in projects.items():
if files:
os.system(f"dotnet format {path} --include {files}")
subprocess.run(["dotnet", "format", path, "--include", files])

View file

@ -0,0 +1,49 @@
#!/usr/bin/env python3
if __name__ != "__main__":
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')
import os
import shutil
import sys
import urllib.request
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
# Base Godot dependencies path
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
deps_folder = os.getenv("LOCALAPPDATA")
if deps_folder and not os.getenv("MSYSTEM"):
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
else:
deps_folder = os.path.join("bin", "build_deps")
# AccessKit
ac_version = "0.21.2"
# Create dependencies folder
if not os.path.exists(deps_folder):
os.makedirs(deps_folder)
ac_filename = "accesskit-c-" + ac_version + ".zip"
ac_archive = os.path.join(deps_folder, "accesskit.zip")
ac_folder = os.path.join(deps_folder, "accesskit")
if os.path.isfile(ac_archive):
os.remove(ac_archive)
print(f"Downloading AccessKit {ac_filename} ...")
urllib.request.urlretrieve(
f"https://github.com/godotengine/godot-accesskit-c-static/releases/download/{ac_version}/{ac_filename}",
ac_archive,
)
if os.path.exists(ac_folder):
print(f"Removing existing local AccessKit installation in {ac_folder} ...")
shutil.rmtree(ac_folder)
print(f"Extracting AccessKit {ac_filename} to {ac_folder} ...")
shutil.unpack_archive(ac_archive, deps_folder)
os.remove(ac_archive)
os.rename(os.path.join(deps_folder, "accesskit-c-" + ac_version), ac_folder)
print("AccessKit installed successfully.\n")

View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import os
import platform
import shutil
import sys
import urllib.request
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
from misc.utility.color import Ansi, color_print
# Base Godot dependencies path
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
deps_folder = os.getenv("LOCALAPPDATA")
if deps_folder and not os.getenv("MSYSTEM"):
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
else:
deps_folder = os.path.join("bin", "build_deps")
# ANGLE
# Check for latest version: https://github.com/godotengine/godot-angle-static/releases/latest
angle_version = "chromium/7219"
angle_folder = os.path.join(deps_folder, "angle")
# Create dependencies folder
if not os.path.exists(deps_folder):
os.makedirs(deps_folder)
# Mesa NIR
print(f"Downloading ANGLE {angle_version} ...")
archs = [
"arm64-llvm",
"x86_32-gcc",
"x86_32-llvm",
"x86_64-gcc",
"x86_64-llvm",
]
if platform.system() == "Windows":
# Only download MSVC libraries if we can build using it.
archs.append("arm64-msvc")
archs.append("x86_32-msvc")
archs.append("x86_64-msvc")
elif platform.system() == "Darwin":
# Only download macOS/iOS libraries if we can build for these platforms.
archs.append("arm64-ios")
archs.append("arm64-ios-sim")
archs.append("arm64-macos")
archs.append("x86_64-macos")
for arch in archs:
angle_filename = f"godot-angle-static-{arch}-release.zip"
angle_archive = os.path.join(deps_folder, angle_filename)
angle_folder = os.path.join(deps_folder, f"angle-{arch}")
if os.path.isfile(angle_archive):
os.remove(angle_archive)
print(f"Downloading ANGLE {angle_filename} ...")
urllib.request.urlretrieve(
f"https://github.com/godotengine/godot-angle-static/releases/download/{angle_version}/{angle_filename}",
angle_archive,
)
if os.path.exists(angle_folder):
print(f"Removing existing local ANGLE installation in {angle_folder} ...")
shutil.rmtree(angle_folder)
print(f"Extracting ANGLE {angle_filename} to {angle_folder} ...")
shutil.unpack_archive(angle_archive, angle_folder)
os.remove(angle_archive)
print("ANGLE installed successfully.\n")
# Complete message
color_print(f'{Ansi.GREEN}All ANGLE components were installed to "{deps_folder}" successfully!')
color_print(f'{Ansi.GREEN}You can now build Godot with statically linked ANGLE by running "scons angle=yes".')

View file

@ -25,7 +25,7 @@ args = parser.parse_args()
# Base Godot dependencies path
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
deps_folder = os.getenv("LOCALAPPDATA")
if deps_folder:
if deps_folder and not os.getenv("MSYSTEM"):
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
else:
deps_folder = os.path.join("bin", "build_deps")
@ -33,7 +33,7 @@ else:
# Mesa NIR
# Sync with `drivers/d3d12/SCsub` when updating Mesa.
# Check for latest version: https://github.com/godotengine/godot-nir-static/releases/latest
mesa_version = "25.3.1-1"
mesa_version = "25.3.1-3"
# WinPixEventRuntime
# Check for latest version: https://www.nuget.org/api/v2/package/WinPixEventRuntime (check downloaded filename)
pix_version = "1.0.240308001"

View file

@ -0,0 +1,47 @@
#!/usr/bin/env python
import os
import sys
import tempfile
import urllib.request
from zipfile import ZipFile
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
from misc.utility.color import Ansi, color_print
def get_latest_tag():
import json
url = "https://api.github.com/repos/google/perfetto/releases/latest"
with urllib.request.urlopen(url) as response:
data = json.load(response)
return data["tag_name"]
# Perfetto
# Check for latest version: https://github.com/google/perfetto/releases/latest
perfetto_tag = get_latest_tag()
perfetto_filename = "perfetto-cpp-sdk-src.zip"
perfetto_folder = "thirdparty/perfetto"
perfetto_archive_destination = os.path.join(tempfile.gettempdir(), perfetto_filename)
if os.path.isfile(perfetto_archive_destination):
os.remove(perfetto_archive_destination)
print(f"Downloading Perfetto {perfetto_tag} ...")
urllib.request.urlretrieve(
f"https://github.com/google/perfetto/releases/download/{perfetto_tag}/{perfetto_filename}",
perfetto_archive_destination,
)
print(f"Extracting Perfetto {perfetto_tag} to {perfetto_folder} ...")
with ZipFile(perfetto_archive_destination, "r") as zip_file:
zip_file.extractall(perfetto_folder)
os.remove(perfetto_archive_destination)
print("Perfetto installed successfully.\n")
# Complete message
color_print(f'{Ansi.GREEN}Perfetto was installed to "{perfetto_folder}" successfully!')

View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3
if __name__ != "__main__":
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')
import os
import shutil
import sys
import urllib.request
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
# Base Godot dependencies path
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
deps_folder = os.getenv("LOCALAPPDATA")
if deps_folder and not os.getenv("MSYSTEM"):
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
else:
deps_folder = os.path.join("bin", "build_deps")
# WinRT
winrt_version = "72"
# Create dependencies folder
if not os.path.exists(deps_folder):
os.makedirs(deps_folder)
winrt_filename = "winrt-headers.zip"
winrt_archive = os.path.join(deps_folder, winrt_filename)
winrt_folder = os.path.join(deps_folder, "winrt_mingw")
if os.path.isfile(winrt_archive):
os.remove(winrt_archive)
print(f"Downloading WinRT {winrt_filename} ...")
urllib.request.urlretrieve(
f"https://github.com/godotengine/winrt-mingw/releases/download/{winrt_version}/{winrt_filename}",
winrt_archive,
)
if os.path.exists(winrt_folder):
print(f"Removing existing local WinRT installation in {winrt_folder} ...")
shutil.rmtree(winrt_folder)
print(f"Extracting WinRT {winrt_filename} to {winrt_folder} ...")
shutil.unpack_archive(winrt_archive, winrt_folder)
os.remove(winrt_archive)
print("WinRT installed successfully.\n")

View file

@ -6,7 +6,7 @@
for s in 16 24 32 48 64 128 256 512 1024; do
convert -resize ${s}x$s -antialias \
-background transparent \
../../icon.svg icon$s.png
../../misc/logo/icon.svg icon$s.png
done
# 16px tga file for library

View file

@ -16,7 +16,7 @@ if __name__ == "__main__":
from methods import generate_copyright_header
URL: Final[str] = "https://www.unicode.org/Public/16.0.0/ucd/UnicodeData.txt"
URL: Final[str] = "https://www.unicode.org/Public/17.0.0/ucd/UnicodeData.txt"
lower_to_upper: list[tuple[str, str]] = []

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# Script used to dump char ranges from
# the Unicode Character Database to the `char_range.inc` file.
# the Unicode Character Database to the `unicode_ranges.inc` file.
# NOTE: This script is deliberately not integrated into the build system;
# you should run it manually whenever you want to update the data.
from __future__ import annotations
@ -16,7 +16,7 @@ if __name__ == "__main__":
from methods import generate_copyright_header
URL: Final[str] = "https://www.unicode.org/Public/16.0.0/ucd/Blocks.txt"
URL: Final[str] = "https://www.unicode.org/Public/17.0.0/ucd/Blocks.txt"
ranges: list[tuple[str, str, str]] = []

View file

@ -0,0 +1,148 @@
#!/usr/bin/env python3
if __name__ != "__main__":
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')
import argparse
import re
import subprocess
import sys
sys.path.insert(0, "./")
try:
from methods import print_error, print_info
except ImportError:
raise SystemExit(f"Utility script {__file__} must be run from repository root!")
def glob_to_regex(glob: str) -> re.Pattern[str]:
"""Convert a CODEOWNERS glob to a RegEx pattern."""
# Heavily inspired by: https://github.com/hmarr/codeowners/blob/main/match.go
# Handle specific edgecases first.
if "***" in glob:
raise SyntaxError("Pattern cannot contain three consecutive asterisks")
if glob == "/":
raise SyntaxError('Standalone "/" will not match anything')
if not glob:
raise ValueError("Empty pattern")
segments = glob.split("/")
if not segments[0]:
# Leading slash; relative to root.
segments = segments[1:]
else:
# Check for single-segment pattern, which matches relative to any descendent path.
# This is equivalent to a leading `**/`.
if len(segments) == 1 or (len(segments) == 2 and not segments[1]):
if segments[0] != "**":
segments.insert(0, "**")
if len(segments) > 1 and not segments[-1]:
# A trailing slash is equivalent to `/**`.
segments[-1] = "**"
last_index = len(segments) - 1
need_slash = False
pattern = r"\A"
for index, segment in enumerate(segments):
if segment == "**":
if index == 0 and index == last_index:
pattern += r".+" # Pattern is just `**`; match everything.
elif index == 0:
pattern += r"(?:.+/)?" # Pattern starts with `**`; match any leading path segment.
need_slash = False
elif index == last_index:
pattern += r"/.*" # Pattern ends with `**`; match any trailing path segment.
else:
pattern += r"(?:/.+)?" # Pattern contains `**`; match zero or more path segments.
need_slash = True
elif segment == "*":
if need_slash:
pattern += "/"
# Regular wildcard; match any non-separator characters.
pattern += r"[^/]+"
need_slash = True
else:
if need_slash:
pattern += "/"
escape = False
for char in segment:
if escape:
escape = False
pattern += re.escape(char)
continue
elif char == "\\":
escape = True
elif char == "*":
# Multi-character wildcard.
pattern += r"[^/]*"
elif char == "?":
# Single-character wildcard.
pattern += r"[^/]"
else:
# Regular character
pattern += re.escape(char)
if index == last_index:
pattern += r"(?:/.*)?" # No trailing slash; match descendent paths.
need_slash = True
pattern += r"\Z"
return re.compile(pattern)
RE_CODEOWNERS = re.compile(r"^(?P<code>[^#](?:\\ |[^\s])+) +(?P<owners>(?:[^#][^\s]+ ?)+)")
def parse_codeowners() -> list[tuple[re.Pattern[str], list[str]]]:
codeowners = []
with open(".github/CODEOWNERS", encoding="utf-8", newline="\n") as file:
for line in reversed(file.readlines()): # Lower items have higher precedence.
if match := RE_CODEOWNERS.match(line):
codeowners.append((glob_to_regex(match["code"]), match["owners"].split()))
return codeowners
def main() -> int:
parser = argparse.ArgumentParser(description="Utility script for validating CODEOWNERS assignment.")
parser.add_argument("files", nargs="*", help="A list of files to validate. If excluded, checks all owned files.")
parser.add_argument("-u", "--unowned", action="store_true", help="Only output files without an owner.")
args = parser.parse_args()
files: list[str] = args.files
if not files:
files = subprocess.run(["git", "ls-files"], text=True, capture_output=True).stdout.splitlines()
ret = 0
codeowners = parse_codeowners()
for file in files:
matched = False
for code, owners in codeowners:
if code.match(file):
matched = True
if not args.unowned:
print_info(f"{file}: {owners}")
break
if not matched:
print_error(f"{file}: <UNOWNED>")
ret += 1
return ret
try:
raise SystemExit(main())
except KeyboardInterrupt:
import os
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
os.kill(os.getpid(), signal.SIGINT)

View file

@ -69,7 +69,7 @@ while read -r dir; do
get_expected_output "$dir"
# Download the reference extension_api.json
wget -nv --retry-on-http-error=503 --tries=5 --timeout=60 -cO "$reference_file" "https://raw.githubusercontent.com/godotengine/godot-cpp/godot-$reference_tag/gdextension/extension_api.json" || has_problems=1
wget -nv --retry-on-http-error=503 --tries=5 --timeout=60 -cO "$reference_file" "https://raw.githubusercontent.com/godotengine/godot-headers/godot-$reference_tag/extension_api.json" || has_problems=1
# Validate the current API against the reference
"$1" --headless --validate-extension-api "$reference_file" 2>&1 | tee "$validate" | awk '!/^Validate extension JSON:/' - || true
# Collect the expected and actual validation errors

View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
if __name__ != "__main__":
raise ImportError(f'Utility script "{__file__}" should not be used as a module!')
import os
from pathlib import Path
if Path(os.getcwd()).as_posix() != (ROOT := Path(__file__).parent.parent.parent).as_posix():
raise RuntimeError(f'Utility script "{__file__}" must be run from the repository root!')
import argparse
import re
from dataclasses import dataclass
BASE_FOLDER = Path(__file__).resolve().parent.parent.parent
RE_INCLUDES = re.compile(
r"^#(?P<keyword>include|import) " # Both `include` and `import` keywords valid.
r'(?P<sequence>[<"])' # Handle both styles of char wrappers. Does NOT handle macro expansion.
r'(?P<path>.+?)[>"]', # Resolve path of include itself. Can safely assume the sequence matches.
re.RegexFlag.MULTILINE,
)
@dataclass
class IncludeData:
path: str
is_angle: bool
is_import: bool
@staticmethod
def from_match(match: re.Match[str]):
items = match.groupdict()
return IncludeData(
items["path"],
items["sequence"] == "<",
items["keyword"] == "import",
)
def copy(self):
return IncludeData(self.path, self.is_angle, self.is_import)
def __str__(self):
return "#{keyword} {rbracket}{path}{lbracket}".format(
keyword="import" if self.is_import else "include",
rbracket="<" if self.is_angle else '"',
path=self.path,
lbracket=">" if self.is_angle else '"',
)
def validate_includes(path: Path) -> int:
ret = 0
content = path.read_text(encoding="utf-8")
for data in map(IncludeData.from_match, RE_INCLUDES.finditer(content)):
original_data = data.copy()
if "\\" in data.path:
data.path = data.path.replace("\\", "/")
if data.path.startswith("thirdparty/"):
data.is_angle = True
if (relative_path := path.parent / data.path).exists():
# Relative includes are only permitted under certain circumstances.
if relative_path.name.split(".")[0] == path.name.split(".")[0]:
# Identical leading names permitted
pass
elif ("modules" in relative_path.parts and "modules" in path.parts) or (
"platform" in relative_path.parts and "platform" in path.parts
):
# Modules and platforms can use relative includes if constrained to the module/platform itself.
pass
else:
data.path = relative_path.resolve().relative_to(BASE_FOLDER).as_posix()
if original_data != data:
content = content.replace(f"{original_data}", f"{data}")
ret += 1
if ret:
with open(path, "w", encoding="utf-8", newline="\n") as file:
file.write(content)
return ret
def main() -> int:
parser = argparse.ArgumentParser(description="Validate C/C++ includes, correcting if necessary")
parser.add_argument("files", nargs="+", help="A list of files to validate")
args = parser.parse_args()
ret = 0
for file in map(Path, args.files):
ret += validate_includes(file)
return ret
try:
raise SystemExit(main())
except KeyboardInterrupt:
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
os.kill(os.getpid(), signal.SIGINT)