Compare commits

..
1 Commits
Author SHA1 Message Date
quoije a4d132f134 Harden worker cleanup + reduce cover-loading churn
build / Windows x64 (Wine) (push) Successful in 4m22s
build / Linux x86_64 (push) Successful in 3m30s
- 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.
2026-09-14 22:33:49 -04:00
2 changed files with 48 additions and 42 deletions
+24 -22
View File
@@ -797,28 +797,30 @@ def _transparent_rect(img):
def _game_template():
"""Load the PSP game cover template; return ``(template, window)``."""
global _game_template_cache
if _game_template_cache is None:
with _game_template_lock:
if _game_template_cache is None:
try:
from PySide6.QtGui import QImage
except Exception: # noqa: BLE001 - headless API may lack PySide6
_game_template_cache = (None, (0, 0, 0, 0))
return _game_template_cache
img = QImage()
for path in (
config.COVERS_DIR / "UMD_GAME_COVER_TEMPLATE.png",
config.DATA_DIR / "covers" / "UMD_GAME_COVER_TEMPLATE.png",
):
if path.exists():
img.load(str(path))
if not img.isNull():
break
img = img.convertToFormat(QImage.Format_ARGB32) if not img.isNull() else None
_game_template_cache = (
img,
_transparent_rect(img) if img is not None and not img.isNull() else (0, 0, 0, 0),
)
if _game_template_cache is not None:
return _game_template_cache
try:
from PySide6.QtGui import QImage
except Exception: # noqa: BLE001 - headless API may lack PySide6
_game_template_cache = (None, (0, 0, 0, 0))
return _game_template_cache
img = QImage()
for path in (
config.COVERS_DIR / "UMD_GAME_COVER_TEMPLATE.png",
config.DATA_DIR / "covers" / "UMD_GAME_COVER_TEMPLATE.png",
):
if path.exists():
img.load(str(path))
if not img.isNull():
break
img = img.convertToFormat(QImage.Format_ARGB32) if not img.isNull() else None
entry = (
img,
_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
+24 -20
View File
@@ -172,20 +172,22 @@ _template_lock = threading.Lock()
def _umd_template():
"""Return ``(template QImage, transparent window rect)`` — loaded once."""
global _template_cache
if _template_cache is None:
with _template_lock:
if _template_cache is None:
img = QImage()
for path in (
config.COVERS_DIR / "UMD_COVER_TEMPLATE.png",
config.DATA_DIR / "covers" / "UMD_COVER_TEMPLATE.png",
):
if path.exists():
img.load(str(path))
if not img.isNull():
break
img = img.convertToFormat(QImage.Format_ARGB32)
_template_cache = (img, _transparent_window(img))
if _template_cache is not None:
return _template_cache
img = QImage()
for path in (
config.COVERS_DIR / "UMD_COVER_TEMPLATE.png",
config.DATA_DIR / "covers" / "UMD_COVER_TEMPLATE.png",
):
if path.exists():
img.load(str(path))
if not img.isNull():
break
img = img.convertToFormat(QImage.Format_ARGB32)
entry = (img, _transparent_window(img))
with _template_lock:
if _template_cache is None:
_template_cache = entry
return _template_cache
@@ -2285,11 +2287,11 @@ class StorePage(QWidget):
return (0, -date_int, -mtime)
return (1, 0, -mtime)
CHUNK = 150 # items filled per timer tick
COVER_BATCH = 200 # covers fetched for the initially-visible area
COVER_SCROLL_BATCH = 100
CHUNK = 200 # items filled per timer tick
COVER_BATCH = 500 # covers fetched for the initially-visible area
COVER_SCROLL_BATCH = 400
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):
self._generation += 1
@@ -4876,7 +4878,9 @@ class MainWindow(QMainWindow):
thread._worker = worker
thread.started.connect(worker.run)
worker.result.connect(self._on_library_loaded)
worker.finished.connect(worker.deleteLater)
worker.finished.connect(thread.quit)
thread.finished.connect(thread.deleteLater)
thread.finished.connect(self._forget_library_thread)
self._library_threads.append(thread)
self._library_workers.append(worker)
@@ -4898,7 +4902,6 @@ class MainWindow(QMainWindow):
self._library_threads.remove(thread)
if worker is not None and worker in self._library_workers:
self._library_workers.remove(worker)
thread.deleteLater()
def _apply_library_filter(self):
if not hasattr(self, "_library_cache"):
@@ -5034,7 +5037,9 @@ class MainWindow(QMainWindow):
thread._worker = worker
thread.started.connect(worker.run)
worker.result.connect(self._on_metadata)
worker.finished.connect(worker.deleteLater)
worker.finished.connect(thread.quit)
thread.finished.connect(thread.deleteLater)
thread.finished.connect(self._forget_meta_thread)
self._meta_threads.append(thread)
self._meta_workers.append(worker)
@@ -5047,7 +5052,6 @@ class MainWindow(QMainWindow):
self._meta_threads.remove(thread)
if worker is not None and worker in self._meta_workers:
self._meta_workers.remove(worker)
thread.deleteLater()
def _on_metadata(self, gen, meta):
if gen != self._meta_gen: