fix: use signal/slot for thread-safe GUI updates

- Add refresh_needed signal to HUDOverlay
- Connect signal to _refresh_display with QueuedConnection
- Update all update methods to emit signal instead of calling _refresh_display directly
- This ensures all GUI operations (setStyleSheet, setText) happen on main thread
- Fixes crash caused by GUI operations from LogWatcher thread
This commit is contained in:
LemonNexus 2026-02-10 14:27:33 +00:00
parent 0a6e4358de
commit 63f215a3a1
1 changed files with 16 additions and 11 deletions

View File

@ -295,6 +295,7 @@ class HUDOverlay(QWidget):
position_changed = pyqtSignal(QPoint) position_changed = pyqtSignal(QPoint)
stats_updated = pyqtSignal(dict) stats_updated = pyqtSignal(dict)
refresh_needed = pyqtSignal() # Signal to trigger refresh on main thread
def __init__(self, parent=None, config_path: Optional[str] = None): def __init__(self, parent=None, config_path: Optional[str] = None):
super().__init__(parent) super().__init__(parent)
@ -323,6 +324,9 @@ class HUDOverlay(QWidget):
self._timer = QTimer(self) self._timer = QTimer(self)
self._timer.timeout.connect(self._update_session_time) self._timer.timeout.connect(self._update_session_time)
# Connect refresh signal to ensure GUI updates happen on main thread
self.refresh_needed.connect(self._refresh_display, Qt.ConnectionType.QueuedConnection)
def _load_hud_config(self) -> HUDConfig: def _load_hud_config(self) -> HUDConfig:
"""Load HUD configuration.""" """Load HUD configuration."""
try: try:
@ -883,7 +887,8 @@ class HUDOverlay(QWidget):
self._stats.loot_other += value_ped self._stats.loot_other += value_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() # Emit signal to refresh on main thread instead of calling directly
self.refresh_needed.emit()
logger.debug(f"[HUD] update_loot complete: shrapnel={self._stats.shrapnel_total}, other={self._stats.loot_other}") logger.debug(f"[HUD] update_loot complete: shrapnel={self._stats.shrapnel_total}, other={self._stats.loot_other}")
def update_shrapnel(self, amount: Decimal): def update_shrapnel(self, amount: Decimal):
@ -897,7 +902,7 @@ class HUDOverlay(QWidget):
self._stats.weapon_cost_total += cost_ped self._stats.weapon_cost_total += cost_ped
self._stats.cost_total += cost_ped self._stats.cost_total += cost_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self.refresh_needed.emit()
def update_armor_cost(self, cost_ped: Decimal): def update_armor_cost(self, cost_ped: Decimal):
"""Update armor cost.""" """Update armor cost."""
@ -906,7 +911,7 @@ class HUDOverlay(QWidget):
self._stats.armor_cost_total += cost_ped self._stats.armor_cost_total += cost_ped
self._stats.cost_total += cost_ped self._stats.cost_total += cost_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self.refresh_needed.emit()
def update_healing_cost(self, cost_ped: Decimal): def update_healing_cost(self, cost_ped: Decimal):
"""Update healing cost.""" """Update healing cost."""
@ -915,14 +920,14 @@ class HUDOverlay(QWidget):
self._stats.healing_cost_total += cost_ped self._stats.healing_cost_total += cost_ped
self._stats.cost_total += cost_ped self._stats.cost_total += cost_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self.refresh_needed.emit()
def update_kills(self, count: int = 1): def update_kills(self, count: int = 1):
"""Update kill count.""" """Update kill count."""
logger.debug(f"[HUD] update_kills called: count={count}, session_active={self.session_active}") logger.debug(f"[HUD] update_kills called: count={count}, session_active={self.session_active}")
if self.session_active: if self.session_active:
self._stats.kills += count self._stats.kills += count
self._refresh_display() self.refresh_needed.emit()
def update_skill(self, skill_name: str, amount: Decimal): def update_skill(self, skill_name: str, amount: Decimal):
"""Update skill gain tracking.""" """Update skill gain tracking."""
@ -934,13 +939,13 @@ class HUDOverlay(QWidget):
self._stats.skill_gains[skill_name] += amount self._stats.skill_gains[skill_name] += amount
# Update total # Update total
self._stats.total_skill_gained += amount self._stats.total_skill_gained += amount
self._refresh_display() self.refresh_needed.emit()
def update_globals(self): def update_globals(self):
"""Update global count.""" """Update global count."""
if self.session_active: if self.session_active:
self._stats.globals_count += 1 self._stats.globals_count += 1
self._refresh_display() self.refresh_needed.emit()
def update_damage(self, dealt: Decimal = Decimal('0'), taken: Decimal = Decimal('0')): def update_damage(self, dealt: Decimal = Decimal('0'), taken: Decimal = Decimal('0')):
"""Update damage stats.""" """Update damage stats."""
@ -949,7 +954,7 @@ class HUDOverlay(QWidget):
self._stats.damage_dealt += dealt self._stats.damage_dealt += dealt
if taken > 0: if taken > 0:
self._stats.damage_taken += taken self._stats.damage_taken += taken
self._refresh_display() self.refresh_needed.emit()
# === Event Handlers for LogWatcher === # === Event Handlers for LogWatcher ===
@ -957,20 +962,20 @@ class HUDOverlay(QWidget):
"""Handle global event from LogWatcher.""" """Handle global event from LogWatcher."""
if self.session_active: if self.session_active:
self._stats.globals_count += 1 self._stats.globals_count += 1
self._refresh_display() self.refresh_needed.emit()
def on_hof(self, value_ped=Decimal('0.0')): def on_hof(self, value_ped=Decimal('0.0')):
"""Handle Hall of Fame event from LogWatcher.""" """Handle Hall of Fame event from LogWatcher."""
if self.session_active: if self.session_active:
self._stats.hofs_count += 1 self._stats.hofs_count += 1
self._refresh_display() self.refresh_needed.emit()
def on_personal_global(self, value_ped=Decimal('0.0')): def on_personal_global(self, value_ped=Decimal('0.0')):
"""Handle personal global event from LogWatcher.""" """Handle personal global event from LogWatcher."""
# Only count personal globals, not all globals # Only count personal globals, not all globals
if self.session_active: if self.session_active:
self._stats.globals_count += 1 self._stats.globals_count += 1
self._refresh_display() self.refresh_needed.emit()
def on_damage_dealt(self, damage): def on_damage_dealt(self, damage):
"""Handle damage dealt event from LogWatcher.""" """Handle damage dealt event from LogWatcher."""