Skip to content

Commit 531b15a

Browse files
committed
Add C struct & inline methods bindings for builtins in godot/_hazmat/gdapi.pxd
1 parent 608ac58 commit 531b15a

File tree

6 files changed

+144
-53
lines changed

6 files changed

+144
-53
lines changed

scripts/extension_api_parser/builtins.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import enum
2-
from typing import Dict, Optional, List, Tuple
2+
from typing import Dict, Optional, List
33
from dataclasses import dataclass
44
from string import ascii_uppercase
55

6-
from .utils import correct_name, correct_type_name, assert_api_consistency
6+
from .utils import correct_name, assert_api_consistency
77
from .type import TypeInUse, ValueInUse
88

99

@@ -213,12 +213,12 @@ class BuiltinMethodSpec:
213213
def parse(cls, item: dict) -> "BuiltinMethodSpec":
214214
item.setdefault("original_name", item["name"])
215215
item.setdefault("arguments", [])
216-
item.setdefault("return_type", None)
216+
item.setdefault("return_type", "Nil")
217217
assert_api_consistency(cls, item)
218218
return cls(
219219
name=correct_name(item["name"]),
220220
original_name=item["original_name"],
221-
return_type=TypeInUse(item["return_type"]) if item["return_type"] else None,
221+
return_type=TypeInUse(item["return_type"]),
222222
is_vararg=item["is_vararg"],
223223
is_const=item["is_const"],
224224
is_static=item["is_static"],
@@ -249,9 +249,12 @@ def parse(cls, item: dict) -> "BuiltinEnumSpec":
249249

250250
@dataclass
251251
class BuiltinSpec:
252+
# Name as it is in `extension_api.json`
253+
original_name: str
254+
# Name used for the binding (e.g. `from godot import Vector2`)
252255
name: str
256+
# Name used for the C structure binding (e.g. `from godot.hazmat.gdapi cimport Vector2`)
253257
c_struct_name: str
254-
original_name: str
255258
is_scalar: bool
256259
size: int
257260
indexing_return_type: Optional[str]
@@ -288,7 +291,14 @@ def parse(cls, item: dict) -> "BuiltinSpec":
288291
snake += c
289292
item["variant_type_name"] = f"GDNATIVE_VARIANT_TYPE_{snake.upper()}"
290293
item.setdefault("original_name", item["name"])
291-
item.setdefault("c_struct_name", f"C{item['original_name']}")
294+
# Special case for the String type, this is because `String` is too
295+
# broad of a name (it's easy to mix with Python's regular `str`)
296+
# On top of that `str` and Godot `String` are two totally separated
297+
# string types that require conversions to work together, so it's better
298+
# to make extra clear they are not the same types !
299+
if item["name"] == "String":
300+
item["name"] = "GDString"
301+
item.setdefault("c_struct_name", item["name"])
292302
item.setdefault("indexing_return_type", None)
293303
item.setdefault("methods", [])
294304
item.setdefault("members", [])
@@ -299,7 +309,8 @@ def parse(cls, item: dict) -> "BuiltinSpec":
299309

300310
return cls(
301311
original_name=item["original_name"],
302-
name=correct_type_name(item["name"]),
312+
# name=correct_type_name(item["name"]),
313+
name=item["name"],
303314
c_struct_name=item["c_struct_name"],
304315
is_scalar=item["is_scalar"],
305316
size=item["size"],

scripts/extension_api_parser/type.py

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, Dict, Iterable, Union, Optional
1+
from typing import TYPE_CHECKING, Dict, Iterable
22
from dataclasses import dataclass, replace
33

44

@@ -8,6 +8,10 @@
88
from .builtins import BuiltinSpec
99

1010

11+
# Type alias
12+
TypeDBEntry = str
13+
14+
1115
# We devide types into three categories:
1216
# - scalars (native types already existing in C/Cython, e.g. float, int32 etc.)
1317
# - builtins
@@ -30,11 +34,13 @@ class TypeSpec:
3034
is_object: bool = False
3135
# Type is a Godot builtin (e.g. Vector2)
3236
is_builtin: bool = False
37+
# Nil type (aka None, or NULL) is very special ;-)
38+
is_nil: bool = False
3339
# Type is a scalar (e.g. int, float) or void
3440
is_scalar: bool = False
3541
# Type doesn't use the heap (hence no need for freeing it)
3642
is_stack_only: bool = False
37-
# # Type is an enum (e.g. godot_error, Camera::KeepAspect)
43+
# Type is an enum (e.g. godot_error, Camera::KeepAspect)
3844
is_enum: bool = False
3945
# e.g. `GDNATIVE_VARIANT_TYPE_BOOL`
4046
# Default to an invalid value so that we detect incorrect use during Cython compilation
@@ -54,11 +60,58 @@ def __post_init__(self):
5460
assert not self.is_scalar
5561

5662

57-
# TODO: put variant_type_name into TypeSpec
63+
# `Nil` is a special case, it is only needed for `BuiltinOperatorSpec.right_type`
64+
# and in `ValueInUse`. But it meaning can differ:
65+
# - `BuiltinOperatorSpec.right_type`: `Nil` represents the absence of value
66+
# - `ValueInUse`: `Nil` represents a singleton value (like `None` in Python)
67+
# So the template code is expected to use the `is_nil` attribute and do ad-hoc
68+
# code according to it need instead of relying on py/c/cy_type
69+
70+
71+
class NilTypeSpec(TypeSpec):
72+
def __init__(self):
73+
# Cannot use super().__init__() given py/cy/c_type are read-only !
74+
self.__dict__ = {
75+
# Default values from parent
76+
**{f.name: f.default for f in super().__dataclass_fields__.values()},
77+
# Our config values
78+
**{
79+
"size": 0,
80+
"gdapi_type": "Nil",
81+
"is_scalar": True,
82+
"is_stack_only": True,
83+
"is_nil": True,
84+
"variant_type_name": "GDNATIVE_VARIANT_TYPE_NIL",
85+
},
86+
}
87+
88+
def __repr__(self):
89+
return f"<NilTypeSpec {id(self)}>"
90+
91+
@property
92+
def py_type(self):
93+
raise RuntimeError(
94+
"Nil type ! Should handle this by hand with a if condition on `<my_type>.is_nil`"
95+
)
96+
97+
@property
98+
def cy_type(self):
99+
raise RuntimeError(
100+
"Nil type ! Should handle this by hand with a if condition on `<my_type>.is_nil`"
101+
)
102+
103+
@property
104+
def c_type(self):
105+
raise RuntimeError(
106+
"Nil type ! Should handle this by hand with a if condition on `<my_type>.is_nil`"
107+
)
108+
109+
58110
# TODO: Object type should match GDNATIVE_VARIANT_TYPE_OBJECT
59111

60112

61-
TYPES_DB: Dict[str, TypeSpec] = {
113+
TYPES_DB: Dict[TypeDBEntry, TypeSpec] = {
114+
"Nil": NilTypeSpec(),
62115
# Types marked as `meta` are used in the classes method args/return types
63116
"meta:int8": TypeSpec(
64117
size=1,
@@ -179,7 +232,7 @@ def register_variant_in_types_db(variant_size: int) -> None:
179232
TYPES_DB["Variant"] = TypeSpec(
180233
size=variant_size,
181234
gdapi_type="Variant",
182-
c_type="CVariant",
235+
c_type="Variant",
183236
cy_type="object",
184237
py_type="GDAny",
185238
is_builtin=True,
@@ -189,9 +242,10 @@ def register_variant_in_types_db(variant_size: int) -> None:
189242
def register_builtins_in_types_db(builtins: Iterable["BuiltinSpec"]) -> None:
190243
for spec in builtins:
191244
if spec.name == "Nil":
192-
# `Nil` is a special case, it is only needed for
193-
# `BuiltinOperatorSpec.right_type` and in `ValueInUse`.
194-
# So better skip it and use ad-hoc workaround instead.
245+
# `Nil` is already part of `TYPES_DB`
246+
nil_spec = TYPES_DB["Nil"]
247+
assert nil_spec.gdapi_type == spec.original_name # Sanity check
248+
assert nil_spec.variant_type_name == spec.variant_type_name # Sanity check
195249
continue
196250
assert spec.size is not None
197251
if spec.name == "bool":
@@ -224,7 +278,7 @@ def register_builtins_in_types_db(builtins: Iterable["BuiltinSpec"]) -> None:
224278
size=spec.size,
225279
gdapi_type=spec.original_name,
226280
py_type=spec.name,
227-
c_type=f"C{spec.name}",
281+
c_type=spec.c_struct_name,
228282
cy_type=spec.name,
229283
is_stack_only=not spec.has_destructor,
230284
is_builtin=True,
@@ -279,7 +333,7 @@ def register_global_enums_in_types_db(enums: Iterable["GlobalEnumSpec"]) -> None
279333

280334
@dataclass(repr=False)
281335
class TypeInUse:
282-
type_name: str
336+
type_name: TypeDBEntry
283337

284338
def __repr__(self) -> str:
285339
try:

src/godot/_builtins.pyi.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class {{spec.name}}:
3030
# Methods
3131
{% endif %}
3232
{% for m in spec.methods %}
33-
def {{m.name}}(self{% for a in m.arguments %}, {{ render_arg(a) }}{% endfor %}) -> {{ m.return_type.py_type if m.return_type is not none else "None" }}: ...
33+
def {{m.name}}(self{% for a in m.arguments %}, {{ render_arg(a) }}{% endfor %}) -> {{ "None" if m.return_type.is_nil else m.return_type.py_type }}: ...
3434
{% endfor %}
3535
{% if spec.constants %}
3636
# Constants

src/godot/_builtins_pyx/class.j2

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -84,37 +84,7 @@ cdef class {{ spec.name }}:
8484
{{ arg.name }},
8585
{% endfor %}
8686
):
87-
# TODO
88-
# cdef GDNativePtrBuiltInMethod ptrmethod = pythonscript_gdapi.variant_get_ptr_builtin_method(
89-
# {{ spec.variant_type_name }},
90-
# "{{ m.original_name }}",
91-
# {{ m.hash }}
92-
# )
93-
# if ptrmethod == NULL:
94-
# raise RuntimeError("Cannot load method from Godot")
95-
96-
{% if m.arguments | length == 0 %}
97-
# {% if m.return_type %}
98-
# # TODO
99-
# # cdef {{ m.return_type.cy_type }} ret = {{ m.return_type.cy_type }}.__new__()
100-
# {% set retval_as_arg = "NULL" %}
101-
# {% else %}
102-
# {% set retval_as_arg = "NULL" %}
103-
# {% endif %}
104-
105-
# with nogil:
106-
# ptrmethod(
107-
# self._ptr,
108-
# NULL, # args
109-
# # return
110-
# 0, # args_count
111-
# )
112-
113-
return
114-
{% else %}
11587
raise NotImplemented
116-
{% endif %}
117-
11888
{% endfor %}
11989

12090
{%- endmacro %}

src/godot/_hazmat/gdapi.pxd.j2

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from libc.stdint cimport *
2-
from godot._hazmat.gdnative_interface cimport GDNativeInterface
2+
from godot._hazmat.gdnative_interface cimport *
33

44

55
cdef extern from * nogil:
@@ -20,20 +20,76 @@ cdef extern from * nogil:
2020
cdef const GDNativeInterface *pythonscript_gdapi
2121

2222

23-
cdef struct Variant:
24-
char _gd_data[{{ api.variant_size }}]
23+
# Following stuff are all the Godot builtins exposed as:
24+
# - a C structure (with typed fields if the structure is not opaque)
25+
# - the function pointer of all the builtin's constructors/destructors/methods/operators
26+
# - Inlines functions for a nicer way to call the constructors/destructors/methods/operators
27+
2528

29+
cdef struct Variant:
30+
char _gd_opaque[{{ api.variant_size }}]
2631

2732
{% for spec in api["builtins"] if not spec.is_scalar %}
33+
34+
##############################################################################
35+
#{{ "{:^76}".format(spec.name) }}#
36+
##############################################################################
37+
38+
{#
39+
Builtins C struct
40+
#}
2841
cdef struct {{ spec.c_struct_name }}:
2942
{% set c_struct_members = spec.c_struct_members %}
3043
{% if c_struct_members %}
3144
{% for member in c_struct_members %}
3245
{{ member.type.c_type }} {{ member.name }}
3346
{% endfor %}
3447
{% else %}
35-
char _gd_data[{{ spec.size }}]
48+
char _gd_opaque[{{ spec.size }}]
3649
{% endif %}
3750

51+
{#
52+
Builtins C method call
53+
#}
54+
{% for c in spec.constructors %}
55+
cdef GDNativePtrConstructor __{{ spec.name }}_constructor_{{ c.index }}
56+
{% endfor %}
57+
{% if spec.has_destructor %}
58+
cdef GDNativePtrDestructor __{{ spec.name }}_destructor
59+
{% endif %}
60+
{% for m in spec.methods %}
61+
cdef GDNativePtrBuiltInMethod __{{ spec.name }}_meth_{{ m.name }}
62+
{% endfor %}
63+
{% for o in spec.operators %}
64+
cdef GDNativePtrOperatorEvaluator __{{ spec.name }}_op_{{ o.name }}
65+
{% endfor %}
66+
{#
67+
Builtins C method call
68+
#}
69+
{% for meth in spec.methods if meth.offest is not none %}
3870

71+
cdef inline {{ "void" if meth.return_type.is_nil else meth.return_type.c_type }} {{ spec.name | lower }}_{{ meth.name }}(
72+
{{ spec.name }} *self,
73+
{% for arg in meth.arguments %}
74+
{{ arg.type.c_type }}* {{ arg.name }},
75+
{% endfor %}
76+
):
77+
cdef GDNativeTypePtr[{{ meth.arguments | length }}] p_args = [{% for arg in meth.arguments %}{{ arg.name }},{% endfor %}]
78+
{% if not meth.return_type.is_nil %}
79+
cdef {{ meth.return_type.c_type }} r_return
80+
{% endif %}
81+
__{{ spec.name }}_meth_{{ meth.name }}(
82+
{# GDNativeTypePtr p_base #}
83+
self,
84+
{# const GDNativeTypePtr *p_args #}
85+
p_args,
86+
{# GDNativeTypePtr r_return #}
87+
{{ "NULL" if meth.return_type.is_nil else "&r_return" }},
88+
{# int p_argument_count #}
89+
{{ meth.arguments | length }}
90+
)
91+
{% if not meth.return_type.is_nil %}
92+
return r_return
93+
{% endif %}
94+
{% endfor %}
3995
{% endfor %}

src/godot/_hazmat/gdapi_ptrs.pxi.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ cdef GDNativePtrDestructor __{{ spec.name }}_destructor = pythonscript_gdapi.var
2626
{% endif %}
2727

2828
{% for m in spec.methods %}
29-
cdef GDNativeMethodBindPtr __{{ spec.name }}_meth_{{ m.name }} = pythonscript_gdapi.variant_get_ptr_builtin_method(
29+
cdef GDNativePtrBuiltInMethod __{{ spec.name }}_meth_{{ m.name }} = pythonscript_gdapi.variant_get_ptr_builtin_method(
3030
{{ spec.variant_type_name }}, "{{ m.original_name }}", {{ m.hash }}
3131
)
3232
{% endfor %}

0 commit comments

Comments
 (0)