""" ui/dialogs/benchmark_dialog.py BenchmarkDialog — 一鍵啟動 Benchmark 的 QDialog。 顯示三階段進度條(熱機/循序/平行)、即時 FPS、完成後加速倍數大字體 以及循序 vs 平行的 FPS 與延遲對比表。 Benchmark 執行透過 QThread 進行,避免 UI 凍結。 若 pipeline_config 為空,顯示提示訊息並禁用開始按鈕。 """ from typing import Any, List, Optional from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QLabel, QProgressBar, QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) class _BenchmarkWorker(QThread): """在背景執行緒執行 benchmark,避免 UI 凍結。""" progress_updated = pyqtSignal(str, int) result_ready = pyqtSignal(object, object, float) error_occurred = pyqtSignal(str) def __init__(self, benchmarker: Any) -> None: super().__init__() self._benchmarker = benchmarker def run(self) -> None: try: seq_result, par_result, speedup = self._benchmarker.run_full_benchmark( progress_callback=self._on_progress ) self.result_ready.emit(seq_result, par_result, speedup) except Exception as exc: self.error_occurred.emit(str(exc)) def _on_progress(self, phase: str, value: int) -> None: self.progress_updated.emit(phase, value) class BenchmarkDialog(QDialog): """Benchmark 觸發與結果顯示對話框。 Args: parent: 父視窗。 pipeline_config: 目前的 pipeline Stage 設定列表。若為空,禁用開始按鈕。 """ def __init__( self, parent: Optional[QWidget], pipeline_config: List[Any], ) -> None: super().__init__(parent) self._pipeline_config = pipeline_config self.seq_result: Optional[Any] = None self.par_result: Optional[Any] = None self.current_phase: str = "" self._worker: Optional[_BenchmarkWorker] = None self.setWindowTitle("Performance Benchmark") # UI 元件 self.info_label = QLabel("") self.progress_bar = QProgressBar() self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(100) self.fps_label = QLabel("FPS: —") self.phase_label = QLabel("") self.speedup_label = QLabel("") self.result_table = QTableWidget(2, 3) self.result_table.setHorizontalHeaderLabels(["模式", "FPS", "Avg Latency (ms)"]) self.start_button = QPushButton("開始 Benchmark") self.close_button = QPushButton("關閉") self._setup_ui() self._apply_initial_state() def _setup_ui(self) -> None: layout = QVBoxLayout() layout.addWidget(self.info_label) progress_row = QHBoxLayout() progress_row.addWidget(self.progress_bar) progress_row.addWidget(self.phase_label) layout.addLayout(progress_row) fps_row = QHBoxLayout() fps_row.addWidget(QLabel("即時 FPS:")) fps_row.addWidget(self.fps_label) layout.addLayout(fps_row) layout.addWidget(self.speedup_label) layout.addWidget(self.result_table) btn_row = QHBoxLayout() btn_row.addWidget(self.start_button) btn_row.addWidget(self.close_button) layout.addLayout(btn_row) self.setLayout(layout) def _apply_initial_state(self) -> None: if not self._pipeline_config: self.info_label.setText("尚未設定 Pipeline,請先在 Pipeline Editor 中建立 Stage。") self.start_button.setEnabled(False) else: self.info_label.setText(f"已載入 {len(self._pipeline_config)} 個 Stage,可開始 Benchmark。") self.start_button.setEnabled(True) def start_benchmark(self, benchmarker: Any) -> None: """在 QThread 中執行 benchmark,避免 UI 凍結。 Args: benchmarker: PerformanceBenchmarker 實例。 """ self._worker = _BenchmarkWorker(benchmarker) self._worker.progress_updated.connect(self.update_progress) self._worker.result_ready.connect(self._on_result_ready) self._worker.error_occurred.connect(self._on_error) self._worker.finished.connect(self._worker.deleteLater) self.start_button.setEnabled(False) self._worker.start() def update_progress(self, phase: str, value: int) -> None: """更新進度條與當前階段。 Args: phase: 當前階段名稱("warmup" / "sequential" / "parallel")。 value: 進度值(0–100)。 """ _PHASE_LABELS = { "warmup": "熱機中...", "sequential": "循序測試...", "parallel": "平行測試...", } self.current_phase = phase self.progress_bar.setValue(value) self.phase_label.setText(_PHASE_LABELS.get(phase, phase)) def show_result( self, seq_result: Any, par_result: Any, speedup: float, ) -> None: """顯示 benchmark 結果。 Args: seq_result: 循序模式的 BenchmarkResult。 par_result: 平行模式的 BenchmarkResult。 speedup: 加速倍數(par.fps / seq.fps)。 """ self.seq_result = seq_result self.par_result = par_result font = self.speedup_label.font() font.setPointSize(20) font.setBold(True) self.speedup_label.setFont(font) self.speedup_label.setText(f"{speedup:.1f}x FASTER") self._populate_table(seq_result, par_result) def _populate_table(self, seq_result: Any, par_result: Any) -> None: rows = [ ("循序", seq_result), ("平行", par_result), ] for row_idx, (mode_label, result) in enumerate(rows): self.result_table.setItem(row_idx, 0, QTableWidgetItem(mode_label)) try: self.result_table.setItem(row_idx, 1, QTableWidgetItem(f"{result.fps:.1f}")) self.result_table.setItem( row_idx, 2, QTableWidgetItem(f"{result.avg_latency_ms:.1f}") ) except (AttributeError, TypeError): pass def _on_result_ready( self, seq_result: Any, par_result: Any, speedup: float, ) -> None: self.show_result(seq_result, par_result, speedup) def _on_error(self, message: str) -> None: self.info_label.setText(f"Benchmark 失敗:{message}") self.progress_bar.setValue(0) self._worker = None self.start_button.setEnabled(True)