Skip to content

Commit 1d55499

Browse files
committed
Fix uneeded use of destructor after converting builtins from/into pyobject/variant
1 parent 6edcc9e commit 1d55499

File tree

4 files changed

+32
-27
lines changed

4 files changed

+32
-27
lines changed

src/godot/_lang_resource_format_loader.pxi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ cdef class PythonResourceFormatLoader:
160160
script._py_cls = klass
161161
script._script_instance_info = generate_instance_info()
162162
# script._set_source_code(source_code.into_gd_data())
163-
# # `into_gd_data()` steal the underlying Godot string, so `source_code`
163+
# # `into_gd_data()` steals the underlying Godot string, so `source_code`
164164
# # ends up containing nothing and we'd rather destroy it early to avoid
165165
# # confusions.
166166
# del source_code

src/godot/builtins_pxd/method.pxd.j2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ cdef inline {{ render_method_signature(m) }}:
8282
{% if m.return_type.is_object %}
8383
return BaseGDObject.cast_from_object(ret)
8484
{% elif m.return_type.is_variant %}
85-
# Note the "steal" means we don't need a final `gdapi.gd_variant_del(&ret)`
85+
# Note `gd_variant_steal_into_pyobj(&ret)` calls `ret`'s destructor
8686
return gd_variant_steal_into_pyobj(&ret)
8787
{% else %}
8888
return ret
@@ -147,7 +147,7 @@ cdef inline {{ render_method_signature(m) }}:
147147
{% if m.return_type.is_object %}
148148
return BaseGDObject.cast_from_object(ret)
149149
{% elif m.return_type.is_variant %}
150-
# Note the "steal" means we don't need a final `gdapi.gd_variant_del(&ret)`
150+
# Note `gd_variant_steal_into_pyobj(&ret)` calls `ret`'s destructor
151151
return gd_variant_steal_into_pyobj(&ret)
152152
{% else %}
153153
return ret

src/godot/builtins_pyx/conversion.pyx.j2

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ cdef gdextension_interface.GDExtensionVariantType py_to_gd_type(object pytype):
6666
#########################################################################
6767

6868

69+
# Consume the variant (i.e. the caller shouldn't call destructor on it) and
70+
# convert in into a proper builtin (e.g. `int`, `Vector2`, `StringName` etc.).
6971
cdef object gd_variant_steal_into_pyobj(gd_variant_t *gdvar):
7072
cdef gdextension_interface.GDExtensionVariantType gdtype = gdptrs.gdptr_variant_get_type(gdvar)
7173
if gdtype == gdextension_interface.GDEXTENSION_VARIANT_TYPE_OBJECT:
@@ -96,12 +98,8 @@ cdef object gd_variant_steal_into_pyobj(gd_variant_t *gdvar):
9698
cdef inline object _gd_variant_steal_into_pyobj_{{ builtin.cy_type }}(gd_variant_t *gdvar):
9799
cdef {{ builtin.cy_type }} ret = {{ builtin.cy_type }}.__new__({{ builtin.cy_type }})
98100
ret._gd_data = gdapi.gd_{{ builtin.snake_name }}_from_variant(gdvar)
99-
{% if builtin.is_stack_only %}
100-
# Skip `gdapi.gd_variant_del(gdvar)` since this variant contains a builtin
101-
# that doesn't have destructor.
102-
{% else %}
103-
gdapi.gd_variant_del(gdvar)
104-
{% endif %}
101+
# Note `gdapi.gd_{{ builtin.snake_name }}_from_variant(gdvar)` has already
102+
# called `gdvar`'s destructor.
105103
return ret
106104
{% endfor %}
107105

@@ -117,7 +115,17 @@ cdef object gd_variant_copy_into_pyobj(const gd_variant_t *gdvar):
117115
#########################################################################
118116

119117

120-
# TODO rename given stealing is not possible with GDExtension C++ orientated API
118+
# Consuming the Python object is a bit peculiar here: since a Python object is
119+
# refcounted, there is no guarantee we are the only one referencing it.
120+
# So this function "consumes" the Python object by:
121+
# - Doing nothing for Python scalar types such as `int` or `str`
122+
# - Doing nothing for builtin types that don't have a destructor (e.g. `Vector2`)
123+
# - For builtin types with a destructor (e.g. `StringName`), the destructor is called.
124+
# The trick is that the destructor makes sure the object is still valid once destructed
125+
# (typically freed pointer are set to NULL, and all code accessing pointers first
126+
# does a null pointer check).
127+
# Hence we end up with a Python object containing an empty (but valid !) Godot builtin
128+
# object that will be destroyed a second time once the Python object refcount reaches zero.
121129
cdef bint gd_variant_steal_from_pyobj(object pyobj, gd_variant_t *gdvar):
122130
if pyobj is None:
123131
gdptrs.gdptr_variant_new_nil(gdvar)
@@ -143,6 +151,5 @@ cdef bint gd_variant_steal_from_pyobj(object pyobj, gd_variant_t *gdvar):
143151
cdef inline void _gd_variant_steal_from_pyobj_pystr(object pyobj, gd_variant_t *gdvar):
144152
cdef gd_string_t gdstr = gdapi.gd_string_from_unchecked_pystr(pyobj)
145153
gdvar[0] = gdapi.gd_string_into_variant(&gdstr)
146-
gdapi.gd_string_del(&gdstr)
147-
# into conversion steals the ownership, so don't need to call `gdapi.gd_string_del(gdstr)`
154+
# Note `gd_string_into_variant(&gdstr)` has already called `gdstr`'s destructor.
148155
{% endmacro %}

src/godot/classes.pyx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ cdef object _load_class(str name):
113113
cdef StringName gdname_classdb = StringName("ClassDB")
114114
cdef gd_object_t classdb = gdptrs.gdptr_global_get_singleton(&gdname_classdb._gd_data)
115115

116-
gd_name = GDString(name)
116+
gd_name = StringName(name)
117117
parent = spec[0]
118118
items_spec = iter(spec[1:])
119119
if parent:
@@ -179,7 +179,7 @@ cdef object _load_class(str name):
179179
_prop_setter,
180180
_prop_index,
181181
):
182-
gd_prop_name = GDString(prop_name)
182+
gd_prop_name = StringName(prop_name)
183183
# TODO: ptrcall on getter/setter
184184
@property
185185
def _property(self):
@@ -205,7 +205,7 @@ cdef object _load_class(str name):
205205
signal_name,
206206
signal_arguments_count,
207207
):
208-
gd_signal_name = GDString(signal_name)
208+
gd_signal_name = StringName(signal_name)
209209
for _ in range(signal_arguments_count):
210210
_arg_name = next(items_spec)
211211
_arg_type = next(items_spec)
@@ -230,7 +230,7 @@ cdef object _load_class(str name):
230230
_method_return_type,
231231
method_arguments_count,
232232
):
233-
gd_method_name = GDString(method_name)
233+
gd_method_name = StringName(method_name)
234234
for _ in range(method_arguments_count):
235235
_arg_name = next(items_spec)
236236
_arg_type = next(items_spec)
@@ -280,21 +280,20 @@ cdef object _object_call(gd_object_t obj, str meth, list args):
280280
cdef gdextension_interface.GDExtensionMethodBindPtr Object_call = gdptrs.gdptr_classdb_get_method_bind(&gdname_object._gd_data, &gdname_call._gd_data, 3400424181)
281281

282282
cdef gdextension_interface.GDExtensionInt args_with_meth_len = len(args) + 1
283-
if args_with_meth_len > 9:
283+
# Currently the worst method takes 14 arguments, so this hack should be enough...
284+
if args_with_meth_len > 15:
284285
# TODO: handle this
285-
gdptrs.gdptr_print_error("more than 8 params is not supported !", "_object_call", "", 0, False)
286+
gdptrs.gdptr_print_error("Calling with more than 14 parameters is not supported (wtf are you calling ? :/)", "_object_call", "", 0, False)
286287
return None
287-
cdef gd_variant_t[9] variant_args
288-
cdef (gd_variant_t*)[9] variant_args_ptrs
288+
cdef gd_variant_t[15] variant_args
289+
cdef (gd_variant_t*)[15] variant_args_ptrs
289290
for i in range(args_with_meth_len):
290291
variant_args_ptrs[i] = &variant_args[i]
291292

292293
# TODO: provide a helper for string name from Python str creation
293294
cdef gd_string_name_t meth_gdstrname = gdapi.gd_string_name_from_unchecked_pystr(meth)
294295
variant_args[0] = gdapi.gd_string_name_into_variant(&meth_gdstrname)
295-
gdapi.gd_string_name_del(&meth_gdstrname)
296-
# TODO: rename !
297-
# Into conversion steals the owneship, so no need to delete meth_gdstrname
296+
# Note `gd_string_name_into_variant(&meth_gdstrname)` already calls `meth_gdstrname`'s destructor
298297

299298
for i, arg in enumerate(args, 1):
300299
variant_args[i] = ensure_is_gdany_and_borrow_ref(arg)
@@ -309,11 +308,10 @@ cdef object _object_call(gd_object_t obj, str meth, list args):
309308
&ret,
310309
&call_error,
311310
)
312-
for i in range(args_with_meth_len):
313-
gdapi.gd_variant_del(&variant_args[i])
314-
# gdapi.gd_variant_del(&variant_args[0]) # Only param we created without stealing ownership
311+
# In Godot the callee is responsible to destroy the provided parameters.
312+
# Hence we don't have to call `gd_variant_del()` on `variant_args`.
315313
if call_error.error == gdextension_interface.GDEXTENSION_CALL_OK:
316-
# No need to destroy ret given the conversion has stolen ownership on data !
314+
# Note `gd_variant_steal_into_pyobj(&ret)` already calls `ret`'s destructor
317315
return gd_variant_steal_into_pyobj(&ret)
318316

319317
# TODO: improve ret error raised exception type ?

0 commit comments

Comments
 (0)