Initial commit. Added https://github.com/touilleMan/godot-python b9757da859a4d as a base, but without the submodule.

This commit is contained in:
Relintai 2023-05-23 19:06:25 +02:00
commit d2bbeb79d2
296 changed files with 52918 additions and 0 deletions

9
.bumpversion.cfg Normal file
View File

@ -0,0 +1,9 @@
[bumpversion]
current_version = 0.9.0
commit = True
tag = True
[bumpversion:file:pythonscript/cffi_bindings/mod_godot.inc.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'

251
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,251 @@
name: CI build
on:
push:
branches:
- master
pull_request:
branches:
- master
# Global Settings
env:
PYTHON_VERSION: "3.7"
GODOT_BINARY_VERSION: "3.2.3"
jobs:
static-checks:
name: '📊 Static checks'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@f1d3225b5376a0791fdee5a0e8eac5289355e43a # pin@v2
with:
submodules: true
- name: Set up Python
uses: actions/setup-python@0291cefc54fa79cd1986aee8fa5ecb89ad4defea # pin@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Bootstrap
run: |
set -eux
python --version
pip install pre-commit
- name: Pre-commit hooks check
run: |
pre-commit run --all-files --show-diff-on-failure
#################################################################################
linux-build:
name: '🐧 Linux build'
runs-on: ubuntu-latest
env:
CC: clang
PLATFORM: 'x11-64'
steps:
- name: 'Checkout'
uses: actions/checkout@f1d3225b5376a0791fdee5a0e8eac5289355e43a # pin@v2
with:
submodules: true
- name: 'Set up Python'
uses: actions/setup-python@0291cefc54fa79cd1986aee8fa5ecb89ad4defea # pin@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: 'Setup venv'
run: |
set -eux
${{ env.CC }} --version
python --version
pip install -U pip
pip install -r requirements.txt
# Configuration for scons
echo 'godot_binary = "${{ env.GODOT_BINARY_VERSION }}"' >> custom.py
echo 'platform = "${{ env.PLATFORM }}"' >> custom.py
echo 'CC = "${{ env.CC }}"' >> custom.py
- name: 'Build project'
run: |
set -eux
scons build -j2
- name: 'Start xvfb'
run: |
/usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
echo ">>> Started xvfb"
- name: 'Run tests'
run: |
set -eux
scons tests headless=true
env:
DISPLAY: ':99.0'
- name: 'Generate artifact archive'
run: |
set -eux
scons release
- name: 'Export release artifact'
uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
with:
name: ${{ env.PLATFORM }}-release
path: 'build/godot-python-*.tar.bz2'
#################################################################################
windows-build:
name: '🏁 Windows build'
runs-on: windows-latest
strategy:
matrix:
include:
- PLATFORM: 'windows-64'
PYTHON_ARCH: 'x64'
VS_ARCH: 'amd64'
- PLATFORM: 'windows-32'
PYTHON_ARCH: 'x86'
VS_ARCH: 'x86'
steps:
- name: 'Checkout'
uses: actions/checkout@f1d3225b5376a0791fdee5a0e8eac5289355e43a # pin@v2
with:
submodules: true
- name: 'Set up Python'
uses: actions/setup-python@0291cefc54fa79cd1986aee8fa5ecb89ad4defea # pin@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.PYTHON_ARCH }}
- name: 'Setup venv'
shell: bash
run: |
set -eux
python --version
python -m pip install --user -U pip
python -m pip install --user -r requirements.txt
# Configuration for scons
echo 'godot_binary = "${{ env.GODOT_BINARY_VERSION }}"' >> custom.py
echo 'platform = "${{ matrix.PLATFORM }}"' >> custom.py
echo 'MSVC_USE_SCRIPT = True' >> custom.py
echo 'TARGET_ARCH = "${{ matrix.VS_ARCH }}"' >> custom.py
echo 'CC = "cl.exe"' >> custom.py
- name: 'Build project'
shell: bash
run: |
set -eux
scons build -j2
- name: 'Install Mesa3D OpenGL'
shell: bash
run: |
set -eux
# Azure pipelines doesn't provide a GPU with an OpenGL driver,
# hence we use Mesa3D as software OpenGL driver
pushd build/${{ matrix.PLATFORM }}/platforms/
if [ "${{ matrix.PLATFORM }}" = "windows-64" ]
then
curl https://downloads.fdossena.com/Projects/Mesa3D/Builds/MesaForWindows-x64-20.0.7.7z -o mesa.7z
else
curl https://downloads.fdossena.com/Projects/Mesa3D/Builds/MesaForWindows-20.0.7.7z -o mesa.7z
fi
# opengl32.dll must be extracted in the same directory than Godot binary
7z.exe x mesa.7z
ls -lh opengl32.dll # Sanity check
popd
- name: 'Run tests'
shell: bash
run: |
set -eux
scons tests
- name: 'Generate artifact archive'
shell: bash
run: |
scons release
- name: 'Export release artifact'
uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
with:
name: ${{ matrix.PLATFORM }}-release
path: 'build/godot-python-*.zip'
#################################################################################
macos-build:
name: '🍎 macOS build'
runs-on: macos-latest
env:
CC: clang
PLATFORM: 'osx-64'
steps:
- name: 'Checkout'
uses: actions/checkout@f1d3225b5376a0791fdee5a0e8eac5289355e43a # pin@v2
with:
submodules: true
- name: 'Set up Python'
uses: actions/setup-python@0291cefc54fa79cd1986aee8fa5ecb89ad4defea # pin@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: 'Setup venv'
run: |
set -eux
${{ env.CC }} --version
python --version
brew update
brew install zlib openssl
brew install --cask xquartz
pip install -U pip
pip install -r requirements.txt
# Configuration for scons
echo 'godot_binary = "${{ env.GODOT_BINARY_VERSION }}"' >> custom.py
echo 'platform = "${{ env.PLATFORM }}"' >> custom.py
echo 'CC = "${{ env.CC }}"' >> custom.py
- name: 'Build project'
run: |
set -eux
scons build -j2
- name: 'Run tests'
run: |
set -eux
scons tests
- name: 'Generate artifact archive'
run: |
set -eux
scons release
- name: 'Export release artifact'
uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
with:
name: ${{ env.PLATFORM }}-release
path: 'build/godot-python-*.tar.bz2'
#################################################################################
publish-release:
name: 'Publish ${{ matrix.PLATFORM }} release'
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
needs:
- linux-build
- windows-build
- macos-build
strategy:
matrix:
include:
- PLATFORM: x11-64
- PLATFORM: windows-64
- PLATFORM: windows-32
- PLATFORM: osx-64
steps:
- uses: actions/download-artifact@0ede0875b5db9a2824878bbbbe3d758a75eb8268 # pin@v2
name: ${{ matrix.PLATFORM }}-release
- name: 'Upload release'
uses: svenstaro/upload-release-action@483c1e56f95e88835747b1c7c60581215016cbf2 # pin@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: godot-python-*.*
file_glob: true
overwrite: true

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Python stuff
/venv*
__pycache__
*.pyc
.mypy_cache
# IDE stuff
.vs
.vscode
.idea
# mac os thumbs files
.DS_Store
# Godot import folders
.import
.cache
# Godot runtime logs
logs
# Scons build artefact
.sconsign.dblite
# scons stuff
/custom.py
# Build directory
/build/
# Lazy generated symlinks on build
/examples/*/addons
/tests/*/addons
/tests/*/lib

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "godot_headers"]
path = godot_headers
url = https://github.com/godotengine/godot_headers.git
branch = 3.3

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

@ -0,0 +1,26 @@
repos:
- repo: https://github.com/ambv/black
rev: 19.3b0
hooks:
- id: black
types: [file] # override `types: [python]`
files: (\.py$|^SConstruct$|/SConscript$)
exclude: (^tests/_lib_vendors|^(tests|examples)/lib) # Ignore 3rd party stuff
args:
- "--line-length=100"
language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.2.3
hooks:
- id: mixed-line-ending
exclude: (^tests/_lib_vendors|^(tests|examples)/lib) # Ignore 3rd party stuff
- id: trailing-whitespace
exclude: (^tests/_lib_vendors|^(tests|examples)/lib) # Ignore 3rd party stuff
- repo: local
hooks:
- id: git_actions_pin
name: "Gitub actions pin 3rd party repos"
entry: python ./misc/pin_github_actions.py check
language: python
language_version: python3
files: ^.github/

23
AUTHORS.rst Normal file
View File

@ -0,0 +1,23 @@
=======
Credits
=======
Development Lead
----------------
* Emmanuel Leblond `@touilleMan <https://github.com/touilleMan>`_
Godot Python logo
-----------------
* `@Pinswell <https://github.com/Pinswell>`_
Contributors
------------
* Răzvan Cosmin Rădulescu `@razvanc-r <https://github.com/razvanc-r>`_
* Max Hilbrunner `@mhilbrunner <https://github.com/mhilbrunner>`_
* Chris Ridenour `@cridenour <https://github.com/cridenour>`_
* Gary Oberbrunner `@garyo <https://github.com/garyo>`_
* Paolo Barresi `@paolobb4 <https://github.com/paolobb4>`_
* Colin Kinloch `@ColinKinloch <https://github.com/ColinKinloch>`_

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
Godot Python Copyright (c) 2016 by Emmanuel Leblond.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Godot Python Logo (C) Pinswell
Distributed under the terms of the Creative Commons Attribution License
version 3.0 (CC-BY 3.0)
https://creativecommons.org/licenses/by/3.0/legalcode.

31
Makefile Normal file
View File

@ -0,0 +1,31 @@
.PHONY: all build clean test example
BASEDIR = $(shell pwd)
BACKEND ?= cpython
PLATFORM ?= x11-64
EXTRA_OPTS ?=
SCONS_BIN ?= scons
SCONS_CMD = $(SCONS_BIN) backend=$(BACKEND) platform=$(PLATFORM) $(EXTRA_OPTS)
# Add `LIBGL_ALWAYS_SOFTWARE=1` if you computer sucks with opengl3...
all: build
build:
$(SCONS_CMD)
clean:
$(SCONS_CMD) -c
test:
$(SCONS_CMD) test
example:
$(SCONS_CMD) example

403
README.rst Normal file
View File

@ -0,0 +1,403 @@
.. image:: https://github.com/touilleMan/godot-python/actions/workflows/build.yml/badge.svg
:target: https://github.com/touilleMan/godot-python/actions
:alt: Github action tests
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:alt: Code style: black
================================================
Godot Python, because you want Python on Godot !
================================================
🚧🚨 **Heavy refactoring in progress** 🚨🚧
The project is under heavy refactoring to support Godot4 (which is totally incompatible
with the current codebase).
Development is done on the `godot4-meson branch <https://github.com/touilleMan/godot-python/tree/godot4-meson>`_
until things start getting usable.
.. image:: https://github.com/touilleMan/godot-python/raw/master/misc/godot_python.svg
:width: 200px
:align: right
The goal of this project is to provide Python language support as a scripting
module for the `Godot <http://godotengine.org>`_ game engine.
Quickstart
==========
By order of simplicity:
- Directly download the project from within Godot with the asset library tab.
- Download from the `asset library website <https://godotengine.org/asset-library/asset/179>`_.
- Finally you can also head to the project `release page <https://github.com/touilleMan/godot-python/releases>`_ if you want to only download one specific platform build
.. image:: https://github.com/touilleMan/godot-python/raw/master/misc/showcase.png
:align: center
API
===
example:
.. code-block:: python
# Explicit is better than implicit
from godot import exposed, export, Vector2, Node2D, ResourceLoader
WEAPON_RES = ResourceLoader.load("res://weapon.tscn")
SPEED = Vector2(10, 10)
@exposed
class Player(Node2D):
"""
This is the file's main class which will be made available to Godot. This
class must inherit from `godot.Node` or any of its children (e.g.
`godot.KinematicBody`).
Because Godot scripts only accept file paths, you can't have two `exposed` classes in the same file.
"""
# Exposed class can define some attributes as export(<type>) to achieve
# similar goal than GDSscript's `export` keyword
name = export(str)
# Can export property as well
@export(int)
@property
def age(self):
return self._age
@age.setter
def age(self, value):
self._age = value
# All methods are exposed to Godot
def talk(self, msg):
print(f"I'm saying {msg}")
def _ready(self):
# Don't confuse `__init__` with Godot's `_ready`!
self.weapon = WEAPON_RES.instance()
self._age = 42
# Of course you can access property & methods defined in the parent
name = self.get_name()
print(f"{name} position x={self.position.x}, y={self.position.y}")
def _process(self, delta):
self.position += SPEED * delta
...
class Helper:
"""
Other classes are considered helpers and cannot be called from outside
Python. However they can be imported from another python module.
"""
...
Building
========
To build the project from source, first checkout the repo or download the
latest tarball.
Godot-Python requires Python >= 3.7 and a C compiler.
Godot GDNative header
---------------------
The Godot GDNative headers are provided as git submodule:
.. code-block:: bash
$ git submodule init
$ git submodule update
Alternatively, you can get them `from github <https://github.com/GodotNativeTools/godot_headers>`_.
Linux
-----
On a fresh Ubuntu install, you will need to install these:
.. code-block:: bash
$ apt install python3 python3-pip python3-venv build-essential
On top of that build the CPython interpreter requires development headers
of it `extension modules <https://devguide.python.org/setup/#install-dependencies>`_
(for instance if you lack sqlite dev headers, your Godot-Python build won't
contain the sqlite3 python module)
The simplest way is to uncomment the main deb-src in `/etc/apt/sources.list`:
.. code-block:: bash
deb-src http://archive.ubuntu.com/ubuntu/ artful main
and instruct apt to install the needed packages:
.. code-block:: bash
$ apt update
$ apt build-dep python3.6
See the `Python Developer's Guide <https://devguide.python.org/setup/#build-dependencies>`_
for instructions on additional platforms.
MacOS
-----
With MacOS, you will need XCode installed and install the command line tools.
.. code-block:: bash
$ xcode-select --install
If you are using CPython as your backend, you will need these. To install with Homebrew:
.. code-block:: bash
$ brew install python3 openssl zlib
You will also need virtualenv for your python.
Windows
-------
Install VisualStudio and Python3, then submit a PR to improve this paragraph ;-)
Create the virtual env
----------------------
Godot-Python build system is heavily based on Python (mainly Scons, Cython and Jinja2).
Hence we have to create a Python virtual env to install all those dependencies
without clashing with your global Python configuration.
.. code-block:: bash
$ cd <godot-python-dir>
godot-python$ python3 -m venv venv
Now you need to activate the virtual env, this is something you should do
every time you want to use the virtual env.
For Linux/MacOS:
.. code-block:: bash
godot-python$ . ./venv/bin/activate
For Windows:
.. code-block:: bash
godot-python$ ./venv/bin/activate.bat
Finally we can install dependencies:
.. code-block:: bash
godot-python(venv)$ pip install -r requirements.txt
Running the build
-----------------
For Linux:
.. code-block:: bash
godot-python(venv)$ scons platform=x11-64 release
For Windows:
.. code-block:: bash
godot-python(venv)$ scons platform=windows-64 release
For MacOS:
.. code-block:: bash
godot-python(venv)$ scons platform=osx-64 CC=clang release
Valid platforms are `x11-64`, `x11-32`, `windows-64`, `windows-32` and `osx-64`.
Check Travis or Appveyor links above to see the current status of your platform.
This command will checkout CPython repo, move to a pinned commit and build
CPython from source.
It will then generate ``pythonscript/godot/bindings.pyx`` (Godot api bindings)
from GDNative's ``api.json`` and compile it.
This part is long and really memory demanding so be patient ;-)
When hacking godot-python you can heavily speedup this step by passing
``sample=true`` to scons in order to build only a small subset of the bindings.
Eventually the rest of the source will be compiled and a zip build archive
will be available in the build directory.
Testing your build
------------------
.. code-block:: bash
godot-python(venv)$ scons platform=<platform> test
This will run pytests defined in `tests/bindings` inside the Godot environment.
If not present, will download a precompiled Godot binary (defined in SConstruct
and platform specific SCSub files) to and set the correct library path for
the GDNative wrapper.
Running the example project
---------------------------
.. code-block:: bash
godot-python(venv)$ scons platform=<platform> example
This will run the converted pong example in `examples/pong` inside the Godot
environment. If not present, will download a precompiled Godot binary
(defined in SConstruct) to and set the correct library path for the GDNative
wrapper.
Using a local Godot version
---------------------------
If you have a pre-existing version of godot, you can instruct the build script to
use that the static library and binary for building and tests.
.. code-block:: bash
godot-python(venv)$ scons platform=x11-64 godot_binary=../godot/bin/godot.x11.opt.64
Additional build options
------------------------
You check out all the build options `in this file <https://github.com/touilleMan/godot-python/blob/master/SConstruct#L23>`_.
FAQ
===
**How can I export my project?**
Currently, godot-python does not support automatic export, which means that the python environment is not copied to the release when using Godot's export menu. A release can be created manually:
First, export the project in .zip format.
Second, extract the .zip in a directory. For sake of example let's say the directory is called :code:`godotpythonproject`.
Third, copy the correct Python environment into this folder (if it hasn't been automatically included in the export). Inside your project folder, you will need to find :code:`/addons/pythonscript/x11-64`, replacing "x11-64" with the correct target system you are deploying to. Copy the entire folder for your system, placing it at the same relative position, e.g. :code:`godotpythonproject/addons/pythonscript/x11-64` if your unzipped directory was "godotpythonproject". Legally speaking you should also copy LICENSE.txt from the pythonscript folder. (The lazy option at this point is to simply copy the entire addons folder from your project to your unzipped directory.)
Fourth, place a godot release into the directory. The Godot export menu has probably downloaded an appropriate release already, or you can go to Editor -> Manage Export Templates inside Godot to download fresh ones. These are stored in a location which depends on your operating system. For example, on Windows they may be found at :code:`%APPDATA%\Godot\templates\ `; in Linux or OSX it is :code:`~/.godot/templates/`. Copy the file matching your export. (It may matter whether you selected "Export With Debug" when creating the .zip file; choose the debug or release version accordingly.)
Running the Godot release should now properly execute your release. However, if you were developing on a different Python environment (say, the one held in the osx-64 folder) than you include with the release (for example the windows-64 folder), and you make any alterations to that environment, such as installing Python packages, these will not carry over; take care to produce a suitable Python environment for the target platform.
See also `this issue <https://github.com/touilleMan/godot-python/issues/146>`_.
**How can I use Python packages in my project?**
In essence, godot-python installs a python interpreter inside your project which can then be distributed as part of the final game. Python packages you want to use need to be installed for that interpreter and of course included in the final release. This can be accomplished by using pip to install packages; however, pip is not provided, so it must be installed too.
First, locate the correct python interpreter. This will be inside your project at :code:`addons\pythonscript\windows-64\python.exe` for 64-bit Windows, :code:`addons/pythonscript/ox-64/bin/python3` for OSX, etc. Then install pip by running:
.. code-block::
addons\pythonscript\windows-64\python.exe -m ensurepip
(substituting the correct python for your system). Any other method of installing pip at this location is fine too, and this only needs to be done once. Afterward, any desired packages can be installed by running
.. code-block::
addons\pythonscript\windows-64\python.exe -m pip install numpy
again, substituting the correct python executable, and replacing numpy with whatever packages you desire. The package can now be imported in your Python code as normal.
Note that this will only install packages onto the target platform (here, windows-64), so when exporting the project to a different platform, care must be taken to provide all the necessary libraries.
**How can I debug my project with PyCharm?**
This can be done using "Attach to Local Process", but first you have to change the Godot binary filename to include :code:`python`, for example :code:`Godot_v3.0.2-stable_win64.exe` to :code:`python_Godot_v3.0.2-stable_win64.exe`.
For more detailed guide and explanation see this `external blog post <https://medium.com/@prokopst/debugging-godot-python-with-pycharm-b5f9dd2cf769>`_.
**How can I autoload a python script without attaching it to a Node?**
In your :code:`project.godot` file, add the following section::
[autoload]
autoloadpy="*res://autoload.py"
In addition to the usual::
[gdnative]
singletons=[ "res://pythonscript.gdnlib" ]
You can use any name for the python file and the class name
:code:`autoloadpy`.
Then :code:`autoload.py` can expose a Node::
from godot import exposed, export
from godot.bindings import *
@exposed
class autoload(Node):
def hi(self, to):
return 'Hello %s from Python !' % to
which can then be called from your gdscript code as an attribute of
the :code:`autoloadpy` class (use the name defined in your :code:`project.godot`)::
print(autoloadpy.hi('root'))
**How can I efficiently access PoolArrays?**
:code:`PoolIntArray`, :code:`PoolFloatArray`, :code:`PoolVector3Array`
and the other pool arrays can't be accessed directly because they must
be locked in memory first. Use the :code:`arr.raw_access()` context
manager to lock it::
arr = PoolIntArray() # create the array
arr.resize(10000)
with arr.raw_access() as ptr:
for i in range(10000):
ptr[i] = i # this is fast
# read access:
with arr.raw_access() as ptr:
for i in range(10000):
assert ptr[i] == i # so is this
Keep in mind great performances comes with great responsabilities: there is no
boundary check so you may end up with memory corruption if you don't take care ;-)
See the `godot-python issue <https://github.com/touilleMan/godot-python/issues/84>`_.

234
SConstruct Normal file
View File

@ -0,0 +1,234 @@
import os
import re
import shutil
from datetime import datetime
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Errors import UserError
EnsurePythonVersion(3, 7)
EnsureSConsVersion(3, 0)
def extract_version():
# Hold my beer...
gl = {}
exec(open("pythonscript/godot/_version.py").read(), gl)
return gl["__version__"]
def godot_binary_converter(val, env):
file = File(val)
if file.exists():
# Note here `env["godot_binary_download_version"]` is not defined, this is ok given
# this variable shouldn't be needed if Godot doesn't have to be downloaded
return file
# Provided value is version information with format <major>.<minor>.<patch>[-<extra>]
match = re.match(r"^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-(\w+))?$", val)
if match:
major, minor, patch, extra = match.groups()
else:
raise UserError(
f"`{val}` is neither an existing file nor a valid <major>.<minor>.<patch>[-<extra>] Godot version format"
)
env["godot_binary_download_version"] = (major, minor, patch, extra or "stable")
# `godot_binary` is set to None to indicate it should be downloaded
return None
vars = Variables("custom.py")
vars.Add(
EnumVariable(
"platform",
"Target platform",
"",
allowed_values=("x11-64", "x11-32", "windows-64", "windows-32", "osx-64"),
)
)
vars.Add("pytest_args", "Pytest arguments passed to tests functions", "")
vars.Add(
"godot_args", "Additional arguments passed to godot binary when running tests&examples", ""
)
vars.Add("release_suffix", "Suffix to add to the release archive", extract_version())
vars.Add(
"godot_binary",
"Path to Godot binary or version of Godot to use",
default="3.2.2",
converter=godot_binary_converter,
)
vars.Add("godot_headers", "Path to Godot GDnative headers", "")
vars.Add("debugger", "Run test with a debugger", "")
vars.Add(BoolVariable("debug", "Compile with debug symbols", False))
vars.Add(BoolVariable("headless", "Run tests in headless mode", False))
vars.Add(BoolVariable("compressed_stdlib", "Compress Python std lib as a zip to save space", True))
vars.Add(
BoolVariable(
"bindings_generate_sample",
"Generate only a subset of the bindings (faster build time)",
False,
)
)
vars.Add("CC", "C compiler")
vars.Add("CFLAGS", "Custom flags for the C compiler")
vars.Add("LINK", "linker")
vars.Add("LINKFLAGS", "Custom flags for the linker")
vars.Add("CPYTHON_CFLAGS", "Custom flags for the C compiler used to compile CPython")
vars.Add("CPYTHON_LINKFLAGS", "Custom flags for the linker used to compile CPython")
vars.Add("OPENSSL_PATH", "Path to the root of openssl installation to link CPython against")
vars.Add(
"MSVC_VERSION",
"MSVC version to use (Windows only) -- version num X.Y. Default: highest installed.",
)
vars.Add(
BoolVariable(
"MSVC_USE_SCRIPT",
(
"Set to True to let SCons find compiler (with MSVC_VERSION and TARGET_ARCH), "
"False to use cmd.exe env (MSVC_VERSION and TARGET_ARCH will be ignored), "
"or vcvarsXY.bat script name to use."
),
True,
)
)
# Set Visual Studio arch according to platform target
vanilla_vars_update = vars.Update
def _patched_vars_update(env, args=None):
vanilla_vars_update(env, args=None)
if env["platform"] == "windows-64":
env["TARGET_ARCH"] = "x86_64"
elif env["platform"] == "windows-32":
env["TARGET_ARCH"] = "x86"
vars.Update = _patched_vars_update
env = Environment(
variables=vars,
tools=["default", "cython", "symlink", "virtual_target", "download"],
ENV=os.environ,
# ENV = {'PATH' : os.environ['PATH']},
)
# Detect compiler
env["CC_IS_MSVC"] = env.get("CC") in ("cl", "cl.exe")
env["CC_IS_GCC"] = "gcc" in env.get("CC")
env["CC_IS_CLANG"] = "clang" in env.get("CC")
Help(vars.GenerateHelpText(env))
# if env["HOST_OS"] == "win32":
# # Fix ImportVirtualenv raising error if PATH make reference to other drives
# from SCons.Platform import virtualenv
# vanilla_IsInVirtualenv = virtualenv.IsInVirtualenv
# def patched_IsInVirtualenv(path):
# try:
# return vanilla_IsInVirtualenv(path)
# except ValueError:
# return False
# virtualenv.IsInVirtualenv = patched_IsInVirtualenv
# ImportVirtualenv(env)
if env["godot_headers"]:
env["godot_headers"] = Dir(env["godot_headers"])
else:
env["godot_headers"] = Dir("godot_headers")
env.AppendUnique(CPPPATH=["$godot_headers"])
# TODO: not sure why, but CPPPATH scan result for cython modules change between
# first and subsequent runs of scons (module is considered to no longer depend
# on godot_headers on subsequent run, so the build redone)
SetOption("implicit_cache", 1)
### Save my eyes plz ###
env["ENV"]["TERM"] = os.environ.get("TERM", "")
if env["CC_IS_CLANG"]:
env.Append(CCFLAGS=["-fcolor-diagnostics"])
if env["CC_IS_GCC"]:
env.Append(CCFLAGS=["-fdiagnostics-color=always"])
### Default compile flags ###
if not env["CC_IS_MSVC"]:
if env["debug"]:
env.Append(CFLAGS=["-g", "-ggdb"])
env.Append(LINKFLAGS=["-g", "-ggdb"])
else:
env.Append(CFLAGS=["-O2"])
else:
if env["debug"]:
env.Append(CFLAGS=["/DEBUG:FULL"])
env.Append(LINKFLAGS=["/DEBUG:FULL"])
else:
env.Append(CFLAGS=["/WX", "/W2"])
env["DIST_ROOT"] = Dir(f"build/dist")
env["DIST_PLATFORM"] = Dir(f"{env['DIST_ROOT']}/addons/pythonscript/{env['platform']}")
VariantDir(f"build/{env['platform']}/platforms", f"platforms")
VariantDir(f"build/{env['platform']}/pythonscript", "pythonscript")
### Load sub scons scripts ###
Export(env=env)
SConscript(
[
f"build/{env['platform']}/platforms/SConscript", # Must be kept first
f"build/{env['platform']}/pythonscript/SConscript",
"tests/SConscript",
"examples/SConscript",
]
)
### Define default target ###
env.Default(env["DIST_ROOT"])
env.Alias("build", env["DIST_ROOT"])
### Static files added to dist ###
env.VanillaInstallAs(
target="$DIST_ROOT/pythonscript.gdnlib", source="#/misc/release_pythonscript.gdnlib"
)
env.VanillaInstallAs(
target="$DIST_ROOT/addons/pythonscript/LICENSE.txt", source="#/misc/release_LICENSE.txt"
)
env.Command(target="$DIST_ROOT/addons/pythonscript/.gdignore", source=None, action=Touch("$TARGET"))
# SCons install on directory doesn't check for file changes
for item in env.Glob("addons/pythonscript_repl/*"):
env.VanillaInstall(target="$DIST_ROOT/addons/pythonscript_repl", source=item)
### Release archive ###
def generate_release(target, source, env):
for suffix, format in [(".zip", "zip"), (".tar.bz2", "bztar")]:
if target[0].name.endswith(suffix):
base_name = target[0].abspath[: -len(suffix)]
break
shutil.make_archive(base_name, format, root_dir=source[0].abspath)
# Zip format doesn't support symlinks that are needed for Linux&macOS
if env["platform"].startswith("windows"):
release_target = "build/godot-python-${release_suffix}-${platform}.zip"
else:
release_target = "build/godot-python-${release_suffix}-${platform}.tar.bz2"
release = env.Command(release_target, env["DIST_ROOT"], generate_release)
env.Alias("release", release)
env.AlwaysBuild("release")

View File

@ -0,0 +1,7 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://addons/pythonscript_repl/hack_regular.ttf" type="DynamicFontData" id=1]
[resource]
size = 14
font_data = ExtResource( 1 )

Binary file not shown.

View File

@ -0,0 +1,16 @@
from godot import exposed, InputEventKey, KEY_UP, KEY_DOWN, LineEdit
@exposed(tool=True)
class InputBox(LineEdit):
def _enter_tree(self):
self.repl_node = self.get_parent().get_parent()
def _gui_input(self, event):
if isinstance(event, InputEventKey) and event.pressed:
if event.scancode == KEY_UP:
self.repl_node.up_pressed()
self.accept_event()
elif event.scancode == KEY_DOWN:
self.repl_node.down_pressed()
self.accept_event()

View File

@ -0,0 +1,7 @@
[plugin]
name="pythonscript_repl"
description=""
author="godot-python"
version="0.1"
script="plugin.py"

View File

@ -0,0 +1,19 @@
from godot import exposed, EditorPlugin, ProjectSettings, ResourceLoader
BASE_RES = str(ProjectSettings.localize_path(__file__)).rsplit("/", 1)[0]
PYTHON_REPL_RES = ResourceLoader.load(f"{BASE_RES}/python_repl.tscn")
@exposed(tool=True)
class plugin(EditorPlugin):
def _enter_tree(self):
# Initialization of the plugin goes here
self.repl = PYTHON_REPL_RES.instance()
self.repl_button = self.add_control_to_bottom_panel(self.repl, "Python REPL")
def _exit_tree(self):
# Clean-up of the plugin goes here
self.remove_control_from_bottom_panel(self.repl)
self.repl.queue_free()
self.repl = None

View File

@ -0,0 +1,241 @@
import sys
import ctypes
from code import InteractiveConsole
from collections import deque
from threading import Thread, Lock, Event
from queue import SimpleQueue
from _godot import StdoutStderrCaptureToGodot, StdinCapture
from godot import exposed, export, ResourceLoader, VBoxContainer
from .plugin import BASE_RES
FONT = ResourceLoader.load(f"{BASE_RES}/hack_regular.tres")
class StdoutStderrCaptureToBufferAndPassthrough(StdoutStderrCaptureToGodot):
def __init__(self):
super().__init__()
self._buffer = ""
def _write(self, buff):
# _write always executed with _lock taken
super()._write(buff)
self._buffer += buff
def read_buffer(self):
with self._lock:
buffer = self._buffer
self._buffer = ""
return buffer
class StdinCaptureToBuffer(StdinCapture):
def __init__(self):
super().__init__()
self._lock = Lock()
self._has_data = Event()
self._buffer = ""
self._closed = False
def _read(self, size=-1):
if self._closed:
raise EOFError
if size < 0 or size > len(self._buffer):
data = self._buffer
self._buffer = ""
else:
data = self._buffer[:size]
self._buffer = self._buffer[size:]
if not self._buffer:
self._has_data.clear()
return data
def read(self, size=-1):
while True:
self._has_data.wait()
with self._lock:
# Check if a concurrent readinto has already processed the data
if not self._has_data.is_set():
continue
return self._read(size)
def readline(size=-1):
while True:
self._has_data.wait()
with self._lock:
# Check if a concurrent readinto has already processed the data
if not self._has_data.is_set():
continue
if size < 0:
size = len(self._buffer)
try:
size = min(size, self._buffer.index("\n") + 1)
except ValueError:
# \n not in self._buffer
pass
return self._read(size)
def write(self, buffer):
if not buffer:
return
with self._lock:
self._has_data.set()
self._buffer += buffer
def close(self):
self._closed = True
# Ensure read is waken up so it can raise EOFError
self._has_data.set()
class InteractiveConsoleInREPL(InteractiveConsole):
def __init__(self, repl_write, repl_read):
super().__init__(locals={"__name__": "__console__", "__doc__": None})
# Default write/raw_input relies on stderr/stdin, overwrite them
# to only talk with the REPL
self.write = repl_write
# Note overwritting `InteractiveConsole.raw_input` doesn't prevent
# from user code directly calling `input` (for instance when typing
# `help()` which makes use of a pager).
self.repl_read = repl_read
self.thread = None
def raw_input(self, prompt):
data = self.repl_read()
# Print the command line in the ouput box, this is needed given
# we have a separate input box that is cleared each time
# the user hit enter (unlike regular terminal where input and output
# are mixed together and enter only jumps to next line)
self.write(f"{prompt}{data}")
return data
def start_in_thread(self):
assert not self.thread
self.thread = Thread(target=self.interact)
self.thread.start()
def send_keyboard_interrupt(self):
# Inject a exception in the thread running the interpreter.
# This is not 100% perfect given the thread checks for exception only
# when it is actually running Python code so we cannot interrupt native
# code (for instance calling `time.sleep` cannot be interrupted)
ctypes.pythonapi.PyThreadState_SetAsyncExc(
self.thread.ident, ctypes.py_object(KeyboardInterrupt)
)
@exposed(tool=True)
class PythonREPL(VBoxContainer):
__STREAMS_CAPTURE_INSTALLED = False
def _enter_tree(self):
self.__plugin_instantiated = False
self.history = []
self.selected_history = 0
self.output_box = self.get_node("OutputBox")
self.output_box.add_font_override("normal_font", FONT)
self.output_box.add_font_override("mono_font", FONT)
self.run_button = self.get_node("FooterContainer/RunButton")
self.run_button.connect("pressed", self, "execute")
self.clear_button = self.get_node("HeaderContainer/ClearButton")
self.clear_button.connect("pressed", self, "clear")
self.interrupt_button = self.get_node("HeaderContainer/KeyboardInterruptButton")
self.interrupt_button.connect("pressed", self, "send_keyboard_interrupt")
self.input_box = self.get_node("FooterContainer/InputBox")
self.input_box.connect("text_entered", self, "execute")
# Hijack stdout/stderr/stdin streams
self.stdout_stderr_capture = StdoutStderrCaptureToBufferAndPassthrough()
self.stdin_capture = StdinCaptureToBuffer()
# Only overwrite streams if the scene has been created by the
# pythonscript_repl plugin. This avoid concurrent streams patching
# when the scene is opened from the editor (typically when we want
# to edit the repl GUI)
# TODO: find a way to differentiate plugin instantiated from other
# instantiations instead of relying on "first instantiated is plugin"
if not PythonREPL.__STREAMS_CAPTURE_INSTALLED:
PythonREPL.__STREAMS_CAPTURE_INSTALLED = True
self.__plugin_instantiated = True
self.stdout_stderr_capture.install()
self.stdin_capture.install()
# Finally start the Python interpreter, it must be running it in own
# thread given it does blocking reads on stdin
self.interpreter = InteractiveConsoleInREPL(
repl_write=self.write, repl_read=self.stdin_capture.read
)
self.interpreter.start_in_thread()
def _exit_tree(self):
# Closing our custom stdin stream should make `InteractiveConsole.interact`
# return, hence finishing the interpreter thread
self.stdin_capture.close()
self.interpreter.thread.join()
# Our custom stream capture must be removed before this node is destroyed,
# otherwise segfault will occur on next print !
if self.__plugin_instantiated:
PythonREPL.__STREAMS_CAPTURE_INSTALLED = False
self.stdout_stderr_capture.remove()
self.stdin_capture.remove()
def write(self, buffer):
for line in buffer.splitlines():
self.output_box.push_mono()
self.output_box.add_text(line)
self.output_box.newline()
self.output_box.pop()
def _process(self, delta):
if not hasattr(self, "stdout_stderr_capture"):
return
# Display new lines
self.write(self.stdout_stderr_capture.read_buffer())
def remove_last_line(self):
self.output_box.remove_line(self.output_box.get_line_count() - 2)
self.output_box.scroll_to_line(self.output_box.get_line_count() - 1)
def execute(self, *args, **kwargs):
string = str(self.input_box.get_text())
# Avoid adding multiple repeated entries to the command history
if not (len(self.history) > 0 and self.history[-1] == string):
self.history.append(string)
self.selected_history = 0
self.input_box.clear()
# Send the line into stdin and let the interpret do the rest
self.stdin_capture.write(string + "\n")
def up_pressed(self):
if len(self.history) >= abs(self.selected_history - 1):
self.selected_history -= 1
self.input_box.clear()
val = str(self.history[self.selected_history])
self.input_box.set_text(val)
self.input_box.set_cursor_position(len(val))
self.input_box.grab_focus()
def down_pressed(self):
if self.selected_history + 1 == 0:
self.selected_history += 1
self.input_box.clear()
elif self.selected_history + 1 < 0:
self.selected_history += 1
self.input_box.clear()
val = str(self.history[self.selected_history])
self.input_box.set_text(val)
self.input_box.set_cursor_position(len(val))
self.input_box.grab_focus()
def clear(self):
self.output_box.clear()
def send_keyboard_interrupt(self):
self.interpreter.send_keyboard_interrupt()

View File

@ -0,0 +1,66 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://addons/pythonscript_repl/python_repl.py" type="Script" id=1]
[ext_resource path="res://addons/pythonscript_repl/hack_regular.tres" type="DynamicFont" id=2]
[ext_resource path="res://addons/pythonscript_repl/input_box.py" type="Script" id=3]
[node name="Python REPL" type="VBoxContainer"]
margin_right = 580.0
margin_bottom = 234.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HeaderContainer" type="HBoxContainer" parent="."]
margin_right = 580.0
margin_bottom = 20.0
[node name="Label" type="Label" parent="HeaderContainer"]
margin_top = 3.0
margin_right = 459.0
margin_bottom = 17.0
size_flags_horizontal = 3
text = "Python REPL:"
[node name="KeyboardInterruptButton" type="Button" parent="HeaderContainer"]
margin_left = 463.0
margin_right = 532.0
margin_bottom = 20.0
text = "Interrupt"
[node name="ClearButton" type="Button" parent="HeaderContainer"]
margin_left = 536.0
margin_right = 580.0
margin_bottom = 20.0
text = "Clear"
[node name="OutputBox" type="RichTextLabel" parent="."]
margin_top = 24.0
margin_right = 580.0
margin_bottom = 206.0
rect_min_size = Vector2( 0, 180 )
focus_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
custom_fonts/mono_font = ExtResource( 2 )
custom_fonts/normal_font = ExtResource( 2 )
scroll_following = true
selection_enabled = true
[node name="FooterContainer" type="HBoxContainer" parent="."]
margin_top = 210.0
margin_right = 580.0
margin_bottom = 234.0
[node name="InputBox" type="LineEdit" parent="FooterContainer"]
margin_right = 540.0
margin_bottom = 24.0
size_flags_horizontal = 3
script = ExtResource( 3 )
[node name="RunButton" type="Button" parent="FooterContainer"]
margin_left = 544.0
margin_right = 580.0
margin_bottom = 24.0
text = "Run"

20
docs/io.rst Normal file
View File

@ -0,0 +1,20 @@
IO model
========
Python is supposed to interact with outside world (i.e. everything
outside the interpretor) through numerous ways:
- ``open`` and ``input`` builtin functions.
- ``os`` module (e.g. ``os.open`` function)
- ``stdout``, ``stderr`` and ``stdin`` files descriptors
- ``__import__`` & co
- ``ctypes`` & micropython's ``ffi`` libraries
- ...
However those functions are no longer relevant when python is embedded
into Godot. They can even be dangerous when opening a Godot application to
modding given a 3rd party python code has suddently full access to the computer !
Hence, those functions needs to be adapted to Godot:
- ``ctype``, ``ffi`` and ``open`` disabled
- ``stdout``, ``stderr`` and ``stdin`` redirected to Godot editor's console

74
docs/memory.rst Normal file
View File

@ -0,0 +1,74 @@
Object conversion model
=======================
Base object types
-----------------
Godot Variant
- standalone: bool, int, real
- pointer to builtin type (e.g. ``Matrix32``, ``AABB``, etc.)
- pointer to generic ``Object``
Python mp_obj_t
- standalone: bool, small int, real (depend of implementation), qstr
- pointer to generic struct (must have ``mp_obj_base_t base`` as first attribute)
.. note:
Variant and mp_obj_t instances are only used by copy, no memory management
needed on themselves.
Naming conventions:
- GST: Godot STandalone
- GPB: Godot Pointer Builtin
- GPO: Godot Pointer Object
- PST: Python STandalone
- PPB: Python Pointer Binding (proxy to Godot data)
- PPE: Python Pointer Exposed (defined with `@exposed` decorator)
- PPI: Python Pointer Internal
Variant memory management
-------------------------
For GPO, Variant contains a raw pointer on the Object and (not necessary) a
reference on the Object.
- If a reference is present, ref counting is at work.
- If not, user need to do manual memory management by calling ``free`` method.
For GPB, there is 3 possibilities:
- No allocated memory for data (e.g. ``Rect2``), so nothing is done.
- Data is stored in a memory pool (e.g. ``Dictionary``), so data's destructor
is called which make use of ref counting to know what to do.
- Classic C++ allocation for data (e.g. ``Matrix3``) so regular ``delete``
is called on it.
Conversions implicating a standalone
------------------------------------
Standalone doesn't need garbage collection and doesn't hold reference on
other objects. Hence conversion is trivial.
Conversion Godot -> Python
--------------------------
Each GPB has a corresponding PPB, acting like a proxy from within the
Python interpreter.
GPO binding is done dynamically with the ``DynamicBinder`` using Godot
introspection (i.e. ``ObjectTypeDB``).
It is possible in the future that to create static proxy for core GPO and rely
on dynamic method as a fall-back for unknown classes (i.g. added by 3rd party).
Conversion Python -> Godot
--------------------------
PPB -> GPB described earlier.
PPI objects cannot be converted back to Godot.
PPE instance are exposed as ``PyInstance`` (class exposed as ``PyScript``).

10
examples/SConscript Normal file
View File

@ -0,0 +1,10 @@
Import("env")
for test in ["pong", "pong_multiplayer"]:
dist_symlink = env.Symlink(f"{test}/addons", "$DIST_ROOT/addons")
target = env.Command(
test, ["$godot_binary", dist_symlink], "${SOURCE.abspath} ${godot_args} --path ${TARGET}"
)
env.AlwaysBuild(target)
env.Alias("example", "pong")

60
examples/pong/ball.gd Normal file
View File

@ -0,0 +1,60 @@
extends Area2D
const DEFAULT_SPEED=220
var direction = Vector2(1,0)
var ball_speed = DEFAULT_SPEED
var stopped=false
onready var screen_size = get_viewport_rect().size
func _reset_ball(for_left):
position = screen_size / 2
if (for_left):
direction = Vector2(-1,0)
else:
direction = Vector2( 1,0)
ball_speed = DEFAULT_SPEED
func stop():
stopped=true
func _process(delta):
# ball will move normally for both players
# even if it's sightly out of sync between them
# so each player sees the motion as smooth and not jerky
if (not stopped):
translate( direction * ball_speed * delta )
# check screen bounds to make ball bounce
if ((position.y < 0 and direction.y < 0) or (position.y > screen_size.y and direction.y > 0)):
direction.y = -direction.y
if (position.x < 0 or position.x > screen_size.x):
var for_left = position.x > 0
get_parent().update_score(for_left)
_reset_ball(for_left)
sync func bounce(left,random):
#using sync because both players can make it bounce
if (left):
direction.x = abs(direction.x)
else:
direction.x = -abs(direction.x)
ball_speed *= 1.1
direction.y = random*2.0 - 1
direction = direction.normalized()
func _ready():
set_process(true)

BIN
examples/pong/ball.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ball.png"
dest_files=[ "res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

53
examples/pong/ball.py Normal file
View File

@ -0,0 +1,53 @@
from godot import exposed, Vector2, Area2D
DEFAULT_SPEED = 220
@exposed
class Ball(Area2D):
def _reset_ball(self, for_left):
self.position = self.screen_size / 2
if for_left:
self.direction = Vector2(-1, 0)
else:
self.direction = Vector2(1, 0)
self.ball_speed = DEFAULT_SPEED
def stop(self):
self.stopped = True
def _process(self, delta):
# ball will move normally for both players
# even if it's sightly out of sync between them
# so each player sees the motion as smooth and not jerky
if not self.stopped:
self.translate(self.direction * self.ball_speed * delta)
# check screen bounds to make ball bounce
if (self.position.y < 0 and self.direction.y < 0) or (
self.position.y > self.screen_size.y and self.direction.y > 0
):
self.direction.y = -self.direction.y
if self.position.x < 0 or self.position.x > self.screen_size.x:
for_left = self.position.x > 0
self.get_parent().update_score(for_left)
self._reset_ball(for_left)
def bounce(self, left, random):
# using sync because both players can make it bounce
if left:
self.direction.x = abs(self.direction.x)
else:
self.direction.x = -abs(self.direction.x)
self.ball_speed *= 1.1
self.direction.y = random * 2.0 - 1
self.direction = self.direction.normalized()
def _ready(self):
self.direction = Vector2(1, 0)
self.ball_speed = DEFAULT_SPEED
self.stopped = False
self.screen_size = self.get_viewport_rect().size
self.set_process(True) # REMOVE ME

33
examples/pong/ball.tscn Normal file
View File

@ -0,0 +1,33 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://ball.py" type="Script" id=1]
[ext_resource path="res://ball.png" type="Texture" id=2]
[sub_resource type="CircleShape2D" id=1]
custom_solver_bias = 0.0
radius = 5.11969
[node name="ball" type="Area2D"]
input_pickable = true
shapes/0/shape = SubResource( 1 )
shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 )
shapes/0/trigger = false
gravity_vec = Vector2( 0, 1 )
gravity = 98.0
linear_damp = 0.1
angular_damp = 1.0
script = ExtResource( 1 )
[node name="sprite" type="Sprite" parent="."]
texture = ExtResource( 2 )
[node name="shape" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
trigger = false
_update_shape_index = 0

BIN
examples/pong/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

36
examples/pong/paddle.gd Normal file
View File

@ -0,0 +1,36 @@
extends Area2D
export var left=false
const MOTION_SPEED=150
var motion = 0
var can_move = true
var action_prefix = ''
onready var screen_size = get_viewport_rect().size
func _process(delta):
#is the master of the paddle
motion = 0
if (Input.is_action_pressed(action_prefix + "_move_up")):
motion -= 1
elif (Input.is_action_pressed(action_prefix + "_move_down")):
motion += 1
motion*=MOTION_SPEED
if can_move:
translate( Vector2(0,motion*delta) )
# set screen limits
if (position.y < 0 ):
position.y = 0
elif (position.y > screen_size.y):
position.y = screen_size.y
func _ready():
set_process(true)
func _on_paddle_area_enter( area ):
area.bounce(left, randf()) #random for new direction generated on each peer

BIN
examples/pong/paddle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://paddle.png"
dest_files=[ "res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

41
examples/pong/paddle.py Normal file
View File

@ -0,0 +1,41 @@
from random import random
from godot import exposed, export, Vector2, GDString, Area2D, Input
MOTION_SPEED = 150
@exposed
class Paddle(Area2D):
left = export(bool, default=False)
action_prefix = export(str, default="")
can_move = export(bool, default=False)
def _ready(self):
self.motion = 0
self.can_move = True
self.screen_size = self.get_viewport_rect().size
self.set_process(True)
def _process(self, delta):
motion = 0
if Input.is_action_pressed(self.action_prefix + GDString("_move_up")):
motion -= 1
elif Input.is_action_pressed(self.action_prefix + GDString("_move_down")):
motion += 1
motion *= MOTION_SPEED
if self.can_move:
self.translate(Vector2(0, motion * delta))
# set screen limits
if self.position.y < 0:
self.position.y = 0
elif self.position.y > self.screen_size.y:
self.position.y = self.screen_size.y
def _on_paddle_area_enter(self, area):
# random for new direction generated on each peer
area.bounce(self.left, random())

37
examples/pong/paddle.tscn Normal file
View File

@ -0,0 +1,37 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://paddle.py" type="Script" id=1]
[ext_resource path="res://paddle.png" type="Texture" id=2]
[sub_resource type="CapsuleShape2D" id=1]
custom_solver_bias = 0.0
radius = 4.78568
height = 23.6064
[node name="paddle" type="Area2D"]
input_pickable = true
shapes/0/shape = SubResource( 1 )
shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 )
shapes/0/trigger = false
gravity_vec = Vector2( 0, 1 )
gravity = 98.0
linear_damp = 0.1
angular_damp = 1.0
script = ExtResource( 1 )
left = false
[node name="sprite" type="Sprite" parent="."]
texture = ExtResource( 2 )
[node name="shape" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
trigger = false
_update_shape_index = 0
[connection signal="area_entered" from="." to="." method="_on_paddle_area_enter"]

42
examples/pong/pong.gd Normal file
View File

@ -0,0 +1,42 @@
extends Node2D
const SCORE_TO_WIN = 2
var score_left = 0
var score_right = 0
signal game_finished()
func update_score(add_to_left):
if (add_to_left):
score_left+=1
get_node("score_left").set_text( str(score_left) )
else:
score_right+=1
get_node("score_right").set_text( str(score_right) )
var game_ended = false
if (score_left==SCORE_TO_WIN):
get_node("winner_left").show()
game_ended=true
elif (score_right==SCORE_TO_WIN):
get_node("winner_right").show()
game_ended=true
if (game_ended):
get_node("ball").stop()
get_node("player1").can_move=false
get_node("player2").can_move=false
func _ready():
#let each paddle know which one is left, too
get_node("player1").left=true
get_node("player2").left=false
get_node("player1").action_prefix = 'p1'
get_node("player2").action_prefix = 'p2'

41
examples/pong/pong.py Normal file
View File

@ -0,0 +1,41 @@
from godot import exposed, signal, export, Node2D
SCORE_TO_WIN = 2
@exposed
class Pong(Node2D):
game_finished = signal()
def _ready(self):
self.score_left = 0
self.score_right = 0
# let each paddle know which one is left, too
p1 = self.get_node("player1")
p2 = self.get_node("player2")
p1.left = True
p2.left = False
p1.action_prefix = "p1"
p2.action_prefix = "p2"
def update_score(self, add_to_left):
if add_to_left:
self.score_left += 1
self.get_node("score_left").set_text(str(self.score_left))
else:
self.score_right += 1
self.get_node("score_right").set_text(str(self.score_right))
game_ended = False
if self.score_left == SCORE_TO_WIN:
self.get_node("winner_left").show()
game_ended = True
elif self.score_right == SCORE_TO_WIN:
self.get_node("winner_right").show()
game_ended = True
if game_ended:
self.get_node("ball").stop()
self.get_node("player1").can_move = False
self.get_node("player2").can_move = False

69
examples/pong/pong.tscn Normal file
View File

@ -0,0 +1,69 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://pong.py" type="Script" id=1]
[ext_resource path="res://separator.png" type="Texture" id=2]
[ext_resource path="res://paddle.tscn" type="PackedScene" id=3]
[ext_resource path="res://ball.tscn" type="PackedScene" id=5]
[node name="pong" type="Node2D"]
script = ExtResource( 1 )
[node name="separator" type="Sprite" parent="."]
position = Vector2( 512.309, 298.233 )
scale = Vector2( 1.04883, 1.4884 )
texture = ExtResource( 2 )
[node name="player1" parent="." instance=ExtResource( 3 )]
position = Vector2( 19.9447, 267.036 )
[node name="sprite" parent="player1" index="0"]
modulate = Color( 1, 0, 0.960938, 1 )
[node name="player2" parent="." instance=ExtResource( 3 )]
position = Vector2( 995.015, 244.876 )
[node name="sprite" parent="player2" index="0"]
modulate = Color( 0, 0.929688, 1, 1 )
[node name="ball" parent="." instance=ExtResource( 5 )]
position = Vector2( 513.02, 248.2 )
[node name="score_left" type="Label" parent="."]
margin_left = 96.0
margin_top = 57.0
margin_right = 104.0
margin_bottom = 71.0
size_flags_vertical = 0
text = "0"
align = 1
[node name="score_right" type="Label" parent="."]
margin_left = 907.0
margin_top = 62.0
margin_right = 915.0
margin_bottom = 76.0
size_flags_vertical = 0
text = "0"
align = 1
[node name="winner_left" type="Label" parent="."]
visible = false
margin_left = 60.0
margin_top = 33.0
margin_right = 137.0
margin_bottom = 47.0
size_flags_vertical = 0
text = "The Winner!"
[node name="winner_right" type="Label" parent="."]
visible = false
margin_left = 872.0
margin_top = 41.0
margin_right = 949.0
margin_bottom = 55.0
size_flags_vertical = 0
text = "The Winner!"
[editable path="player1"]
[editable path="player2"]

View File

@ -0,0 +1,67 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
_global_script_classes=[ ]
_global_script_class_icons={
}
[application]
run/main_scene="res://pong.tscn"
name="Pong"
main_scene="res://pong.tscn"
disable_stdout=true
icon="res://icon.png"
[display]
width=640
height=400
stretch_2d=true
[editor_plugins]
enabled=PoolStringArray( "pythonscript_repl" )
[gdnative]
singletons=[ "res://pythonscript.gdnlib" ]
[input]
p1_move_up={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null)
]
}
p1_move_down={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"unicode":0,"echo":false,"script":null)
]
}
p2_move_up={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
]
}
p2_move_down={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
]
}
[memory]
multithread/thread_rid_pool_prealloc=60
[render]
default_clear_color=Color( 0, 0, 0, 1 )

View File

@ -0,0 +1,23 @@
[general]
singleton=true
load_once=true
symbol_prefix="godot_"
[entry]
X11.64="res://addons/pythonscript/x11-64/libpythonscript.so"
X11.32="res://addons/pythonscript/x11-32/libpythonscript.so"
Server.64="res://addons/pythonscript/x11-64/libpythonscript.so"
Windows.64="res://addons/pythonscript/windows-64/pythonscript.dll"
Windows.32="res://addons/pythonscript/windows-32/pythonscript.dll"
OSX.64="res://addons/pythonscript/osx-64/libpythonscript.dylib"
[dependencies]
X11.64=[]
X11.32=[]
Server.64=[]
Windows.64=[]
Windows.32=[]
OSX.64=[]

BIN
examples/pong/separator.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://separator.png"
dest_files=[ "res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,73 @@
extends Area2D
const DEFAULT_SPEED=80
var direction = Vector2(1,0)
var ball_speed = DEFAULT_SPEED
var stopped=false
onready var screen_size = get_viewport_rect().size
sync func _reset_ball(for_left):
position = screen_size / 2
if (for_left):
direction = Vector2(-1,0)
else:
direction = Vector2( 1,0)
ball_speed = DEFAULT_SPEED
sync func stop():
stopped=true
func _process(delta):
# ball will move normally for both players
# even if it's sightly out of sync between them
# so each player sees the motion as smooth and not jerky
if (not stopped):
translate( direction * ball_speed * delta )
# check screen bounds to make ball bounce
if ((position.y < 0 and direction.y < 0) or (position.y > screen_size.y and direction.y > 0)):
direction.y = -direction.y
if (is_network_master()):
# only master will decide when the ball is out in the left side (it's own side)
# this makes the game playable even if latency is high and ball is going fast
# otherwise ball might be out in the other player's screen but not this one
if (position.x < 0 ):
get_parent().rpc("update_score",false)
rpc("_reset_ball",false)
else:
# only the slave will decide when the ball is out in the right side (it's own side)
# this makes the game playable even if latency is high and ball is going fast
# otherwise ball might be out in the other player's screen but not this one
if (position.x > screen_size.x):
get_parent().rpc("update_score",true)
rpc("_reset_ball",true)
sync func bounce(left,random):
#using sync because both players can make it bounce
if (left):
direction.x = abs(direction.x)
else:
direction.x = -abs(direction.x)
ball_speed *= 1.1
direction.y = random*2.0 - 1
direction = direction.normalized()
func _ready():
set_process(true)

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

View File

@ -0,0 +1,24 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,65 @@
from godot import exposed, rpcsync, Area2D, Vector2
DEFAULT_SPEED = 80
@exposed
class Ball(Area2D):
@rpcsync
def _reset_ball(self, for_left):
print("RESET BALL", for_left)
self.position = self.screen_size / 2
if for_left:
self.direction = Vector2(-1, 0)
else:
self.direction = Vector2(1, 0)
self.ball_speed = DEFAULT_SPEED
@rpcsync
def stop(self):
self.stopped = True
def _process(self, delta):
# ball will move normally for both players
# even if it's sightly out of sync between them
# so each player sees the motion as smooth and not jerky
if not self.stopped:
self.translate(self.direction * self.ball_speed * delta)
# check screen bounds to make ball bounce
if (self.position.y < 0 and self.direction.y < 0) or (
self.position.y > self.screen_size.y and self.direction.y > 0
):
self.direction.y = -self.direction.y
if self.is_network_master():
# only master will decide when the ball is out in the left side (it's own side)
# this makes the game playable even if latency is high and ball is going fast
# otherwise ball might be out in the other player's screen but not this one
if self.position.x < 0:
self.get_parent().rpc("update_score", False)
self.rpc("_reset_ball", False)
else:
# only the slave will decide when the ball is out in the right side (it's own side)
# this makes the game playable even if latency is high and ball is going fast
# otherwise ball might be out in the other player's screen but not this one
if self.position.x > self.screen_size.x:
self.get_parent().rpc("update_score", True)
self.rpc("_reset_ball", True)
@rpcsync
def bounce(self, left, random):
# using sync because both players can make it bounce
if self.left:
self.direction.x = abs(self.direction.x)
else:
self.direction.x = -abs(self.direction.x)
self.ball_speed *= 1.1
self.direction.y = random * 2.0 - 1
self.direction = self.direction.normalized()
def _ready(self):
self.direction = Vector2(1, 0)
self.ball_speed = DEFAULT_SPEED
self.stopped = False
self.screen_size = self.get_viewport_rect().size
self.set_process(True) # REMOVE ME

View File

@ -0,0 +1,33 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://ball.py" type="Script" id=1]
[ext_resource path="res://ball.png" type="Texture" id=2]
[sub_resource type="CircleShape2D" id=1]
custom_solver_bias = 0.0
radius = 5.11969
[node name="ball" type="Area2D"]
input_pickable = true
shapes/0/shape = SubResource( 1 )
shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 )
shapes/0/trigger = false
gravity_vec = Vector2( 0, 1 )
gravity = 98.0
linear_damp = 0.1
angular_damp = 1.0
script = ExtResource( 1 )
[node name="sprite" type="Sprite" parent="."]
texture = ExtResource( 2 )
[node name="shape" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
trigger = false
_update_shape_index = 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

View File

@ -0,0 +1,24 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,106 @@
extends Control
const DEFAULT_PORT = 8910 # some random number, pick your port properly
#### Network callbacks from SceneTree ####
# callback from SceneTree
func _player_connected(id):
#someone connected, start the game!
var pong = load("res://pong.tscn").instance()
pong.connect("game_finished",self,"_end_game",[],CONNECT_DEFERRED) # connect deferred so we can safely erase it from the callback
get_tree().get_root().add_child(pong)
hide()
func _player_disconnected(id):
if (get_tree().is_network_server()):
_end_game("Client disconnected")
else:
_end_game("Server disconnected")
# callback from SceneTree, only for clients (not server)
func _connected_ok():
# will not use this one
pass
# callback from SceneTree, only for clients (not server)
func _connected_fail():
_set_status("Couldn't connect",false)
get_tree().set_network_peer(null) #remove peer
get_node("panel/join").set_disabled(false)
get_node("panel/host").set_disabled(false)
func _server_disconnected():
_end_game("Server disconnected")
##### Game creation functions ######
func _end_game(with_error=""):
if (has_node("/root/pong")):
#erase pong scene
get_node("/root/pong").free() # erase immediately, otherwise network might show errors (this is why we connected deferred above)
show()
get_tree().set_network_peer(null) #remove peer
get_node("panel/join").set_disabled(false)
get_node("panel/host").set_disabled(false)
_set_status(with_error,false)
func _set_status(text,isok):
#simple way to show status
if (isok):
get_node("panel/status_ok").set_text(text)
get_node("panel/status_fail").set_text("")
else:
get_node("panel/status_ok").set_text("")
get_node("panel/status_fail").set_text(text)
func _on_host_pressed():
var host = NetworkedMultiplayerENet.new()
host.set_compression_mode(NetworkedMultiplayerENet.COMPRESS_RANGE_CODER)
var err = host.create_server(DEFAULT_PORT,1) # max: 1 peer, since it's a 2 players game
if (err!=OK):
#is another server running?
_set_status("Can't host, address in use.",false)
return
get_tree().set_network_peer(host)
get_node("panel/join").set_disabled(true)
get_node("panel/host").set_disabled(true)
_set_status("Waiting for player..",true)
func _on_join_pressed():
var ip = get_node("panel/address").get_text()
if (not ip.is_valid_ip_address()):
_set_status("IP address is invalid",false)
return
var host = NetworkedMultiplayerENet.new()
host.set_compression_mode(NetworkedMultiplayerENet.COMPRESS_RANGE_CODER)
host.create_client(ip,DEFAULT_PORT)
get_tree().set_network_peer(host)
_set_status("Connecting..",true)
### INITIALIZER ####
func _ready():
# connect all the callbacks related to networking
get_tree().connect("network_peer_connected",self,"_player_connected")
get_tree().connect("network_peer_disconnected",self,"_player_disconnected")
get_tree().connect("connected_to_server",self,"_connected_ok")
get_tree().connect("connection_failed",self,"_connected_fail")
get_tree().connect("server_disconnected",self,"_server_disconnected")

View File

@ -0,0 +1,184 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://lobby.gd" type="Script" id=1]
[node name="lobby" type="Control"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
size_flags_horizontal = 1
size_flags_vertical = 1
script = ExtResource( 1 )
[node name="title" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 214.0
margin_top = 7.0
margin_right = 321.0
margin_bottom = 21.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
text = "Multiplayer Pong"
align = 1
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="panel" type="Panel" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="address_label" type="Label" parent="panel"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 162.0
margin_top = 54.0
margin_right = 214.0
margin_bottom = 68.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
text = "Address"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="address" type="LineEdit" parent="panel"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 163.0
margin_top = 74.0
margin_right = 242.0
margin_bottom = 98.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
size_flags_horizontal = 1
size_flags_vertical = 1
text = "127.0.0.1"
expand_to_len = false
focus_mode = 2
placeholder_alpha = 0.6
caret_blink = false
caret_blink_speed = 0.65
[node name="host" type="Button" parent="panel"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 105.0
margin_top = 107.0
margin_right = 147.0
margin_bottom = 127.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Host"
flat = false
[node name="join" type="Button" parent="panel"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 109.0
margin_top = 79.0
margin_right = 144.0
margin_bottom = 99.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Join"
flat = false
[node name="status_ok" type="Label" parent="panel"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 265.0
margin_top = 43.0
margin_right = 303.0
margin_bottom = 57.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
custom_colors/font_color = Color( 0, 1, 0.015625, 1 )
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="status_fail" type="Label" parent="panel"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 261.0
margin_top = 78.0
margin_right = 295.0
margin_bottom = 92.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
custom_colors/font_color = Color( 1, 0, 0, 1 )
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[connection signal="pressed" from="panel/host" to="." method="_on_host_pressed"]
[connection signal="pressed" from="panel/join" to="." method="_on_join_pressed"]

View File

@ -0,0 +1,63 @@
extends Area2D
export var left=false
const MOTION_SPEED=150
var motion = 0
var you_hidden=false
onready var screen_size = get_viewport_rect().size
#synchronize position and speed to the other peers
slave func set_pos_and_motion(p_pos,p_motion):
position = p_pos
motion=p_motion
func _hide_you_label():
you_hidden=true
get_node("you").hide()
func _process(delta):
#is the master of the paddle
if (is_network_master()):
motion = 0
if (Input.is_action_pressed("move_up")):
motion -= 1
elif (Input.is_action_pressed("move_down")):
motion += 1
if (not you_hidden and motion!=0):
_hide_you_label()
motion*=MOTION_SPEED
#using unreliable to make sure position is updated as fast as possible, even if one of the calls is dropped
rpc_unreliable("set_pos_and_motion",position,motion)
else:
if (not you_hidden):
_hide_you_label()
translate( Vector2(0,motion*delta) )
# set screen limits
if (position.y < 0 ):
position.y = 0
elif (position.y > screen_size.y):
position.y = screen_size.y
func _ready():
set_process(true)
func _on_paddle_area_enter( area ):
if (is_network_master()):
area.rpc("bounce",left,randf()) #random for new direction generated on each peer

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,24 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,52 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://paddle.gd" type="Script" id=1]
[ext_resource path="res://paddle.png" type="Texture" id=2]
[sub_resource type="CapsuleShape2D" id=1]
custom_solver_bias = 0.0
radius = 4.78568
height = 23.6064
[node name="paddle" type="Area2D"]
input_pickable = true
shapes/0/shape = SubResource( 1 )
shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 )
shapes/0/trigger = false
gravity_vec = Vector2( 0, 1 )
gravity = 98.0
linear_damp = 0.1
angular_damp = 1.0
script = ExtResource( 1 )
left = false
[node name="sprite" type="Sprite" parent="."]
texture = ExtResource( 2 )
[node name="shape" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
trigger = false
_update_shape_index = 0
[node name="you" type="Label" parent="."]
margin_left = -12.0
margin_top = 21.0
margin_right = 11.0
margin_bottom = 35.0
rect_clip_content = false
mouse_filter = 2
size_flags_vertical = 0
text = "You"
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[connection signal="area_entered" from="." to="." method="_on_paddle_area_enter"]

View File

@ -0,0 +1,53 @@
extends Node2D
const SCORE_TO_WIN=10
var score_left = 0
var score_right = 0
signal game_finished()
sync func update_score(add_to_left):
if (add_to_left):
score_left+=1
get_node("score_left").set_text( str(score_left) )
else:
score_right+=1
get_node("score_right").set_text( str(score_right) )
var game_ended = false
if (score_left==SCORE_TO_WIN):
get_node("winner_left").show()
game_ended=true
elif (score_right==SCORE_TO_WIN):
get_node("winner_right").show()
game_ended=true
if (game_ended):
get_node("exit_game").show()
get_node("ball").rpc("stop")
func _on_exit_game_pressed():
emit_signal("game_finished")
func _ready():
# by default, all nodes in server inherit from master
# while all nodes in clients inherit from slave
if (get_tree().is_network_server()):
#set to not control player 2. since it's master as everything else
# get_node("player2").set_network_mode(NETWORK_MODE_SLAVE)
get_node("player2").set_network_master(2, true)
else:
#set to control player 2, as it's slave as everything else
get_node("player2").set_network_mode(NETWORK_MODE_MASTER)
#let each paddle know which one is left, too
get_node("player1").left=true
get_node("player2").left=false

View File

@ -0,0 +1,172 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://pong.gd" type="Script" id=1]
[ext_resource path="res://separator.png" type="Texture" id=2]
[ext_resource path="res://paddle.tscn" type="PackedScene" id=3]
[ext_resource path="res://ball.tscn" type="PackedScene" id=4]
[ext_resource path="res://ball.gd" type="Script" id=5]
[node name="pong" type="Node2D"]
script = ExtResource( 1 )
[node name="separator" type="Sprite" parent="."]
position = Vector2( 512.309, 298.233 )
scale = Vector2( 1.04883, 1.4884 )
texture = ExtResource( 2 )
[node name="player1" parent="." instance=ExtResource( 3 )]
position = Vector2( 19.9447, 267.036 )
audio_bus_override = false
audio_bus_name = "Master"
[node name="sprite" parent="player1"]
modulate = Color( 1, 0, 0.960938, 1 )
[node name="you" parent="player1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
rect_pivot_offset = Vector2( 0, 0 )
size_flags_horizontal = 1
[node name="player2" parent="." instance=ExtResource( 3 )]
position = Vector2( 995.015, 244.876 )
audio_bus_override = false
audio_bus_name = "Master"
[node name="sprite" parent="player2"]
modulate = Color( 0, 0.929688, 1, 1 )
[node name="you" parent="player2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
rect_pivot_offset = Vector2( 0, 0 )
size_flags_horizontal = 1
[node name="ball" parent="." instance=ExtResource( 4 )]
position = Vector2( 513.02, 248.2 )
audio_bus_override = false
audio_bus_name = "Master"
script = ExtResource( 5 )
[node name="score_left" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 96.0
margin_top = 57.0
margin_right = 104.0
margin_bottom = 71.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
text = "0"
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="score_right" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 907.0
margin_top = 62.0
margin_right = 915.0
margin_bottom = 76.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
text = "0"
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="winner_left" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 60.0
margin_top = 33.0
margin_right = 137.0
margin_bottom = 47.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
text = "The Winner!"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="winner_right" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 872.0
margin_top = 41.0
margin_right = 949.0
margin_bottom = 55.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
text = "The Winner!"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="exit_game" type="Button" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 412.0
margin_top = 20.0
margin_right = 489.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Exit Game"
flat = false
[connection signal="pressed" from="exit_game" to="." method="_on_exit_game_pressed"]
[editable path="player1"]
[editable path="player2"]

View File

@ -0,0 +1,41 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=3
[application]
run/main_scene="res://lobby.tscn"
name="Pong Multiplayer"
main_scene="res://lobby.tscn"
icon="res://icon.png"
[display]
width=640
height=400
stretch_2d=true
[gdnative]
singletons=[ ]
[input]
move_up=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
]
move_down=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
]
[memory]
multithread/thread_rid_pool_prealloc=60
[render]
default_clear_color=Color( 0, 0, 0, 1 )

View File

@ -0,0 +1,23 @@
[general]
singleton=true
load_once=true
symbol_prefix="godot_"
[entry]
X11.64="res://addons/pythonscript/x11-64/libpythonscript.so"
X11.32="res://addons/pythonscript/x11-32/libpythonscript.so"
Server.64="res://addons/pythonscript/x11-64/libpythonscript.so"
Windows.64="res://addons/pythonscript/windows-64/pythonscript.dll"
Windows.32="res://addons/pythonscript/windows-32/pythonscript.dll"
OSX.64="res://addons/pythonscript/osx-64/libpythonscript.dylib"
[dependencies]
X11.64=[]
X11.32=[]
Server.64=[]
Windows.64=[]
Windows.32=[]
OSX.64=[]

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

View File

@ -0,0 +1,24 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=true
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,13 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_bindings.py`
from godot._hazmat.gdnative_api_struct cimport *
from godot._hazmat.gdapi cimport pythonscript_gdapi10 as gdapi10
from godot.builtins cimport *
{% from 'class.tmpl.pxd' import render_class_pxd -%}
{%- for cls in classes %}
{{ render_class_pxd(cls) }}
{%- endfor %}
cdef void _initialize_bindings()

View File

@ -0,0 +1,164 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_bindings.py`
# Imports needed for typing
# (Note PEP484 state that import without as and * are not exposed by the stub file)
from typing import Any, Union
from enum import IntFlag
from godot.builtins import (
AABB,
Array,
Basis,
Color,
Dictionary,
NodePath,
Plane,
Quat,
Rect2,
RID,
Transform2D,
Transform,
Vector2,
Vector3,
PoolByteArray,
PoolIntArray,
PoolRealArray,
PoolStringArray,
PoolVector2Array,
PoolVector3Array,
PoolColorArray,
GDString,
)
class Error(IntFlag):
OK: int
FAILED: int
ERR_UNAVAILABLE: int
ERR_UNCONFIGURED: int
ERR_UNAUTHORIZED: int
ERR_PARAMETER_RANGE_ERROR: int
ERR_OUT_OF_MEMORY: int
ERR_FILE_NOT_FOUND: int
ERR_FILE_BAD_DRIVE: int
ERR_FILE_BAD_PATH: int
ERR_FILE_NO_PERMISSION: int
ERR_FILE_ALREADY_IN_USE: int
ERR_FILE_CANT_OPEN: int
ERR_FILE_CANT_WRITE: int
ERR_FILE_CANT_READ: int
ERR_FILE_UNRECOGNIZED: int
ERR_FILE_CORRUPT: int
ERR_FILE_MISSING_DEPENDENCIES: int
ERR_FILE_EOF: int
ERR_CANT_OPEN: int
ERR_CANT_CREATE: int
ERR_QUERY_FAILED: int
ERR_ALREADY_IN_USE: int
ERR_LOCKED: int
ERR_TIMEOUT: int
ERR_CANT_CONNECT: int
ERR_CANT_RESOLVE: int
ERR_CONNECTION_ERROR: int
ERR_CANT_ACQUIRE_RESOURCE: int
ERR_CANT_FORK: int
ERR_INVALID_DATA: int
ERR_INVALID_PARAMETER: int
ERR_ALREADY_EXISTS: int
ERR_DOES_NOT_EXIST: int
ERR_DATABASE_CANT_READ: int
ERR_DATABASE_CANT_WRITE: int
ERR_COMPILATION_FAILED: int
ERR_METHOD_NOT_FOUND: int
ERR_LINK_FAILED: int
ERR_SCRIPT_FAILED: int
ERR_CYCLIC_LINK: int
ERR_INVALID_DECLARATION: int
ERR_DUPLICATE_SYMBOL: int
ERR_PARSE_ERROR: int
ERR_BUSY: int
ERR_SKIP: int
ERR_HELP: int
ERR_BUG: int
ERR_PRINTER_ON_FIRE: int
class VariantType(IntFlag):
NIL: int
BOOL: int
INT: int
REAL: int
STRING: int
VECTOR2: int
RECT2: int
VECTOR3: int
TRANSFORM2D: int
PLANE: int
QUAT: int
AABB: int
BASIS: int
TRANSFORM: int
COLOR: int
NODE_PATH: int
RID: int
OBJECT: int
DICTIONARY: int
ARRAY: int
POOL_BYTE_ARRAY: int
POOL_INT_ARRAY: int
POOL_REAL_ARRAY: int
POOL_STRING_ARRAY: int
POOL_VECTOR2_ARRAY: int
POOL_VECTOR3_ARRAY: int
POOL_COLOR_ARRAY: int
class VariantOperator(IntFlag):
EQUAL: int
NOT_EQUAL: int
LESS: int
LESS_EQUAL: int
GREATER: int
GREATER_EQUAL: int
ADD: int
SUBTRACT: int
MULTIPLY: int
DIVIDE: int
NEGATE: int
POSITIVE: int
MODULE: int
STRING_CONCAT: int
SHIFT_LEFT: int
SHIFT_RIGHT: int
BIT_AND: int
BIT_OR: int
BIT_XOR: int
BIT_NEGATE: int
AND: int
OR: int
XOR: int
NOT: int
IN: int
MAX: int
### Classes ###
{% from 'class.tmpl.pyi' import render_class, render_class_gdapi_ptrs_init -%}
{%- for cls in classes %}
{{ render_class(cls) }}
{%- endfor %}
### Global constants ###
{% for key, value in constants.items() %}
{{key}}: int
{% endfor %}
### Singletons ###
{% for cls in classes %}
{% if cls.singleton %}
{{ cls.singleton }}: {{ cls.name }}
{% endif %}
{% endfor %}

View File

@ -0,0 +1,191 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_bindings.py`
from godot._hazmat.gdnative_api_struct cimport *
from godot._hazmat.gdapi cimport pythonscript_gdapi10 as gdapi10
from godot._hazmat.conversion cimport *
from godot.builtins cimport *
from enum import IntFlag
__ERR_MSG_BINDING_NOT_AVAILABLE = "No Godot binding available"
class Error(IntFlag):
OK = godot_error.GODOT_OK
FAILED = godot_error.GODOT_FAILED
ERR_UNAVAILABLE = godot_error.GODOT_ERR_UNAVAILABLE
ERR_UNCONFIGURED = godot_error.GODOT_ERR_UNCONFIGURED
ERR_UNAUTHORIZED = godot_error.GODOT_ERR_UNAUTHORIZED
ERR_PARAMETER_RANGE_ERROR = godot_error.GODOT_ERR_PARAMETER_RANGE_ERROR
ERR_OUT_OF_MEMORY = godot_error.GODOT_ERR_OUT_OF_MEMORY
ERR_FILE_NOT_FOUND = godot_error.GODOT_ERR_FILE_NOT_FOUND
ERR_FILE_BAD_DRIVE = godot_error.GODOT_ERR_FILE_BAD_DRIVE
ERR_FILE_BAD_PATH = godot_error.GODOT_ERR_FILE_BAD_PATH
ERR_FILE_NO_PERMISSION = godot_error.GODOT_ERR_FILE_NO_PERMISSION
ERR_FILE_ALREADY_IN_USE = godot_error.GODOT_ERR_FILE_ALREADY_IN_USE
ERR_FILE_CANT_OPEN = godot_error.GODOT_ERR_FILE_CANT_OPEN
ERR_FILE_CANT_WRITE = godot_error.GODOT_ERR_FILE_CANT_WRITE
ERR_FILE_CANT_READ = godot_error.GODOT_ERR_FILE_CANT_READ
ERR_FILE_UNRECOGNIZED = godot_error.GODOT_ERR_FILE_UNRECOGNIZED
ERR_FILE_CORRUPT = godot_error.GODOT_ERR_FILE_CORRUPT
ERR_FILE_MISSING_DEPENDENCIES = godot_error.GODOT_ERR_FILE_MISSING_DEPENDENCIES
ERR_FILE_EOF = godot_error.GODOT_ERR_FILE_EOF
ERR_CANT_OPEN = godot_error.GODOT_ERR_CANT_OPEN
ERR_CANT_CREATE = godot_error.GODOT_ERR_CANT_CREATE
ERR_QUERY_FAILED = godot_error.GODOT_ERR_QUERY_FAILED
ERR_ALREADY_IN_USE = godot_error.GODOT_ERR_ALREADY_IN_USE
ERR_LOCKED = godot_error.GODOT_ERR_LOCKED
ERR_TIMEOUT = godot_error.GODOT_ERR_TIMEOUT
ERR_CANT_CONNECT = godot_error.GODOT_ERR_CANT_CONNECT
ERR_CANT_RESOLVE = godot_error.GODOT_ERR_CANT_RESOLVE
ERR_CONNECTION_ERROR = godot_error.GODOT_ERR_CONNECTION_ERROR
ERR_CANT_ACQUIRE_RESOURCE = godot_error.GODOT_ERR_CANT_ACQUIRE_RESOURCE
ERR_CANT_FORK = godot_error.GODOT_ERR_CANT_FORK
ERR_INVALID_DATA = godot_error.GODOT_ERR_INVALID_DATA
ERR_INVALID_PARAMETER = godot_error.GODOT_ERR_INVALID_PARAMETER
ERR_ALREADY_EXISTS = godot_error.GODOT_ERR_ALREADY_EXISTS
ERR_DOES_NOT_EXIST = godot_error.GODOT_ERR_DOES_NOT_EXIST
ERR_DATABASE_CANT_READ = godot_error.GODOT_ERR_DATABASE_CANT_READ
ERR_DATABASE_CANT_WRITE = godot_error.GODOT_ERR_DATABASE_CANT_WRITE
ERR_COMPILATION_FAILED = godot_error.GODOT_ERR_COMPILATION_FAILED
ERR_METHOD_NOT_FOUND = godot_error.GODOT_ERR_METHOD_NOT_FOUND
ERR_LINK_FAILED = godot_error.GODOT_ERR_LINK_FAILED
ERR_SCRIPT_FAILED = godot_error.GODOT_ERR_SCRIPT_FAILED
ERR_CYCLIC_LINK = godot_error.GODOT_ERR_CYCLIC_LINK
ERR_INVALID_DECLARATION = godot_error.GODOT_ERR_INVALID_DECLARATION
ERR_DUPLICATE_SYMBOL = godot_error.GODOT_ERR_DUPLICATE_SYMBOL
ERR_PARSE_ERROR = godot_error.GODOT_ERR_PARSE_ERROR
ERR_BUSY = godot_error.GODOT_ERR_BUSY
ERR_SKIP = godot_error.GODOT_ERR_SKIP
ERR_HELP = godot_error.GODOT_ERR_HELP
ERR_BUG = godot_error.GODOT_ERR_BUG
ERR_PRINTER_ON_FIRE = godot_error.GODOT_ERR_PRINTER_ON_FIRE
class VariantType(IntFlag):
NIL = godot_variant_type.GODOT_VARIANT_TYPE_NIL
BOOL = godot_variant_type.GODOT_VARIANT_TYPE_BOOL
INT = godot_variant_type.GODOT_VARIANT_TYPE_INT
REAL = godot_variant_type.GODOT_VARIANT_TYPE_REAL
STRING = godot_variant_type.GODOT_VARIANT_TYPE_STRING
VECTOR2 = godot_variant_type.GODOT_VARIANT_TYPE_VECTOR2
RECT2 = godot_variant_type.GODOT_VARIANT_TYPE_RECT2
VECTOR3 = godot_variant_type.GODOT_VARIANT_TYPE_VECTOR3
TRANSFORM2D = godot_variant_type.GODOT_VARIANT_TYPE_TRANSFORM2D
PLANE = godot_variant_type.GODOT_VARIANT_TYPE_PLANE
QUAT = godot_variant_type.GODOT_VARIANT_TYPE_QUAT
AABB = godot_variant_type.GODOT_VARIANT_TYPE_AABB
BASIS = godot_variant_type.GODOT_VARIANT_TYPE_BASIS
TRANSFORM = godot_variant_type.GODOT_VARIANT_TYPE_TRANSFORM
COLOR = godot_variant_type.GODOT_VARIANT_TYPE_COLOR
NODE_PATH = godot_variant_type.GODOT_VARIANT_TYPE_NODE_PATH
RID = godot_variant_type.GODOT_VARIANT_TYPE_RID
OBJECT = godot_variant_type.GODOT_VARIANT_TYPE_OBJECT
DICTIONARY = godot_variant_type.GODOT_VARIANT_TYPE_DICTIONARY
ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_ARRAY
POOL_BYTE_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY
POOL_INT_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_INT_ARRAY
POOL_REAL_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_REAL_ARRAY
POOL_STRING_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_STRING_ARRAY
POOL_VECTOR2_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_VECTOR2_ARRAY
POOL_VECTOR3_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_VECTOR3_ARRAY
POOL_COLOR_ARRAY = godot_variant_type.GODOT_VARIANT_TYPE_POOL_COLOR_ARRAY
class VariantOperator(IntFlag):
EQUAL = godot_variant_operator.GODOT_VARIANT_OP_EQUAL
NOT_EQUAL = godot_variant_operator.GODOT_VARIANT_OP_NOT_EQUAL
LESS = godot_variant_operator.GODOT_VARIANT_OP_LESS
LESS_EQUAL = godot_variant_operator.GODOT_VARIANT_OP_LESS_EQUAL
GREATER = godot_variant_operator.GODOT_VARIANT_OP_GREATER
GREATER_EQUAL = godot_variant_operator.GODOT_VARIANT_OP_GREATER_EQUAL
ADD = godot_variant_operator.GODOT_VARIANT_OP_ADD
SUBTRACT = godot_variant_operator.GODOT_VARIANT_OP_SUBTRACT
MULTIPLY = godot_variant_operator.GODOT_VARIANT_OP_MULTIPLY
DIVIDE = godot_variant_operator.GODOT_VARIANT_OP_DIVIDE
NEGATE = godot_variant_operator.GODOT_VARIANT_OP_NEGATE
POSITIVE = godot_variant_operator.GODOT_VARIANT_OP_POSITIVE
MODULE = godot_variant_operator.GODOT_VARIANT_OP_MODULE
STRING_CONCAT = godot_variant_operator.GODOT_VARIANT_OP_STRING_CONCAT
SHIFT_LEFT = godot_variant_operator.GODOT_VARIANT_OP_SHIFT_LEFT
SHIFT_RIGHT = godot_variant_operator.GODOT_VARIANT_OP_SHIFT_RIGHT
BIT_AND = godot_variant_operator.GODOT_VARIANT_OP_BIT_AND
BIT_OR = godot_variant_operator.GODOT_VARIANT_OP_BIT_OR
BIT_XOR = godot_variant_operator.GODOT_VARIANT_OP_BIT_XOR
BIT_NEGATE = godot_variant_operator.GODOT_VARIANT_OP_BIT_NEGATE
AND = godot_variant_operator.GODOT_VARIANT_OP_AND
OR = godot_variant_operator.GODOT_VARIANT_OP_OR
XOR = godot_variant_operator.GODOT_VARIANT_OP_XOR
NOT = godot_variant_operator.GODOT_VARIANT_OP_NOT
IN = godot_variant_operator.GODOT_VARIANT_OP_IN
MAX = godot_variant_operator.GODOT_VARIANT_OP_MAX
### Classes ###
{% from 'class.tmpl.pyx' import render_class, render_class_gdapi_ptrs_init -%}
{%- for cls in classes %}
{{ render_class(cls) }}
{%- endfor %}
### Global constants ###
{% for key, value in constants.items() %}
{{key}} = {{value}}
{% endfor %}
### Class&singletons needed for Pythonscript bootstrap ###
# Godot classes&singletons are not all available when loading Pythonscript.
# Hence greedy loading is done only for items needed for Pythonscript
# bootstrap.
# The remaining loading will be achieved when loading the first python script
# (where at this point Godot should have finished it initialization).
{% set early_needed_bindings = ["_OS", "_ProjectSettings"] %}
cdef godot_object *_ptr
{% for cls in classes %}
{% if cls.name in early_needed_bindings %}
{{ render_class_gdapi_ptrs_init(cls) }}
{% if cls.singleton %}
_ptr = gdapi10.godot_global_get_singleton("{{ cls.singleton }}")
if _ptr != NULL:
{{ cls.singleton }} = {{ cls.name }}.from_ptr(_ptr)
else:
print("ERROR: cannot load singleton `{{ cls.singleton }}` required for Pythonscript init")
{% endif %}
{% endif %}
{% endfor %}
### Remining bindings late intialization ###
cdef bint _bindings_initialized = False
{% for cls in classes %}
{% if cls.name not in early_needed_bindings %}
{% if cls.singleton %}
{{ cls.singleton }} = {{ cls.name }}.from_ptr(NULL)
{% endif %}
{% endif %}
{% endfor %}
cdef void _initialize_bindings():
global _bindings_initialized
if _bindings_initialized:
return
{%- for cls in classes %}
{%- if cls.name not in early_needed_bindings %}
{{ render_class_gdapi_ptrs_init(cls) | indent }}
{%- if cls.singleton %}
global {{ cls.singleton }}
(<{{ cls["name"] }}>{{ cls.singleton }})._gd_ptr = gdapi10.godot_global_get_singleton("{{ cls.singleton }}")
if (<{{ cls["name"] }}>{{ cls.singleton }})._gd_ptr == NULL:
print('Cannot retreive singleton {{ cls.singleton }}')
{%- endif %}
{%- endif %}
{%- endfor %}
_bindings_initialized = True

View File

@ -0,0 +1,19 @@
{% from 'method.tmpl.pyx' import get_method_bind_register_name, render_method_signature %}
{% macro render_class_pxd(cls) %}
cdef class {{ cls.name }}({{ cls.base_class }}):
{% if not cls.base_class %}
cdef godot_object *_gd_ptr
@staticmethod
cdef inline Object cast_from_variant(const godot_variant *p_gdvar)
@staticmethod
cdef inline Object cast_from_ptr(godot_object *ptr)
{% endif %}
@staticmethod
cdef {{ cls.name }} from_ptr(godot_object *_ptr)
{% endmacro %}

View File

@ -0,0 +1,77 @@
{# TODO: Handle signals #}
{% macro render_class(cls) %}
class {{ cls.name }}({{ cls.base_class }}):
{% if not cls.base_class %}
def free(self) -> None: ...
def __init__(self): ...
def __repr__(self) -> str: ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, other: object) -> bool: ...
def __getattr__(self, name: str) -> Any: ...
def __setattr__(self, name: str, value: Any): ...
def call(self, name: str, *args) -> Any: ...
{% endif %}
{% if not cls.singleton and cls.instantiable %}
{% if cls.is_reference %}
def __init__(self): ...
{% else %}
@staticmethod
def new() -> {{ cls.name }}: ...
{% endif %}
{% if cls.name == "Reference" %}
@classmethod
def new(cls) -> Reference: ...
{% endif %}
{% endif %}
{% if cls.constants | length %}
# Constants
{% endif %}
{% for key, value in cls.constants.items() %}
{{ key }}: int
{% endfor %}
{% if cls.enums | length %}
# Enums
{% endif %}
{% for enum in cls.enums %}
class {{ enum.name }}(IntFlag):
{% for key, value in enum.values.items() %}
{{ key }}: int
{% endfor %}
{% endfor %}
{% if cls.methods | length %}
# Methods
{% endif %}
{# TODO: Use typing for params&return #}
{% for method in cls.methods %}
{% if method.name != "free" %}
def {{ method.name }}(self,
{%- for arg in method.arguments %}
{{ arg.name }}: {{ arg.type.py_type }}
{%- if arg.has_default_value %}
={{ arg.default_value }}
{%- endif %}
,
{%- endfor %}
) -> {{ method.return_type.py_type }}: ...
{% endif %}
{% endfor %}
{% if cls.properties | length %}
# Properties
{% endif %}
{% for prop in cls.properties %}
{{ prop.name }}: {{ prop.type.py_type }}
{% endfor %}
{% if not cls.constants and not cls.enums and not cls.methods and not cls.properties %}
pass
{% endif %}
{% endmacro %}

View File

@ -0,0 +1,280 @@
{% from 'method.tmpl.pyx' import render_method, get_method_bind_register_name %}
{% macro render_class_gdapi_ptrs_init(cls) %}
{% if not cls.singleton %}
global __{{ cls.name }}_constructor
__{{ cls.name }}_constructor = gdapi10.godot_get_class_constructor("{{ cls.name }}")
{% endif %}
{% for method in cls.methods %}
global {{ get_method_bind_register_name(cls, method) }}
{{ get_method_bind_register_name(cls, method) }} = gdapi10.godot_method_bind_get_method("{{ cls.bind_register_name }}", "{{ method.name }}")
{% endfor %}
{% endmacro %}
{# TODO: Handle signals #}
{% macro render_class(cls) %}
{% if not cls.base_class %}
from cpython.object cimport PyObject_GenericGetAttr, PyObject_GenericSetAttr
{% endif %}
{% if not cls.singleton %}
cdef godot_class_constructor __{{ cls.name }}_constructor = NULL
{% endif %}
{% for method in cls.methods %}
cdef godot_method_bind *{{ get_method_bind_register_name(cls, method) }} = NULL
{% endfor %}
cdef class {{ cls.name }}({{ cls.base_class }}):
{% if not cls.base_class %}
# free is virtual but this is not marked in api.json :'(
def free(self):
with nogil:
gdapi10.godot_object_destroy(self._gd_ptr)
def __init__(self):
raise RuntimeError(
f"Use `new()` method to instantiate non-refcounted Godot object (and don't forget to free it !)"
)
def __repr__(self):
return f"<{type(self).__name__} wrapper on 0x{<size_t>self._gd_ptr:x}>"
@staticmethod
cdef inline Object cast_from_variant(const godot_variant *p_gdvar):
cdef godot_object *ptr = gdapi10.godot_variant_as_object(p_gdvar)
# Retreive class
cdef GDString classname = GDString.__new__(GDString)
with nogil:
gdapi10.godot_method_bind_ptrcall(
__methbind__Object__get_class,
ptr,
NULL,
&classname._gd_data
)
return globals()[str(classname)]._from_ptr(<size_t>ptr)
@staticmethod
cdef inline Object cast_from_ptr(godot_object *ptr):
# Retreive class
cdef GDString classname = GDString.__new__(GDString)
with nogil:
gdapi10.godot_method_bind_ptrcall(
__methbind__Object__get_class,
ptr,
NULL,
&classname._gd_data
)
return globals()[str(classname)]._from_ptr(<size_t>ptr)
def __eq__(self, other):
try:
return self._gd_ptr == (<{{ cls.name }}>other)._gd_ptr
except TypeError:
return False
def __ne__(self, other):
try:
return self._gd_ptr != (<{{ cls.name }}>other)._gd_ptr
except TypeError:
return True
def __getattr__(self, name):
cdef GDString gdname = GDString(name)
cdef GDString gdnamefield = GDString("name")
# If a script is attached to the object, we expose here it methods
if not hasattr(type(self), '__exposed_python_class'):
if self.has_method(name):
def _call(*args):
return {{ cls.name }}.callv(self, gdname, Array(args))
return _call
# from functools import partial
# return partial(self.call, gdname)
elif any(x for x in self.get_property_list() if x[gdnamefield] == gdname):
# TODO: Godot currently lacks a `has_property` method
return self.get(gdname)
raise AttributeError(
f"`{type(self).__name__}` object has no attribute `{name}`"
)
def __setattr__(self, name, value):
cdef GDString gdname = GDString(name)
cdef GDString gdnamefield = GDString("name")
if hasattr(type(self), '__exposed_python_class'):
PyObject_GenericSetAttr(self, name, value)
return
# Could retrieve the item inside the Godot class, try to look into
# the attached script if it has one
else:
if any(x for x in self.get_property_list() if x[gdnamefield] == gdname):
# TODO: Godot currently lacks a `has_property` method
self.set(name, value)
return
raise AttributeError(
f"`{type(self).__name__}` object has no attribute `{name}`"
)
def call(self, name, *args):
return self.callv(name, Array(args))
{% endif %}
{% if not cls.singleton and cls.instantiable %}
{% if cls.is_reference %}
def __init__(self):
if __{{ cls.name }}_constructor == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
cdef godot_bool __ret
with nogil:
self._gd_ptr = __{{ cls["name"] }}_constructor()
if self._gd_ptr is NULL:
raise MemoryError
gdapi10.godot_method_bind_ptrcall(
__methbind__Reference__init_ref,
self._gd_ptr,
NULL,
&__ret
)
{% else %}
@staticmethod
def new():
if __{{ cls.name }}_constructor == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
with nogil:
wrapper._gd_ptr = __{{ cls.name }}_constructor()
if wrapper._gd_ptr is NULL:
raise MemoryError
return wrapper
{% endif %}
{% if cls.name == "Reference" %}
@classmethod
def new(cls):
raise RuntimeError(f"Refcounted Godot object must be created with `{ cls.__name__ }()`")
def __dealloc__(self):
cdef godot_bool __ret
if self._gd_ptr == NULL:
return
with nogil:
gdapi10.godot_method_bind_ptrcall(
__methbind__Reference__unreference,
self._gd_ptr,
NULL,
&__ret
)
if __ret:
gdapi10.godot_object_destroy(self._gd_ptr)
{% endif %}
{% endif %}
@staticmethod
cdef {{ cls.name }} from_ptr(godot_object *_ptr):
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
wrapper._gd_ptr = _ptr
{% if cls.is_reference %}
# Note we steal the reference from the caller given we
# don't call `Reference.reference` here
{% endif %}
return wrapper
{% if not cls.singleton and cls.instantiable %}
@classmethod
def _new(cls):
cdef godot_object* ptr = __{{ cls.name }}_constructor()
if ptr is NULL:
raise MemoryError
return <size_t>ptr
{% endif %}
@staticmethod
def _from_ptr(ptr):
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
# /!\ doing `<godot_object*>ptr` would return the address of
# the PyObject instead of casting it value !
wrapper._gd_ptr = <godot_object *><size_t>ptr
{% if cls.is_reference %}
# Note we steal the reference from the caller given we
# don't call `Reference.reference` here
{% endif %}
return wrapper
{% if cls.constants | length %}
# Constants
{% endif %}
{% for key, value in cls.constants.items() %}
{{ key }} = {{ value }}
{% endfor %}
{% if cls.enums | length %}
# Enums
{% endif %}
{% for enum in cls.enums %}
{{ enum.name }} = IntFlag("{{ enum.name }}", {
{% for key, value in enum.values.items() %}
"{{ key }}": {{ value }},
{% endfor %}
})
{% endfor %}
{% if cls.methods | length %}
# Methods
{% endif %}
{# TODO: Use typing for params&return #}
{% for method in cls.methods %}
{% if method.name != "free" %}
{{ render_method(cls, method) | indent }}
{% endif %}
{% endfor %}
{% if cls.properties | length %}
# Properties
{% endif %}
{#
TODO: some properties has / in there name
TODO: some properties pass a parameter to the setter/getter
TODO: see PinJoint.params/bias for a good example
#}
{% for prop in cls.properties %}
@property
def {{ prop.name }}(self):
{% if prop.is_supported %}
return self.{{ prop.getter }}({% if prop.index is not none %}{{ prop.index }}{% endif %})
{% else %}
raise NotImplementedError("{{prop.unsupported_reason}}")
{% endif %}
{% if prop.setter %}
@{{ prop.name }}.setter
def {{ prop.name }}(self, val):
{% if prop.is_supported %}
self.{{ prop.setter }}({% if prop.index is not none %}{{ prop.index }},{% endif %}val)
{% else %}
raise NotImplementedError("{{prop.unsupported_reason}}")
{% endif %}
{% endif %}
{% endfor %}
{% endmacro %}

View File

@ -0,0 +1,166 @@
{% macro get_method_bind_register_name(cls, method) -%}
__methbind__{{ cls.name }}__{{ method.name }}
{%- endmacro %}
{% macro render_method_c_signature(method) %}
{{ method.return_type.c_type }} {{ method.name }}(self,
{%- for arg in method.arguments %}
{{ arg.type.c_type }} {{ arg.name }},
{%- endfor %}
)
{%- endmacro %}
{% macro render_method_signature(method) %}
{{ method.name }}(self,
{%- for arg in method.arguments %}
{%- if arg.type.c_type in ("godot_string", "godot_node_path") %}
object {{ arg.name }}
{%- else %}
{{ arg.type.cy_type }} {{ arg.name }}
{#- `not None` is only for Python arguments so no need for base type #}
{#- if default value is NULL, None should be allowed #}
{%- if not arg.type.is_base_type and not (arg.has_default_value and arg.default_value == "None") %}
not None
{%- endif %}
{%- endif %}
{%- if arg.has_default_value %}
={{ arg.default_value }}
{%- endif %}
,
{%- endfor %}
)
{%- endmacro %}
{% macro _render_method_return(method, retval="__ret") %}
{% if method.return_type.c_type == "void" %}
return
{% elif method.return_type.is_object %}
if {{ retval }} == NULL:
return None
else:
return Object.cast_from_ptr({{ retval }})
{% elif method.return_type.c_type == "godot_variant" %}
try:
return godot_variant_to_pyobj(&{{ retval }})
finally:
with nogil:
gdapi10.godot_variant_destroy(&{{ retval }})
{% elif method.return_type.is_enum %}
return {{ method.return_type.py_type }}({{ retval }})
{% else %}
return {{ retval }}
{% endif %}
{%- endmacro %}
{% macro _render_method_cook_args(method, argsval="__args") %}
{% if (method.arguments | length ) != 0 %}
cdef const void *{{ argsval }}[{{ method.arguments | length }}]
{% endif %}
{% for arg in method.arguments %}
{% set i = loop.index - 1 %}
# {{ arg.type.c_type }} {{ arg.name }}
{% if arg.type.c_type == "godot_string" %}
cdef GDString __gdstr_{{ arg.name }} = ensure_is_gdstring({{ arg.name }})
{{ argsval }}[{{ i }}] = <void*>(&__gdstr_{{ arg.name }}._gd_data)
{% elif arg.type.c_type == "godot_node_path" %}
cdef NodePath __nodepath_{{ arg.name }} = ensure_is_nodepath({{ arg.name }})
{{ argsval }}[{{ i }}] = <void*>(&__nodepath_{{ arg.name }}._gd_data)
{% elif arg.type.is_object %}
{%- if arg.has_default_value and arg.default_value == "None" %}
{{ argsval }}[{{ i }}] = <void*>{{ arg.name }}._gd_ptr if {{ arg.name }} is not None else NULL
{%- else %}
{{ argsval }}[{{ i }}] = <void*>{{ arg.name }}._gd_ptr
{%- endif %}
{% elif arg.type.c_type == "godot_variant" %}
cdef godot_variant __var_{{ arg.name }}
pyobj_to_godot_variant({{ arg.name }}, &__var_{{ arg.name }})
{{ argsval }}[{{ i }}] = <void*>(&__var_{{ arg.name }})
{% elif arg.type.is_builtin %}
{{ argsval }}[{{ i }}] = <void*>(&{{ arg.name }}._gd_data)
{% elif arg.type.c_type == "godot_real" %}
# ptrcall does not work with single precision floats, so we must convert to a double
cdef double {{ arg.name }}_d = <double>{{ arg.name }};
{{ argsval }}[{{ i }}] = &{{ arg.name }}_d
{% else %}
{{ argsval }}[{{ i }}] = &{{ arg.name }}
{% endif %}
{% endfor %}
{%- endmacro %}
{% macro _render_method_destroy_args(method) %}
{% for arg in method.arguments %}
{% set i = loop.index - 1 %}
{% if arg.type.c_type == "godot_variant" %}
with nogil:
gdapi10.godot_variant_destroy(&__var_{{ arg.name }})
{% endif %}
{% endfor %}
{%- endmacro %}
{% macro _render_method_call(cls, method, argsval="__args", retval="__ret") %}
{% if method.return_type.c_type == "void" %}
{% set retval_as_arg = "NULL" %}
{% elif method.return_type.is_object %}
# It's important to initialize this pointer to null given
# in case of Reference, Godot will try to decrease the
# refcount if the pointer is valid !
# (see https://github.com/godotengine/godot/issues/35609)
cdef godot_object *{{ retval }} = NULL
{% set retval_as_arg = "&{}".format(retval) %}
{% elif method.return_type.c_type == "godot_variant" %}
cdef godot_variant {{ retval }}
{% set retval_as_arg = "&{}".format(retval) %}
{% elif method.return_type.is_builtin %}
{% set cy_type = method.return_type.cy_type %}
cdef {{ cy_type }} {{ retval }} = {{ cy_type }}.__new__({{ cy_type }})
{% set retval_as_arg = "&{}._gd_data".format(retval) %}
{% elif method.return_type.c_type == "godot_real" %}
# ptrcall does not work with single precision floats, so we must convert to a double
cdef double {{ retval }}
{% set retval_as_arg = "&{}".format(retval) %}
{% else %}
cdef {{ method.return_type.c_type }} {{ retval }}
{% set retval_as_arg = "&{}".format(retval) %}
{% endif %}
if {{ get_method_bind_register_name(cls, method) }} == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
with nogil:
gdapi10.godot_method_bind_ptrcall(
{{ get_method_bind_register_name(cls, method) }},
self._gd_ptr,
{% if (method.arguments | length ) != 0 %}
{{ argsval }},
{%else %}
NULL,
{% endif %}
{{ retval_as_arg }}
)
{%- endmacro %}
{% macro render_method(cls, method) %}
# {{ render_method_c_signature(method) }}
def {{ render_method_signature(method) }}:
{% if method.is_virtual %}
cdef Array args = Array()
{% for arg in method.arguments %}
args.append({{ arg.name }})
{% endfor %}
return Object.callv(self, "{{ method.name }}", args)
{% else %}
{% if method.is_supported %}
{{ _render_method_cook_args(method) | indent }}
{{ _render_method_call(cls, method) | indent }}
{{ _render_method_destroy_args(method) | indent }}
{{ _render_method_return(method) | indent }}
{% else %}
raise NotImplementedError("{{method.unsupported_reason}}")
{% endif %}
{% endif %}
{% endmacro %}

View File

@ -0,0 +1,75 @@
{%- block pxd_header -%}
{%- endblock -%}
{%- block pyx_header -%}
{%- endblock -%}
@cython.final
cdef class AABB:
{% block cdef_attributes %}
cdef godot_aabb _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, Vector3 pos not None=Vector3(), Vector3 size not None=Vector3()):
{{ force_mark_rendered("godot_aabb_new" )}}
gdapi10.godot_aabb_new(&self._gd_data, &pos._gd_data, &size._gd_data)
def __repr__(self):
return f"<AABB({self.as_string()})>"
@property
def position(AABB self) -> Vector3:
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_aabb_get_position" )}}
ret._gd_data = gdapi10.godot_aabb_get_position(&self._gd_data)
return ret
@position.setter
def position(AABB self, Vector3 val not None) -> None:
{{ force_mark_rendered("godot_aabb_set_position" )}}
gdapi10.godot_aabb_set_position(&self._gd_data, &val._gd_data)
@property
def size(AABB self) -> Vector3:
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_aabb_get_size" )}}
ret._gd_data = gdapi10.godot_aabb_get_size(&self._gd_data)
return ret
@size.setter
def size(AABB self, Vector3 val not None) -> None:
{{ force_mark_rendered("godot_aabb_set_size" )}}
gdapi10.godot_aabb_set_size(&self._gd_data, &val._gd_data)
@property
def end(AABB self) -> Vector3:
cdef godot_vector3 position = gdapi10.godot_aabb_get_position(&self._gd_data)
cdef godot_vector3 size = gdapi10.godot_aabb_get_size(&self._gd_data)
cdef Vector3 ret = Vector3.__new__(Vector3)
ret._gd_data = gdapi10.godot_vector3_operator_add(&position, &size)
return ret
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("get_area") | indent }}
{{ render_method("has_no_area") | indent }}
{{ render_method("has_no_surface") | indent }}
{{ render_method("intersects") | indent }}
{{ render_method("encloses") | indent }}
{{ render_method("merge") | indent }}
{{ render_method("intersection") | indent }}
{{ render_method("intersects_plane") | indent }}
{{ render_method("intersects_segment") | indent }}
{{ render_method("has_point") | indent }}
{{ render_method("get_support") | indent }}
{{ render_method("get_longest_axis") | indent }}
{{ render_method("get_longest_axis_index") | indent }}
{{ render_method("get_longest_axis_size") | indent }}
{{ render_method("get_shortest_axis") | indent }}
{{ render_method("get_shortest_axis_index") | indent }}
{{ render_method("get_shortest_axis_size") | indent }}
{{ render_method("expand") | indent }}
{{ render_method("grow") | indent }}
{{ render_method("get_endpoint") | indent }}
{% endblock %}

View File

@ -0,0 +1,249 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
{# TODO: conversion from pool arrays is not supported #}
{{ force_mark_rendered("godot_array_new_pool_byte_array") }}
{{ force_mark_rendered("godot_array_new_pool_color_array") }}
{{ force_mark_rendered("godot_array_new_pool_int_array") }}
{{ force_mark_rendered("godot_array_new_pool_real_array") }}
{{ force_mark_rendered("godot_array_new_pool_string_array") }}
{{ force_mark_rendered("godot_array_new_pool_vector2_array") }}
{{ force_mark_rendered("godot_array_new_pool_vector3_array") }}
{# We can't do const in Python #}
{{ force_mark_rendered("godot_array_operator_index_const") }}
@cython.final
cdef class Array:
{% block cdef_attributes %}
cdef godot_array _gd_data
@staticmethod
cdef inline Array new()
@staticmethod
cdef inline Array from_ptr(const godot_array *_ptr)
cdef inline Array operator_getslice(self, godot_int start, godot_int stop, godot_int step)
cdef inline bint operator_equal(self, Array other)
cdef inline Array operator_add(self, Array items)
cdef inline operator_iadd(self, Array items)
{% endblock %}
{% block python_defs %}
def __init__(self, iterable=None):
{{ force_mark_rendered("godot_array_new") }}
{{ force_mark_rendered("godot_array_duplicate") }}
if not iterable:
gdapi10.godot_array_new(&self._gd_data)
elif isinstance(iterable, Array):
self._gd_data = gdapi11.godot_array_duplicate(&(<Array>iterable)._gd_data, False)
# TODO: handle Pool*Array
else:
gdapi10.godot_array_new(&self._gd_data)
for x in iterable:
self.append(x)
@staticmethod
cdef inline Array new():
# Call to __new__ bypasses __init__ constructor
cdef Array ret = Array.__new__(Array)
gdapi10.godot_array_new(&ret._gd_data)
return ret
@staticmethod
cdef inline Array from_ptr(const godot_array *_ptr):
# Call to __new__ bypasses __init__ constructor
cdef Array ret = Array.__new__(Array)
# `godot_array` is a cheap structure pointing on a refcounted vector
# of variants. Unlike it name could let think, `godot_array_new_copy`
# only increment the refcount of the underlying structure.
{{ force_mark_rendered("godot_array_new_copy") }}
gdapi10.godot_array_new_copy(&ret._gd_data, _ptr)
return ret
def __dealloc__(self):
# /!\ if `__init__` is skipped, `_gd_data` must be initialized by
# hand otherwise we will get a segfault here
{{ force_mark_rendered("godot_array_destroy") }}
gdapi10.godot_array_destroy(&self._gd_data)
def __repr__(self):
return f"<{type(self).__name__}([{', '.join([repr(x) for x in self])}])>"
# Operators
cdef inline Array operator_getslice(self, godot_int start, godot_int stop, godot_int step):
{{ force_mark_rendered("godot_array_slice") }}
cdef Array ret = Array.__new__(Array)
ret._gd_data = gdapi12.godot_array_slice(&self._gd_data, start, stop, step, False)
return ret
# TODO: support slice
def __getitem__(self, index):
{{ force_mark_rendered("godot_array_operator_index") }}
cdef godot_int size = self.size()
cdef godot_int start
cdef godot_int stop
cdef godot_int step
if isinstance(index, slice):
step = index.step if index.step is not None else 1
if step == 0:
raise ValueError("slice step cannot be zero")
elif step > 0:
start = index.start if index.start is not None else 0
stop = index.stop if index.stop is not None else size
else:
start = index.start if index.start is not None else size
stop = index.stop if index.stop is not None else -size - 1
return Array.operator_getslice(self, start, stop, step)
if index < 0:
index = index + size
if index < 0 or index >= size:
raise IndexError("list index out of range")
cdef godot_variant *p_ret = gdapi10.godot_array_operator_index(&self._gd_data, index)
return godot_variant_to_pyobj(p_ret)
# TODO: support slice
def __setitem__(self, godot_int index, object value):
cdef godot_int size = self.size()
index = size + index if index < 0 else index
if abs(index) >= size:
raise IndexError("list index out of range")
cdef godot_variant *p_ret = gdapi10.godot_array_operator_index(&self._gd_data, index)
gdapi10.godot_variant_destroy(p_ret)
pyobj_to_godot_variant(value, p_ret)
# TODO: support slice
def __delitem__(self, godot_int index):
cdef godot_int size = self.size()
index = size + index if index < 0 else index
if abs(index) >= size:
raise IndexError("list index out of range")
gdapi10.godot_array_remove(&self._gd_data, index)
def __iter__(self):
# TODO: mid iteration mutation should throw exception ?
cdef int i
for i in range(self.size()):
yield self.get(i)
def __copy__(self):
return self.duplicate(False)
def __deepcopy__(self):
return self.duplicate(True)
cdef inline bint operator_equal(self, Array other):
# TODO `godot_array_operator_equal` is missing in gdapi, submit a PR ?
cdef godot_int size = self.size()
if size != other.size():
return False
cdef int i
for i in range(size):
if not gdapi10.godot_variant_operator_equal(
gdapi10.godot_array_operator_index(&self._gd_data, i),
gdapi10.godot_array_operator_index(&other._gd_data, i)
):
return False
return True
def __eq__(self, other):
try:
return Array.operator_equal(self, <Array?>other)
except TypeError:
return False
def __ne__(self, other):
try:
return not Array.operator_equal(self, <Array?>other)
except TypeError:
return True
cdef inline operator_iadd(self, Array items):
cdef godot_int self_size = self.size()
cdef godot_int items_size = items.size()
gdapi10.godot_array_resize(&self._gd_data, self_size + items_size)
cdef int i
for i in range(items_size):
Array.set(self, self_size + i, items.get(i))
# TODO: support __iadd__ for other types than Array ?
def __iadd__(self, items not None):
try:
Array.operator_iadd(self, items)
except TypeError:
for x in items:
self.append(x)
return self
cdef inline Array operator_add(self, Array items):
cdef godot_int self_size = self.size()
cdef godot_int items_size = items.size()
cdef Array ret = Array.new()
gdapi10.godot_array_resize(&ret._gd_data, self_size + items_size)
cdef int i
for i in range(self_size):
Array.set(ret, i, self.get(i))
for i in range(items_size):
Array.set(ret, self_size + i, items.get(i))
return ret
# TODO: support __add__ for other types than Array ?
def __add__(self, items not None):
try:
return Array.operator_add(self, items)
except TypeError:
ret = Array.duplicate(self, False)
for x in items:
ret.append(x)
return ret
{{ render_method("size", py_name="__len__") | indent }}
{{ render_method("hash", py_name="__hash__") | indent }}
{{ render_method("has", py_name="__contains__") | indent }}
{{ render_method("hash") | indent }}
{{ render_method("size") | indent }}
{{ render_method("duplicate") | indent }}
{{ render_method("get") | indent }}
{{ render_method("set") | indent }}
{{ render_method("append") | indent }}
{{ render_method("clear") | indent }}
{{ render_method("empty") | indent }}
{{ render_method("count") | indent }}
{{ render_method("erase") | indent }}
{{ render_method("front") | indent }}
{{ render_method("back") | indent }}
{{ render_method("find") | indent }}
{{ render_method("find_last") | indent }}
{{ render_method("insert") | indent }}
{{ render_method("invert") | indent }}
{{ render_method("pop_back") | indent }}
{{ render_method("pop_front") | indent }}
{{ render_method("push_back") | indent }}
{{ render_method("push_front") | indent }}
{{ render_method("remove") | indent }}
{{ render_method("resize") | indent }}
{{ render_method("rfind") | indent }}
{{ render_method("sort") | indent }}
{#- TODO: opaque object as param is not supported #}
{{- force_mark_rendered("godot_array_sort_custom") }}
{#- {{ render_method("sort_custom") | indent }} #}
{{ render_method("bsearch") | indent }}
{#- TODO: opaque object as param is not supported #}
{{- force_mark_rendered("godot_array_bsearch_custom") }}
{#- {{ render_method("bsearch_custom") | indent }} #}
{{ render_method("max") | indent }}
{{ render_method("min") | indent }}
{{ render_method("shuffle") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock %}

View File

@ -0,0 +1,135 @@
{%- block pxd_header -%}
{%- endblock -%}
{%- block pyx_header -%}
cdef inline Basis Basis_multiply_vector(Basis self, Basis b):
cdef Basis ret = Basis.__new__(Basis)
{{ force_mark_rendered("godot_basis_operator_multiply_vector") }}
ret._gd_data = gdapi10.godot_basis_operator_multiply_vector(&self._gd_data, &b._gd_data)
return ret
cdef inline Basis Basis_multiply_scalar(Basis self, godot_real b):
cdef Basis ret = Basis.__new__(Basis)
{{ force_mark_rendered("godot_basis_operator_multiply_scalar") }}
ret._gd_data = gdapi10.godot_basis_operator_multiply_scalar(&self._gd_data, b)
return ret
{%- endblock %}
@cython.final
cdef class Basis:
{% block cdef_attributes %}
cdef godot_basis _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, Vector3 x not None=Vector3.RIGHT, Vector3 y not None=Vector3.UP, Vector3 z not None=Vector3.BACK):
{{ force_mark_rendered("godot_basis_new") }} {# We always use the `with_rows` version #}
{{ force_mark_rendered("godot_basis_new_with_rows") }}
gdapi10.godot_basis_new_with_rows(&self._gd_data, &(<Vector3>x)._gd_data, &(<Vector3>y)._gd_data, &(<Vector3>z)._gd_data)
@staticmethod
def from_euler(from_):
cdef Basis ret = Basis.__new__(Basis)
try:
{{ force_mark_rendered("godot_basis_new_with_euler") }}
gdapi10.godot_basis_new_with_euler(&ret._gd_data, &(<Vector3?>from_)._gd_data)
return ret
except TypeError:
pass
try:
{{ force_mark_rendered("godot_basis_new_with_euler_quat") }}
gdapi10.godot_basis_new_with_euler_quat(&ret._gd_data, &(<Quat?>from_)._gd_data)
return ret
except TypeError:
raise TypeError('`from_` must be Quat or Vector3')
@staticmethod
def from_axis_angle(Vector3 axis not None, phi):
cdef Basis ret = Basis.__new__(Basis)
{{ force_mark_rendered("godot_basis_new_with_axis_and_angle") }}
gdapi10.godot_basis_new_with_axis_and_angle(&ret._gd_data, &axis._gd_data, phi)
return ret
def __repr__(self):
return f"<Basis({self.as_string()})>"
@property
def x(Basis self) -> Vector3:
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_basis_get_axis") }}
ret._gd_data = gdapi10.godot_basis_get_axis(&self._gd_data, 0)
return ret
@x.setter
def x(Basis self, Vector3 val not None) -> None:
{{ force_mark_rendered("godot_basis_set_axis") }}
gdapi10.godot_basis_set_axis(&self._gd_data, 0, &val._gd_data)
@property
def y(Basis self) -> Vector3:
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_basis_get_axis") }}
ret._gd_data = gdapi10.godot_basis_get_axis(&self._gd_data, 1)
return ret
@y.setter
def y(Basis self, Vector3 val not None) -> None:
{{ force_mark_rendered("godot_basis_set_axis") }}
gdapi10.godot_basis_set_axis(&self._gd_data, 1, &val._gd_data)
@property
def z(Basis self) -> Vector3:
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_basis_get_axis") }}
ret._gd_data = gdapi10.godot_basis_get_axis(&self._gd_data, 2)
return ret
@z.setter
def z(Basis self, Vector3 val not None) -> None:
{{ force_mark_rendered("godot_basis_set_axis") }}
gdapi10.godot_basis_set_axis(&self._gd_data, 2, &val._gd_data)
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("operator_add", py_name="__add__") | indent }}
{{ render_method("operator_subtract", py_name="__sub__") | indent }}
def __mul__(Basis self, val):
cdef Basis _val
try:
_val = <Basis?>val
except TypeError:
return Basis_multiply_scalar(self, val)
else:
return Basis_multiply_vector(self, _val)
{{ render_method("as_string") | indent }}
{{ render_method("inverse") | indent }}
{{ render_method("transposed") | indent }}
{{ render_method("orthonormalized") | indent }}
{{ render_method("determinant") | indent }}
{{ render_method("rotated") | indent }}
{{ render_method("scaled") | indent }}
{{ render_method("get_scale") | indent }}
{{ render_method("get_euler") | indent }}
{{ render_method("get_quat") | indent }}
{{ render_method("set_quat") | indent }}
{{ render_method("set_axis_angle_scale") | indent }}
{{ render_method("set_euler_scale") | indent }}
{{ render_method("set_quat_scale") | indent }}
{{ render_method("tdotx") | indent }}
{{ render_method("tdoty") | indent }}
{{ render_method("tdotz") | indent }}
{{ render_method("xform") | indent }}
{{ render_method("xform_inv") | indent }}
{{ render_method("get_orthogonal_index") | indent }}
{{ render_method("get_elements") | indent }}
{{ render_method("get_row") | indent }}
{{ render_method("set_row") | indent }}
{{ render_method("slerp") | indent }}
{% endblock %}

View File

@ -0,0 +1,46 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_builtins.py`
cimport cython
from godot._hazmat.gdnative_api_struct cimport *
from godot.pool_arrays cimport (
PoolIntArray,
PoolRealArray,
PoolByteArray,
PoolVector2Array,
PoolVector3Array,
PoolColorArray,
PoolStringArray,
)
{% set render_target = "rid" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "vector3" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "vector2" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "aabb" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "basis" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "color" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "gdstring" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "rect2" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "transform2d" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "plane" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "quat" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "transform" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "node_path" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "dictionary" %}
{% include 'render.tmpl.pxd' with context %}
{% set render_target = "array" %}
{% include 'render.tmpl.pxd' with context %}

View File

@ -0,0 +1,35 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_builtins.py`
from typing import Union
{% set render_target = "rid" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "vector3" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "vector2" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "aabb" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "basis" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "color" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "gdstring" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "rect2" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "transform2d" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "plane" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "quat" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "transform" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "node_path" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "dictionary" %}
{% include 'render.tmpl.pyi' with context %}
{% set render_target = "array" %}
{% include 'render.tmpl.pyi' with context %}

View File

@ -0,0 +1,54 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_builtins.py`
from typing import Union
cimport cython
from godot._hazmat.gdnative_api_struct cimport *
from godot._hazmat.gdapi cimport (
pythonscript_gdapi10 as gdapi10,
pythonscript_gdapi11 as gdapi11,
pythonscript_gdapi12 as gdapi12,
)
from godot._hazmat.conversion cimport *
from godot.pool_arrays cimport (
PoolIntArray,
PoolRealArray,
PoolByteArray,
PoolVector2Array,
PoolVector3Array,
PoolColorArray,
PoolStringArray,
)
{% set render_target = "rid" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "vector3" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "vector2" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "aabb" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "basis" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "color" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "gdstring" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "rect2" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "transform2d" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "plane" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "quat" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "transform" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "node_path" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "dictionary" %}
{% include 'render.tmpl.pyx' with context %}
{% set render_target = "array" %}
{% include 'render.tmpl.pyx' with context %}

View File

@ -0,0 +1,244 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
from libc.stdint cimport uint8_t
{% endblock -%}
@cython.final
cdef class Color:
{% block cdef_attributes %}
cdef godot_color _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, godot_real r=0, godot_real g=0, godot_real b=0, a=None):
if a is None:
{{ force_mark_rendered("godot_color_new_rgb")}}
gdapi10.godot_color_new_rgb(&self._gd_data, r, g, b)
else:
{{ force_mark_rendered("godot_color_new_rgba")}}
gdapi10.godot_color_new_rgba(&self._gd_data, r, g, b, a)
def __repr__(self):
return f"<Color(r={self.r}, g={self.g}, b={self.b}, a={self.a})>"
@staticmethod
def from_resource(Resource resource not None):
# Call to __new__ bypasses __init__ constructor
cdef RID ret = RID.__new__(RID)
gdapi10.godot_rid_new_with_resource(&ret._gd_data, resource._gd_ptr)
return ret
@property
def r8(Color self):
return int(self.r * 256)
@r8.setter
def r8(Color self, uint8_t val):
self.r = (float(val) / 256)
@property
def g8(Color self):
return int(self.g * 256)
@g8.setter
def g8(Color self, uint8_t val):
self.g = (float(val) / 256)
@property
def b8(Color self):
return int(self.b * 256)
@b8.setter
def b8(Color self, uint8_t val):
self.b = (float(val) / 256)
@property
def a8(Color self):
return int(self.a * 256)
@a8.setter
def a8(Color self, uint8_t val):
self.a = (float(val) / 256)
{{ render_property("r", getter="get_r", setter="set_r") | indent }}
{{ render_property("g", getter="get_g", setter="set_g") | indent }}
{{ render_property("b", getter="get_b", setter="set_b") | indent }}
{{ render_property("a", getter="get_a", setter="set_a") | indent }}
{{ render_property("h", getter="get_h") | indent }}
{{ render_property("s", getter="get_s") | indent }}
{{ render_property("v", getter="get_v") | indent }}
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_operator_lt() | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("to_rgba32") | indent }}
{{ render_method("to_abgr32") | indent }}
{{ render_method("to_abgr64") | indent }}
{{ render_method("to_argb64") | indent }}
{{ render_method("to_rgba64") | indent }}
{{ render_method("to_argb32") | indent }}
{{ render_method("gray") | indent }}
{{ render_method("inverted") | indent }}
{{ render_method("contrasted") | indent }}
{{ render_method("linear_interpolate") | indent }}
{{ render_method("blend") | indent }}
{{ render_method("darkened") | indent }}
{{ render_method("from_hsv") | indent }}
{{ render_method("lightened") | indent }}
{{ render_method("to_html") | indent }}
{% endblock %}
{%- block python_consts %}
# TODO: gdapi should expose those constants to us
GRAY = Color(0.75, 0.75, 0.75)
ALICEBLUE = Color(0.94, 0.97, 1)
ANTIQUEWHITE = Color(0.98, 0.92, 0.84)
AQUA = Color(0, 1, 1)
AQUAMARINE = Color(0.5, 1, 0.83)
AZURE = Color(0.94, 1, 1)
BEIGE = Color(0.96, 0.96, 0.86)
BISQUE = Color(1, 0.89, 0.77)
BLACK = Color(0, 0, 0)
BLANCHEDALMOND = Color(1, 0.92, 0.8)
BLUE = Color(0, 0, 1)
BLUEVIOLET = Color(0.54, 0.17, 0.89)
BROWN = Color(0.65, 0.16, 0.16)
BURLYWOOD = Color(0.87, 0.72, 0.53)
CADETBLUE = Color(0.37, 0.62, 0.63)
CHARTREUSE = Color(0.5, 1, 0)
CHOCOLATE = Color(0.82, 0.41, 0.12)
CORAL = Color(1, 0.5, 0.31)
CORNFLOWER = Color(0.39, 0.58, 0.93)
CORNSILK = Color(1, 0.97, 0.86)
CRIMSON = Color(0.86, 0.08, 0.24)
CYAN = Color(0, 1, 1)
DARKBLUE = Color(0, 0, 0.55)
DARKCYAN = Color(0, 0.55, 0.55)
DARKGOLDENROD = Color(0.72, 0.53, 0.04)
DARKGRAY = Color(0.66, 0.66, 0.66)
DARKGREEN = Color(0, 0.39, 0)
DARKKHAKI = Color(0.74, 0.72, 0.42)
DARKMAGENTA = Color(0.55, 0, 0.55)
DARKOLIVEGREEN = Color(0.33, 0.42, 0.18)
DARKORANGE = Color(1, 0.55, 0)
DARKORCHID = Color(0.6, 0.2, 0.8)
DARKRED = Color(0.55, 0, 0)
DARKSALMON = Color(0.91, 0.59, 0.48)
DARKSEAGREEN = Color(0.56, 0.74, 0.56)
DARKSLATEBLUE = Color(0.28, 0.24, 0.55)
DARKSLATEGRAY = Color(0.18, 0.31, 0.31)
DARKTURQUOISE = Color(0, 0.81, 0.82)
DARKVIOLET = Color(0.58, 0, 0.83)
DEEPPINK = Color(1, 0.08, 0.58)
DEEPSKYBLUE = Color(0, 0.75, 1)
DIMGRAY = Color(0.41, 0.41, 0.41)
DODGERBLUE = Color(0.12, 0.56, 1)
FIREBRICK = Color(0.7, 0.13, 0.13)
FLORALWHITE = Color(1, 0.98, 0.94)
FORESTGREEN = Color(0.13, 0.55, 0.13)
FUCHSIA = Color(1, 0, 1)
GAINSBORO = Color(0.86, 0.86, 0.86)
GHOSTWHITE = Color(0.97, 0.97, 1)
GOLD = Color(1, 0.84, 0)
GOLDENROD = Color(0.85, 0.65, 0.13)
GREEN = Color(0, 1, 0)
GREENYELLOW = Color(0.68, 1, 0.18)
HONEYDEW = Color(0.94, 1, 0.94)
HOTPINK = Color(1, 0.41, 0.71)
INDIANRED = Color(0.8, 0.36, 0.36)
INDIGO = Color(0.29, 0, 0.51)
IVORY = Color(1, 1, 0.94)
KHAKI = Color(0.94, 0.9, 0.55)
LAVENDER = Color(0.9, 0.9, 0.98)
LAVENDERBLUSH = Color(1, 0.94, 0.96)
LAWNGREEN = Color(0.49, 0.99, 0)
LEMONCHIFFON = Color(1, 0.98, 0.8)
LIGHTBLUE = Color(0.68, 0.85, 0.9)
LIGHTCORAL = Color(0.94, 0.5, 0.5)
LIGHTCYAN = Color(0.88, 1, 1)
LIGHTGOLDENROD = Color(0.98, 0.98, 0.82)
LIGHTGRAY = Color(0.83, 0.83, 0.83)
LIGHTGREEN = Color(0.56, 0.93, 0.56)
LIGHTPINK = Color(1, 0.71, 0.76)
LIGHTSALMON = Color(1, 0.63, 0.48)
LIGHTSEAGREEN = Color(0.13, 0.7, 0.67)
LIGHTSKYBLUE = Color(0.53, 0.81, 0.98)
LIGHTSLATEGRAY = Color(0.47, 0.53, 0.6)
LIGHTSTEELBLUE = Color(0.69, 0.77, 0.87)
LIGHTYELLOW = Color(1, 1, 0.88)
LIME = Color(0, 1, 0)
LIMEGREEN = Color(0.2, 0.8, 0.2)
LINEN = Color(0.98, 0.94, 0.9)
MAGENTA = Color(1, 0, 1)
MAROON = Color(0.69, 0.19, 0.38)
MEDIUMAQUAMARINE = Color(0.4, 0.8, 0.67)
MEDIUMBLUE = Color(0, 0, 0.8)
MEDIUMORCHID = Color(0.73, 0.33, 0.83)
MEDIUMPURPLE = Color(0.58, 0.44, 0.86)
MEDIUMSEAGREEN = Color(0.24, 0.7, 0.44)
MEDIUMSLATEBLUE = Color(0.48, 0.41, 0.93)
MEDIUMSPRINGGREEN = Color(0, 0.98, 0.6)
MEDIUMTURQUOISE = Color(0.28, 0.82, 0.8)
MEDIUMVIOLETRED = Color(0.78, 0.08, 0.52)
MIDNIGHTBLUE = Color(0.1, 0.1, 0.44)
MINTCREAM = Color(0.96, 1, 0.98)
MISTYROSE = Color(1, 0.89, 0.88)
MOCCASIN = Color(1, 0.89, 0.71)
NAVAJOWHITE = Color(1, 0.87, 0.68)
NAVYBLUE = Color(0, 0, 0.5)
OLDLACE = Color(0.99, 0.96, 0.9)
OLIVE = Color(0.5, 0.5, 0)
OLIVEDRAB = Color(0.42, 0.56, 0.14)
ORANGE = Color(1, 0.65, 0)
ORANGERED = Color(1, 0.27, 0)
ORCHID = Color(0.85, 0.44, 0.84)
PALEGOLDENROD = Color(0.93, 0.91, 0.67)
PALEGREEN = Color(0.6, 0.98, 0.6)
PALETURQUOISE = Color(0.69, 0.93, 0.93)
PALEVIOLETRED = Color(0.86, 0.44, 0.58)
PAPAYAWHIP = Color(1, 0.94, 0.84)
PEACHPUFF = Color(1, 0.85, 0.73)
PERU = Color(0.8, 0.52, 0.25)
PINK = Color(1, 0.75, 0.8)
PLUM = Color(0.87, 0.63, 0.87)
POWDERBLUE = Color(0.69, 0.88, 0.9)
PURPLE = Color(0.63, 0.13, 0.94)
REBECCAPURPLE = Color(0.4, 0.2, 0.6)
RED = Color(1, 0, 0)
ROSYBROWN = Color(0.74, 0.56, 0.56)
ROYALBLUE = Color(0.25, 0.41, 0.88)
SADDLEBROWN = Color(0.55, 0.27, 0.07)
SALMON = Color(0.98, 0.5, 0.45)
SANDYBROWN = Color(0.96, 0.64, 0.38)
SEAGREEN = Color(0.18, 0.55, 0.34)
SEASHELL = Color(1, 0.96, 0.93)
SIENNA = Color(0.63, 0.32, 0.18)
SILVER = Color(0.75, 0.75, 0.75)
SKYBLUE = Color(0.53, 0.81, 0.92)
SLATEBLUE = Color(0.42, 0.35, 0.8)
SLATEGRAY = Color(0.44, 0.5, 0.56)
SNOW = Color(1, 0.98, 0.98)
SPRINGGREEN = Color(0, 1, 0.5)
STEELBLUE = Color(0.27, 0.51, 0.71)
TAN = Color(0.82, 0.71, 0.55)
TEAL = Color(0, 0.5, 0.5)
THISTLE = Color(0.85, 0.75, 0.85)
TOMATO = Color(1, 0.39, 0.28)
TURQUOISE = Color(0.25, 0.88, 0.82)
VIOLET = Color(0.93, 0.51, 0.93)
WEBGRAY = Color(0.5, 0.5, 0.5)
WEBGREEN = Color(0, 0.5, 0)
WEBMAROON = Color(0.5, 0, 0)
WEBPURPLE = Color(0.5, 0, 0.5)
WHEAT = Color(0.96, 0.87, 0.7)
WHITE = Color(1, 1, 1)
WHITESMOKE = Color(0.96, 0.96, 0.96)
YELLOW = Color(1, 1, 0)
YELLOWGREEN = Color(0.6, 0.8, 0.2)
{% endblock %}

View File

@ -0,0 +1,210 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
{# We can't do const in Python #}
{{ force_mark_rendered("godot_dictionary_operator_index_const") }}
@cython.final
cdef class Dictionary:
{% block cdef_attributes %}
cdef godot_dictionary _gd_data
@staticmethod
cdef inline Dictionary new()
@staticmethod
cdef inline Dictionary from_ptr(const godot_dictionary *_ptr)
cdef inline operator_update(self, Dictionary items)
cdef inline bint operator_equal(self, Dictionary other)
{% endblock %}
{% block python_defs %}
def __init__(self, iterable=None):
{{ force_mark_rendered("godot_dictionary_new") }}
if not iterable:
gdapi10.godot_dictionary_new(&self._gd_data)
elif isinstance(iterable, Dictionary):
self._gd_data = gdapi12.godot_dictionary_duplicate(&(<Dictionary>iterable)._gd_data, False)
# TODO: handle Pool*Array
elif isinstance(iterable, dict):
gdapi10.godot_dictionary_new(&self._gd_data)
for k, v in iterable.items():
self[k] = v
else:
gdapi10.godot_dictionary_new(&self._gd_data)
try:
for k, v in iterable:
self[k] = v
except ValueError as exc:
raise ValueError("dictionary update sequence element has length 1; 2 is required")
def __dealloc__(self):
{{ force_mark_rendered("godot_dictionary_destroy") }}
# /!\ if `__init__` is skipped, `_gd_data` must be initialized by
# hand otherwise we will get a segfault here
gdapi10.godot_dictionary_destroy(&self._gd_data)
@staticmethod
cdef inline Dictionary new():
# Call to __new__ bypasses __init__ constructor
cdef Dictionary ret = Dictionary.__new__(Dictionary)
gdapi10.godot_dictionary_new(&ret._gd_data)
return ret
@staticmethod
cdef inline Dictionary from_ptr(const godot_dictionary *_ptr):
# Call to __new__ bypasses __init__ constructor
cdef Dictionary ret = Dictionary.__new__(Dictionary)
# `godot_dictionary` is a cheap structure pointing on a refcounted hashmap
# of variants. Unlike it name could let think, `godot_dictionary_new_copy`
# only increment the refcount of the underlying structure.
{{ force_mark_rendered("godot_dictionary_new_copy") }}
gdapi10.godot_dictionary_new_copy(&ret._gd_data, _ptr)
return ret
def __repr__(self):
repr_dict = {}
for k, v in self.items():
if isinstance(k, GDString):
k = str(k)
if isinstance(v, GDString):
v = str(v)
repr_dict[k] = v
return f"<Dictionary({repr_dict})>"
def __getitem__(self, object key):
{{ force_mark_rendered("godot_dictionary_operator_index") }}
cdef godot_variant var_key
if not pyobj_to_godot_variant(key, &var_key):
raise TypeError(f"Cannot convert `{key!r}` to Godot Variant")
cdef godot_variant *p_var_ret = gdapi10.godot_dictionary_operator_index(&self._gd_data, &var_key)
gdapi10.godot_variant_destroy(&var_key)
if p_var_ret == NULL:
raise KeyError(key)
else:
return godot_variant_to_pyobj(p_var_ret)
{{ render_method("set", py_name="__setitem__") | indent }}
def __delitem__(self, object key):
{{ force_mark_rendered("godot_dictionary_erase_with_return") }}
cdef godot_variant var_key
if not pyobj_to_godot_variant(key, &var_key):
raise TypeError(f"Cannot convert `{key!r}` to Godot Variant")
cdef godot_bool ret = gdapi11.godot_dictionary_erase_with_return(&self._gd_data, &var_key)
gdapi10.godot_variant_destroy(&var_key)
if not ret:
raise KeyError(key)
def __iter__(self):
{{ force_mark_rendered("godot_dictionary_next") }}
cdef godot_variant *p_key = NULL
# TODO: mid iteration mutation should throw exception ?
while True:
p_key = gdapi10.godot_dictionary_next(&self._gd_data, p_key)
if p_key == NULL:
return
yield godot_variant_to_pyobj(p_key)
def __copy__(self):
return self.duplicate(False)
def __deepcopy__(self):
return self.duplicate(True)
def get(self, object key, object default=None):
{{ force_mark_rendered("godot_dictionary_get") }}
{{ force_mark_rendered("godot_dictionary_get_with_default") }}
cdef godot_variant var_key
pyobj_to_godot_variant(key, &var_key)
cdef godot_variant var_ret
cdef godot_variant var_default
if default is not None:
pyobj_to_godot_variant(default, &var_default)
var_ret = gdapi11.godot_dictionary_get_with_default(&self._gd_data, &var_key, &var_default)
gdapi10.godot_variant_destroy(&var_default)
else:
var_ret = gdapi10.godot_dictionary_get(&self._gd_data, &var_key)
gdapi10.godot_variant_destroy(&var_key)
cdef object ret = godot_variant_to_pyobj(&var_ret)
gdapi10.godot_variant_destroy(&var_ret)
return ret
cdef inline operator_update(self, Dictionary items):
cdef godot_variant *p_value
cdef godot_variant *p_key = NULL
while True:
p_key = gdapi10.godot_dictionary_next(&items._gd_data, p_key)
if p_key == NULL:
break
p_value = gdapi10.godot_dictionary_operator_index(&items._gd_data, p_key)
gdapi10.godot_dictionary_set(&self._gd_data, p_key, p_value)
return self
def update(self, other):
cdef object k
cdef object v
if isinstance(other, Dictionary):
Dictionary.operator_update(self, other)
elif isinstance(other, dict):
for k, v in other.items():
self[k] = v
else:
raise TypeError("other must be godot.Dictionary or dict")
def items(self):
cdef godot_variant *p_key = NULL
cdef godot_variant *p_value
# TODO: mid iteration mutation should throw exception ?
while True:
p_key = gdapi10.godot_dictionary_next(&self._gd_data, p_key)
if p_key == NULL:
return
p_value = gdapi10.godot_dictionary_operator_index(&self._gd_data, p_key)
yield godot_variant_to_pyobj(p_key), godot_variant_to_pyobj(p_value)
cdef inline bint operator_equal(self, Dictionary other):
if other is None:
return False
cdef godot_int size = self.size()
if size != other.size():
return False
# TODO: gdnative should provide a function to do that
return dict(self) == dict(other)
def __eq__(self, other):
{# see https://github.com/godotengine/godot/issues/27615 #}
{{ force_mark_rendered("godot_dictionary_operator_equal") }}
try:
return Dictionary.operator_equal(self, <Dictionary?>other)
except TypeError:
return False
def __ne__(self, other):
try:
return not Dictionary.operator_equal(self, <Dictionary?>other)
except TypeError:
return True
{{ render_method("size", py_name="__len__") | indent }}
{{ render_method("hash", py_name="__hash__") | indent }}
{{ render_method("has", py_name="__contains__") | indent }}
{{ render_method("duplicate") | indent }}
{{ render_method("size") | indent }}
{{ render_method("empty") | indent }}
{{ render_method("clear") | indent }}
{{ render_method("has") | indent }}
{{ render_method("has_all") | indent }}
{{ render_method("erase") | indent }}
{{ render_method("hash") | indent }}
{{ render_method("keys") | indent }}
{{ render_method("values") | indent }}
{{ render_method("to_json") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock %}

View File

@ -0,0 +1,255 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
from libc.stdint cimport int8_t
{% endblock -%}
{# godot_char_string is not really a bultin type...#}
{{ force_mark_rendered("godot_char_string_destroy") }}
{{ force_mark_rendered("godot_char_string_get_data") }}
{{ force_mark_rendered("godot_char_string_length") }}
{# Those methods are present in gdnative_api.json but not in the Godot documentation... #}
{{ force_mark_rendered("godot_string_ascii") }}
{{ force_mark_rendered("godot_string_ascii_extended") }}
{{ force_mark_rendered("godot_string_begins_with_char_array") }}
{{ force_mark_rendered("godot_string_c_escape_multiline") }}
{{ force_mark_rendered("godot_string_camelcase_to_underscore") }}
{{ force_mark_rendered("godot_string_camelcase_to_underscore_lowercased") }}
{{ force_mark_rendered("godot_string_char_lowercase") }}
{{ force_mark_rendered("godot_string_char_to_double") }}
{{ force_mark_rendered("godot_string_char_to_int") }}
{{ force_mark_rendered("godot_string_char_to_int64_with_len") }}
{{ force_mark_rendered("godot_string_char_to_int_with_len") }}
{{ force_mark_rendered("godot_string_char_uppercase") }}
{{ force_mark_rendered("godot_string_chars_to_utf8") }}
{{ force_mark_rendered("godot_string_chars_to_utf8_with_len") }}
{{ force_mark_rendered("godot_string_chr") }}
{{ force_mark_rendered("godot_string_find_from") }}
{{ force_mark_rendered("godot_string_findmk") }}
{{ force_mark_rendered("godot_string_findmk_from") }}
{{ force_mark_rendered("godot_string_findmk_from_in_place") }}
{{ force_mark_rendered("godot_string_findn_from") }}
{{ force_mark_rendered("godot_string_format_with_custom_placeholder") }}
{{ force_mark_rendered("godot_string_get_slice") }}
{{ force_mark_rendered("godot_string_get_slice_count") }}
{{ force_mark_rendered("godot_string_get_slicec") }}
{{ force_mark_rendered("godot_string_hash64") }}
{{ force_mark_rendered("godot_string_hash_chars") }}
{{ force_mark_rendered("godot_string_hash_chars_with_len") }}
{{ force_mark_rendered("godot_string_hash_utf8_chars") }}
{{ force_mark_rendered("godot_string_hash_utf8_chars_with_len") }}
{{ force_mark_rendered("godot_string_hex_encode_buffer") }}
{{ force_mark_rendered("godot_string_hex_to_int64") }}
{{ force_mark_rendered("godot_string_hex_to_int64_with_prefix") }}
{{ force_mark_rendered("godot_string_hex_to_int_without_prefix") }}
{{ force_mark_rendered("godot_string_is_numeric") }}
{{ force_mark_rendered("godot_string_is_resource_file") }}
{{ force_mark_rendered("godot_string_lpad") }}
{{ force_mark_rendered("godot_string_lpad_with_custom_character") }}
{{ force_mark_rendered("godot_string_md5") }}
{{ force_mark_rendered("godot_string_name_destroy") }}
{{ force_mark_rendered("godot_string_name_get_data_unique_pointer") }}
{{ force_mark_rendered("godot_string_name_get_hash") }}
{{ force_mark_rendered("godot_string_name_get_name") }}
{{ force_mark_rendered("godot_string_name_new") }}
{{ force_mark_rendered("godot_string_name_new_data") }}
{{ force_mark_rendered("godot_string_name_operator_equal") }}
{{ force_mark_rendered("godot_string_name_operator_less") }}
{{ force_mark_rendered("godot_string_naturalnocasecmp_to") }}
{{ force_mark_rendered("godot_string_num") }}
{{ force_mark_rendered("godot_string_num_int64") }}
{{ force_mark_rendered("godot_string_num_int64_capitalized") }}
{{ force_mark_rendered("godot_string_num_real") }}
{{ force_mark_rendered("godot_string_num_scientific") }}
{{ force_mark_rendered("godot_string_num_with_decimals") }}
{{ force_mark_rendered("godot_string_operator_index") }}
{{ force_mark_rendered("godot_string_operator_index_const") }}
{{ force_mark_rendered("godot_string_parse_utf8") }}
{{ force_mark_rendered("godot_string_parse_utf8_with_len") }}
{{ force_mark_rendered("godot_string_path_to") }}
{{ force_mark_rendered("godot_string_path_to_file") }}
{{ force_mark_rendered("godot_string_replace_first") }}
{{ force_mark_rendered("godot_string_rfind_from") }}
{{ force_mark_rendered("godot_string_rfindn_from") }}
{{ force_mark_rendered("godot_string_rpad") }}
{{ force_mark_rendered("godot_string_rpad_with_custom_character") }}
{{ force_mark_rendered("godot_string_simplify_path") }}
{{ force_mark_rendered("godot_string_split_allow_empty") }}
{{ force_mark_rendered("godot_string_split_floats_allows_empty") }}
{{ force_mark_rendered("godot_string_split_floats_mk") }}
{{ force_mark_rendered("godot_string_split_floats_mk_allows_empty") }}
{{ force_mark_rendered("godot_string_split_ints") }}
{{ force_mark_rendered("godot_string_split_ints_allows_empty") }}
{{ force_mark_rendered("godot_string_split_ints_mk") }}
{{ force_mark_rendered("godot_string_split_ints_mk_allows_empty") }}
{{ force_mark_rendered("godot_string_split_spaces") }}
{{ force_mark_rendered("godot_string_sprintf") }}
{{ force_mark_rendered("godot_string_to_double") }}
{{ force_mark_rendered("godot_string_to_int64") }}
{{ force_mark_rendered("godot_string_unicode_char_to_double") }}
{{ force_mark_rendered("godot_string_utf8") }}
{{ force_mark_rendered("godot_string_wchar_to_int") }}
{{ force_mark_rendered("godot_string_wide_str") }}
{{ force_mark_rendered("godot_string_word_wrap") }}
{{ force_mark_rendered("godot_string_xml_escape_with_quotes") }}
@cython.final
cdef class GDString:
{% block cdef_attributes %}
cdef godot_string _gd_data
@staticmethod
cdef inline GDString new()
@staticmethod
cdef inline GDString new_with_wide_string(wchar_t *content, int size)
@staticmethod
cdef inline GDString from_ptr(const godot_string *_ptr)
{% endblock %}
{% block python_defs %}
def __init__(self, str pystr=None):
if not pystr:
{{ force_mark_rendered("godot_string_new" )}}
gdapi10.godot_string_new(&self._gd_data)
else:
pyobj_to_godot_string(pystr, &self._gd_data)
@staticmethod
cdef inline GDString new():
# Call to __new__ bypasses __init__ constructor
cdef GDString ret = GDString.__new__(GDString)
gdapi10.godot_string_new(&ret._gd_data)
return ret
@staticmethod
cdef inline GDString new_with_wide_string(wchar_t *content, int size):
{{ force_mark_rendered("godot_string_new_with_wide_string") }}
# Call to __new__ bypasses __init__ constructor
cdef GDString ret = GDString.__new__(GDString)
gdapi10.godot_string_new_with_wide_string(&ret._gd_data, content, size)
return ret
@staticmethod
cdef inline GDString from_ptr(const godot_string *_ptr):
# Call to __new__ bypasses __init__ constructor
cdef GDString ret = GDString.__new__(GDString)
# `godot_string` is a cheap structure pointing on a refcounted buffer.
# Unlike it name could let think, `godot_string_new_copy` only
# increments the refcount of the underlying structure.
{{ force_mark_rendered("godot_string_new_copy") }}
gdapi10.godot_string_new_copy(&ret._gd_data, _ptr)
return ret
def __dealloc__(GDString self):
# /!\ if `__init__` is skipped, `_gd_data` must be initialized by
# hand otherwise we will get a segfault here
{{ force_mark_rendered("godot_string_destroy" )}}
gdapi10.godot_string_destroy(&self._gd_data)
def __repr__(GDString self):
return f"<GDString({str(self)!r})>"
def __str__(GDString self):
return godot_string_to_pyobj(&self._gd_data)
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_operator_lt() | indent }}
{{ render_method("hash", py_name="__hash__") | indent }}
{{ render_method("operator_plus", py_name="__add__") | indent }}
{{ render_method("begins_with") | indent }}
{{ render_method("bigrams") | indent }}
{{ render_method("c_escape") | indent }}
{{ render_method("c_unescape") | indent }}
{{ render_method("capitalize") | indent }}
{{ render_method("casecmp_to") | indent }}
{{ render_method("count") | indent }}
{{ render_method("countn") | indent }}
{{ render_method("dedent") | indent }}
{{ render_method("empty") | indent }}
{{ render_method("ends_with") | indent }}
{{ render_method("erase") | indent }}
{{ render_method("find") | indent }}
{{ render_method("find_last") | indent }}
{{ render_method("findn") | indent }}
{{ render_method("format") | indent }}
{{ render_method("get_base_dir") | indent }}
{{ render_method("get_basename") | indent }}
{{ render_method("get_extension") | indent }}
{{ render_method("get_file") | indent }}
{{ render_method("hash") | indent }}
{{ render_method("hex_to_int") | indent }}
{{ render_method("http_escape") | indent }}
{{ render_method("http_unescape") | indent }}
@staticmethod
def humanize_size(size_t size):
{{ force_mark_rendered("godot_string_humanize_size") }}
cdef GDString __ret = GDString.__new__(GDString)
__ret._gd_data = gdapi10.godot_string_humanize_size(size)
return __ret
{{ render_method("insert") | indent }}
{{ render_method("is_abs_path") | indent }}
{{ render_method("is_rel_path") | indent }}
{{ render_method("is_subsequence_of") | indent }}
{{ render_method("is_subsequence_ofi") | indent }}
{#- {{ render_method("is_valid_filename") | indent }} # TODO: Missing from binding ! #}
{{ render_method("is_valid_float") | indent }}
{{ render_method("is_valid_hex_number") | indent }}
{{ render_method("is_valid_html_color") | indent }}
{{ render_method("is_valid_identifier") | indent }}
{{ render_method("is_valid_integer") | indent }}
{{ render_method("is_valid_ip_address") | indent }}
{{ render_method("json_escape") | indent }}
{{ render_method("left") | indent }}
{{ render_method("length") | indent }}
{#- {{ render_method("lstrip") | indent }} # TODO: Missing from binding ! #}
{{ render_method("match") | indent }}
{{ render_method("matchn") | indent }}
{{ render_method("md5_buffer") | indent }}
{{ render_method("md5_text") | indent }}
{{ render_method("nocasecmp_to") | indent }}
{{ render_method("ord_at") | indent }}
{{ render_method("pad_decimals") | indent }}
{{ render_method("pad_zeros") | indent }}
{{ render_method("percent_decode") | indent }}
{{ render_method("percent_encode") | indent }}
{{ render_method("plus_file") | indent }}
{#- {{ render_method("repeat") | indent }} # TODO: Missing from binding ! #}
{{ render_method("replace") | indent }}
{{ render_method("replacen") | indent }}
{{ render_method("rfind") | indent }}
{{ render_method("rfindn") | indent }}
{{ render_method("right") | indent }}
{{ render_method("rsplit") | indent }}
{{ render_method("rstrip") | indent }}
{#- {{ render_method("sha1_buffer") | indent }} # TODO: Missing from binding ! #}
{#- {{ render_method("sha1_text") | indent }} # TODO: Missing from binding ! #}
{{ render_method("sha256_buffer") | indent }}
{{ render_method("sha256_text") | indent }}
{{ render_method("similarity") | indent }}
{{ render_method("split") | indent }}
{{ render_method("split_floats") | indent }}
{{ render_method("strip_edges") | indent }}
{{ render_method("strip_escapes") | indent }}
{{ render_method("substr") | indent }}
{#- {{ render_method("to_ascii") | indent }} # TODO: Missing from binding ! #}
{{ render_method("to_float") | indent }}
{{ render_method("to_int") | indent }}
{{ render_method("to_lower") | indent }}
{{ render_method("to_upper") | indent }}
{#- {{ render_method("to_utf8") | indent }} # TODO: Missing from binding ! #}
{{ render_method("trim_prefix") | indent }}
{{ render_method("trim_suffix") | indent }}
{{ render_method("xml_escape") | indent }}
{{ render_method("xml_unescape") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock -%}

View File

@ -0,0 +1,55 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
{{ force_mark_rendered("godot_node_path_new_copy") }} {# NodePath is const, why does this exists in the first place ? #}
@cython.final
cdef class NodePath:
{% block cdef_attributes %}
cdef godot_node_path _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, from_):
{{ force_mark_rendered("godot_node_path_new") }}
cdef godot_string gd_from
try:
gdapi10.godot_node_path_new(&self._gd_data, &(<GDString?>from_)._gd_data)
except TypeError:
if not isinstance(from_, str):
raise TypeError("`from_` must be str or GDString")
pyobj_to_godot_string(from_, &gd_from)
gdapi10.godot_node_path_new(&self._gd_data, &gd_from)
gdapi10.godot_string_destroy(&gd_from)
def __dealloc__(NodePath self):
{{ force_mark_rendered("godot_node_path_destroy") }}
# /!\ if `__init__` is skipped, `_gd_data` must be initialized by
# hand otherwise we will get a segfault here
gdapi10.godot_node_path_destroy(&self._gd_data)
def __repr__(NodePath self):
return f"<NodePath({self.as_string()})>"
def __str__(NodePath self):
return str(self.as_string())
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("destroy") | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("is_absolute") | indent }}
{{ render_method("get_name_count") | indent }}
{{ render_method("get_name") | indent }}
{{ render_method("get_subname_count") | indent }}
{{ render_method("get_subname") | indent }}
{{ render_method("get_concatenated_subnames") | indent }}
{{ render_method("is_empty") | indent }}
{{ render_method("get_as_property_path") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock %}

View File

@ -0,0 +1,89 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
@cython.final
cdef class Plane:
{% block cdef_attributes %}
cdef godot_plane _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, godot_real a, godot_real b, godot_real c, godot_real d):
{{ force_mark_rendered("godot_plane_new_with_reals") }}
gdapi10.godot_plane_new_with_reals(&self._gd_data, a, b, c, d)
@staticmethod
def from_vectors(Vector3 v1 not None, Vector3 v2 not None, Vector3 v3 not None):
cdef Plane ret = Plane.__new__(Plane)
{{ force_mark_rendered("godot_plane_new_with_vectors") }}
gdapi10.godot_plane_new_with_vectors(&ret._gd_data, &v1._gd_data, &v2._gd_data, &v3._gd_data)
return ret
@staticmethod
def from_normal(Vector3 normal not None, godot_real d):
cdef Plane ret = Plane.__new__(Plane)
{{ force_mark_rendered("godot_plane_new_with_normal") }}
gdapi10.godot_plane_new_with_normal(&ret._gd_data, &normal._gd_data, d)
return ret
def __repr__(Plane self):
return f"<Plane({self.as_string()})>"
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("operator_neg", py_name="__neg__") | indent }}
def __pos__(Plane self):
return self
{{ render_property("normal", getter="get_normal", setter="set_normal") | indent }}
{{ render_property("d", getter="get_d", setter="set_d") | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("normalized") | indent }}
{{ render_method("center") | indent }}
{{ render_method("get_any_point") | indent }}
{{ render_method("is_point_over") | indent }}
{{ render_method("distance_to") | indent }}
{{ render_method("has_point") | indent }}
{{ render_method("project") | indent }}
def intersects_segment(Plane self, Vector3 begin not None, Vector3 end not None):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_plane_intersects_segment") }}
if gdapi10.godot_plane_intersects_segment(&self._gd_data, &ret._gd_data, &begin._gd_data, &end._gd_data):
return ret
else:
return None
def intersects_ray(Plane self, Vector3 from_ not None, Vector3 dir not None):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_plane_intersects_ray") }}
if gdapi10.godot_plane_intersects_ray(&self._gd_data, &ret._gd_data, &from_._gd_data, &dir._gd_data):
return ret
else:
return None
def intersect_3(Plane self, Plane b not None, Plane c not None):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_plane_intersect_3") }}
if gdapi10.godot_plane_intersect_3(&self._gd_data, &ret._gd_data, &b._gd_data, &c._gd_data):
return ret
else:
return None
{{ render_method("set_normal") | indent }}
{{ render_method("get_normal") | indent }}
{{ render_method("get_d") | indent }}
{{ render_method("set_d") | indent }}
{% endblock %}
{%- block python_consts %}
PLANE_YZ = Plane(1, 0, 0, 0)
PLANE_XZ = Plane(0, 1, 0, 0)
PLANE_XY = Plane(0, 0, 1, 0)
{% endblock %}

View File

@ -0,0 +1,86 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
@cython.final
cdef class Quat:
{% block cdef_attributes %}
cdef godot_quat _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, x=0, y=0, z=0, w=0):
{{ force_mark_rendered("godot_quat_new") }}
gdapi10.godot_quat_new(&self._gd_data, x, y, z, w)
@staticmethod
def from_axis_angle(Vector3 axis not None, godot_real angle):
# Call to __new__ bypasses __init__ constructor
cdef Quat ret = Quat.__new__(Quat)
{{ force_mark_rendered("godot_quat_new_with_axis_angle") }}
gdapi10.godot_quat_new_with_axis_angle(&ret._gd_data, &axis._gd_data, angle)
return ret
@staticmethod
def from_basis(Basis basis not None):
# Call to __new__ bypasses __init__ constructor
cdef Quat ret = Quat.__new__(Quat)
{{ force_mark_rendered("godot_quat_new_with_basis") }}
gdapi11.godot_quat_new_with_basis(&ret._gd_data, &basis._gd_data)
return ret
@staticmethod
def from_euler(Vector3 euler not None):
# Call to __new__ bypasses __init__ constructor
cdef Quat ret = Quat.__new__(Quat)
{{ force_mark_rendered("godot_quat_new_with_euler") }}
gdapi11.godot_quat_new_with_euler(&ret._gd_data, &euler._gd_data)
return ret
def __repr__(Quat self):
return f"<Quat(x={self.x}, y={self.y}, z={self.z}, w={self.w})>"
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("operator_neg", py_name="__neg__") | indent }}
def __pos__(Quat self):
return self
{{ render_method("operator_add", py_name="__add__") | indent }}
{{ render_method("operator_subtract", py_name="__sub__") | indent }}
{{ render_method("operator_multiply", py_name="__mul__") | indent }}
def __truediv__(Quat self, godot_real val):
if val == 0:
raise ZeroDivisionError
cdef Quat ret = Quat.__new__(Quat)
{{ force_mark_rendered("godot_quat_operator_divide") }}
ret._gd_data = gdapi10.godot_quat_operator_divide(&self._gd_data, val)
return ret
{{ render_property("x", getter="get_x", setter="set_x") | indent }}
{{ render_property("y", getter="get_y", setter="set_y") | indent }}
{{ render_property("z", getter="get_z", setter="set_z") | indent }}
{{ render_property("w", getter="get_w", setter="set_w") | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("length") | indent }}
{{ render_method("length_squared") | indent }}
{{ render_method("normalized") | indent }}
{{ render_method("is_normalized") | indent }}
{{ render_method("inverse") | indent }}
{{ render_method("dot") | indent }}
{{ render_method("xform") | indent }}
{{ render_method("slerp") | indent }}
{{ render_method("slerpni") | indent }}
{{ render_method("cubic_slerp") | indent }}
{{ render_method("set_axis_angle") | indent }}
{% endblock %}
{%- block python_consts %}
IDENTITY = Quat(0, 0, 0, 1)
{% endblock %}

View File

@ -0,0 +1,58 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
@cython.final
cdef class Rect2:
{% block cdef_attributes %}
cdef godot_rect2 _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, godot_real x=0.0, godot_real y=0.0, godot_real width=0.0, godot_real height=0.0):
{{ force_mark_rendered("godot_rect2_new") }}
gdapi10.godot_rect2_new(&self._gd_data, x, y, width, height)
@staticmethod
def from_pos_size(Vector2 position not None, Vector2 size not None):
{{ force_mark_rendered("godot_rect2_new_with_position_and_size") }}
cdef Rect2 ret = Rect2.__new__(Rect2)
gdapi10.godot_rect2_new_with_position_and_size(&ret._gd_data, &position._gd_data, &size._gd_data)
return ret
def __repr__(Rect2 self):
return f"<Rect2({self.as_string()})>"
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_property("size", getter="get_size", setter="set_size") | indent }}
{{ render_property("position", getter="get_position", setter="set_position") | indent }}
@property
def end(Rect2 self) -> Vector2:
cdef godot_vector2 position = gdapi10.godot_rect2_get_position(&self._gd_data)
cdef godot_vector2 size = gdapi10.godot_rect2_get_size(&self._gd_data)
cdef Vector2 ret = Vector2.__new__(Vector2)
ret._gd_data = gdapi10.godot_vector2_operator_add(&position, &size)
return ret
{{ render_method("as_string") | indent }}
{{ render_method("get_area") | indent }}
{{ render_method("intersects") | indent }}
{{ render_method("encloses") | indent }}
{{ render_method("has_no_area") | indent }}
{{ render_method("clip") | indent }}
{{ render_method("merge") | indent }}
{{ render_method("has_point") | indent }}
{{ render_method("grow") | indent }}
{{ render_method("grow_individual") | indent }}
{{ render_method("grow_margin") | indent }}
{{ render_method("abs") | indent }}
{{ render_method("expand") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock %}

View File

@ -0,0 +1,20 @@
{#- `render_target` must be defined by calling context -#}
{% set get_target_method_spec = get_target_method_spec_factory(render_target) %}
{#- Define rendering macros -#}
{% macro render_method(method_name, py_name=None, default_args={}) %}{% endmacro %}
{% macro render_property(py_name, getter, setter=None) %}{% endmacro %}
{% macro render_operator_eq() %}{% endmacro %}
{% macro render_operator_ne() %}{% endmacro %}
{% macro render_operator_lt() %}{% endmacro %}
{#- Overwrite blocks to be ignored -#}
{% block pyx_header %}{% endblock %}
{% block python_defs %}{% endblock %}
{% block python_consts %}{% endblock %}
{#- Now the template will be generated with the context -#}
{% extends render_target_to_template(render_target) %}

View File

@ -0,0 +1,44 @@
{#- `render_target` must be defined by calling context -#}
{% set get_target_method_spec = get_target_method_spec_factory(render_target) %}
{#- Define rendering macros -#}
{% macro render_method(method_name, py_name=None, default_args={}) %}
{% set spec = get_target_method_spec(method_name) %}
def {{ py_name or spec.py_name }}(self{%- if spec.args -%},{%- endif -%}
{%- for arg in spec.args %}
{{ arg.name }}: {{ arg.type.py_type }}
,
{%- endfor -%}
) -> {{ spec.return_type.py_type }}: ...
{% endmacro %}
{% macro render_operator_eq() %}
def __eq__(self, other) -> bool: ...
{% endmacro %}
{% macro render_operator_ne() %}
def __ne__(self, other) -> bool: ...
{% endmacro %}
{% macro render_operator_lt() %}
def __lt__(self, other) -> bool: ...
{% endmacro %}
{% macro render_property(py_name, getter, setter=None) %}
{{ pyname }}: {{ getter.return_type.py_type }}
{% endmacro %}
{#- Overwrite blocks to be ignored -#}
{% block python_defs %}
pass
{% endblock %}
{% block pxd_header %}{% endblock %}
{% block pyx_header %}{% endblock %}
{% block python_consts %}{% endblock %}
{% block cdef_attributes %}{% endblock %}
{#- Now the template will be generated with the context -#}
{% extends render_target_to_template(render_target) %}

View File

@ -0,0 +1,120 @@
{#- `render_target` must be defined by calling context -#}
{% set get_target_method_spec = get_target_method_spec_factory(render_target) %}
{#- Define rendering macros -#}
{% macro render_method(method_name, py_name=None, default_args={}) %}
{% set spec = get_target_method_spec(method_name) %}
{% set args_without_self = spec.args[1:] %}
def {{ py_name or spec.py_name }}({{ spec.klass.cy_type }} self{%- if args_without_self -%},{%- endif -%}
{%- for arg in args_without_self %}
{{ arg.cy_type }} {{ arg.name }}
{%- if not arg.is_base_type and not arg.is_variant %}
not None
{%- endif -%}
,
{%- endfor -%}
) -> {{ spec.return_type.py_type }}:
{% for arg in args_without_self %}
{% if arg.is_variant %}
cdef godot_variant __var_{{ arg.name }}
if not pyobj_to_godot_variant({{ arg.name }}, &__var_{{ arg.name }}):
{% for initialized_arg in args_without_self %}
{% if initialized_arg.name == arg.name %}
{% break %}
{% endif %}
{% if initialized_arg.is_variant %}
gdapi10.godot_variant_destroy(&__var_{{ initialized_arg.name }})
{% endif %}
{% endfor %}
raise TypeError(f"Cannot convert `{ {{ arg.name}} !r}` to Godot Variant")
{% endif %}
{% endfor %}
{% if spec.return_type.is_variant %}
cdef godot_variant __var_ret = (
{%- elif spec.return_type.is_builtin %}
cdef {{ spec.return_type.cy_type }} __ret = {{ spec.return_type.cy_type }}.__new__({{ spec.return_type.cy_type }})
__ret._gd_data = (
{%- elif spec.return_type.is_object %}
cdef {{ spec.return_type.cy_type }} __ret = {{ spec.return_type.cy_type }}.__new__({{ spec.return_type.cy_type }})
__ret._gd_ptr = (
{%- elif not spec.return_type.is_void %}
cdef {{ spec.return_type.cy_type }} __ret = (
{%- else %}
(
{%- endif %}
{{ spec.gdapi }}.{{ spec.c_name }}(&self._gd_data,
{%- for arg in args_without_self %}
{%- if arg.is_variant %}
&__var_{{ arg.name }},
{%- elif arg.is_builtin %}
{%- if arg.is_ptr %}
&{{ arg.name }}._gd_data,
{%- else %}
{{ arg.name }}._gd_data,
{%- endif %}
{%- elif arg.is_object %}
{{ arg.name }}._gd_ptr,
{%- else %}
{{ arg.name }},
{%- endif %}
{% endfor %}
))
{% for arg in args_without_self %}
{% if arg.is_variant %}
gdapi10.godot_variant_destroy(&__var_{{ arg.name }})
{% endif %}
{% endfor %}
{% if spec.return_type.is_variant %}
cdef object __ret = godot_variant_to_pyobj(&__var_ret)
gdapi10.godot_variant_destroy(&__var_ret)
return __ret
{% elif not spec.return_type.is_void %}
return __ret
{% endif %}
{% endmacro %}
{% macro render_operator_eq() %}
{% set spec = get_target_method_spec("operator_equal") %}
def __eq__({{ spec.klass.cy_type }} self, other):
try:
return {{ spec.gdapi }}.{{ spec.c_name }}(&self._gd_data, &(<{{ spec.klass.cy_type }}?>other)._gd_data)
except TypeError:
return False
{% endmacro %}
{% macro render_operator_ne() %}
{% set spec = get_target_method_spec("operator_equal") %}
def __ne__({{ spec.klass.cy_type }} self, other):
try:
return not {{ spec.gdapi }}.{{ spec.c_name }}(&self._gd_data, &(<{{ spec.klass.cy_type }}?>other)._gd_data)
except TypeError:
return True
{% endmacro %}
{% macro render_operator_lt() %}
{% set spec = get_target_method_spec("operator_less") %}
def __lt__({{ spec.klass.cy_type }} self, other):
try:
return {{ spec.gdapi }}.{{ spec.c_name }}(&self._gd_data, &(<{{ spec.klass.cy_type }}?>other)._gd_data)
except TypeError:
return False
{% endmacro %}
{% macro render_property(py_name, getter, setter=None) %}
@property
{{ render_method(getter, py_name=py_name) }}
{% if setter %}
@{{ py_name }}.setter
{{ render_method(setter, py_name=py_name) }}
{% endif %}
{% endmacro %}
{#- Overwrite blocks to be ignored -#}
{% block pxd_header %}{% endblock %}
{% block cdef_attributes %}{% endblock %}
{#- Now the template will be generated with the context -#}
{% extends render_target_to_template(render_target) %}

View File

@ -0,0 +1,44 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
from godot.bindings cimport Resource
{% endblock -%}
@cython.final
cdef class RID:
{% block cdef_attributes %}
cdef godot_rid _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, Resource from_=None):
if from_ is not None:
{{ force_mark_rendered("godot_rid_new_with_resource") }}
gdapi10.godot_rid_new_with_resource(
&self._gd_data,
from_._gd_ptr
)
else:
{{ force_mark_rendered("godot_rid_new") }}
gdapi10.godot_rid_new(&self._gd_data)
def __repr__(RID self):
return f"<RID(id={self.get_id()})>"
@staticmethod
def from_resource(Resource resource not None):
# Call to __new__ bypasses __init__ constructor
cdef RID ret = RID.__new__(RID)
gdapi10.godot_rid_new_with_resource(&ret._gd_data, resource._gd_ptr)
return ret
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_operator_lt() | indent }}
{{ render_method("get_id") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock -%}

View File

@ -0,0 +1,74 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
@cython.final
cdef class Transform:
{% block cdef_attributes %}
cdef godot_transform _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, x_axis=None, y_axis=None, z_axis=None, origin=None):
if x_axis is None and y_axis is None and z_axis is None and origin is None:
{{ force_mark_rendered("godot_transform_new_identity") }}
gdapi10.godot_transform_new_identity(&self._gd_data)
else:
{{ force_mark_rendered("godot_transform_new_with_axis_origin") }}
gdapi10.godot_transform_new_with_axis_origin(
&self._gd_data,
&(<Vector3?>x_axis)._gd_data,
&(<Vector3?>y_axis)._gd_data,
&(<Vector3?>z_axis)._gd_data,
&(<Vector3?>origin)._gd_data,
)
@staticmethod
def from_basis_origin(Basis basis not None, Vector3 origin not None):
cdef Transform ret = Transform.__new__(Transform)
{{ force_mark_rendered("godot_transform_new") }}
gdapi10.godot_transform_new(&ret._gd_data, &basis._gd_data, &origin._gd_data)
return ret
@staticmethod
def from_quat(Quat quat not None):
cdef Transform ret = Transform.__new__(Transform)
{{ force_mark_rendered("godot_transform_new_with_quat") }}
gdapi11.godot_transform_new_with_quat(&ret._gd_data, &quat._gd_data)
return ret
def __repr__(Transform self):
return f"<Transform({self.as_string()})>"
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("operator_multiply", py_name="__mul__") | indent }}
{{ render_property("basis", getter="get_basis", setter="set_basis") | indent }}
{{ render_property("origin", getter="get_origin", setter="set_origin") | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("inverse") | indent }}
{{ render_method("affine_inverse") | indent }}
{{ render_method("orthonormalized") | indent }}
{{ render_method("rotated") | indent }}
{{ render_method("scaled") | indent }}
{{ render_method("translated") | indent }}
{{ render_method("looking_at") | indent }}
{{ render_method("xform_plane") | indent }}
{{ render_method("xform_inv_plane") | indent }}
{{ render_method("xform_vector3") | indent }}
{{ render_method("xform_inv_vector3") | indent }}
{{ render_method("xform_aabb") | indent }}
{{ render_method("xform_inv_aabb") | indent }}
{% endblock %}
{%- block python_consts %}
IDENTITY = Transform(Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1), Vector3(0, 0, 0))
FLIP_X = Transform(Vector3(-1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1), Vector3(0, 0, 0))
FLIP_Y = Transform(Vector3(1, 0, 0), Vector3(0, -1, 0), Vector3(0, 0, 1), Vector3(0, 0, 0))
FLIP_Z = Transform(Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, -1), Vector3(0, 0, 0))
{% endblock %}

View File

@ -0,0 +1,97 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
{% endblock -%}
@cython.final
cdef class Transform2D:
{% block cdef_attributes %}
cdef godot_transform2d _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, x_axis=None, y_axis=None, origin=None):
if x_axis is None and y_axis is None and origin is None:
{{ force_mark_rendered("godot_transform2d_new_identity") }}
gdapi10.godot_transform2d_new_identity(&self._gd_data)
else:
{{ force_mark_rendered("godot_transform2d_new_axis_origin") }}
gdapi10.godot_transform2d_new_axis_origin(
&self._gd_data,
&(<Vector2?>x_axis)._gd_data,
&(<Vector2?>y_axis)._gd_data,
&(<Vector2?>origin)._gd_data,
)
@staticmethod
def from_rot_pos(godot_real rot, Vector2 pos not None):
cdef Transform2D ret = Transform2D.__new__(Transform2D)
{{ force_mark_rendered("godot_transform2d_new") }}
gdapi10.godot_transform2d_new(&ret._gd_data, rot, &pos._gd_data)
return ret
def __repr__(Transform2D self):
return f"<Transform2D({self.as_string()})>"
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_method("operator_multiply", py_name="__mul__") | indent }}
# TODO: add axis properties once gdnative is updated
{{ render_property("origin", getter="get_origin") | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("inverse") | indent }}
{{ render_method("affine_inverse") | indent }}
{{ render_method("get_rotation") | indent }}
{{ render_method("get_scale") | indent }}
{{ render_method("orthonormalized") | indent }}
{{ render_method("rotated") | indent }}
{{ render_method("scaled") | indent }}
{{ render_method("translated") | indent }}
def xform(Transform2D self, v):
cdef Vector2 ret_v2
cdef Rect2 ret_r2
try:
ret_v2 = Vector2.__new__(Vector2)
{{ force_mark_rendered("godot_transform2d_xform_vector2") }}
ret_v2._gd_data = gdapi10.godot_transform2d_xform_vector2(&self._gd_data, &(<Vector2?>v)._gd_data)
return ret_v2
except TypeError:
pass
try:
ret_r2 = Rect2.__new__(Rect2)
{{ force_mark_rendered("godot_transform2d_xform_rect2") }}
ret_r2._gd_data = gdapi10.godot_transform2d_xform_rect2(&self._gd_data, &(<Rect2?>v)._gd_data)
return ret_r2
except TypeError:
raise TypeError("`v` must be Vector2 or Rect2")
def xform_inv(Transform2D self, v):
cdef Vector2 ret_v2
cdef Rect2 ret_r2
try:
ret_v2 = Vector2.__new__(Vector2)
{{ force_mark_rendered("godot_transform2d_xform_inv_vector2") }}
ret_v2._gd_data = gdapi10.godot_transform2d_xform_inv_vector2(&self._gd_data, &(<Vector2?>v)._gd_data)
return ret_v2
except TypeError:
pass
try:
ret_r2 = Rect2.__new__(Rect2)
{{ force_mark_rendered("godot_transform2d_xform_inv_rect2") }}
ret_r2._gd_data = gdapi10.godot_transform2d_xform_inv_rect2(&self._gd_data, &(<Rect2?>v)._gd_data)
return ret_r2
except TypeError:
raise TypeError("`v` must be Vector2 or Rect2")
{{ render_method("basis_xform_vector2", py_name="basis_xform") | indent }}
{{ render_method("basis_xform_inv_vector2", py_name="basis_xform_inv") | indent }}
{{ render_method("interpolate_with") | indent }}
{% endblock %}
{%- block python_consts %}
{% endblock %}

View File

@ -0,0 +1,123 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
import math
cdef inline Vector2 Vector2_multiply_vector(Vector2 self, Vector2 b):
cdef Vector2 ret = Vector2.__new__(Vector2)
{{ force_mark_rendered("godot_vector2_operator_multiply_vector") }}
ret._gd_data = gdapi10.godot_vector2_operator_multiply_vector(&self._gd_data, &b._gd_data)
return ret
cdef inline Vector2 Vector2_multiply_scalar(Vector2 self, godot_real b):
cdef Vector2 ret = Vector2.__new__(Vector2)
{{ force_mark_rendered("godot_vector2_operator_multiply_scalar") }}
ret._gd_data = gdapi10.godot_vector2_operator_multiply_scalar(&self._gd_data, b)
return ret
cdef inline Vector2 Vector2_divide_vector(Vector2 self, Vector2 b):
cdef Vector2 ret = Vector2.__new__(Vector2)
{{ force_mark_rendered("godot_vector2_operator_divide_vector") }}
ret._gd_data = gdapi10.godot_vector2_operator_divide_vector(&self._gd_data, &b._gd_data)
return ret
cdef inline Vector2 Vector2_divide_scalar(Vector2 self, godot_real b):
cdef Vector2 ret = Vector2.__new__(Vector2)
{{ force_mark_rendered("godot_vector2_operator_divide_scalar") }}
ret._gd_data = gdapi10.godot_vector2_operator_divide_scalar(&self._gd_data, b)
return ret
{% endblock -%}
@cython.final
cdef class Vector2:
{% block cdef_attributes %}
cdef godot_vector2 _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, godot_real x=0.0, godot_real y=0.0):
{{ force_mark_rendered("godot_vector2_new") }}
gdapi10.godot_vector2_new(&self._gd_data, x, y)
def __repr__(Vector2 self):
return f"<Vector2(x={self.x}, y={self.y})>"
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_operator_lt() | indent }}
{{ render_method("operator_neg", py_name="__neg__") | indent }}
def __pos__(Vector2 self):
return self
{{ render_method("operator_add", py_name="__add__") | indent }}
{{ render_method("operator_subtract", py_name="__sub__") | indent }}
def __mul__(Vector2 self, val):
cdef Vector2 _val
try:
_val = <Vector2?>val
except TypeError:
return Vector2_multiply_scalar(self, val)
else:
return Vector2_multiply_vector(self, _val)
def __truediv__(Vector2 self, val):
cdef Vector2 _val
try:
_val = <Vector2?>val
except TypeError:
if val is 0:
raise ZeroDivisionError()
return Vector2_divide_scalar(self, val)
else:
if _val.x == 0 or _val.y == 0:
raise ZeroDivisionError()
return Vector2_divide_vector(self, _val)
{{ render_property("x", "get_x", "set_x") | indent }}
{{ render_property("y", "get_y", "set_y") | indent }}
{{ render_property("width", "get_x", "set_x") | indent }}
{{ render_property("height", "get_y", "set_y") | indent }}
{{ render_method("as_string") | indent }}
{{ render_method("normalized") | indent }}
{{ render_method("length") | indent }}
{{ render_method("angle") | indent }}
{{ render_method("length_squared") | indent }}
{{ render_method("is_normalized") | indent }}
{{ render_method("distance_to") | indent }}
{{ render_method("distance_squared_to") | indent }}
{{ render_method("angle_to") | indent }}
{{ render_method("angle_to_point") | indent }}
{{ render_method("linear_interpolate") | indent }}
{{ render_method("cubic_interpolate") | indent }}
{{ render_method("move_toward") | indent }}
{{ render_method("direction_to") | indent }}
{{ render_method("rotated") | indent }}
{{ render_method("tangent") | indent }}
{{ render_method("floor") | indent }}
{{ render_method("snapped") | indent }}
{{ render_method("aspect") | indent }}
{{ render_method("dot") | indent }}
{{ render_method("slide") | indent }}
{{ render_method("bounce") | indent }}
{{ render_method("reflect") | indent }}
{{ render_method("abs") | indent }}
{{ render_method("clamped") | indent }}
{% endblock %}
{%- block python_consts %}
AXIS_X = 0
AXIS_Y = 0
ZERO = Vector2(0, 0)
ONE = Vector2(1, 1)
INF = Vector2(math.inf, math.inf)
LEFT = Vector2(-1, 0)
RIGHT = Vector2(1, 0)
UP = Vector2(0, -1)
DOWN = Vector2(0, 1)
{% endblock %}

View File

@ -0,0 +1,160 @@
{%- block pxd_header %}
{% endblock -%}
{%- block pyx_header %}
from godot._hazmat.gdnative_api_struct cimport godot_vector3_axis
import math
from enum import IntEnum
cdef inline Vector3_multiply_vector(Vector3 self, Vector3 b):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_vector3_operator_multiply_vector") }}
ret._gd_data = gdapi10.godot_vector3_operator_multiply_vector(&self._gd_data, &b._gd_data)
return ret
cdef inline Vector3_multiply_scalar(Vector3 self, godot_real b):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_vector3_operator_multiply_scalar") }}
ret._gd_data = gdapi10.godot_vector3_operator_multiply_scalar(&self._gd_data, b)
return ret
cdef inline Vector3_divide_vector(Vector3 self, Vector3 b):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_vector3_operator_divide_vector") }}
ret._gd_data = gdapi10.godot_vector3_operator_divide_vector(&self._gd_data, &b._gd_data)
return ret
cdef inline Vector3_divide_scalar(Vector3 self, godot_real b):
cdef Vector3 ret = Vector3.__new__(Vector3)
{{ force_mark_rendered("godot_vector3_operator_divide_scalar") }}
ret._gd_data = gdapi10.godot_vector3_operator_divide_scalar(&self._gd_data, b)
return ret
{% endblock -%}
@cython.final
cdef class Vector3:
{% block cdef_attributes %}
cdef godot_vector3 _gd_data
{% endblock %}
{% block python_defs %}
def __init__(self, godot_real x=0.0, godot_real y=0.0, godot_real z=0.0):
{{ force_mark_rendered("godot_vector3_new") }}
gdapi10.godot_vector3_new(&self._gd_data, x, y, z)
def __repr__(self):
return f"<Vector3(x={self.x}, y={self.y}, z={self.z})>"
@property
def x(self) -> godot_real:
{{ force_mark_rendered("godot_vector3_get_axis") }}
return gdapi10.godot_vector3_get_axis(&self._gd_data, godot_vector3_axis.GODOT_VECTOR3_AXIS_X)
@x.setter
def x(self, godot_real val) -> None:
{{ force_mark_rendered("godot_vector3_set_axis") }}
gdapi10.godot_vector3_set_axis(&self._gd_data, godot_vector3_axis.GODOT_VECTOR3_AXIS_X, val)
@property
def y(self) -> godot_real:
{{ force_mark_rendered("godot_vector3_get_axis") }}
return gdapi10.godot_vector3_get_axis(&self._gd_data, godot_vector3_axis.GODOT_VECTOR3_AXIS_Y)
@y.setter
def y(self, godot_real val) -> None:
{{ force_mark_rendered("godot_vector3_set_axis") }}
gdapi10.godot_vector3_set_axis(&self._gd_data, godot_vector3_axis.GODOT_VECTOR3_AXIS_Y, val)
@property
def z(self) -> godot_real:
{{ force_mark_rendered("godot_vector3_get_axis") }}
return gdapi10.godot_vector3_get_axis(&self._gd_data, godot_vector3_axis.GODOT_VECTOR3_AXIS_Z)
@z.setter
def z(self, godot_real val) -> None:
{{ force_mark_rendered("godot_vector3_set_axis") }}
gdapi10.godot_vector3_set_axis(&self._gd_data, godot_vector3_axis.GODOT_VECTOR3_AXIS_Z, val)
{{ render_operator_eq() | indent }}
{{ render_operator_ne() | indent }}
{{ render_operator_lt() | indent }}
{{ render_method("operator_neg", py_name="__neg__") | indent }}
def __pos__(Vector3 self):
return self
{{ render_method("operator_add", py_name="__add__") | indent }}
{{ render_method("operator_subtract", py_name="__sub__") | indent }}
def __mul__(Vector3 self, val):
cdef Vector3 _val
try:
_val = <Vector3?>val
except TypeError:
return Vector3_multiply_scalar(self, val)
else:
return Vector3_multiply_vector(self, _val)
def __truediv__(Vector3 self, val):
cdef Vector3 _val
try:
_val = <Vector3?>val
except TypeError:
if val is 0:
raise ZeroDivisionError()
return Vector3_divide_scalar(self, val)
else:
if _val.x == 0 or _val.y == 0 or _val.z == 0:
raise ZeroDivisionError()
return Vector3_divide_vector(self, _val)
{{ render_method("as_string") | indent }}
{{ render_method("min_axis") | indent }}
{{ render_method("max_axis") | indent }}
{{ render_method("length") | indent }}
{{ render_method("length_squared") | indent }}
{{ render_method("is_normalized") | indent }}
{{ render_method("normalized") | indent }}
{{ render_method("inverse") | indent }}
{{ render_method("snapped") | indent }}
{{ render_method("rotated") | indent }}
{{ render_method("linear_interpolate") | indent }}
{{ render_method("cubic_interpolate") | indent }}
{{ render_method("move_toward") | indent }}
{{ render_method("direction_to") | indent }}
{{ render_method("dot") | indent }}
{{ render_method("cross") | indent }}
{{ render_method("outer") | indent }}
{{ render_method("to_diagonal_matrix") | indent }}
{{ render_method("abs") | indent }}
{{ render_method("floor") | indent }}
{{ render_method("ceil") | indent }}
{{ render_method("distance_to") | indent }}
{{ render_method("distance_squared_to") | indent }}
{{ render_method("angle_to") | indent }}
{{ render_method("slide") | indent }}
{{ render_method("bounce") | indent }}
{{ render_method("reflect") | indent }}
{% endblock %}
{%- block python_consts %}
AXIS = IntEnum("AXIS", {
"X": godot_vector3_axis.GODOT_VECTOR3_AXIS_X,
"Y": godot_vector3_axis.GODOT_VECTOR3_AXIS_Y,
"Z": godot_vector3_axis.GODOT_VECTOR3_AXIS_Z,
})
ZERO = Vector3(0, 0, 0) # Zero vector.
ONE = Vector3(1, 1, 1) # One vector.
INF = Vector3(math.inf, math.inf, math.inf) # Infinite vector.
LEFT = Vector3(-1, 0, 0) # Left unit vector.
RIGHT = Vector3(1, 0, 0) # Right unit vector.
UP = Vector3(0, 1, 0) # Up unit vector.
DOWN = Vector3(0, -1, 0) # Down unit vector.
FORWARD = Vector3(0, 0, -1) # Forward unit vector.
BACK = Vector3(0, 0, 1) # Back unit vector.
{% endblock %}

View File

@ -0,0 +1,613 @@
import os
import argparse
import json
import re
from warnings import warn
from keyword import iskeyword
from collections import defaultdict
from jinja2 import Environment, FileSystemLoader
from dataclasses import dataclass, replace
from typing import Optional, Dict, List, Tuple
from type_specs import TypeSpec, ALL_TYPES_EXCEPT_OBJECTS
BASEDIR = os.path.dirname(__file__)
env = Environment(
loader=FileSystemLoader(f"{BASEDIR}/bindings_templates"), trim_blocks=True, lstrip_blocks=True
)
@dataclass
class PropertyInfo:
name: str
type: TypeSpec
getter: str
setter: str
index: Optional[int]
# If using feature we don't support yet
unsupported_reason: Optional[str] = None
@property
def is_supported(self) -> bool:
return self.unsupported_reason is None
@dataclass
class ArgumentInfo:
name: str
type: TypeSpec
default_value: Optional[str]
@property
def has_default_value(self):
return self.default_value is not None
@dataclass
class SignalInfo:
name: str
arguments: List[ArgumentInfo]
# If using feature we don't support yet
unsupported_reason: Optional[str] = None
@property
def is_supported(self) -> bool:
return self.unsupported_reason is None
@dataclass
class MethodInfo:
name: str
return_type: TypeSpec
is_editor: bool
is_noscript: bool
is_const: bool
is_reverse: bool
is_virtual: bool
has_varargs: bool
is_from_script: bool
arguments: List[ArgumentInfo]
# If using feature we don't support yet
unsupported_reason: Optional[str] = None
@property
def is_supported(self) -> bool:
return self.unsupported_reason is None
@dataclass
class EnumInfo:
name: str
values: Dict[str, int]
@dataclass
class ClassInfo:
# Cleaned up name (mainly ensure singleton classes have a leading underscore)
name: str
# Name as provided in api.json (needed to use GDNative's ClassDB)
bind_register_name: str
# Parent class name (also cleaned up)
base_class: str
singleton: Optional[str]
instantiable: bool
is_reference: bool
constants: Dict[str, int]
properties: List[PropertyInfo]
signals: List[SignalInfo]
methods: List[MethodInfo]
enums: List[EnumInfo]
TYPES = {t.gdapi_type: t for t in ALL_TYPES_EXCEPT_OBJECTS}
# Basically provide enough to run the tests and the pong demo
SAMPLE_CLASSES = {
"Object",
"_ProjectSettings",
"_Input",
"_InputMap",
"MainLoop",
"SceneTree",
"Node",
"CanvasItem",
"Node2D",
"Reference",
"Resource",
"OpenSimplexNoise",
"CollisionObject2D",
"Area2D",
"ARVRInterface",
"ARVRInterfaceGDNative",
"Environment",
"Viewport",
"Script",
"PluginScript",
"GDScript",
"Control",
"Label",
# "_ClassDB",
# "_Engine",
# "_Geometry",
# "_JSON",
"_OS",
"_ResourceLoader",
# "_ResourceSaver",
# "_VisualScriptEditor",
"SurfaceTool",
"Mesh",
"ArrayMesh",
"Spatial",
"VisualInstance",
"GeometryInstance",
"MeshInstance",
# For REPL editor plugin
"GlobalConstants",
"EditorPlugin",
"PackedScene",
"BaseButton",
"Button",
"ToolButton",
"Panel",
"Container",
"BoxContainer",
"VBoxContainer",
"HBoxContainer",
"RichTextLabel",
"LineEdit",
"Font",
"BitmapFont",
"DynamicFont",
"DynamicFontData",
# Input event & friends stuff
"InputEvent",
"InputEventAction",
"InputEventJoypadButton",
"InputEventJoypadMotion",
"InputEventMIDI",
"InputEventScreenDrag",
"InputEventScreenTouch",
"InputEventWithModifiers",
"InputEventGesture",
"InputEventMagnifyGesture",
"InputEventPanGesture",
"InputEventKey",
"InputEventMouse",
"InputEventMouseButton",
"InputEventMouseMotion",
}
SUPPORTED_TYPES = {
"void",
"godot_bool",
"godot_int",
"godot_real",
"godot_string",
"godot_variant",
"godot_object",
"godot_aabb",
"godot_array",
"godot_basis",
"godot_color",
"godot_dictionary",
"godot_node_path",
"godot_plane",
"godot_quat",
"godot_rect2",
"godot_rid",
"godot_transform",
"godot_transform2d",
"godot_vector2",
"godot_vector3",
"godot_pool_byte_array",
"godot_pool_int_array",
"godot_pool_real_array",
"godot_pool_string_array",
"godot_pool_vector2_array",
"godot_pool_vector3_array",
"godot_pool_color_array",
}
def pre_cook_patch_stuff(raw_data):
for klass in raw_data:
# see https://github.com/godotengine/godot/pull/40386
if klass["name"] == "Reference":
klass["is_reference"] = True
for prop in klass["properties"]:
prop["name"] = prop["name"].replace("/", "_")
# see https://github.com/godotengine/godot/pull/40383
if prop["type"] == "17/17:RichTextEffect":
prop["type"] = "Array"
for meth in klass["methods"]:
if meth["is_noscript"]:
warn(
f"`{klass['name']}.{meth['name']}` has `is_noscript=True`"
" (should never be the case...)"
)
if meth["is_from_script"]:
warn(
f"`{klass['name']}.{meth['name']}` has `is_from_script=True`"
" (should never be the case...)"
)
def post_cook_patch_stuff(classes):
for klass in classes:
# See https://github.com/godotengine/godot/issues/34254
if klass.name == "_OS":
for meth in klass.methods:
if meth.name in (
"get_static_memory_usage",
"get_static_memory_peak_usage",
"get_dynamic_memory_usage",
):
meth.return_type.c_type = "uint64_t"
def strip_unsupported_stuff(classes):
supported_classes = {k.name for k in classes}
def _is_supported_type(specs):
if specs.is_builtin:
return specs.c_type in SUPPORTED_TYPES
elif specs.is_object:
return specs.cy_type in supported_classes
else:
return True
for klass in classes:
for meth in klass.methods:
unsupported_reason = None
# TODO: handle default param values
# TODO: handle those flags
if meth.is_editor:
unsupported_reason = "attribute `is_editor=True` not supported"
if meth.is_reverse:
unsupported_reason = "attribute `is_reverse=True` not supported"
if meth.has_varargs:
unsupported_reason = "attribute `has_varargs=True` not supported"
if not _is_supported_type(meth.return_type):
unsupported_reason = f"return type {meth.return_type} not supported"
bad_arg = next(
(arg for arg in meth.arguments if not _is_supported_type(arg.type)), None
)
if bad_arg:
unsupported_reason = f"argument type {bad_arg} not supported"
if unsupported_reason:
warn(f"Ignoring `{klass.name}.{meth.name}` ({unsupported_reason})")
meth.unsupported_reason = unsupported_reason
for prop in klass.properties:
if not _is_supported_type(prop.type):
unsupported_reason = f"property type {prop.type} not supported"
warn(f"Ignoring property `{klass.name}.{prop.name}` ({unsupported_reason})")
prop.unsupported_reason = unsupported_reason
for signal in klass.signals:
bad_arg = next(
(arg for arg in signal.arguments if not _is_supported_type(arg.type)), None
)
if bad_arg:
unsupported_reason = f"argument type {bad_arg} not supported"
warn(f"Ignoring signal `{klass.name}.{signal.name}` ({unsupported_reason})")
signal.unsupported_reason = unsupported_reason
def strip_sample_stuff(classes):
def _is_supported(type):
return not type.is_object or type.cy_type in SAMPLE_CLASSES
classes2 = [klass for klass in classes if klass.name in SAMPLE_CLASSES]
for klass in classes2:
klass.methods = [
meth
for meth in klass.methods
if all(_is_supported(arg.type) for arg in meth.arguments)
and _is_supported(meth.return_type)
]
klass.signals = [
signal
for signal in klass.signals
if all(_is_supported(arg.type) for arg in signal.arguments)
]
klass.properties = [prop for prop in klass.properties if _is_supported(prop.type)]
classes[:] = classes2
def camel_to_snake(name):
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
def build_class_renames(data):
renames = {"": ""}
for item in data:
old_name = item["name"]
# In api.json, some singletons have underscore and others don't (
# e.g. ARVRServer vs _OS). But to access them with `get_singleton_object`
# we always need the name without underscore...
if item["singleton"] and not old_name.startswith("_"):
new_name = f"_{old_name}"
else:
new_name = old_name
renames[old_name] = new_name
return renames
def cook_data(data):
classes = []
constants = {}
class_renames = build_class_renames(data)
def _cook_type(type_):
try:
return TYPES[type_]
except KeyError:
if type_.startswith("enum."):
# typically somethin like ``enum.AnimationTree::AnimationProcessMode``
pcls, ecls = re.match(r"enum.(\w+)::(\w+)", type_).groups()
return TypeSpec(
gdapi_type=type_,
c_type="godot_int",
cy_type="godot_int",
py_type=f"{class_renames[pcls]}.{ecls}",
is_base_type=True,
is_stack_only=True,
is_enum=True,
)
# TODO: improve handling of resources
if "," in type_:
return TypeSpec(
gdapi_type=type_,
c_type="godot_object",
cy_type="Resource",
py_type=f"Union[{','.join([class_renames[x] for x in type_.split(',')])}]",
is_object=True,
)
else:
return TypeSpec(
gdapi_type=type_,
c_type="godot_object",
cy_type=class_renames[type_],
is_object=True,
)
def _cook_name(name):
if iskeyword(name) or name in ("char", "bool", "int", "float", "short", "type"):
return f"{name}_"
else:
return name
def _cook_default_value(type, value, has_default_value):
if not has_default_value:
return None
# Mostly ad-hoc stuff given default values format in api.json is broken
if type in ("godot_bool", "godot_int", "godot_real", "godot_variant"):
if value == "Null":
return "None"
else:
return value
elif type == "godot_string":
return f'"{value}"'
elif type == "godot_object" and value in ("[Object:null]", "Null"):
return "None"
elif type == "godot_dictionary" and value == "{}":
return "Dictionary()"
elif type == "godot_vector2":
return f"Vector2{value}"
elif type == "godot_rect2":
return f"Rect2{value}"
elif type == "godot_vector3":
return f"Vector3{value}"
elif type == "godot_transform" and value == "1, 0, 0, 0, 1, 0, 0, 0, 1 - 0, 0, 0":
return (
"Transform(Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1), Vector3(0, 0, 0))"
)
elif type == "godot_transform2d" and value == "((1, 0), (0, 1), (0, 0))":
return "Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0, 0))"
elif value == "[RID]":
return "RID()"
elif type == "godot_color":
return f"Color({value})"
elif type == "godot_pool_color_array" and value == "[PoolColorArray]":
return "PoolColorArray()"
elif type == "godot_array" and value == "[]":
return f"Array()"
elif type == "godot_pool_vector2_array" and value == "[]":
return f"PoolVector2Array()"
elif type == "godot_pool_vector3_array" and value == "[]":
return f"PoolVector3Array()"
elif type == "godot_pool_int_array" and value == "[]":
return f"PoolIntArray()"
elif type == "godot_pool_real_array" and value == "[]":
return f"PoolRealArray()"
elif type == "godot_pool_string_array" and value == "[]":
return f"PoolStringArray()"
elif value == "Null":
return "None"
else:
warn(f"Unknown default arg value: type=`{type}`, value=`{value}`")
return "None"
for cls_data in data:
if cls_data["name"] == "GlobalConstants":
constants = cls_data["constants"]
continue
cls_info = {
"bind_register_name": cls_data["name"],
"name": class_renames[cls_data["name"]],
"base_class": class_renames[cls_data["base_class"]],
"instantiable": cls_data["instanciable"],
"is_reference": cls_data["is_reference"],
"constants": cls_data["constants"],
"properties": [],
"signals": [],
"methods": [],
"enums": [],
}
if cls_data["singleton"]:
# Strip the leading underscore
cls_info["singleton"] = cls_info["name"][1:]
else:
cls_info["singleton"] = None
for prop_data in cls_data["properties"]:
cls_info["properties"].append(
PropertyInfo(
name=_cook_name(prop_data["name"]),
type=_cook_type(prop_data["type"]),
getter=prop_data["getter"],
setter=prop_data["setter"],
index=prop_data["index"] if prop_data["index"] != -1 else None,
)
)
for signal_data in cls_data["signals"]:
args_info = [
ArgumentInfo(
name=_cook_name(arg_data["name"]),
type=_cook_type(arg_data["type"]),
default_value=None,
)
for arg_data in signal_data["arguments"]
]
if any(arg_data["default_value"] != "" for arg_data in signal_data["arguments"]):
warn(
f"{cls_info['name']}.{signal_data['name']}: default value are not supported for signals"
)
cls_info["signals"].append(
SignalInfo(name=_cook_name(signal_data["name"]), arguments=args_info)
)
for meth_data in cls_data["methods"]:
args_info = [
ArgumentInfo(
name=_cook_name(arg_data["name"]),
type=_cook_type(arg_data["type"]),
default_value=_cook_default_value(
_cook_type(arg_data["type"]).c_type,
arg_data["default_value"],
arg_data["has_default_value"],
),
)
for arg_data in meth_data["arguments"]
]
meth_info = {
"name": _cook_name(meth_data["name"]),
"return_type": _cook_type(meth_data["return_type"]),
"is_editor": meth_data["is_editor"],
"is_noscript": meth_data["is_noscript"],
"is_const": meth_data["is_const"],
"is_reverse": meth_data["is_reverse"],
"is_virtual": meth_data["is_virtual"],
"has_varargs": meth_data["has_varargs"],
"is_from_script": meth_data["is_from_script"],
"arguments": args_info,
}
cls_info["methods"].append(MethodInfo(**meth_info))
for enum_data in cls_data["enums"]:
cls_info["enums"].append(
EnumInfo(name=_cook_name(enum_data["name"]), values=enum_data["values"])
)
classes.append(ClassInfo(**cls_info))
# Order classes by inheritance
inheritances = defaultdict(list)
for klass in classes:
inheritances[klass.base_class].append(klass)
sorted_classes = [*inheritances[""]]
todo_base_classes = [*inheritances[""]]
while todo_base_classes:
base_class = todo_base_classes.pop()
children_classes = inheritances[base_class.name]
todo_base_classes += children_classes
sorted_classes += children_classes
return sorted_classes, constants
def load_bindings_specs_from_api_json(
api_json: dict, sample: bool
) -> Tuple[List[ClassInfo], Dict[str, int]]:
pre_cook_patch_stuff(api_json)
classes, constants = cook_data(api_json)
if sample:
strip_sample_stuff(classes)
strip_unsupported_stuff(classes)
post_cook_patch_stuff(classes)
return classes, constants
def generate_bindings(
no_suffix_output_path: str, classes_specs: List[ClassInfo], constants_specs: Dict[str, int]
):
pyx_output_path = f"{no_suffix_output_path}.pyx"
print(f"Generating {pyx_output_path}")
template = env.get_template("bindings.tmpl.pyx")
out = template.render(classes=classes_specs, constants=constants_specs)
with open(pyx_output_path, "w") as fd:
fd.write(out)
pyi_output_path = f"{no_suffix_output_path}.pyi"
print(f"Generating {pyi_output_path}")
template = env.get_template("bindings.tmpl.pyi")
out = template.render(classes=classes_specs, constants=constants_specs)
with open(pyi_output_path, "w") as fd:
fd.write(out)
pxd_output_path = f"{no_suffix_output_path}.pxd"
print(f"Generating {pxd_output_path}")
template = env.get_template("bindings.tmpl.pxd")
out = template.render(classes=classes_specs, constants=constants_specs)
with open(pxd_output_path, "w") as fd:
fd.write(out)
if __name__ == "__main__":
def _parse_output(val):
suffix = ".pyx"
if not val.endswith(suffix):
raise argparse.ArgumentTypeError(f"Must have a `{suffix}` suffix")
return val[: -len(suffix)]
parser = argparse.ArgumentParser(description="Generate godot api bindings bindings files")
parser.add_argument(
"--input",
"-i",
required=True,
metavar="API_PATH",
type=argparse.FileType("r", encoding="utf8"),
help="Path to Godot api.json file",
)
parser.add_argument(
"--output",
"-o",
required=True,
metavar="BINDINGS_PYX",
type=_parse_output,
help="Path to store the generated bindings.pyx (also used to determine .pxd/.pyi output path)",
)
parser.add_argument(
"--sample",
action="store_true",
help="Generate a subset of the bindings (faster to build, useful for dev)",
)
args = parser.parse_args()
api_json = json.load(args.input)
classes_specs, constants_specs = load_bindings_specs_from_api_json(api_json, args.sample)
generate_bindings(args.output, classes_specs, constants_specs)

View File

@ -0,0 +1,400 @@
import os
import argparse
import json
import re
from warnings import warn
from functools import partial
from keyword import iskeyword
from dataclasses import dataclass, replace
from collections import defaultdict
from itertools import product
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from typing import List, Set
from type_specs import (
TypeSpec,
ALL_TYPES_EXCEPT_OBJECTS,
TYPE_RID,
TYPE_VECTOR3,
TYPE_VECTOR2,
TYPE_AABB,
TYPE_BASIS,
TYPE_COLOR,
TYPE_STRING,
TYPE_RECT2,
TYPE_TRANSFORM2D,
TYPE_PLANE,
TYPE_QUAT,
TYPE_TRANSFORM,
TYPE_NODEPATH,
TYPE_DICTIONARY,
TYPE_ARRAY,
)
# TODO: after all, it may not be a great idea to share TypeSpec between builtin and binding scripts...
# Bonus types
TYPES_SIZED_INT = [
TypeSpec(
gdapi_type=f"{signed}int{size}_t",
c_type=f"{signed}int{size}_t",
cy_type=f"{signed}int{size}_t",
py_type="int",
is_base_type=True,
is_stack_only=True,
)
for signed, size in product(["u", ""], [8, 32, 64])
]
ALL_TYPES = [
*ALL_TYPES_EXCEPT_OBJECTS,
*TYPES_SIZED_INT,
TypeSpec(
gdapi_type="godot_object",
c_type="godot_object",
cy_type="object",
py_type="Object",
is_object=True,
),
TypeSpec(
gdapi_type="int",
c_type="int",
cy_type="int",
py_type="int",
is_base_type=True,
is_stack_only=True,
),
TypeSpec(
gdapi_type="size_t",
c_type="size_t",
cy_type="size_t",
py_type="int",
is_base_type=True,
is_stack_only=True,
),
# /!\ godot_real is a C float (note py_type is still `float` given that's how Python call all floating point numbers)
TypeSpec(
gdapi_type="double",
c_type="double",
cy_type="double",
py_type="float",
is_base_type=True,
is_stack_only=True,
),
TypeSpec(
gdapi_type="wchar_t",
c_type="wchar_t",
cy_type="wchar_t",
is_base_type=True,
is_stack_only=True,
),
TypeSpec(
gdapi_type="char", c_type="char", cy_type="char", is_base_type=True, is_stack_only=True
),
TypeSpec(
gdapi_type="schar",
c_type="schar",
cy_type="signed char",
is_base_type=True,
is_stack_only=True,
),
TypeSpec(
gdapi_type="godot_char_string",
c_type="godot_char_string",
cy_type="godot_char_string",
py_type="str",
is_builtin=True,
),
TypeSpec(
gdapi_type="godot_string_name",
c_type="godot_string_name",
cy_type="godot_string_name",
py_type="str",
is_builtin=True,
),
TypeSpec(
gdapi_type="bool",
c_type="bool",
cy_type="bool",
py_type="bool",
is_base_type=True,
is_stack_only=True,
),
]
C_NAME_TO_TYPE_SPEC = {s.c_type: s for s in ALL_TYPES}
BUILTINS_TYPES = [s for s in ALL_TYPES if s.is_builtin]
TARGET_TO_TYPE_SPEC = {
"rid": TYPE_RID,
"vector3": TYPE_VECTOR3,
"vector2": TYPE_VECTOR2,
"aabb": TYPE_AABB,
"basis": TYPE_BASIS,
"color": TYPE_COLOR,
"gdstring": TYPE_STRING,
"rect2": TYPE_RECT2,
"transform2d": TYPE_TRANSFORM2D,
"plane": TYPE_PLANE,
"quat": TYPE_QUAT,
"transform": TYPE_TRANSFORM,
"node_path": TYPE_NODEPATH,
"dictionary": TYPE_DICTIONARY,
"array": TYPE_ARRAY,
}
@dataclass
class ArgumentSpec:
name: str
type: TypeSpec
is_ptr: bool
is_const: bool
def __getattr__(self, key):
return getattr(self.type, key)
@dataclass
class BuiltinMethodSpec:
# Builtin type this method apply on (e.g. Vector2)
klass: TypeSpec
# Name of the function in the GDNative C API
c_name: str
# Basically gd_name without the `godot_<type>_` prefix
py_name: str
return_type: TypeSpec
args: List[ArgumentSpec]
gdapi: str
def cook_name(name):
return f"{name}_" if iskeyword(name) else name
BASEDIR = os.path.dirname(__file__)
env = Environment(
loader=FileSystemLoader(f"{BASEDIR}/builtins_templates"),
trim_blocks=True,
lstrip_blocks=False,
extensions=["jinja2.ext.loopcontrols"],
undefined=StrictUndefined,
)
env.filters["merge"] = lambda x, **kwargs: {**x, **kwargs}
def load_builtin_method_spec(func: dict, gdapi: str) -> BuiltinMethodSpec:
c_name = func["name"]
assert c_name.startswith("godot_"), func
for builtin_type in BUILTINS_TYPES:
prefix = f"{builtin_type.c_type}_"
if c_name.startswith(prefix):
py_name = c_name[len(prefix) :]
break
else:
# This function is not part of a builtin class (e.g. godot_print), we can ignore it
return
def _cook_type(raw_type):
# Hack type detection, might need to be improved with api evolutions
match = re.match(r"^(const\W+|)([a-zA-Z_0-9]+)(\W*\*|)$", raw_type.strip())
if not match:
raise RuntimeError(f"Unsuported type `{raw_type}` in function `{c_name}`")
is_const = bool(match.group(1))
c_type = match.group(2)
is_ptr = bool(match.group(3))
for type_spec in ALL_TYPES:
if c_type == type_spec.c_type:
break
else:
raise RuntimeError(f"Unsuported type `{raw_type}` in function `{c_name}`")
return is_const, is_ptr, type_spec
args = []
for arg_type, arg_name in func["arguments"]:
if arg_name.startswith("p_"):
arg_name = arg_name[2:]
arg_name = cook_name(arg_name)
arg_is_const, arg_is_ptr, arg_type_spec = _cook_type(arg_type)
args.append(
ArgumentSpec(
name=arg_name, type=arg_type_spec, is_ptr=arg_is_ptr, is_const=arg_is_const
)
)
ret_is_const, ret_is_ptr, ret_type_spec = _cook_type(func["return_type"])
return_type = ArgumentSpec(
name="", type=ret_type_spec, is_ptr=ret_is_ptr, is_const=ret_is_const
)
return BuiltinMethodSpec(
klass=builtin_type,
c_name=c_name,
py_name=py_name,
return_type=return_type,
args=args,
gdapi=gdapi,
)
def pre_cook_patch_stuff(gdnative_api):
revision = gdnative_api["core"]
while revision:
for func in revision["api"]:
# `signed char` is used in some string methods to return comparison
# information (see `godot_string_casecmp_to`).
# The type in two word messes with our (poor) type parsing.
if func["return_type"] == "signed char":
func["return_type"] = "int8_t"
revision = revision["next"]
def load_builtins_specs_from_gdnative_api_json(gdnative_api: dict) -> List[BuiltinMethodSpec]:
pre_cook_patch_stuff(gdnative_api)
revision = gdnative_api["core"]
specs = []
while revision:
revision_gdapi = f"gdapi{revision['version']['major']}{revision['version']['minor']}"
for func in revision["api"]:
assert func["name"] not in specs
# Ignore godot pool (generate by another script)
if func["name"].startswith("godot_pool_") or func["name"].startswith("godot_variant_"):
continue
spec = load_builtin_method_spec(func, gdapi=revision_gdapi)
if spec:
specs.append(spec)
revision = revision["next"]
return specs
def generate_builtins(
no_suffix_output_path: str, methods_specs: List[BuiltinMethodSpec]
) -> Set[str]:
methods_c_name_to_spec = {s.c_name: s for s in methods_specs}
# Track the methods used in the templates to enforce they are in sync with the gdnative_api.json
rendered_methods = set()
def _mark_rendered(method_c_name):
rendered_methods.add(method_c_name)
return "" # Return empty string to not output anything when used in a template
def _render_target_to_template(render_target):
assert isinstance(render_target, str)
return f"{render_target}.tmpl.pxi"
def _get_builtin_method_spec(method_c_name):
assert isinstance(method_c_name, str)
try:
_mark_rendered(method_c_name)
return methods_c_name_to_spec[method_c_name]
except KeyError:
raise RuntimeError(f"Unknown method `{method_c_name}`")
def _get_type_spec(py_type):
assert isinstance(py_type, str)
try:
return next(t for t in ALL_TYPES if t.py_type == py_type)
except StopIteration:
raise RuntimeError(f"Unknown type `{py_type}`")
def _get_target_method_spec_factory(render_target):
assert isinstance(render_target, str)
try:
type_spec = TARGET_TO_TYPE_SPEC[render_target]
except KeyError:
raise RuntimeError(f"Unknown target `{render_target}`")
def _get_target_method_spec(method_py_name):
return _get_builtin_method_spec(f"{type_spec.c_type}_{method_py_name}")
return _get_target_method_spec
context = {
"render_target_to_template": _render_target_to_template,
"get_builtin_method_spec": _get_builtin_method_spec,
"get_type_spec": _get_type_spec,
"get_target_method_spec_factory": _get_target_method_spec_factory,
"force_mark_rendered": _mark_rendered,
}
template = env.get_template("builtins.tmpl.pyx")
pyx_output_path = f"{no_suffix_output_path}.pyx"
print(f"Generating {pyx_output_path}")
out = template.render(**context)
with open(pyx_output_path, "w") as fd:
fd.write(out)
pyi_output_path = f"{no_suffix_output_path}.pyi"
print(f"Generating {pyi_output_path}")
template = env.get_template("builtins.tmpl.pyi")
out = template.render(**context)
with open(pyi_output_path, "w") as fd:
fd.write(out)
pxd_output_path = f"{no_suffix_output_path}.pxd"
print(f"Generating {pxd_output_path}")
template = env.get_template("builtins.tmpl.pxd")
out = template.render(**context)
with open(pxd_output_path, "w") as fd:
fd.write(out)
return rendered_methods
def ensure_all_methods_has_been_rendered(
methods_specs: List[BuiltinMethodSpec], rendered_methods: Set[str]
):
all_methods = {s.c_name for s in methods_specs}
unknown_rendered_methods = rendered_methods - all_methods
for method in sorted(unknown_rendered_methods):
print(f"ERROR: `{method}` is used in the templates but not present in gnative_api.json")
not_rendered_methods = all_methods - rendered_methods
for method in sorted(not_rendered_methods):
print(f"ERROR: `{method}` is listed in gnative_api.json but not used in the templates")
return not unknown_rendered_methods and not not_rendered_methods
if __name__ == "__main__":
def _parse_output(val):
suffix = ".pyx"
if not val.endswith(suffix):
raise argparse.ArgumentTypeError(f"Must have a `{suffix}` suffix")
return val[: -len(suffix)]
parser = argparse.ArgumentParser(
description="Generate godot builtins bindings files (except pool arrays)"
)
parser.add_argument(
"--input",
"-i",
required=True,
metavar="GDNATIVE_API_PATH",
type=argparse.FileType("r", encoding="utf8"),
help="Path to Godot gdnative_api.json file",
)
parser.add_argument(
"--output",
"-o",
required=True,
metavar="BUILTINS_PYX",
type=_parse_output,
help="Path to store the generated builtins.pyx (also used to determine .pxd/.pyi output path)",
)
args = parser.parse_args()
gdnative_api_json = json.load(args.input)
methods_specs = load_builtins_specs_from_gdnative_api_json(gdnative_api_json)
rendered_methods = generate_builtins(args.output, methods_specs)
if not ensure_all_methods_has_been_rendered(methods_specs, rendered_methods):
raise SystemExit(
"Generated builtins are not in line with the provided gdnative_api.json :'("
)

View File

@ -0,0 +1,399 @@
import re
from typing import List, Dict
import argparse
from pathlib import Path
from pycparser import CParser, c_ast
from autopxd import AutoPxd
# List the includes to the stdlib we are expecting. This is needed to hack
# around them given they are needed for pycparser, but should endup in the pxd
# as `from libc.stdint cimport uint8_t` instead of being inside the `cdef extern`
# describing the whole header stuff.
STDLIB_INCLUDES = {
"stdbool.h": ["bool"],
"stdint.h": [
"uint8_t",
"int8_t",
"uint16_t",
"int16_t",
"uint32_t",
"int32_t",
"uint64_t",
"int64_t",
],
"wchar.h": ["wchar_t", "size_t"],
}
STDLIB_TYPES = {t for m in STDLIB_INCLUDES.values() for t in m}
class CCCP:
"""
CCCP: the Cheap&Coarse C Preprocessor
PyCParser needs to get passed preprocessed C code, but we don't want to
use a real one:
- different OS have different preprocessors (msvc vs clang vs gcc)
- we can control which #include to follow given we don't care about stdlibs ones
- we can easily tweak the behavior of #ifdef parts to ignore platform specificities
In the end remember that we are not compiling a C program, but creating a
.pxd file that will (in conjuction with a .pyx) be used to generate a .c
file that will include the godot api headers. So there is no need to handle
platform specific (or even opaque structure size !) detail here: they will
be ignored by cython and left to the final C compilation.
"""
def __init__(
self, include_dirs: List[str], forced_defined_vars: Dict[str, str], debug: bool = False
):
self.source = []
self.source_cursor = 0
self.forced_defined_vars = forced_defined_vars.keys()
self.defined_vars = {**forced_defined_vars}
self.include_dirs = [Path(p) for p in include_dirs]
self.ingnored_includes = set()
self.debug = debug
@staticmethod
def source_to_lines(src: str) -> List[str]:
# First remove all comments
src = re.sub(r"(//.*$)", "", src, flags=re.MULTILINE)
src = re.sub(r"/\*.*?\*/", "", src, flags=re.DOTALL)
# Split lines, taking care of backslashes
lines = []
multi_lines = ""
for line in src.splitlines():
line = line.rstrip()
if line.endswith("\\"):
multi_lines += line[:-1]
continue
lines.append(multi_lines + line)
multi_lines = ""
return lines
def debug_explain(self, msg):
if self.debug:
print(msg)
def error_occurred(self, msg):
extract = "\n".join(self.source[max(0, self.source_cursor - 5) : self.source_cursor + 5])
raise RuntimeError(f"{msg}\n\nOccurred around:\n{extract}")
def handle_include(self, line):
match_include = re.match(r"^\s*#\s*include\s+[<\"]([a-zA-Z0-9_./]+)[>\"]$", line)
if not match_include:
return None
include_name = match_include.group(1)
if include_name in STDLIB_INCLUDES.keys():
self.debug_explain(f"INCLUDE INGNORED {include_name}")
self.source.pop(self.source_cursor)
return 0
for include_dir in self.include_dirs:
include_path = include_dir / include_name
try:
included_source = include_path.read_text()
# Remove #include line and replace it by included source
self.source = (
self.source[: self.source_cursor]
+ self.source_to_lines(included_source)
+ self.source[self.source_cursor + 1 :]
)
self.debug_explain(f"INCLUDE {include_name}")
return 0
except FileNotFoundError:
pass
self.error_occurred(f"Cannot resolve import `{line}`")
def handle_define(self, line):
match_define = re.match(r"^\s*#\s*define\s+([a-zA-Z0-9_]+)(\s+|$)", line)
if not match_define:
return None
define_name = match_define.group(1)
define_value = line[len(match_define.group(0)) :]
if define_name not in self.forced_defined_vars:
self.defined_vars[define_name] = self.expand_macros(define_value)
self.debug_explain(f"DEF {define_name}={define_value}")
else:
self.debug_explain(f"DEF IGNORED {define_name}={define_value}")
self.source.pop(self.source_cursor)
return 0
def handle_define_macro(self, line):
match_define_macro = re.match(r"^\s*#\s*define\s+([a-zA-Z0-9_]+)\(", line)
if not match_define_macro:
return None
define_name = match_define_macro.group(1)
define_value = line[len(match_define_macro.group(0)) :]
# Macro are not supported, this is ok given they are not used
# (but some are defined) in the gdnative headers.
# As a sanity measure, we make sure the code generated if the macro
# is used will cause the C parser to crash.
self.defined_vars[define_name] = f"#error unsuported macro {define_name}"
self.debug_explain(f"DEF MACRO {define_name}=__UNSUPORTED__")
self.source.pop(self.source_cursor)
return 0
def handle_undef(self, line):
match_undefine = re.match(r"^\s*#\s*undef\s+([a-zA-Z0-9_]+)$", line)
if not match_undefine:
return None
define_name = match_undefine.group(1)
if define_name not in self.forced_defined_vars:
self.defined_vars.pop(define_name)
self.debug_explain(f"UNDEF {define_name}")
else:
self.debug_explain(f"UNDEF INGNORED {define_name}")
self.source.pop(self.source_cursor)
return 0
def handle_if(self, line):
# Replace ifdef/ifndef by generic if to simplify parsing
line = re.sub(r"^\s*#\s*ifdef\s+([a-zA-Z0-9_]+)$", r"#if defined(\1)", line)
line = re.sub(r"^\s*#\s*ifndef\s+([a-zA-Z0-9_]+)$", r"#if !defined(\1)", line)
match_if = re.match(r"^\s*#\s*if\s+", line)
if not match_if:
return None
def _eval_if_condition(condition):
# Turn condition into Python code and eval it \o/
expr = condition.replace("||", " or ")
expr = expr.replace("&&", " and ")
expr = expr.replace("!", " not ")
expr = re.sub(r"defined\(([a-zA-Z0-9_]+)\)", r"defined('\1')", expr)
try:
return eval(
expr, {"defined": lambda key: key in self.defined_vars}, self.defined_vars
)
except Exception as exc:
self.error_occurred(
f"Error {exc} while evaluating `{expr}` (generated from `{condition}`)"
)
def _keep_until_next_condition(offset):
nested_count = 0
kept_body = []
while True:
try:
line = self.source[self.source_cursor + offset]
except IndexError:
self.error_occurred("Reach end of file without #endif")
if re.match(r"^\s*#\s*(if|ifdef|ifndef)(\s+|$)", line):
# Nested #if
nested_count += 1
else_match = re.match(r"^\s*#\s*(else$|elif\s+)", line)
if else_match:
if nested_count == 0:
condition_type = else_match.group(1).strip()
condition = line[len(else_match.group(1)) :]
return kept_body, condition_type, condition, offset + 1
if re.match(r"^\s*#\s*endif$", line):
if nested_count == 0:
return kept_body, "endif", "", offset + 1
else:
nested_count -= 1
offset += 1
kept_body.append(line)
def _retreive_kept_body(condition, offset):
if _eval_if_condition(condition):
kept_body, condition_type, condition, offset = _keep_until_next_condition(offset)
# Skip other else/elif body parts until the matching endif
while condition_type != "endif":
_, condition_type, _, offset = _keep_until_next_condition(offset)
return kept_body, offset
else:
# Ignore the if body part
_, condition_type, condition, offset = _keep_until_next_condition(offset)
if condition_type == "elif":
return _retreive_kept_body(condition, offset)
elif condition_type == "else":
return _retreive_kept_body("True", offset)
else: # endif
return [], offset
if_condition = line[len(match_if.group()) :]
body, offset = _retreive_kept_body(if_condition, offset=1)
if_starts = self.source_cursor
if_ends = self.source_cursor + offset
self.source[if_starts:if_ends] = body
self.debug_explain(f"IF ({line}) ==> {if_starts} {if_ends}")
return 0 # 0 is not equivalent to None !
def handle_unknown(self, line):
match_unknown = re.match(r"^\s*#", line)
if not match_unknown:
return None
self.error_occurred(f"Unknown preprocessor command `{line}`")
def expand_macros(self, line):
# Simple optim to discard most of the lines given regex search is cpu heavy
if not line or all(key not in line for key in self.defined_vars.keys()):
return line
expanded_line = line
# Recursive expansion given a macro can reference another one
while True:
for key, value in self.defined_vars.items():
expanded_line = re.sub(
f"(^|[^a-zA-Z0-9_]){key}([^a-zA-Z0-9_]|$)",
f"\\g<1>{value}\\g<2>",
expanded_line,
)
if expanded_line == line:
break
line = expanded_line
return line
def parse(self, src: str) -> str:
self.source = self.source_to_lines(src)
cpp_handlers = (
self.handle_define,
self.handle_define_macro,
self.handle_if,
self.handle_include,
self.handle_undef,
self.handle_unknown,
)
while True:
try:
source_line = self.source[self.source_cursor]
except IndexError:
# Parsing is done
break
for cpp_handler in cpp_handlers:
eaten_lines = cpp_handler(source_line)
if eaten_lines is not None:
self.source_cursor += eaten_lines
break
else:
# Not a preprocessor line
self.source[self.source_cursor] = self.expand_macros(source_line)
self.source_cursor += 1
return "\n".join(self.source)
class PatchedAutoPxd(AutoPxd):
def visit_TypeDecl(self, node):
# Ignore types from stdlib (will be provided by the
# `from libc.stdint cimport uint8_t` syntax)
if node.declname in STDLIB_TYPES:
return
else:
return super().visit_TypeDecl(node)
def visit_ArrayDecl(self, node):
# autopxd doesn't support array with an expression as size, but in:
# typedef struct {uint8_t _dont_touch_that[GODOT_VECTOR3_SIZE];} godot_vector3;
# `GODOT_VECTOR3_SIZE` gets resolved as `sizeof(void*)` :(
if node.type.declname == "_dont_touch_that":
# Of course the 0 size is wrong, but it's not an issue given
# we don't touch this array in Cython code (hence the name ^^)
node.dim = c_ast.Constant(type="int", value="0")
return super().visit_ArrayDecl(node)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Convert gdnative_api_struct.gen.h into Cython .pxd"
)
parser.add_argument(
"--input",
"-i",
required=True,
metavar="GODOT_HEADERS_PATH",
help="Path to Godot GDNative headers",
)
parser.add_argument(
"--output",
"-o",
required=True,
type=argparse.FileType("w", encoding="utf8"),
metavar="GDNATIVE_API_STRUCT_PXD",
help="Path to store the generated gdnative_api_struct.pxd file",
)
args = parser.parse_args()
# Step 1: preprocessing
header_name = "gdnative_api_struct.gen.h"
with open(f"{args.input}/{header_name}", "r") as fd:
source = fd.read()
# салют товарищ !
cccp = CCCP(
include_dirs=[args.input],
forced_defined_vars={"GDAPI": "", "GDN_EXPORT": "", "GDCALLINGCONV": ""},
)
preprocessed = ""
# pycparser requires each symbol must be defined, hence provide a dummy
# definition of the needed stdlib types.
# Note those definitions will then be detected and ignored by PatchedAutoPxd.
for stdtype in STDLIB_TYPES:
preprocessed += f"typedef int {stdtype};\n"
preprocessed += cccp.parse(source)
with open("output.preprocessed.c", "w") as fd:
fd.write(preprocessed)
# Step 2: C parsing
parser = CParser()
ast = parser.parse(preprocessed)
# Step 3: .pxd generation
p = PatchedAutoPxd(header_name)
p.visit(ast)
pxd_cdef = p.lines()
# Remove the cdef part given we want to add the `nogil` option and
# we also want to add the `godot_method_flags` C inline code
assert pxd_cdef[0].startswith("cdef extern from")
pxd_cdef_body = "\n".join(pxd_cdef[1:])
pxd = f"""\
# /!\\ Autogenerated code, modifications will be lost /!\\
# see `generation/generate_gdnative_api_struct.py`
from libc.stddef cimport wchar_t, size_t
from libc.stdint cimport {', '.join(STDLIB_INCLUDES['stdint.h'])}
cdef extern from "{header_name}" nogil:
\"\"\"
typedef enum {{
GODOT_METHOD_FLAG_NORMAL = 1,
GODOT_METHOD_FLAG_EDITOR = 2,
GODOT_METHOD_FLAG_NOSCRIPT = 4,
GODOT_METHOD_FLAG_CONST = 8,
GODOT_METHOD_FLAG_REVERSE = 16,
GODOT_METHOD_FLAG_VIRTUAL = 32,
GODOT_METHOD_FLAG_FROM_SCRIPT = 64,
GODOT_METHOD_FLAG_VARARG = 128,
GODOT_METHOD_FLAGS_DEFAULT = GODOT_METHOD_FLAG_NORMAL
}} godot_method_flags;
\"\"\"
ctypedef enum godot_method_flags:
GODOT_METHOD_FLAG_NORMAL = 1
GODOT_METHOD_FLAG_EDITOR = 2
GODOT_METHOD_FLAG_NOSCRIPT = 4
GODOT_METHOD_FLAG_CONST = 8
GODOT_METHOD_FLAG_REVERSE = 16 # used for events
GODOT_METHOD_FLAG_VIRTUAL = 32
GODOT_METHOD_FLAG_FROM_SCRIPT = 64
GODOT_METHOD_FLAG_VARARG = 128
GODOT_METHOD_FLAGS_DEFAULT = 1 # METHOD_FLAG_NORMAL
ctypedef bint bool
{pxd_cdef_body}
"""
args.output.write(pxd)

View File

@ -0,0 +1,105 @@
import os
import argparse
import json
import re
from keyword import iskeyword
from collections import defaultdict
from jinja2 import Environment, FileSystemLoader
BASEDIR = os.path.dirname(__file__)
env = Environment(
loader=FileSystemLoader(f"{BASEDIR}/pool_arrays_templates"),
trim_blocks=True,
lstrip_blocks=True,
)
class TypeItem:
def __init__(self, **kwargs):
self.__dict__.update(**kwargs)
TYPES = [
# Base types
TypeItem(
gd_pool=f"godot_pool_int_array",
py_pool=f"PoolIntArray",
gd_value=f"godot_int",
py_value=f"godot_int",
is_base_type=True,
is_stack_only=True,
),
TypeItem(
gd_pool=f"godot_pool_real_array",
py_pool=f"PoolRealArray",
gd_value=f"godot_real",
py_value=f"godot_real",
is_base_type=True,
is_stack_only=True,
),
TypeItem(
gd_pool="godot_pool_byte_array",
py_pool="PoolByteArray",
gd_value="uint8_t",
py_value="uint8_t",
is_base_type=True,
is_stack_only=True,
),
# Stack only builtin types
TypeItem(
gd_pool=f"godot_pool_vector2_array",
py_pool=f"PoolVector2Array",
gd_value=f"godot_vector2",
py_value=f"Vector2",
is_base_type=False,
is_stack_only=True,
),
TypeItem(
gd_pool=f"godot_pool_vector3_array",
py_pool=f"PoolVector3Array",
gd_value=f"godot_vector3",
py_value=f"Vector3",
is_base_type=False,
is_stack_only=True,
),
TypeItem(
gd_pool=f"godot_pool_color_array",
py_pool=f"PoolColorArray",
gd_value=f"godot_color",
py_value=f"Color",
is_base_type=False,
is_stack_only=True,
),
# Stack&heap builtin types
TypeItem(
gd_pool="godot_pool_string_array",
py_pool="PoolStringArray",
gd_value="godot_string",
py_value="GDString",
is_base_type=False,
is_stack_only=False,
),
]
def generate_pool_array(output_path):
template = env.get_template("pool_arrays.tmpl.pyx")
out = template.render(types=TYPES)
with open(output_path, "w") as fd:
fd.write(out)
pxd_output_path = output_path.rsplit(".", 1)[0] + ".pxd"
template = env.get_template("pool_arrays.tmpl.pxd")
out = template.render(types=TYPES)
with open(pxd_output_path, "w") as fd:
fd.write(out)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate godot pool_x_array builtins bindings files"
)
parser.add_argument("--output", "-o", default=None)
args = parser.parse_args()
generate_pool_array(args.output or f"pool_arrays.pyx")

View File

@ -0,0 +1,32 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_pool_arrays.py`
cimport cython
from godot._hazmat.gdapi cimport (
pythonscript_gdapi10 as gdapi10,
pythonscript_gdapi11 as gdapi11,
pythonscript_gdapi12 as gdapi12,
)
from godot._hazmat.gdnative_api_struct cimport (
{% for t in types %}
{{ t.gd_value }},
{{ t.gd_pool }},
{{ t.gd_pool }}_write_access,
{{ t.gd_pool }}_read_access,
{% endfor %}
)
from godot.builtins cimport (
Array,
{% for t in types %}
{% if not t.is_base_type %}
{{ t.py_value }},
{% endif %}
{% endfor %}
)
{% from 'pool_x_array.tmpl.pxd' import render_pool_array_pxd %}
{% for t in types %}
{{ render_pool_array_pxd(t) }}
{% endfor %}

View File

@ -0,0 +1,35 @@
# /!\ Autogenerated code, modifications will be lost /!\
# see `generation/generate_pool_arrays.py`
cimport cython
from libc.stdint cimport uintptr_t
from godot._hazmat.gdapi cimport (
pythonscript_gdapi10 as gdapi10,
pythonscript_gdapi11 as gdapi11,
pythonscript_gdapi12 as gdapi12,
)
from godot._hazmat.gdnative_api_struct cimport (
{% for t in types %}
{{ t.gd_value }},
{{ t.gd_pool }},
{{ t.gd_pool }}_write_access,
{{ t.gd_pool }}_read_access,
{% endfor %}
)
from godot.builtins cimport (
Array,
{% for t in types %}
{% if not t.is_base_type %}
{{ t.py_value }},
{% endif %}
{% endfor %}
)
from contextlib import contextmanager
{% from 'pool_x_array.tmpl.pyx' import render_pool_array_pyx %}
{% for t in types %}
{{ render_pool_array_pyx(t) }}
{% endfor %}

View File

@ -0,0 +1,33 @@
{% macro render_pool_array_pxd(t) %}
@cython.final
cdef class {{ t.py_pool }}:
cdef {{ t.gd_pool }} _gd_data
@staticmethod
cdef inline {{ t.py_pool }} new()
@staticmethod
cdef inline {{ t.py_pool }} new_with_array(Array other)
# Operators
cdef inline bint operator_equal(self, {{ t.py_pool }} other)
cdef inline {{ t.py_value }} operator_getitem(self, godot_int index)
cdef inline {{ t.py_pool }} operator_getslice(self, godot_int start, godot_int end, godot_int step)
# Methods
cpdef inline {{ t.py_pool }} copy(self)
cpdef inline void append(self, {{ t.py_value }} data)
cdef inline void append_array(self, {{ t.py_pool }} array)
cpdef inline void invert(self)
cpdef inline void push_back(self, {{ t.py_value }} data)
cpdef inline void resize(self, godot_int size)
cdef inline godot_int size(self)
@cython.final
cdef class {{ t.py_pool }}WriteAccess:
cdef {{ t.gd_value }} *_gd_ptr
{% endmacro %}

View File

@ -0,0 +1,326 @@
{% macro gd_to_py(type, src, dst) %}
{% if type['gd_value'] == type['py_value'] %}
{{ dst }} = {{ src }}
{% else %}
dst = godot_string_to_pyobj(&src)
gdapi10.godot_string_destroy(&src)
{% endif %}
{% endmacro %}
{% macro py_to_gd(target) %}
{% endmacro %}
{% macro render_pool_array_pyx(t) %}
@cython.final
cdef class {{ t.py_pool }}:
def __init__(self, other=None):
cdef {{ t.py_pool }} other_as_pool_array
cdef Array other_as_array
if other is None:
gdapi10.{{ t.gd_pool }}_new(&self._gd_data)
else:
try:
other_as_pool_array = <{{ t.py_pool }}?>other
gdapi10.{{ t.gd_pool }}_new_copy(&self._gd_data, &other_as_pool_array._gd_data)
except TypeError:
try:
other_as_array = <Array?>other
gdapi10.{{ t.gd_pool }}_new_with_array(&self._gd_data, &other_as_array._gd_data)
except TypeError:
gdapi10.{{ t.gd_pool }}_new(&self._gd_data)
for item in other:
{% if t.is_base_type %}
{{ t.py_pool }}.append(self, item)
{% else %}
{{ t.py_pool }}.append(self, (<{{ t.py_value }}?>item))
{% endif %}
def __dealloc__(self):
# /!\ if `__init__` is skipped, `_gd_data` must be initialized by
# hand otherwise we will get a segfault here
gdapi10.{{ t.gd_pool }}_destroy(&self._gd_data)
@staticmethod
cdef inline {{ t.py_pool }} new():
# Call to __new__ bypasses __init__ constructor
cdef {{ t.py_pool }} ret = {{ t.py_pool }}.__new__({{ t.py_pool }})
gdapi10.{{ t.gd_pool }}_new(&ret._gd_data)
return ret
@staticmethod
cdef inline {{ t.py_pool }} new_with_array(Array other):
# Call to __new__ bypasses __init__ constructor
cdef {{ t.py_pool }} ret = {{ t.py_pool }}.__new__({{ t.py_pool }})
gdapi10.{{ t.gd_pool }}_new_with_array(&ret._gd_data, &other._gd_data)
return ret
def __repr__(self):
return f"<{{ t.py_pool }}([{', '.join(repr(x) for x in self)}])>"
# Operators
def __getitem__(self, index):
cdef godot_int size = self.size()
cdef godot_int start
cdef godot_int stop
cdef godot_int step
if isinstance(index, slice):
step = index.step if index.step is not None else 1
if step == 0:
raise ValueError("range() arg 3 must not be zero")
elif step > 0:
start = index.start if index.start is not None else 0
stop = index.stop if index.stop is not None else size
else:
start = index.start if index.start is not None else size
stop = index.stop if index.stop is not None else -size - 1
return self.operator_getslice(
start,
stop,
step,
)
else:
if index < 0:
index = index + size
if index < 0 or index >= size:
raise IndexError("list index out of range")
return self.operator_getitem(index)
cdef inline {{ t.py_value }} operator_getitem(self, godot_int index):
{% if t.is_base_type %}
return gdapi10.{{ t.gd_pool }}_get(&self._gd_data, index)
{% else %}
cdef {{ t.py_value }} ret = {{ t.py_value }}.__new__({{ t.py_value }})
ret._gd_data = gdapi10.{{ t.gd_pool }}_get(&self._gd_data, index)
return ret
{% endif %}
cdef inline {{ t.py_pool }} operator_getslice(self, godot_int start, godot_int stop, godot_int step):
cdef {{ t.py_pool }} ret = {{ t.py_pool }}.new()
cdef godot_int size = self.size()
if start > size - 1:
start = size - 1
elif start < 0:
start += size
if start < 0:
start = 0
if stop > size:
stop = size
elif stop < -size:
stop = -1
elif stop < 0:
stop += size
if step > 0:
if start >= stop:
return ret
items = 1 + (stop - start - 1) // step
if items <= 0:
return ret
else:
if start <= stop:
return ret
items = 1 + (stop - start + 1) // step
if items <= 0:
return ret
ret.resize(items)
cdef {{ t.gd_pool }}_read_access *src_access = gdapi10.{{ t.gd_pool }}_read(
&self._gd_data
)
cdef {{ t.gd_pool }}_write_access *dst_access = gdapi10.{{ t.gd_pool }}_write(
&ret._gd_data
)
cdef const {{ t.gd_value }} *src_ptr = gdapi10.{{ t.gd_pool }}_read_access_ptr(src_access)
cdef {{ t.gd_value }} *dst_ptr = gdapi10.{{ t.gd_pool }}_write_access_ptr(dst_access)
cdef godot_int i
for i in range(items):
{% if t.is_stack_only %}
dst_ptr[i] = src_ptr[i * step + start]
{% else %}
gdapi10.{{ t.gd_value }}_destroy(&dst_ptr[i])
gdapi10.{{ t.gd_value }}_new_copy(&dst_ptr[i], &src_ptr[i * step + start])
{% endif %}
gdapi10.{{ t.gd_pool }}_read_access_destroy(src_access)
gdapi10.{{ t.gd_pool }}_write_access_destroy(dst_access)
return ret
# TODO: support slice
def __setitem__(self, godot_int index, {{ t.py_value }} value):
cdef godot_int size
size = self.size()
if index < 0:
index += size
if index < 0 or index >= size:
raise IndexError("list index out of range")
{% if t.is_base_type %}
gdapi10.{{ t.gd_pool }}_set(&self._gd_data, index, value)
{% else %}
gdapi10.{{ t.gd_pool }}_set(&self._gd_data, index, &value._gd_data)
{% endif %}
# TODO: support slice
def __delitem__(self, godot_int index):
cdef godot_int size
size = self.size()
if index < 0:
index += size
if index < 0 or index >= size:
raise IndexError("list index out of range")
gdapi10.{{ t.gd_pool }}_remove(&self._gd_data, index)
def __len__(self):
return self.size()
def __iter__(self):
# TODO: mid iteration mutation should throw exception ?
cdef int i
{% if not t.is_base_type %}
cdef {{ t.py_value }} item
{% endif %}
for i in range(self.size()):
{% if t.is_base_type %}
yield gdapi10.{{ t.gd_pool }}_get(&self._gd_data, i)
{% else %}
item = {{ t.py_value }}.__new__({{ t.py_value }})
item._gd_data = gdapi10.{{ t.gd_pool }}_get(&self._gd_data, i)
yield item
{% endif %}
def __copy__(self):
return self.copy()
def __eq__(self, other):
try:
return {{ t.py_pool }}.operator_equal(self, other)
except TypeError:
return False
def __ne__(self, other):
try:
return not {{ t.py_pool }}.operator_equal(self, other)
except TypeError:
return True
def __iadd__(self, {{ t.py_pool }} items not None):
self.append_array(items)
return self
def __add__(self, {{ t.py_pool }} items not None):
cdef {{ t.py_pool }} ret = {{ t.py_pool }}.copy(self)
ret.append_array(items)
return ret
cdef inline bint operator_equal(self, {{ t.py_pool }} other):
if other is None:
return False
# TODO `godot_array_operator_equal` is missing in gdapi, submit a PR ?
cdef godot_int size = self.size()
if size != other.size():
return False
cdef {{ t.gd_pool }}_read_access *a_access = gdapi10.{{ t.gd_pool }}_read(
&self._gd_data
)
cdef {{ t.gd_pool }}_read_access *b_access = gdapi10.{{ t.gd_pool }}_read(
&other._gd_data
)
cdef const {{ t.gd_value }} *a_ptr = gdapi10.{{ t.gd_pool }}_read_access_ptr(a_access)
cdef const {{ t.gd_value }} *b_ptr = gdapi10.{{ t.gd_pool }}_read_access_ptr(b_access)
cdef godot_int i
cdef bint ret = True
for i in range(size):
{% if t.is_base_type %}
if a_ptr[i] != b_ptr[i]:
{% else %}
if not gdapi10.{{ t.gd_value }}_operator_equal(&a_ptr[i], &b_ptr[i]):
{% endif %}
ret = False
break
gdapi10.{{ t.gd_pool }}_read_access_destroy(a_access)
gdapi10.{{ t.gd_pool }}_read_access_destroy(b_access)
return ret
# Methods
cpdef inline {{ t.py_pool }} copy(self):
# Call to __new__ bypasses __init__ constructor
cdef {{ t.py_pool }} ret = {{ t.py_pool }}.__new__({{ t.py_pool }})
gdapi10.{{ t.gd_pool }}_new_copy(&ret._gd_data, &self._gd_data)
return ret
cpdef inline void append(self, {{ t.py_value }} data):
{% if t.is_base_type %}
gdapi10.{{ t.gd_pool }}_append(&self._gd_data, data)
{% else %}
gdapi10.{{ t.gd_pool }}_append(&self._gd_data, &data._gd_data)
{% endif %}
cdef inline void append_array(self, {{ t.py_pool }} array):
gdapi10.{{ t.gd_pool }}_append_array(&self._gd_data, &array._gd_data)
cpdef inline void invert(self):
gdapi10.{{ t.gd_pool }}_invert(&self._gd_data)
cpdef inline void push_back(self, {{ t.py_value }} data):
{% if t.is_base_type %}
gdapi10.{{ t.gd_pool }}_push_back(&self._gd_data, data)
{% else %}
gdapi10.{{ t.gd_pool }}_push_back(&self._gd_data, &data._gd_data)
{% endif %}
cpdef inline void resize(self, godot_int size):
gdapi10.{{ t.gd_pool }}_resize(&self._gd_data, size)
cdef inline godot_int size(self):
return gdapi10.{{ t.gd_pool }}_size(&self._gd_data)
# Raw access
@contextmanager
def raw_access(self):
cdef {{ t.gd_pool }}_write_access *access = gdapi10.{{ t.gd_pool }}_write(
&self._gd_data
)
cdef {{ t.py_pool }}WriteAccess pyaccess = {{ t.py_pool }}WriteAccess.__new__({{ t.py_pool }}WriteAccess)
pyaccess._gd_ptr = gdapi10.{{ t.gd_pool }}_write_access_ptr(access)
try:
yield pyaccess
finally:
gdapi10.{{ t.gd_pool }}_write_access_destroy(access)
@cython.final
cdef class {{ t.py_pool }}WriteAccess:
def get_address(self):
return <uintptr_t>self._gd_ptr
def __getitem__(self, int idx):
{% if t.is_base_type %}
return self._gd_ptr[idx]
{% else %}
cdef {{ t.py_value }} ret = {{ t.py_value }}.__new__({{ t.py_value }})
{% if t.is_stack_only %}
ret._gd_data = self._gd_ptr[idx]
{% else %}
gdapi10.{{ t.gd_value }}_new_copy(&ret._gd_data, &self._gd_ptr[idx])
{% endif %}
return ret
{% endif %}
def __setitem__(self, int idx, {{ t.py_value }} val):
{% if t.is_base_type %}
self._gd_ptr[idx] = val
{% elif t.is_stack_only %}
self._gd_ptr[idx] = val._gd_data
{% else %}
gdapi10.{{ t.gd_value }}_new_copy(&self._gd_ptr[idx], &val._gd_data)
{% endif %}
{% endmacro %}

265
generation/type_specs.py Normal file
View File

@ -0,0 +1,265 @@
# Describe all base types (i.e. scalar such as int and Godot builtins)
from dataclasses import dataclass
@dataclass
class TypeSpec:
# Type used within Godot api.json
gdapi_type: str
# Type used when calling C api functions
c_type: str
# Type used in Cython, basically similar to c_type for scalars&enums
# and to py_type for Godot objects&builtins
cy_type: str
# TODO: typing should be divided between argument and return (e.g. `Union[str, NodePath]` vs `NodePath`)
# Type used for PEP 484 Python typing
py_type: str = ""
# Type is a Godot object (i.e. defined in api.json)
is_object: bool = False
# Type is a Godot builtin (e.g. Vector2)
is_builtin: bool = False
# Type is a scalar (e.g. int, float) or void
is_base_type: bool = False
# Type doesn't use the heap (hence no need for freeing it)
is_stack_only: bool = False
# Type is an enum (e.g. godot_error, Camera::KeepAspect)
is_enum: bool = False
@property
def is_void(self) -> bool:
return self.c_type == "void"
@property
def is_variant(self) -> bool:
return self.c_type == "godot_variant"
def __post_init__(self):
self.py_type = self.py_type or self.cy_type
if self.is_object:
assert not self.is_builtin
assert not self.is_base_type
assert not self.is_stack_only
if self.is_builtin:
assert not self.is_base_type
# Base types
TYPE_VOID = TypeSpec(
gdapi_type="void", c_type="void", cy_type="None", is_base_type=True, is_stack_only=True
)
TYPE_BOOL = TypeSpec(
gdapi_type="bool",
c_type="godot_bool",
cy_type="bint",
py_type="bool",
is_base_type=True,
is_stack_only=True,
)
TYPE_INT = TypeSpec(
gdapi_type="int", c_type="godot_int", cy_type="int", is_base_type=True, is_stack_only=True
)
TYPE_FLOAT = TypeSpec(
gdapi_type="float", c_type="godot_real", cy_type="float", is_base_type=True, is_stack_only=True
)
TYPE_ERROR = TypeSpec(
gdapi_type="enum.Error",
c_type="godot_error",
cy_type="godot_error",
py_type="Error",
is_base_type=True,
is_stack_only=True,
is_enum=True,
)
TYPE_VECTOR3_AXIS = TypeSpec(
gdapi_type="enum.Vector3::Axis",
c_type="godot_vector3_axis",
cy_type="godot_vector3_axis",
py_type="Vector3.Axis",
is_base_type=True,
is_stack_only=True,
is_enum=True,
)
TYPE_VARIANT_TYPE = TypeSpec(
gdapi_type="enum.Variant::Type",
c_type="godot_variant_type",
cy_type="godot_variant_type",
py_type="VariantType",
is_base_type=True,
is_stack_only=True,
is_enum=True,
)
TYPE_VARIANT_OPERATOR = TypeSpec(
gdapi_type="enum.Variant::Operator",
c_type="godot_variant_operator",
cy_type="godot_variant_operator",
py_type="VariantOperator",
is_base_type=True,
is_stack_only=True,
is_enum=True,
)
# Stack&heap types
TYPE_VARIANT = TypeSpec(
gdapi_type="Variant", c_type="godot_variant", cy_type="object", is_builtin=True
)
TYPE_STRING = TypeSpec(
gdapi_type="String",
c_type="godot_string",
cy_type="GDString",
py_type="Union[str, GDString]",
is_builtin=True,
)
# Stack only types
TYPE_AABB = TypeSpec(
gdapi_type="AABB", c_type="godot_aabb", cy_type="AABB", is_builtin=True, is_stack_only=True
)
TYPE_ARRAY = TypeSpec(
gdapi_type="Array", c_type="godot_array", cy_type="Array", is_builtin=True, is_stack_only=True
)
TYPE_BASIS = TypeSpec(
gdapi_type="Basis", c_type="godot_basis", cy_type="Basis", is_builtin=True, is_stack_only=True
)
TYPE_COLOR = TypeSpec(
gdapi_type="Color", c_type="godot_color", cy_type="Color", is_builtin=True, is_stack_only=True
)
TYPE_DICTIONARY = TypeSpec(
gdapi_type="Dictionary",
c_type="godot_dictionary",
cy_type="Dictionary",
is_builtin=True,
is_stack_only=True,
)
TYPE_NODEPATH = TypeSpec(
gdapi_type="NodePath",
c_type="godot_node_path",
cy_type="NodePath",
py_type="Union[str, NodePath]",
is_builtin=True,
is_stack_only=True,
)
TYPE_PLANE = TypeSpec(
gdapi_type="Plane", c_type="godot_plane", cy_type="Plane", is_builtin=True, is_stack_only=True
)
TYPE_QUAT = TypeSpec(
gdapi_type="Quat", c_type="godot_quat", cy_type="Quat", is_builtin=True, is_stack_only=True
)
TYPE_RECT2 = TypeSpec(
gdapi_type="Rect2", c_type="godot_rect2", cy_type="Rect2", is_builtin=True, is_stack_only=True
)
TYPE_RID = TypeSpec(
gdapi_type="RID", c_type="godot_rid", cy_type="RID", is_builtin=True, is_stack_only=True
)
TYPE_TRANSFORM = TypeSpec(
gdapi_type="Transform",
c_type="godot_transform",
cy_type="Transform",
is_builtin=True,
is_stack_only=True,
)
TYPE_TRANSFORM2D = TypeSpec(
gdapi_type="Transform2D",
c_type="godot_transform2d",
cy_type="Transform2D",
is_builtin=True,
is_stack_only=True,
)
TYPE_VECTOR2 = TypeSpec(
gdapi_type="Vector2",
c_type="godot_vector2",
cy_type="Vector2",
is_builtin=True,
is_stack_only=True,
)
TYPE_VECTOR3 = TypeSpec(
gdapi_type="Vector3",
c_type="godot_vector3",
cy_type="Vector3",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLBYTEARRAY = TypeSpec(
gdapi_type="PoolByteArray",
c_type="godot_pool_byte_array",
cy_type="PoolByteArray",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLINTARRAY = TypeSpec(
gdapi_type="PoolIntArray",
c_type="godot_pool_int_array",
cy_type="PoolIntArray",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLREALARRAY = TypeSpec(
gdapi_type="PoolRealArray",
c_type="godot_pool_real_array",
cy_type="PoolRealArray",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLSTRINGARRAY = TypeSpec(
gdapi_type="PoolStringArray",
c_type="godot_pool_string_array",
cy_type="PoolStringArray",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLVECTOR2ARRAY = TypeSpec(
gdapi_type="PoolVector2Array",
c_type="godot_pool_vector2_array",
cy_type="PoolVector2Array",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLVECTOR3ARRAY = TypeSpec(
gdapi_type="PoolVector3Array",
c_type="godot_pool_vector3_array",
cy_type="PoolVector3Array",
is_builtin=True,
is_stack_only=True,
)
TYPE_POOLCOLORARRAY = TypeSpec(
gdapi_type="PoolColorArray",
c_type="godot_pool_color_array",
cy_type="PoolColorArray",
is_builtin=True,
is_stack_only=True,
)
ALL_TYPES_EXCEPT_OBJECTS = [
TYPE_VOID,
TYPE_BOOL,
TYPE_INT,
TYPE_FLOAT,
TYPE_ERROR,
TYPE_VECTOR3_AXIS,
TYPE_VARIANT_TYPE,
TYPE_VARIANT_OPERATOR,
TYPE_VARIANT,
TYPE_STRING,
TYPE_AABB,
TYPE_ARRAY,
TYPE_BASIS,
TYPE_COLOR,
TYPE_DICTIONARY,
TYPE_NODEPATH,
TYPE_PLANE,
TYPE_QUAT,
TYPE_RECT2,
TYPE_RID,
TYPE_TRANSFORM,
TYPE_TRANSFORM2D,
TYPE_VECTOR2,
TYPE_VECTOR3,
TYPE_POOLBYTEARRAY,
TYPE_POOLINTARRAY,
TYPE_POOLREALARRAY,
TYPE_POOLSTRINGARRAY,
TYPE_POOLVECTOR2ARRAY,
TYPE_POOLVECTOR3ARRAY,
TYPE_POOLCOLORARRAY,
]

BIN
misc/godot_python.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

156
misc/godot_python.svg Normal file
View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
id="svg3030"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="godot_python.svg"
inkscape:export-filename="/home/akien/Projects/godot/godot.git/icon.png"
inkscape:export-xdpi="24"
inkscape:export-ydpi="24">
<defs
id="defs3032">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath988">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.33333325"
d="m 417.61523,76.875 c -42.39203,9.42415 -84.32671,22.544934 -123.64257,42.33398 0.89913,34.71618 3.14362,67.97966 7.69336,101.76758 -15.26846,9.78213 -31.31508,18.17757 -45.57618,29.62891 -14.49005,11.14748 -29.2896,21.81232 -42.41015,34.84961 -26.21196,-17.33727 -53.95468,-33.6299 -82.53516,-48.01172 C 100.33704,270.59855 71.53124,306.38341 48,346.42773 c 17.703584,28.63876 36.185068,55.48381 56.14062,80.95899 h 0.5586 v 197.7832 25.1211 22.86132 c 0.44956,0.004 0.89835,0.0209 1.34375,0.0625 l 150.66992,14.52735 c 7.89231,0.76176 14.07748,7.11448 14.62695,15.02343 l 4.64649,66.50977 131.42969,9.37695 9.05468,-61.38476 c 1.17385,-7.95891 8.00029,-13.85742 16.05078,-13.85742 h 158.96094 c 8.04632,0 14.87302,5.89851 16.04688,13.85742 l 9.05468,61.38476 131.4336,-9.37695 4.64258,-66.50977 c 0.55363,-7.90895 6.73464,-14.25751 14.62695,-15.02343 l 150.61133,-14.52735 c 0.4454,-0.0416 0.89028,-0.0584 1.33984,-0.0625 v -19.61132 l 0.0625,-0.0195 v -226.1348 h 0.5586 C 939.81913,401.91154 958.28809,375.06649 976,346.42773 952.47709,306.38341 923.65514,270.59855 892.84766,237.44336 c -28.57217,14.38182 -56.32515,30.67445 -82.53711,48.01172 -13.11639,-13.03729 -27.88953,-23.70213 -42.40039,-34.84961 -14.25694,-11.45134 -30.32318,-19.84678 -45.5625,-29.62891 4.53725,-33.78792 6.7803,-67.0514 7.68359,-101.76758 C 690.71123,99.419934 648.78199,86.29915 606.36914,76.875 c -16.9335,28.45977 -32.41939,59.27922 -45.90625,89.4082 -15.99275,-2.67239 -32.05995,-3.66203 -48.14844,-3.85351 v -0.0254 c -0.11239,0 -0.21676,0.0254 -0.3125,0.0254 -0.0999,0 -0.20478,-0.0254 -0.30468,-0.0254 v 0.0254 c -16.11763,0.19148 -32.17106,1.18112 -48.16797,3.85351 C 450.05076,136.15422 434.5737,105.33477 417.61523,76.875 Z M 104.45117,705.66016 c 0.0624,14.56081 0.24805,30.51338 0.24805,33.68945 0,143.08559 181.51178,211.85949 407.02539,212.65039 h 0.27344 0.27929 c 225.51361,-0.7909 406.96289,-69.5648 406.96289,-212.65039 0,-3.23435 0.19512,-19.12031 0.26172,-33.68945 l -135.42968,13.0625 -4.66797,66.86523 c -0.56195,8.05882 -6.97243,14.47218 -15.03125,15.05078 l -160.48828,11.45117 c -0.39129,0.0291 -0.7828,0.043 -1.16993,0.043 -7.97556,0 -14.85713,-5.85247 -16.03515,-13.86133 L 577.47656,735.8555 H 446.52539 l -9.20312,62.41601 c -1.23629,8.40015 -8.74665,14.43859 -17.20508,13.81836 l -160.48828,-11.4512 c -8.05881,-0.5786 -14.46929,-6.99196 -15.03125,-15.05078 l -4.66602,-66.86523 z"
id="path990"
inkscape:connector-curvature="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="242.96095"
inkscape:cy="357.56137"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1600"
inkscape:window-height="837"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-global="false" />
<metadata
id="metadata3035">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="python"
style="display:inline">
<g
id="g986"
clip-path="url(#clipPath988)">
<g
id="g973">
<path
sodipodi:nodetypes="scccccscccscsss"
id="path8615"
d="m 231.31493,-135.039 c -259.954666,10e-6 -243.721416,112.732019 -243.721416,112.732019 l 0.2898,116.78922 H 235.95173 V 129.54799 L 9.2020729,272.96768 c 0,0 -166.3449429,-18.86504 -166.3449429,243.43162 -2e-5,262.2966 153.7610016,438.7093 153.7610016,438.7093 l 243.7929284,-5.71429 -8.57143,-307.4301 c 0,0 -4.67066,-145.18958 142.87118,-145.18958 h 246.03982 c 0,0 138.23439,2.23457 138.23439,-133.59759 V 138.58239 c 0,-2e-5 -256.00568,-273.62139 -527.67009,-273.62139 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#387eb8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccccccscccscsccc"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffe052;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="m 515.69111,1021.3336 c 259.95474,0 452.29285,-78.44626 452.29285,-78.44626 l -0.28934,-116.78926 -28.06841,8.57143 v -35.06574 l 126.60029,-8.57143 c 0,0 166.3449,18.86497 166.3449,-243.43172 10e-5,-262.29658 -350.90385,-338.70926 -350.90385,-338.70926 h -86.65007 l -2.85714,173.14435 c 0,0 4.67074,145.18958 -142.87118,145.18958 H 403.24932 c 0,0 -138.23438,-2.23457 -138.23438,133.59768 v 224.59466 c 0,0 -20.9878,135.91597 250.67663,135.91597 z"
id="path8620"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
<g
inkscape:label="godot"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-98.519719)"
style="display:inline">
<g
id="g78"
transform="matrix(4.162611,0,0,-4.162611,919.24059,771.67186)"
style="stroke-width:0.32031175">
<path
d="m 0,0 c 0,0 -0.325,1.994 -0.515,1.976 l -36.182,-3.491 c -2.879,-0.278 -5.115,-2.574 -5.317,-5.459 l -0.994,-14.247 -27.992,-1.997 -1.904,12.912 c -0.424,2.872 -2.932,5.037 -5.835,5.037 h -38.188 c -2.902,0 -5.41,-2.165 -5.834,-5.037 l -1.905,-12.912 -27.992,1.997 -0.994,14.247 c -0.202,2.886 -2.438,5.182 -5.317,5.46 l -36.2,3.49 c -0.187,0.018 -0.324,-1.978 -0.511,-1.978 l -0.049,-7.83 30.658,-4.944 1.004,-14.374 c 0.203,-2.91 2.551,-5.263 5.463,-5.472 l 38.551,-2.75 c 0.146,-0.01 0.29,-0.016 0.434,-0.016 2.897,0 5.401,2.166 5.825,5.038 l 1.959,13.286 h 28.005 l 1.959,-13.286 c 0.423,-2.871 2.93,-5.037 5.831,-5.037 0.142,0 0.284,0.005 0.423,0.015 l 38.556,2.75 c 2.911,0.209 5.26,2.562 5.463,5.472 l 1.003,14.374 30.645,4.966 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
id="path80"
inkscape:connector-curvature="0" />
</g>
<g
id="g90-3"
transform="matrix(4.162611,0,0,-4.162611,389.21484,625.67104)"
style="stroke-width:0.32031175">
<path
d="m 0,0 c 0,-12.052 -9.765,-21.815 -21.813,-21.815 -12.042,0 -21.81,9.763 -21.81,21.815 0,12.044 9.768,21.802 21.81,21.802 C -9.765,21.802 0,12.044 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
id="path92-5"
inkscape:connector-curvature="0" />
</g>
<g
id="g94-6"
transform="matrix(4.162611,0,0,-4.162611,367.36686,631.05679)"
style="stroke-width:0.32031175">
<path
d="m 0,0 c 0,-7.994 -6.479,-14.473 -14.479,-14.473 -7.996,0 -14.479,6.479 -14.479,14.473 0,7.994 6.483,14.479 14.479,14.479 C -6.479,14.479 0,7.994 0,0"
style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
id="path96-2"
inkscape:connector-curvature="0" />
</g>
<g
id="g98-9"
transform="matrix(4.162611,0,0,-4.162611,511.99336,724.73954)"
style="stroke-width:0.32031175">
<path
d="m 0,0 c -3.878,0 -7.021,2.858 -7.021,6.381 v 20.081 c 0,3.52 3.143,6.381 7.021,6.381 3.878,0 7.028,-2.861 7.028,-6.381 V 6.381 C 7.028,2.858 3.878,0 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
id="path100-1"
inkscape:connector-curvature="0" />
</g>
<g
id="g102-2"
transform="matrix(4.162611,0,0,-4.162611,634.78706,625.67104)"
style="stroke-width:0.32031175">
<path
d="m 0,0 c 0,-12.052 9.765,-21.815 21.815,-21.815 12.041,0 21.808,9.763 21.808,21.815 0,12.044 -9.767,21.802 -21.808,21.802 C 9.765,21.802 0,12.044 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
id="path104-7"
inkscape:connector-curvature="0" />
</g>
<g
id="g106-0"
transform="matrix(4.162611,0,0,-4.162611,656.64056,631.05679)"
style="stroke-width:0.32031175">
<path
d="m 0,0 c 0,-7.994 6.477,-14.473 14.471,-14.473 8.002,0 14.479,6.479 14.479,14.473 0,7.994 -6.477,14.479 -14.479,14.479 C 6.477,14.479 0,7.994 0,0"
style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
id="path108-9"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -0,0 +1,83 @@
#! /usr/bin/env python
#
# see: https://julienrenaux.fr/2019/12/20/github-actions-security-risk/
# TL;DR: Using GitHub actions with branch names or tags is unsafe. Use commit hash instead.
import re
import sys
import json
import argparse
from pathlib import Path
from functools import lru_cache
from urllib.request import urlopen
GITHUB_CONF_DIR = Path(".").joinpath("../.github").resolve()
REPO_REGEX = r"(?P<repo>[\w\-_]+/[\w\-_]+)"
SHA_REGEX = r"(?P<sha>[a-fA-F0-9]{40})"
TAG_REGEX = r"(?P<tag>[\w\-_]+)"
PIN_REGEX = r"(?P<pin>[\w\-_]+)"
USES_REGEX = re.compile(
rf"uses\W*:\W*{REPO_REGEX}@({SHA_REGEX}|{TAG_REGEX})(\W*#\W*pin@{PIN_REGEX})?", re.MULTILINE
)
def get_files(pathes):
for path in pathes:
if path.is_dir():
yield from path.rglob("*.yml")
elif path.is_file():
yield path
@lru_cache(maxsize=None)
def resolve_tag(repo, tag):
url = f"https://api.github.com/repos/{repo}/git/ref/tags/{tag}"
with urlopen(url) as f:
data = json.loads(f.read())
return data["object"]["sha"]
def add_pin(pathes):
for file in get_files(pathes):
txt = file.read_text()
overwrite_needed = False
# Start by the end so that we can use match.start/end to do in-place modifications
for match in reversed(list(USES_REGEX.finditer(txt))):
repo = match.group("repo")
tag = match.group("tag")
if tag is not None:
sha = resolve_tag(repo, tag)
print(f"Pinning github action {file}: {repo}@{tag} => {sha}")
txt = txt[: match.start()] + f"uses: {repo}@{sha} # pin@{tag}" + txt[match.end() :]
overwrite_needed = True
if overwrite_needed:
file.write_text(txt)
return 0
def check_pin(pathes):
ret = 0
for file in get_files(pathes):
for match in USES_REGEX.finditer(file.read_text()):
repo = match.group("repo")
tag = match.group("tag")
if tag is not None:
print(f"Unpinned github action {file}: {repo}@{tag}")
ret = 1
return ret
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("cmd", choices=["check", "add"])
parser.add_argument(
"files", nargs="*", type=Path, default=[Path(__name__).joinpath("../.github/").resolve()]
)
args = parser.parse_args()
if args.cmd == "check":
sys.exit(check_pin(args.files))
else:
sys.exit(add_pin(args.files))

305
misc/release_LICENSE.txt Normal file
View File

@ -0,0 +1,305 @@
+---------------------------------------------------------------------------+
| Godot Python |
+---------------------------------------------------------------------------+
Copyright (c) 2016 by Emmanuel Leblond.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Godot Python Logo (C) Pinswell
Distributed under the terms of the Creative Commons Attribution License
version 3.0 (CC-BY 3.0)
https://creativecommons.org/licenses/by/3.0/legalcode.
+---------------------------------------------------------------------------+
| CPython |
+---------------------------------------------------------------------------+
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights
Reserved" are retained in Python alone or in any derivative version prepared by
Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the Internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the Internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+---------------------------------------------------------------------------+
| Afterword |
+---------------------------------------------------------------------------+
...
You didn't read a thing, didn't you ? ¯\(ツ)/¯

61
misc/release_README.txt Normal file
View File

@ -0,0 +1,61 @@
________ .___ __ __________ __ .__
/ _____/ ____ __| _/_____/ |_ \______ \___.__._/ |_| |__ ____ ____
/ \ ___ / _ \ / __ |/ _ \ __\ | ___< | |\ __\ | \ / _ \ / \
\ \_\ ( <_> ) /_/ ( <_> ) | | | \___ | | | | Y ( <_> ) | \
\______ /\____/\____ |\____/|__| |____| / ____| |__| |___| /\____/|___| /
\/ \/ \/ \/ \/
v{version} ({date})
Introduction
------------
This is a beta version of the Python module for Godot.
You are likely to encounter bugs and catastrophic crashes, if so please
report them to https://github.com/touilleMan/godot-python/issues.
Working features
----------------
Every Godot core features are expected to work fine:
- builtins (e.g. Vector2)
- Objects classes (e.g. Node)
- signals
- variable export
- rpc synchronisation
On top of that, mixing GDscript and Python code inside a project should work fine.
Using Pip
---------
Pip must be installed first with `ensurepip`:
On Windows:
```
$ <pythonscript_dir>/windows-64/python.exe -m ensurepip # Only need to do that once
$ <pythonscript_dir>/windows-64/python.exe -m pip install whatever
```
On Linux/macOS:
```
$ <pythonscript_dir>/x11-64/bin/python3 -m ensurepip # Only need to do that once
$ <pythonscript_dir>/x11-64/bin/python3 -m pip install whatever
```
Note you must use `python -m pip` to invoke pip (using the command `pip`
directly will likely fail in a cryptic manner)
Not so well features
--------------------
Exporting the project hasn't been tested at all (however exporting for linux should be pretty simple and may work out of the box...).
Have fun ;-)
- touilleMan

Some files were not shown because too many files have changed in this diff Show More