gdnative_python/pythonscript/pandemonium/_hazmat/internal.pyx

87 lines
3.3 KiB
Cython

import threading
from pandemonium.bindings cimport Object
cdef bint __pythonscript_verbose = False
cdef class ModExposedClass:
cdef object kls
cdef int refcount
def __init__(self, object kls):
self.kls = kls
self.refcount = 1
# /!\ Those containers are strictly private /!\
# They contain class objects that are referenced from Pandemonium without refcounting,
# so droping an item from there will likely cause a segfault !
cdef dict __modules_with_exposed_class = {}
cdef list __all_exposed_classes = []
cdef object __exposed_classes_lock = threading.Lock()
cdef object get_exposed_class(str module_name):
with __exposed_classes_lock:
try:
return (<ModExposedClass>__modules_with_exposed_class[module_name]).kls
except KeyError:
return None
cdef void set_exposed_class(object cls):
cdef ModExposedClass mod
cdef str modname = cls.__module__
# Use a threadlock to avoid data races in case pandemonium loads/unloads scripts in multiple threads
with __exposed_classes_lock:
# We must keep track of reference counts for the module when reloading a script,
# pandemonium calls pythonscript_script_init BEFORE pythonscript_script_finish
# this happens because Pandemonium can make multiple PluginScript instances for the same resource.
# Pandemonium calls
try:
mod = __modules_with_exposed_class[modname]
except KeyError:
__modules_with_exposed_class[modname] = ModExposedClass(cls)
else:
# When reloading a script, Pandemonium calls `pythonscript_script_init` BEFORE
# `pythonscript_script_finish`. Hence we drop replace the old class
# here but have to increase the refcount so
mod.kls = cls
mod.refcount += 1
# Sometimes Pandemonium fails to reload a script, and when this happens we end
# up with a stale PyObject* for the class, which is then garbage collected by Python
# so next time a script is instantiated from Pandemonium we end up with a sefault :(
# To avoid this we keep reference forever to all the classes.
# TODO: This may be troublesome when running the Pandemonium editor given the classes are
# reloaded each time they are modified, hence leading to a small memory leak...
__all_exposed_classes.append(cls)
cdef void destroy_exposed_class(object cls):
cdef ModExposedClass mod
cdef str modname = cls.__module__
# Use a threadlock to avoid data races in case pandemonium loads/unloads scripts in multiple threads
with __exposed_classes_lock:
try:
mod = __modules_with_exposed_class[modname]
except KeyError:
print(f'Error: class module is already destroyed: {modname}')
else:
if mod.refcount == 1:
del __modules_with_exposed_class[modname]
# Not safe to ever get rid of all references...
# see: https://github.com/touilleMan/pandemonium-python/issues/170
# and: https://github.com/pandemoniumengine/pandemonium/issues/10946
# sometimes script reloading craps out leaving dangling references
# __all_exposed_classes.remove(modname, cls)
else:
mod.refcount -= 1