Initial commit. Added https://github.com/touilleMan/godot-python b9757da859a4d as a base, but without the submodule.
9
.bumpversion.cfg
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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")
|
7
addons/pythonscript_repl/hack_regular.tres
Normal 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 )
|
BIN
addons/pythonscript_repl/hack_regular.ttf
Normal file
16
addons/pythonscript_repl/input_box.py
Normal 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()
|
7
addons/pythonscript_repl/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="pythonscript_repl"
|
||||
description=""
|
||||
author="godot-python"
|
||||
version="0.1"
|
||||
script="plugin.py"
|
19
addons/pythonscript_repl/plugin.py
Normal 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
|
241
addons/pythonscript_repl/python_repl.py
Normal 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()
|
66
addons/pythonscript_repl/python_repl.tscn
Normal 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
@ -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
@ -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
@ -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
@ -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
After Width: | Height: | Size: 203 B |
34
examples/pong/ball.png.import
Normal 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
@ -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
@ -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
After Width: | Height: | Size: 956 B |
34
examples/pong/icon.png.import
Normal 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
@ -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
After Width: | Height: | Size: 184 B |
34
examples/pong/paddle.png.import
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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"]
|
67
examples/pong/project.godot
Normal 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 )
|
23
examples/pong/pythonscript.gdnlib
Normal 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
After Width: | Height: | Size: 203 B |
34
examples/pong/separator.png.import
Normal 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
|
73
examples/pong_multiplayer/ball.gd
Normal 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)
|
||||
|
BIN
examples/pong_multiplayer/ball.png
Normal file
After Width: | Height: | Size: 203 B |
24
examples/pong_multiplayer/ball.png.import
Normal 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
|
65
examples/pong_multiplayer/ball.py
Normal 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
|
33
examples/pong_multiplayer/ball.tscn
Normal 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_multiplayer/icon.png
Normal file
After Width: | Height: | Size: 956 B |
24
examples/pong_multiplayer/icon.png.import
Normal 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
|
106
examples/pong_multiplayer/lobby.gd
Normal 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")
|
||||
|
184
examples/pong_multiplayer/lobby.tscn
Normal 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"]
|
||||
|
||||
|
63
examples/pong_multiplayer/paddle.gd
Normal 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
|
BIN
examples/pong_multiplayer/paddle.png
Normal file
After Width: | Height: | Size: 184 B |
24
examples/pong_multiplayer/paddle.png.import
Normal 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
|
52
examples/pong_multiplayer/paddle.tscn
Normal 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"]
|
||||
|
||||
|
53
examples/pong_multiplayer/pong.gd
Normal 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
|
||||
|
172
examples/pong_multiplayer/pong.tscn
Normal 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"]
|
41
examples/pong_multiplayer/project.godot
Normal 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 )
|
23
examples/pong_multiplayer/pythonscript.gdnlib
Normal 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_multiplayer/separator.png
Normal file
After Width: | Height: | Size: 203 B |
24
examples/pong_multiplayer/separator.png.import
Normal 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
|
13
generation/bindings_templates/bindings.tmpl.pxd
Normal 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()
|
164
generation/bindings_templates/bindings.tmpl.pyi
Normal 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 %}
|
191
generation/bindings_templates/bindings.tmpl.pyx
Normal 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
|
19
generation/bindings_templates/class.tmpl.pxd
Normal 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 %}
|
77
generation/bindings_templates/class.tmpl.pyi
Normal 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 %}
|
280
generation/bindings_templates/class.tmpl.pyx
Normal 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 %}
|
166
generation/bindings_templates/method.tmpl.pyx
Normal 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 %}
|
75
generation/builtins_templates/aabb.tmpl.pxi
Normal 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 %}
|
249
generation/builtins_templates/array.tmpl.pxi
Normal 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 %}
|
135
generation/builtins_templates/basis.tmpl.pxi
Normal 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 %}
|
46
generation/builtins_templates/builtins.tmpl.pxd
Normal 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 %}
|
35
generation/builtins_templates/builtins.tmpl.pyi
Normal 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 %}
|
54
generation/builtins_templates/builtins.tmpl.pyx
Normal 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 %}
|
244
generation/builtins_templates/color.tmpl.pxi
Normal 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 %}
|
210
generation/builtins_templates/dictionary.tmpl.pxi
Normal 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 %}
|
255
generation/builtins_templates/gdstring.tmpl.pxi
Normal 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 -%}
|
55
generation/builtins_templates/node_path.tmpl.pxi
Normal 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 %}
|
89
generation/builtins_templates/plane.tmpl.pxi
Normal 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 %}
|
86
generation/builtins_templates/quat.tmpl.pxi
Normal 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 %}
|
58
generation/builtins_templates/rect2.tmpl.pxi
Normal 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 %}
|
20
generation/builtins_templates/render.tmpl.pxd
Normal 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) %}
|
44
generation/builtins_templates/render.tmpl.pyi
Normal 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) %}
|
120
generation/builtins_templates/render.tmpl.pyx
Normal 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) %}
|
44
generation/builtins_templates/rid.tmpl.pxi
Normal 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 -%}
|
74
generation/builtins_templates/transform.tmpl.pxi
Normal 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 %}
|
97
generation/builtins_templates/transform2d.tmpl.pxi
Normal 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 %}
|
123
generation/builtins_templates/vector2.tmpl.pxi
Normal 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 %}
|
160
generation/builtins_templates/vector3.tmpl.pxi
Normal 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 %}
|
613
generation/generate_bindings.py
Normal 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)
|
400
generation/generate_builtins.py
Normal 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 :'("
|
||||
)
|
399
generation/generate_gdnative_api_struct.py
Normal 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)
|
105
generation/generate_pool_arrays.py
Normal 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")
|
32
generation/pool_arrays_templates/pool_arrays.tmpl.pxd
Normal 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 %}
|
35
generation/pool_arrays_templates/pool_arrays.tmpl.pyx
Normal 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 %}
|
33
generation/pool_arrays_templates/pool_x_array.tmpl.pxd
Normal 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 %}
|
326
generation/pool_arrays_templates/pool_x_array.tmpl.pyx
Normal 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
@ -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
After Width: | Height: | Size: 36 KiB |
156
misc/godot_python.svg
Normal 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 |
83
misc/pin_github_actions.py
Normal 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
@ -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
@ -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
|