diff --git a/src/_pythonscript.pyx b/src/_pythonscript.pyx index 44438fc8..fbd76d92 100644 --- a/src/_pythonscript.pyx +++ b/src/_pythonscript.pyx @@ -11,7 +11,7 @@ from godot.hazmat.gdextension_interface cimport * from godot.hazmat.gdapi cimport * from godot.hazmat.extension_class cimport * from godot.builtins cimport * -from godot.classes cimport _load_class, _load_singleton +from godot.classes cimport _load_class, _load_singleton, _cleanup_loaded_classes_and_singletons include "_pythonscript_editor.pxi" include "_pythonscript_extension_class_language.pxi" @@ -131,6 +131,11 @@ cdef void _register_pythonscript_classes(): PythonScript._PythonScript__godot_extension_register_class() +cdef void _unregister_pythonscript_classes(): + PythonScript._PythonScript__godot_extension_unregister_class() + PythonScriptLanguage._PythonScriptLanguage__godot_extension_unregister_class() + + cdef void _customize_config(): import sys ProjectSettings = _load_singleton("ProjectSettings") @@ -279,9 +284,47 @@ cdef void _register_pythonscript_language(): return +cdef void _unregister_pythonscript_language(): + global _pythons_script_language + cdef StringName gdname_engine + cdef StringName gdname_unregister_script_language + cdef GDExtensionObjectPtr singleton + cdef GDExtensionMethodBindPtr bind + cdef GDExtensionConstTypePtr[1] args + cdef gd_int_t ret + + gdname_engine = StringName("Engine") + gdname_unregister_script_language = StringName("unregister_script_language") + singleton = pythonscript_gdextension.global_get_singleton(&gdname_engine._gd_data) + if singleton == NULL: + print("Failed to unregister Python from Godot: failed to retreive `Engine` singleton", flush=True) + return + + bind = pythonscript_gdextension.classdb_get_method_bind( + &gdname_engine._gd_data, + &gdname_unregister_script_language._gd_data, + 1850254898, + ) + if bind == NULL: + print("Failed to unregister Python from Godot: failed to retreive `Engine::unregister_script_language`", flush=True) + return + + args = [&_pythons_script_language._gd_ptr] + pythonscript_gdextension.object_method_bind_ptrcall( + bind, + singleton, + args, + &ret, + ) + if ret != 0: # TODO: use `Error.Ok` here + print(f"Failed to unregister Python from Godot: `Engine::unregister_script_language` returned error {ret}", flush=True) + return + + _pythons_script_language = None + + cdef void _print_banner(): import sys - ProjectSettings = _load_singleton("ProjectSettings") if _setup_config_entry("python/print_startup_info", True): from godot._version import __version__ as pythonscript_version @@ -293,15 +336,18 @@ cdef void _print_banner(): cdef public void _pythonscript_initialize(int p_level) noexcept with gil: + # TODO: re-enable me when the GD objects memory leak hunt is over if p_level == GDEXTENSION_INITIALIZATION_SERVERS: _register_pythonscript_classes() + pass # Language registration must be done at `GDEXTENSION_INITIALIZATION_SERVERS` level which # is too early to have have everything we need for (e.g. `ClassDB` & `OS` singletons). # So we have to do another init step at `GDEXTENSION_INITIALIZATION_SCENE` level. if p_level == GDEXTENSION_INITIALIZATION_SCENE: _customize_config() - _register_pythonscript_language() + # TODO: re-enable me when the GD objects memory leak hunt is over + # _register_pythonscript_language() # Finally proudly print banner ;-) _print_banner() @@ -317,52 +363,19 @@ cdef public void _pythonscript_deinitialize(int p_level) noexcept with gil: # That will continue until `godot_gdnative_terminate` is called (which is # responsible for the actual teardown of the interpreter). - cdef GDExtensionObjectPtr singleton - cdef GDExtensionMethodBindPtr bind - cdef GDExtensionConstTypePtr[1] args - cdef StringName gdname_engine - cdef StringName gdname_register_script_language - cdef gd_int_t ret - if p_level >= GDEXTENSION_INITIALIZATION_SCENE: _deinitialize_callback_hook(p_level) if p_level == GDEXTENSION_INITIALIZATION_SCENE and _pythons_script_language is not None: - - # Unregister Python from Godot - - gdname_engine = StringName("Engine") - gdname_unregister_script_language = StringName("unregister_script_language") - singleton = pythonscript_gdextension.global_get_singleton(&gdname_engine._gd_data) - if singleton == NULL: - print("Failed to unregister Python from Godot: failed to retreive `Engine` singleton", flush=True) - return - - bind = pythonscript_gdextension.classdb_get_method_bind( - &gdname_engine._gd_data, - &gdname_unregister_script_language._gd_data, - 1850254898, - ) - if bind == NULL: - print("Failed to unregister Python from Godot: failed to retreive `Engine::unregister_script_language`", flush=True) - return - - args = [&_pythons_script_language._gd_ptr] - pythonscript_gdextension.object_method_bind_ptrcall( - bind, - singleton, - args, - &ret, - ) - if ret != 0: # TODO: use `Error.Ok` here - print(f"Failed to unregister Python from Godot: `Engine::unregister_script_language` returned error {ret}", flush=True) - return - - _pythons_script_language = None + # _unregister_pythonscript_language() + pass if p_level == GDEXTENSION_INITIALIZATION_SERVERS: # Unregister Python classes from Godot's classDB - PythonScript._PythonScript__godot_extension_unregister_class() - PythonScriptLanguage._PythonScriptLanguage__godot_extension_unregister_class() + _unregister_pythonscript_classes() + _cleanup_loaded_classes_and_singletons() + gc_protector = _get_extension_gc_protector() + print('!!!!!!!! gc_protector', repr(gc_protector)) + gc_protector.clear() diff --git a/src/godot/builtins_pyx/conversion.pyx.j2 b/src/godot/builtins_pyx/conversion.pyx.j2 index d5cd9162..7f8e0be1 100644 --- a/src/godot/builtins_pyx/conversion.pyx.j2 +++ b/src/godot/builtins_pyx/conversion.pyx.j2 @@ -95,6 +95,7 @@ cdef object gd_variant_steal_into_pyobj(const gd_variant_t *gdvar): cdef inline object _gd_variant_steal_into_pyobj_{{ builtin.cy_type }}(const gd_variant_t *gdvar): cdef {{ builtin.cy_type }} ret = {{ builtin.cy_type }}.__new__({{ builtin.cy_type }}) ret._gd_data = {{ builtin.c_name_prefix }}_from_variant(gdvar) + gd_variant_del(gdvar) return ret {% endfor %} @@ -111,6 +112,7 @@ cdef object gd_variant_copy_into_pyobj(const gd_variant_t *gdvar): ######################################################################### +# TODO rename given stealing is not possible with GDExtension C++ orientated API cdef bint gd_variant_steal_from_pyobj(object pyobj, gd_variant_t *gdvar): if pyobj is None: pythonscript_gdextension.variant_new_nil(gdvar) @@ -138,5 +140,6 @@ cdef bint gd_variant_steal_from_pyobj(object pyobj, gd_variant_t *gdvar): cdef inline void _gd_variant_steal_from_pyobj_pystr(object pyobj, gd_variant_t *gdvar): cdef gd_string_t gdstr = gd_string_from_unchecked_pystr(pyobj) gdvar[0] = gd_string_into_variant(&gdstr) + gd_string_del(&gdstr) # into conversion steals the ownership, so don't need to call `gd_string_del(gdstr)` {% endmacro %} diff --git a/src/godot/classes.pxd.j2 b/src/godot/classes.pxd.j2 index 551a3a4e..ac48e89a 100644 --- a/src/godot/classes.pxd.j2 +++ b/src/godot/classes.pxd.j2 @@ -83,4 +83,5 @@ cdef class {{ cls.cy_type }}({{ cls.inherits.cy_type if cls.inherits else "" }}) cdef object _load_class(str name) cdef object _load_singleton(str name) +cdef void _cleanup_loaded_classes_and_singletons() cdef object _object_call(gd_object_t obj, str meth, args) diff --git a/src/godot/classes.pyx.j2 b/src/godot/classes.pyx.j2 index feb1776f..3f97d11d 100644 --- a/src/godot/classes.pyx.j2 +++ b/src/godot/classes.pyx.j2 @@ -88,6 +88,11 @@ cdef object _loaded_singletons = {} cdef object _loaded_classes = {} +cdef void _cleanup_loaded_classes_and_singletons(): + _loaded_singletons.clear() + _loaded_classes.clear() + + cdef object _load_singleton(str name): try: return _loaded_singletons[name] @@ -186,6 +191,7 @@ cdef object _load_class(str name): cdef object _object_call(GDExtensionObjectPtr obj, str meth, args): + cdef object pyret cdef gd_variant_t ret cdef GDExtensionCallError call_error @@ -206,6 +212,8 @@ cdef object _object_call(GDExtensionObjectPtr obj, str meth, args): # TODO: provide a helper for string name from Python str creation cdef gd_string_name_t meth_gdstrname = gd_string_name_from_unchecked_pystr(meth) variant_args[0] = gd_string_name_into_variant(&meth_gdstrname) + gd_string_name_del(&meth_gdstrname) + # TODO: rename ! # Into conversion steals the owneship, so no need to delete meth_gdstrname for i, arg in enumerate(args, 1): @@ -222,10 +230,12 @@ cdef object _object_call(GDExtensionObjectPtr obj, str meth, args): &ret, &call_error, ) - gd_variant_del(&variant_args[0]) # Only param we created without stealing ownership + for i in range(args_with_meth_len): + gd_variant_del(&variant_args[i]) + # gd_variant_del(&variant_args[0]) # Only param we created without stealing ownership if call_error.error == GDEXTENSION_CALL_OK: - return gd_variant_steal_into_pyobj(&ret) # No need to destroy ret given the conversion has stolen ownership on data ! + return gd_variant_steal_into_pyobj(&ret) # TODO: improve ret error raised exception type ? elif call_error.error == GDEXTENSION_CALL_ERROR_INVALID_METHOD: