68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""A tiny background-job helper so heavy work doesn't freeze the GUI.
|
|
|
|
The app is otherwise synchronous, but a single ``detector.detect()`` (CPU YOLO) or
|
|
a DeepMosaics restore can block the GUI thread for seconds — ``processEvents`` only
|
|
runs *between* frames, not *inside* one heavy call. So detection and restoration run
|
|
on a ``QThreadPool`` thread via :class:`Job`; results come back to the GUI through
|
|
queued Qt signals.
|
|
|
|
Contract: the job function ``fn(job)`` runs on a worker thread and may ONLY touch
|
|
plain data + the engines (no Qt widgets). It reports progress with ``job.progress``/
|
|
``job.tick`` and checks ``job.cancelled`` to stop early. Its return value is delivered
|
|
on the GUI thread via the ``done`` signal; raising :class:`Cancelled` is reported as a
|
|
clean cancel (``done`` with ``None``), any other exception via ``failed``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from PySide6.QtCore import QObject, QRunnable, Signal
|
|
|
|
from ..core.restore.base import Cancelled
|
|
|
|
|
|
class _Signals(QObject):
|
|
progress = Signal(int, int, str) # done, total, message
|
|
tick = Signal(object) # incremental payload (delivered on GUI thread)
|
|
done = Signal(object) # final result (None if cancelled)
|
|
failed = Signal(str) # error message
|
|
|
|
|
|
class Job(QRunnable):
|
|
"""Runs ``fn(job)`` on a thread pool, marshaling progress/result to the GUI."""
|
|
|
|
def __init__(self, fn: Callable[[Job], Any]) -> None:
|
|
super().__init__()
|
|
self.setAutoDelete(False) # the GUI keeps a reference until `done`/`failed`
|
|
self.signals = _Signals()
|
|
self._fn = fn
|
|
self._cancelled = False
|
|
|
|
# -- called from the GUI thread --
|
|
def cancel(self) -> None:
|
|
self._cancelled = True
|
|
|
|
@property
|
|
def cancelled(self) -> bool:
|
|
return self._cancelled
|
|
|
|
# -- called from the worker thread by `fn` --
|
|
def progress(self, done: int, total: int, message: str = "") -> None:
|
|
self.signals.progress.emit(done, total, message)
|
|
|
|
def tick(self, payload: Any) -> None:
|
|
self.signals.tick.emit(payload)
|
|
|
|
# -- thread entry point --
|
|
def run(self) -> None:
|
|
try:
|
|
result = self._fn(self)
|
|
except Cancelled:
|
|
self.signals.done.emit(None)
|
|
except Exception as exc:
|
|
self.signals.failed.emit(str(exc))
|
|
else:
|
|
self.signals.done.emit(result)
|