Skip to content

Commit 3f851e5

Browse files
committed
Split builtins method cpdef into an inline cdef in builtins.pxd and a regular def in builtins.pyx
This allows shared library compiled from a 3rd party modules that include `builtins.pxd` to only depend on the `gdptr_*` symbols that it actually needs
1 parent 3740848 commit 3f851e5

File tree

4 files changed

+180
-1
lines changed

4 files changed

+180
-1
lines changed

src/godot/builtins_pxd/method.pxd.j2

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,20 @@ object {{ arg.name }}
1818

1919

2020
{% macro _render_non_static_method(builtin, m) %}
21-
cpdef inline {{ render_method_signature(m) }}:
21+
{#
22+
Note we don't use `cpdef` here since Cython would then add the Python flavour of
23+
those methods to any .pyx that include `builtins.pxd`.
24+
Worst: this would also make the compiler consider all `gdptr_*` symbols in
25+
`builtins.so` are used, hence bloating the final shared library of tons of useless
26+
symbols to resolve at runtime !
27+
28+
So instead we define each method twice:
29+
- Once here that will become a static inline C function. Hence the compiler
30+
will add it to any module importing `builtins.pxd` and discard automatically
31+
if it is in fact never used in the module.
32+
- Once in `builtins.pyx` as a regular Python method.
33+
#}
34+
cdef inline {{ render_method_signature(m) }}:
2235
{% if m.contains_unsuported_types %}
2336
raise NotImplementedError # TODO
2437
{% else %}

src/godot/builtins_pyx/class.pyx.j2

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{% from 'builtins_pyx/constructor.pyx.j2' import render_constructors with context %}
22
{% from 'builtins_pyx/member.pyx.j2' import render_member with context %}
3+
{% from 'builtins_pyx/method.pyx.j2' import render_method with context %}
34
{% from 'builtins_pyx/operator.pyx.j2' import render_operators with context %}
45

56

@@ -61,6 +62,14 @@ cdef class {{ builtin.cy_type }}:
6162
{{ render_member(builtin, m) | indent }}
6263
{% endfor %}
6364

65+
{% if builtin.methods %}
66+
# Methods
67+
68+
{% endif %}
69+
{% for m in builtin.methods %}
70+
{{ render_method(builtin, m) | indent }}
71+
{% endfor %}
72+
6473
{% if builtin.enums %}
6574
# Enums
6675

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{% macro render_method_signature(m) -%}
2+
{{ "_" + m.name if m.is_static else m.name }}(
3+
{%- if not m.is_static -%}self, {% endif -%}
4+
{%- for arg in m.arguments -%}
5+
{%- if arg.type.original_name in ("String", "StringName", "NodePath") -%}
6+
object {{ arg.name }}
7+
{%- else -%}
8+
{{ arg.type.cy_type }} {{ arg.name }}
9+
{%- endif -%}
10+
{%- if arg.default_value -%}
11+
={{ arg.default_value.py_value or "None" }}
12+
{%- endif -%}
13+
{%- if not loop.last -%}, {% endif -%}
14+
{%- endfor -%})
15+
{%- endmacro %}
16+
17+
18+
{% macro _render_non_static_method(builtin, m) %}
19+
{#
20+
Note we don't use `cpdef` here since Cython would then add the Python flavour of
21+
those methods to any .pyx that include `builtins.pxd`.
22+
Worst: this would also make the compiler consider all `gdptr_*` symbols in
23+
`builtins.so` are used, hence bloating the final shared library of tons of useless
24+
symbols to resolve at runtime !
25+
#}
26+
def {{ render_method_signature(m) }}:
27+
{% if m.contains_unsuported_types %}
28+
raise NotImplementedError # TODO
29+
{% else %}
30+
{% for arg in m.arguments %}
31+
{% if arg.type.is_variant %}
32+
cdef gd_variant_t __arg{{ loop.index0 }}
33+
__arg{{ loop.index0 }} = ensure_is_gdany_and_borrow_ref({{ arg.name }})
34+
{% elif arg.type.original_name == "String" %}
35+
cdef GDString __arg{{ loop.index0 }}
36+
__arg{{ loop.index0 }} = ensure_is_gdstring({{ arg.name }})
37+
{% elif arg.type.original_name == "StringName" %}
38+
cdef StringName __arg{{ loop.index0 }}
39+
__arg{{ loop.index0 }} = ensure_is_stringname({{ arg.name }})
40+
{% elif arg.type.original_name == "NodePath" %}
41+
cdef NodePath __arg{{ loop.index0 }}
42+
__arg{{ loop.index0 }} = ensure_is_nodepath({{ arg.name }})
43+
{% endif %}
44+
{% endfor %}
45+
{% if m.return_type.is_scalar %}
46+
cdef {{m.return_type.c_type }} ret = gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
47+
{% elif m.return_type.is_builtin %}
48+
# Call to __new__ bypasses __init__ constructor
49+
cdef {{ m.return_type.cy_type }} ret = {{ m.return_type.cy_type }}.__new__({{ m.return_type.cy_type }})
50+
ret._gd_data = gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
51+
{% elif m.return_type.is_object or m.return_type.is_variant %}
52+
cdef {{ m.return_type.c_type }} ret = gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
53+
{% else %}
54+
gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
55+
{% endif %}
56+
&self._gd_data,
57+
{% for arg in m.arguments %}
58+
{% if arg.type.is_scalar %}
59+
{{ arg.name }},
60+
{% elif arg.type.original_name in ("String", "StringName", "NodePath") %}
61+
&__arg{{ loop.index0 }}._gd_data,
62+
{% elif arg.type.is_builtin %}
63+
&{{ arg.name }}._gd_data,
64+
{% elif arg.type.is_object %}
65+
&{{ arg.name }}._gd_ptr,
66+
{% elif arg.type.is_variant %}
67+
&__arg{{ loop.index0 }},
68+
{% else %}
69+
<crash !!!!> # {{ arg.type }} Unsupported type, crash the compilation !
70+
{% endif %}
71+
{% endfor %}
72+
)
73+
{% if not m.return_type.is_nil %}
74+
{% if m.return_type.is_object %}
75+
return BaseGDObject.cast_from_object(ret)
76+
{% elif m.return_type.is_variant %}
77+
# Note the "steal" means we don't need a final `gdapi.gd_variant_del(&ret)`
78+
return gd_variant_steal_into_pyobj(&ret)
79+
{% else %}
80+
return ret
81+
{% endif %}
82+
{% endif %}
83+
{% endif %}
84+
{% endmacro %}
85+
86+
87+
{% macro _render_static_method(builtin, m) %}
88+
@staticmethod
89+
def {{ render_method_signature(m) }}:
90+
{% if m.contains_unsuported_types %}
91+
raise NotImplementedError # TODO
92+
{% else %}
93+
{% for arg in m.arguments %}
94+
{% if arg.type.is_variant %}
95+
cdef gd_variant_t __arg{{ loop.index0 }}
96+
__arg{{ loop.index0 }} = ensure_is_gdany_and_borrow_ref({{ arg.name }})
97+
{% elif arg.type.original_name == "String" %}
98+
cdef GDString __arg{{ loop.index0 }}
99+
__arg{{ loop.index0 }} = ensure_is_gdstring({{ arg.name }})
100+
{% elif arg.type.original_name == "StringName" %}
101+
cdef StringName __arg{{ loop.index0 }}
102+
__arg{{ loop.index0 }} = ensure_is_stringname({{ arg.name }})
103+
{% elif arg.type.original_name == "NodePath" %}
104+
cdef NodePath __arg{{ loop.index0 }}
105+
__arg{{ loop.index0 }} = ensure_is_nodepath({{ arg.name }})
106+
{% endif %}
107+
{% endfor %}
108+
{% if m.return_type.is_scalar %}
109+
cdef {{ m.return_type.cy_type }} ret = gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
110+
{% elif m.return_type.is_builtin %}
111+
# Call to __new__ bypasses __init__ constructor
112+
cdef {{ m.return_type.cy_type }} ret = {{ m.return_type.cy_type }}.__new__({{ m.return_type.cy_type }})
113+
ret._gd_data = gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
114+
{% elif m.return_type.is_object or m.return_type.is_variant %}
115+
cdef {{ m.return_type.c_type }} ret = gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
116+
{% else %}
117+
gdapi.gd_{{ builtin.snake_name }}_meth_{{ m.name }}(
118+
{% endif %}
119+
NULL,
120+
{% for arg in m.arguments %}
121+
{% if arg.type.is_scalar %}
122+
{{ arg.name }},
123+
{% elif arg.type.original_name in ("String", "StringName", "NodePath") %}
124+
&__arg{{ loop.index0 }}._gd_data,
125+
{% elif arg.type.is_builtin %}
126+
&{{ arg.name }}._gd_data,
127+
{% elif arg.type.is_object %}
128+
&{{ arg.name }}._gd_ptr,
129+
{% elif arg.type.is_variant %}
130+
&__arg{{ loop.index0 }},
131+
{% else %}
132+
<crash !!!!> # {{ arg.type }} Unsupported type, crash the compilation !
133+
{% endif %}
134+
{% endfor %}
135+
)
136+
{% if not m.return_type.is_nil %}
137+
{% if m.return_type.is_object %}
138+
return BaseGDObject.cast_from_object(ret)
139+
{% elif m.return_type.is_variant %}
140+
# Note the "steal" means we don't need a final `gdapi.gd_variant_del(&ret)`
141+
return gd_variant_steal_into_pyobj(&ret)
142+
{% else %}
143+
return ret
144+
{% endif %}
145+
{% endif %}
146+
{% endif %}
147+
{% endmacro %}
148+
149+
150+
{% macro render_method(builtin, m) %}
151+
{% if m.is_static %}
152+
{{ _render_static_method(builtin, m) }}
153+
{% else %}
154+
{{ _render_non_static_method(builtin, m) }}
155+
{% endif %}
156+
{% endmacro %}

src/godot/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pyxbuiltins = custom_target(
9292
'builtins_pyx/constructor.pyx.j2',
9393
'builtins_pyx/conversion.pyx.j2',
9494
'builtins_pyx/member.pyx.j2',
95+
'builtins_pyx/method.pyx.j2',
9596
'builtins_pyx/operator.pyx.j2',
9697
),
9798
],

0 commit comments

Comments
 (0)