Skip to content

Commit e941405

Browse files
committed
Rework PythonScriptLanguage register/unregister in src/_pythonscript.pyx to fix object leaks
1 parent 739ab86 commit e941405

File tree

1 file changed

+106
-76
lines changed

1 file changed

+106
-76
lines changed

src/_pythonscript.pyx

Lines changed: 106 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ from godot.hazmat.gdextension_interface cimport *
1111
from godot.hazmat.gdapi cimport *
1212
from godot.hazmat.extension_class cimport *
1313
from godot.builtins cimport *
14-
from godot.classes cimport _load_class, _load_singleton
14+
from godot.classes cimport _load_class, _load_singleton, _cleanup_loaded_classes_and_singletons
1515

1616
include "_pythonscript_editor.pxi"
1717
include "_pythonscript_extension_class_language.pxi"
@@ -131,6 +131,11 @@ cdef void _register_pythonscript_classes():
131131
PythonScript._PythonScript__godot_extension_register_class()
132132

133133

134+
cdef void _unregister_pythonscript_classes():
135+
PythonScript._PythonScript__godot_extension_unregister_class()
136+
PythonScriptLanguage._PythonScriptLanguage__godot_extension_unregister_class()
137+
138+
134139
cdef void _customize_config():
135140
import sys
136141
ProjectSettings = _load_singleton("ProjectSettings")
@@ -241,47 +246,104 @@ cdef void _register_pythonscript_language():
241246
cdef StringName gdname_register_script_language
242247
cdef gd_int_t ret
243248

249+
if _pythons_script_language is not None:
250+
return
251+
252+
# Create the instance of `PythonScriptLanguage` class...
253+
254+
_pythons_script_language = PythonScriptLanguage.__new__(PythonScriptLanguage)
255+
256+
# ... and actually register Python into Godot \o/
257+
258+
gdname_engine = StringName("Engine")
259+
gdname_register_script_language = StringName("register_script_language")
260+
engine = pythonscript_gdextension.global_get_singleton(&gdname_engine._gd_data)
261+
if engine == NULL:
262+
print("Failed to register Python into Godot: failed to retreive `Engine` singleton", flush=True)
263+
return
264+
265+
bind = pythonscript_gdextension.classdb_get_method_bind(
266+
&gdname_engine._gd_data,
267+
&gdname_register_script_language._gd_data,
268+
1850254898,
269+
)
270+
if bind == NULL:
271+
_pythons_script_language = None
272+
print("Failed to register Python into Godot: failed to retreive `Engine::register_script_language`", flush=True)
273+
return
274+
275+
args = [&_pythons_script_language._gd_ptr]
276+
pythonscript_gdextension.object_method_bind_ptrcall(
277+
bind,
278+
engine,
279+
args,
280+
&ret,
281+
)
282+
if ret != Error.OK:
283+
_pythons_script_language = None
284+
print("Failed to register Python into Godot: `Engine::register_script_language` returned error {ret}", flush=True)
285+
return
286+
287+
288+
cdef void _unregister_pythonscript_language():
289+
global _pythons_script_language
290+
cdef StringName gdname_engine
291+
cdef StringName gdname_unregister_script_language
292+
cdef GDExtensionObjectPtr engine
293+
cdef GDExtensionMethodBindPtr bind
294+
cdef GDExtensionConstTypePtr[1] args
295+
cdef gd_int_t ret
296+
244297
if _pythons_script_language is None:
298+
return
299+
300+
# 1) Unregister the languagee
301+
302+
gdname_engine = StringName("Engine")
303+
gdname_unregister_script_language = StringName("unregister_script_language")
304+
engine = pythonscript_gdextension.global_get_singleton(&gdname_engine._gd_data)
305+
if engine == NULL:
306+
print("Failed to unregister Python from Godot: failed to retreive `Engine` singleton", flush=True)
307+
return
245308

246-
# 2) Create the instance of `PythonScriptLanguage` class...
247-
248-
_pythons_script_language = PythonScriptLanguage.__new__(PythonScriptLanguage)
249-
250-
# 3) ... and actually register Python into Godot \o/
251-
252-
gdname_engine = StringName("Engine")
253-
gdname_register_script_language = StringName("register_script_language")
254-
singleton = pythonscript_gdextension.global_get_singleton(&gdname_engine._gd_data)
255-
if singleton == NULL:
256-
print("Failed to register Python into Godot: failed to retreive `Engine` singleton", flush=True)
257-
return
258-
259-
bind = pythonscript_gdextension.classdb_get_method_bind(
260-
&gdname_engine._gd_data,
261-
&gdname_register_script_language._gd_data,
262-
1850254898,
263-
)
264-
if bind == NULL:
265-
_pythons_script_language = None
266-
print("Failed to register Python into Godot: failed to retreive `Engine::register_script_language`", flush=True)
267-
return
268-
269-
args = [&_pythons_script_language._gd_ptr]
270-
pythonscript_gdextension.object_method_bind_ptrcall(
271-
bind,
272-
singleton,
273-
args,
274-
&ret,
275-
)
276-
if ret != 0: # TODO: use `Error.Ok` here
277-
_pythons_script_language = None
278-
print("Failed to register Python into Godot: `Engine::register_script_language` returned error {ret}", flush=True)
279-
return
309+
bind = pythonscript_gdextension.classdb_get_method_bind(
310+
&gdname_engine._gd_data,
311+
&gdname_unregister_script_language._gd_data,
312+
1850254898,
313+
)
314+
if bind == NULL:
315+
print("Failed to unregister Python from Godot: failed to retreive `Engine::unregister_script_language`", flush=True)
316+
return
317+
318+
args = [&_pythons_script_language._gd_ptr]
319+
pythonscript_gdextension.object_method_bind_ptrcall(
320+
bind,
321+
engine,
322+
args,
323+
&ret,
324+
)
325+
if ret != Error.OK:
326+
print(f"Failed to unregister Python from Godot: `Engine::unregister_script_language` returned error {ret}", flush=True)
327+
return
328+
329+
# 2) Free the language instance
330+
331+
pythonscript_gdextension.object_destroy(
332+
_pythons_script_language._gd_ptr,
333+
)
334+
335+
# At this point `_pythons_script_language._gd_ptr` is no longer a valid pointer
336+
# however this is fine since we are clearing the reference to it right now (so
337+
# nobody is going to use it anymore) and `_gd_ptr` field is simply ignored during
338+
# garbage collection.
339+
340+
# 3) Finally clear reference on the language instance Python bindings
341+
342+
_pythons_script_language = None
280343

281344

282345
cdef void _print_banner():
283346
import sys
284-
ProjectSettings = _load_singleton("ProjectSettings")
285347

286348
if _setup_config_entry("python/print_startup_info", True):
287349
from godot._version import __version__ as pythonscript_version
@@ -317,52 +379,20 @@ cdef public void _pythonscript_deinitialize(int p_level) noexcept with gil:
317379
# That will continue until `godot_gdnative_terminate` is called (which is
318380
# responsible for the actual teardown of the interpreter).
319381

320-
cdef GDExtensionObjectPtr singleton
321-
cdef GDExtensionMethodBindPtr bind
322-
cdef GDExtensionConstTypePtr[1] args
323-
cdef StringName gdname_engine
324-
cdef StringName gdname_register_script_language
325-
cdef gd_int_t ret
326-
327382
if p_level >= GDEXTENSION_INITIALIZATION_SCENE:
328383
_deinitialize_callback_hook(p_level)
329384

330385
if p_level == GDEXTENSION_INITIALIZATION_SCENE and _pythons_script_language is not None:
331-
332-
# Unregister Python from Godot
333-
334-
gdname_engine = StringName("Engine")
335-
gdname_unregister_script_language = StringName("unregister_script_language")
336-
singleton = pythonscript_gdextension.global_get_singleton(&gdname_engine._gd_data)
337-
if singleton == NULL:
338-
print("Failed to unregister Python from Godot: failed to retreive `Engine` singleton", flush=True)
339-
return
340-
341-
bind = pythonscript_gdextension.classdb_get_method_bind(
342-
&gdname_engine._gd_data,
343-
&gdname_unregister_script_language._gd_data,
344-
1850254898,
345-
)
346-
if bind == NULL:
347-
print("Failed to unregister Python from Godot: failed to retreive `Engine::unregister_script_language`", flush=True)
348-
return
349-
350-
args = [&_pythons_script_language._gd_ptr]
351-
pythonscript_gdextension.object_method_bind_ptrcall(
352-
bind,
353-
singleton,
354-
args,
355-
&ret,
356-
)
357-
if ret != 0: # TODO: use `Error.Ok` here
358-
print(f"Failed to unregister Python from Godot: `Engine::unregister_script_language` returned error {ret}", flush=True)
359-
return
360-
361-
_pythons_script_language = None
386+
_unregister_pythonscript_language()
362387

363388
if p_level == GDEXTENSION_INITIALIZATION_SERVERS:
364389

365390
# Unregister Python classes from Godot's classDB
366391

367-
PythonScript._PythonScript__godot_extension_unregister_class()
368-
PythonScriptLanguage._PythonScriptLanguage__godot_extension_unregister_class()
392+
_unregister_pythonscript_classes()
393+
_cleanup_loaded_classes_and_singletons()
394+
395+
# TODO: needed ?
396+
# gc_protector = _get_extension_gc_protector()
397+
# print('!!!!!!!! gc_protector', repr(gc_protector))
398+
# gc_protector.clear()

0 commit comments

Comments
 (0)