mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-22 23:08:08 +03:00
Prevent mixin pools from retaining objects
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+91
-19
@@ -32,6 +32,80 @@ logger = logging.getLogger(__name__)
|
||||
__all__ = ['Mixins']
|
||||
|
||||
|
||||
class _WeakObjectsPool:
|
||||
"""Store objects in registration order without extending their lifetime."""
|
||||
def __init__(self):
|
||||
"""Initialize an empty weak object registry."""
|
||||
self._references = {}
|
||||
|
||||
def _removeReference(self, key, reference):
|
||||
"""Remove a weak reference if it is still registered under its object ID."""
|
||||
if self._references.get(key) is reference:
|
||||
self._references.pop(key)
|
||||
|
||||
def append(self, ob):
|
||||
"""Register an object without retaining a strong reference to it."""
|
||||
key = id(ob)
|
||||
current = self._references.get(key)
|
||||
|
||||
if current is not None:
|
||||
if current() is ob:
|
||||
raise ValueError('object is already registered')
|
||||
|
||||
self._references.pop(key)
|
||||
|
||||
reference = weakref.ref(
|
||||
ob,
|
||||
lambda ref, _key=key: self._removeReference(_key, ref),
|
||||
)
|
||||
self._references[key] = reference
|
||||
|
||||
if isinstance(ob, QtCore.QObject):
|
||||
ob.destroyed.connect(
|
||||
lambda *_args, _key=key, _reference=reference: self._removeReference(
|
||||
_key, _reference
|
||||
)
|
||||
)
|
||||
|
||||
def remove(self, ob):
|
||||
"""Remove an object's registration by identity."""
|
||||
key = id(ob)
|
||||
reference = self._references.get(key)
|
||||
|
||||
if reference is None or reference() is not ob:
|
||||
raise ValueError('object is not registered')
|
||||
|
||||
self._references.pop(key)
|
||||
|
||||
def clear(self):
|
||||
"""Remove every registration from the registry."""
|
||||
self._references.clear()
|
||||
|
||||
def prune(self, predicate=None):
|
||||
"""Remove dead objects and live objects rejected by a predicate."""
|
||||
for key, reference in list(self._references.items()):
|
||||
ob = reference()
|
||||
|
||||
if ob is None or (predicate is not None and not predicate(ob)):
|
||||
self._removeReference(key, reference)
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over a stable snapshot of currently live objects."""
|
||||
for key, reference in list(self._references.items()):
|
||||
ob = reference()
|
||||
|
||||
if ob is None:
|
||||
self._removeReference(key, reference)
|
||||
else:
|
||||
yield ob
|
||||
|
||||
def __len__(self):
|
||||
"""Return the number of currently live registrations."""
|
||||
self.prune()
|
||||
|
||||
return len(self._references)
|
||||
|
||||
|
||||
class Mixins:
|
||||
"""Group reusable lifecycle, translation, theme, and Qt context mixins."""
|
||||
@staticmethod
|
||||
@@ -47,7 +121,7 @@ class Mixins:
|
||||
|
||||
class ConnectionAware:
|
||||
"""Represent connection aware."""
|
||||
ObjectsPool = list()
|
||||
ObjectsPool = _WeakObjectsPool()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the ConnectionAware."""
|
||||
@@ -66,7 +140,9 @@ class Mixins:
|
||||
@staticmethod
|
||||
def callConnectedCallback():
|
||||
"""Call connected callback."""
|
||||
for ob in Mixins.ConnectionAware.ObjectsPool:
|
||||
Mixins.ConnectionAware.ObjectsPool.prune(Mixins.qObjectIsValid)
|
||||
|
||||
for ob in list(Mixins.ConnectionAware.ObjectsPool):
|
||||
assert isinstance(ob, Mixins.ConnectionAware)
|
||||
|
||||
ob.connectedCallback()
|
||||
@@ -74,14 +150,16 @@ class Mixins:
|
||||
@staticmethod
|
||||
def callDisconnectedCallback():
|
||||
"""Call disconnected callback."""
|
||||
for ob in Mixins.ConnectionAware.ObjectsPool:
|
||||
Mixins.ConnectionAware.ObjectsPool.prune(Mixins.qObjectIsValid)
|
||||
|
||||
for ob in list(Mixins.ConnectionAware.ObjectsPool):
|
||||
assert isinstance(ob, Mixins.ConnectionAware)
|
||||
|
||||
ob.disconnectedCallback()
|
||||
|
||||
class ThemeAware:
|
||||
"""Represent theme aware."""
|
||||
ObjectsPool = list()
|
||||
ObjectsPool = _WeakObjectsPool()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the ThemeAware."""
|
||||
@@ -96,7 +174,9 @@ class Mixins:
|
||||
@staticmethod
|
||||
def callThemeChangedCallbackUnchecked(theme: str):
|
||||
"""Call theme changed callback unchecked."""
|
||||
for ob in Mixins.ThemeAware.ObjectsPool:
|
||||
Mixins.ThemeAware.ObjectsPool.prune(Mixins.qObjectIsValid)
|
||||
|
||||
for ob in list(Mixins.ThemeAware.ObjectsPool):
|
||||
assert isinstance(ob, Mixins.ThemeAware)
|
||||
|
||||
ob.themeChangedCallback(theme)
|
||||
@@ -123,7 +203,7 @@ class Mixins:
|
||||
|
||||
class CleanupOnExit:
|
||||
"""Represent cleanup on exit."""
|
||||
ObjectsPool = list()
|
||||
ObjectsPool = _WeakObjectsPool()
|
||||
VisitedType = dict()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -141,7 +221,9 @@ class Mixins:
|
||||
@staticmethod
|
||||
def cleanupAll():
|
||||
"""Handle cleanup all for the cleanup on exit."""
|
||||
for ob in Mixins.CleanupOnExit.ObjectsPool:
|
||||
Mixins.CleanupOnExit.ObjectsPool.prune(Mixins.qObjectIsValid)
|
||||
|
||||
for ob in list(Mixins.CleanupOnExit.ObjectsPool):
|
||||
assert isinstance(ob, Mixins.CleanupOnExit)
|
||||
|
||||
if ob.uniqueCleanup:
|
||||
@@ -201,7 +283,7 @@ class Mixins:
|
||||
|
||||
class QTranslatable:
|
||||
"""Represent q translatable."""
|
||||
ObjectsPool = list()
|
||||
ObjectsPool = _WeakObjectsPool()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the QTranslatable."""
|
||||
@@ -212,14 +294,6 @@ class Mixins:
|
||||
|
||||
Mixins.QTranslatable.ObjectsPool.append(self)
|
||||
|
||||
if isinstance(self, QtCore.QObject):
|
||||
selfref = weakref.ref(self)
|
||||
self.destroyed.connect(
|
||||
lambda *_args, _selfref=selfref: Mixins.QTranslatable.unregister(
|
||||
_selfref()
|
||||
)
|
||||
)
|
||||
|
||||
def retranslate(self):
|
||||
"""Refresh translated text for the q translatable."""
|
||||
raise NotImplementedError
|
||||
@@ -235,9 +309,7 @@ class Mixins:
|
||||
@staticmethod
|
||||
def pruneObjectsPool():
|
||||
"""Handle prune objects pool for the q translatable."""
|
||||
Mixins.QTranslatable.ObjectsPool = list(
|
||||
filter(Mixins.qObjectIsValid, Mixins.QTranslatable.ObjectsPool)
|
||||
)
|
||||
Mixins.QTranslatable.ObjectsPool.prune(Mixins.qObjectIsValid)
|
||||
|
||||
@staticmethod
|
||||
def retranslateAll():
|
||||
|
||||
+41
-1
@@ -65,6 +65,28 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
_OpenDialogs = {}
|
||||
|
||||
|
||||
def _releaseOpenDialog(key, *_args):
|
||||
"""Release the temporary strong reference for a finished dialog."""
|
||||
_OpenDialogs.pop(key, None)
|
||||
|
||||
|
||||
def _retainOpenDialog(dialog):
|
||||
"""Keep a shown dialog alive until it finishes or is destroyed."""
|
||||
key = id(dialog)
|
||||
_OpenDialogs[key] = dialog
|
||||
|
||||
if not getattr(dialog, '_furiousOpenDialogLifetimeConnected', False):
|
||||
release = functools.partial(_releaseOpenDialog, key)
|
||||
dialog.finished.connect(release)
|
||||
dialog.destroyed.connect(release)
|
||||
dialog._furiousOpenDialogLifetimeConnected = True
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def moveToCenter(widget, parent=None):
|
||||
"""Move to center."""
|
||||
geometry = widget.frameGeometry()
|
||||
@@ -137,7 +159,14 @@ class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
|
||||
|
||||
def show(self):
|
||||
"""Show and position the app Qt dialog."""
|
||||
super().show()
|
||||
key = _retainOpenDialog(self)
|
||||
|
||||
try:
|
||||
super().show()
|
||||
except Exception:
|
||||
_releaseOpenDialog(key)
|
||||
|
||||
raise
|
||||
|
||||
if PLATFORM == 'Darwin':
|
||||
if self._firstShowCall:
|
||||
@@ -479,6 +508,17 @@ class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
|
||||
|
||||
return self
|
||||
|
||||
def show(self):
|
||||
"""Show the message box and retain it until it finishes."""
|
||||
key = _retainOpenDialog(self)
|
||||
|
||||
try:
|
||||
return super().show()
|
||||
except Exception:
|
||||
_releaseOpenDialog(key)
|
||||
|
||||
raise
|
||||
|
||||
def exec(self):
|
||||
"""Show and execute the app q message box modally."""
|
||||
self.show()
|
||||
|
||||
Reference in New Issue
Block a user