Harden worker cleanup + reduce cover-loading churn
- Library/metadata workers: delete the worker on its own thread via worker.finished -> worker.deleteLater (bound) instead of letting Python GC delete it on the GUI thread after untracking. - Cover templates: compute the transparent-window scan outside the lock so a slow first scan never holds the lock and stalls other cover threads. - Store: larger cover/date batches (500/400/300) so the store loads 3449 archives with far fewer QThread + ThreadPoolExecutor creations.
This commit is contained in:
+24
-22
@@ -797,28 +797,30 @@ def _transparent_rect(img):
|
|||||||
def _game_template():
|
def _game_template():
|
||||||
"""Load the PSP game cover template; return ``(template, window)``."""
|
"""Load the PSP game cover template; return ``(template, window)``."""
|
||||||
global _game_template_cache
|
global _game_template_cache
|
||||||
if _game_template_cache is None:
|
if _game_template_cache is not None:
|
||||||
with _game_template_lock:
|
return _game_template_cache
|
||||||
if _game_template_cache is None:
|
try:
|
||||||
try:
|
from PySide6.QtGui import QImage
|
||||||
from PySide6.QtGui import QImage
|
except Exception: # noqa: BLE001 - headless API may lack PySide6
|
||||||
except Exception: # noqa: BLE001 - headless API may lack PySide6
|
_game_template_cache = (None, (0, 0, 0, 0))
|
||||||
_game_template_cache = (None, (0, 0, 0, 0))
|
return _game_template_cache
|
||||||
return _game_template_cache
|
img = QImage()
|
||||||
img = QImage()
|
for path in (
|
||||||
for path in (
|
config.COVERS_DIR / "UMD_GAME_COVER_TEMPLATE.png",
|
||||||
config.COVERS_DIR / "UMD_GAME_COVER_TEMPLATE.png",
|
config.DATA_DIR / "covers" / "UMD_GAME_COVER_TEMPLATE.png",
|
||||||
config.DATA_DIR / "covers" / "UMD_GAME_COVER_TEMPLATE.png",
|
):
|
||||||
):
|
if path.exists():
|
||||||
if path.exists():
|
img.load(str(path))
|
||||||
img.load(str(path))
|
if not img.isNull():
|
||||||
if not img.isNull():
|
break
|
||||||
break
|
img = img.convertToFormat(QImage.Format_ARGB32) if not img.isNull() else None
|
||||||
img = img.convertToFormat(QImage.Format_ARGB32) if not img.isNull() else None
|
entry = (
|
||||||
_game_template_cache = (
|
img,
|
||||||
img,
|
_transparent_rect(img) if img is not None and not img.isNull() else (0, 0, 0, 0),
|
||||||
_transparent_rect(img) if img is not None and not img.isNull() else (0, 0, 0, 0),
|
)
|
||||||
)
|
with _game_template_lock:
|
||||||
|
if _game_template_cache is None:
|
||||||
|
_game_template_cache = entry
|
||||||
return _game_template_cache
|
return _game_template_cache
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+24
-20
@@ -172,20 +172,22 @@ _template_lock = threading.Lock()
|
|||||||
def _umd_template():
|
def _umd_template():
|
||||||
"""Return ``(template QImage, transparent window rect)`` — loaded once."""
|
"""Return ``(template QImage, transparent window rect)`` — loaded once."""
|
||||||
global _template_cache
|
global _template_cache
|
||||||
if _template_cache is None:
|
if _template_cache is not None:
|
||||||
with _template_lock:
|
return _template_cache
|
||||||
if _template_cache is None:
|
img = QImage()
|
||||||
img = QImage()
|
for path in (
|
||||||
for path in (
|
config.COVERS_DIR / "UMD_COVER_TEMPLATE.png",
|
||||||
config.COVERS_DIR / "UMD_COVER_TEMPLATE.png",
|
config.DATA_DIR / "covers" / "UMD_COVER_TEMPLATE.png",
|
||||||
config.DATA_DIR / "covers" / "UMD_COVER_TEMPLATE.png",
|
):
|
||||||
):
|
if path.exists():
|
||||||
if path.exists():
|
img.load(str(path))
|
||||||
img.load(str(path))
|
if not img.isNull():
|
||||||
if not img.isNull():
|
break
|
||||||
break
|
img = img.convertToFormat(QImage.Format_ARGB32)
|
||||||
img = img.convertToFormat(QImage.Format_ARGB32)
|
entry = (img, _transparent_window(img))
|
||||||
_template_cache = (img, _transparent_window(img))
|
with _template_lock:
|
||||||
|
if _template_cache is None:
|
||||||
|
_template_cache = entry
|
||||||
return _template_cache
|
return _template_cache
|
||||||
|
|
||||||
|
|
||||||
@@ -2285,11 +2287,11 @@ class StorePage(QWidget):
|
|||||||
return (0, -date_int, -mtime)
|
return (0, -date_int, -mtime)
|
||||||
return (1, 0, -mtime)
|
return (1, 0, -mtime)
|
||||||
|
|
||||||
CHUNK = 150 # items filled per timer tick
|
CHUNK = 200 # items filled per timer tick
|
||||||
COVER_BATCH = 200 # covers fetched for the initially-visible area
|
COVER_BATCH = 500 # covers fetched for the initially-visible area
|
||||||
COVER_SCROLL_BATCH = 100
|
COVER_SCROLL_BATCH = 400
|
||||||
RECENT_LIMIT = 300 # default: show only the newest N store items
|
RECENT_LIMIT = 300 # default: show only the newest N store items
|
||||||
DATE_BATCH = 150 # release-date metadata fetched per batch
|
DATE_BATCH = 300 # release-date metadata fetched per batch
|
||||||
|
|
||||||
def _populate(self, items, show_count):
|
def _populate(self, items, show_count):
|
||||||
self._generation += 1
|
self._generation += 1
|
||||||
@@ -4876,7 +4878,9 @@ class MainWindow(QMainWindow):
|
|||||||
thread._worker = worker
|
thread._worker = worker
|
||||||
thread.started.connect(worker.run)
|
thread.started.connect(worker.run)
|
||||||
worker.result.connect(self._on_library_loaded)
|
worker.result.connect(self._on_library_loaded)
|
||||||
|
worker.finished.connect(worker.deleteLater)
|
||||||
worker.finished.connect(thread.quit)
|
worker.finished.connect(thread.quit)
|
||||||
|
thread.finished.connect(thread.deleteLater)
|
||||||
thread.finished.connect(self._forget_library_thread)
|
thread.finished.connect(self._forget_library_thread)
|
||||||
self._library_threads.append(thread)
|
self._library_threads.append(thread)
|
||||||
self._library_workers.append(worker)
|
self._library_workers.append(worker)
|
||||||
@@ -4898,7 +4902,6 @@ class MainWindow(QMainWindow):
|
|||||||
self._library_threads.remove(thread)
|
self._library_threads.remove(thread)
|
||||||
if worker is not None and worker in self._library_workers:
|
if worker is not None and worker in self._library_workers:
|
||||||
self._library_workers.remove(worker)
|
self._library_workers.remove(worker)
|
||||||
thread.deleteLater()
|
|
||||||
|
|
||||||
def _apply_library_filter(self):
|
def _apply_library_filter(self):
|
||||||
if not hasattr(self, "_library_cache"):
|
if not hasattr(self, "_library_cache"):
|
||||||
@@ -5034,7 +5037,9 @@ class MainWindow(QMainWindow):
|
|||||||
thread._worker = worker
|
thread._worker = worker
|
||||||
thread.started.connect(worker.run)
|
thread.started.connect(worker.run)
|
||||||
worker.result.connect(self._on_metadata)
|
worker.result.connect(self._on_metadata)
|
||||||
|
worker.finished.connect(worker.deleteLater)
|
||||||
worker.finished.connect(thread.quit)
|
worker.finished.connect(thread.quit)
|
||||||
|
thread.finished.connect(thread.deleteLater)
|
||||||
thread.finished.connect(self._forget_meta_thread)
|
thread.finished.connect(self._forget_meta_thread)
|
||||||
self._meta_threads.append(thread)
|
self._meta_threads.append(thread)
|
||||||
self._meta_workers.append(worker)
|
self._meta_workers.append(worker)
|
||||||
@@ -5047,7 +5052,6 @@ class MainWindow(QMainWindow):
|
|||||||
self._meta_threads.remove(thread)
|
self._meta_threads.remove(thread)
|
||||||
if worker is not None and worker in self._meta_workers:
|
if worker is not None and worker in self._meta_workers:
|
||||||
self._meta_workers.remove(worker)
|
self._meta_workers.remove(worker)
|
||||||
thread.deleteLater()
|
|
||||||
|
|
||||||
def _on_metadata(self, gen, meta):
|
def _on_metadata(self, gen, meta):
|
||||||
if gen != self._meta_gen:
|
if gen != self._meta_gen:
|
||||||
|
|||||||
Reference in New Issue
Block a user