fix: use batched timer-based refresh instead of signals

- Replace signal/slot approach with QTimer-based batched updates
- Add _refresh_pending flag and _request_refresh() method
- Refresh timer runs at 10Hz (100ms) on main thread
- Prevents race conditions and thread safety issues
- All update methods now just set the pending flag
This commit is contained in:
LemonNexus 2026-02-10 14:31:45 +00:00
parent bd40d3d5e0
commit 4db34c2c2c
1 changed files with 26 additions and 14 deletions

View File

@ -295,7 +295,6 @@ 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)
@ -324,8 +323,11 @@ 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 # Batch refresh timer - prevents too frequent GUI updates
self.refresh_needed.connect(self._refresh_display, Qt.ConnectionType.QueuedConnection) self._refresh_pending = False
self._refresh_timer = QTimer(self)
self._refresh_timer.timeout.connect(self._do_refresh)
self._refresh_timer.start(100) # Refresh at most 10 times per second
def _load_hud_config(self) -> HUDConfig: def _load_hud_config(self) -> HUDConfig:
"""Load HUD configuration.""" """Load HUD configuration."""
@ -784,6 +786,16 @@ class HUDOverlay(QWidget):
# Widget was deleted # Widget was deleted
pass pass
def _request_refresh(self):
"""Request a refresh - will be batched and executed on main thread."""
self._refresh_pending = True
def _do_refresh(self):
"""Actually perform the refresh if pending."""
if self._refresh_pending:
self._refresh_pending = False
self._refresh_display()
def _refresh_display(self): def _refresh_display(self):
"""Refresh all display labels.""" """Refresh all display labels."""
try: try:
@ -869,7 +881,7 @@ class HUDOverlay(QWidget):
self._stats.recalculate() self._stats.recalculate()
# Emit signal to refresh on main thread instead of calling directly # Emit signal to refresh on main thread instead of calling directly
self.refresh_needed.emit() self._request_refresh()
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):
@ -883,7 +895,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_needed.emit() self._request_refresh()
def update_armor_cost(self, cost_ped: Decimal): def update_armor_cost(self, cost_ped: Decimal):
"""Update armor cost.""" """Update armor cost."""
@ -892,7 +904,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_needed.emit() self._request_refresh()
def update_healing_cost(self, cost_ped: Decimal): def update_healing_cost(self, cost_ped: Decimal):
"""Update healing cost.""" """Update healing cost."""
@ -901,14 +913,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_needed.emit() self._request_refresh()
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_needed.emit() self._request_refresh()
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."""
@ -920,13 +932,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_needed.emit() self._request_refresh()
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_needed.emit() self._request_refresh()
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."""
@ -935,7 +947,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_needed.emit() self._request_refresh()
# === Event Handlers for LogWatcher === # === Event Handlers for LogWatcher ===
@ -943,20 +955,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_needed.emit() self._request_refresh()
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_needed.emit() self._request_refresh()
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_needed.emit() self._request_refresh()
def on_damage_dealt(self, damage): def on_damage_dealt(self, damage):
"""Handle damage dealt event from LogWatcher.""" """Handle damage dealt event from LogWatcher."""