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)
stats_updated = pyqtSignal(dict)
refresh_needed = pyqtSignal() # Signal to trigger refresh on main thread
def __init__(self, parent=None, config_path: Optional[str] = None):
super().__init__(parent)
@ -324,8 +323,11 @@ class HUDOverlay(QWidget):
self._timer = QTimer(self)
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)
# Batch refresh timer - prevents too frequent GUI updates
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:
"""Load HUD configuration."""
@ -784,6 +786,16 @@ class HUDOverlay(QWidget):
# Widget was deleted
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):
"""Refresh all display labels."""
try:
@ -869,7 +881,7 @@ class HUDOverlay(QWidget):
self._stats.recalculate()
# 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}")
def update_shrapnel(self, amount: Decimal):
@ -883,7 +895,7 @@ class HUDOverlay(QWidget):
self._stats.weapon_cost_total += cost_ped
self._stats.cost_total += cost_ped
self._stats.recalculate()
self.refresh_needed.emit()
self._request_refresh()
def update_armor_cost(self, cost_ped: Decimal):
"""Update armor cost."""
@ -892,7 +904,7 @@ class HUDOverlay(QWidget):
self._stats.armor_cost_total += cost_ped
self._stats.cost_total += cost_ped
self._stats.recalculate()
self.refresh_needed.emit()
self._request_refresh()
def update_healing_cost(self, cost_ped: Decimal):
"""Update healing cost."""
@ -901,14 +913,14 @@ class HUDOverlay(QWidget):
self._stats.healing_cost_total += cost_ped
self._stats.cost_total += cost_ped
self._stats.recalculate()
self.refresh_needed.emit()
self._request_refresh()
def update_kills(self, count: int = 1):
"""Update kill count."""
logger.debug(f"[HUD] update_kills called: count={count}, session_active={self.session_active}")
if self.session_active:
self._stats.kills += count
self.refresh_needed.emit()
self._request_refresh()
def update_skill(self, skill_name: str, amount: Decimal):
"""Update skill gain tracking."""
@ -920,13 +932,13 @@ class HUDOverlay(QWidget):
self._stats.skill_gains[skill_name] += amount
# Update total
self._stats.total_skill_gained += amount
self.refresh_needed.emit()
self._request_refresh()
def update_globals(self):
"""Update global count."""
if self.session_active:
self._stats.globals_count += 1
self.refresh_needed.emit()
self._request_refresh()
def update_damage(self, dealt: Decimal = Decimal('0'), taken: Decimal = Decimal('0')):
"""Update damage stats."""
@ -935,7 +947,7 @@ class HUDOverlay(QWidget):
self._stats.damage_dealt += dealt
if taken > 0:
self._stats.damage_taken += taken
self.refresh_needed.emit()
self._request_refresh()
# === Event Handlers for LogWatcher ===
@ -943,20 +955,20 @@ class HUDOverlay(QWidget):
"""Handle global event from LogWatcher."""
if self.session_active:
self._stats.globals_count += 1
self.refresh_needed.emit()
self._request_refresh()
def on_hof(self, value_ped=Decimal('0.0')):
"""Handle Hall of Fame event from LogWatcher."""
if self.session_active:
self._stats.hofs_count += 1
self.refresh_needed.emit()
self._request_refresh()
def on_personal_global(self, value_ped=Decimal('0.0')):
"""Handle personal global event from LogWatcher."""
# Only count personal globals, not all globals
if self.session_active:
self._stats.globals_count += 1
self.refresh_needed.emit()
self._request_refresh()
def on_damage_dealt(self, damage):
"""Handle damage dealt event from LogWatcher."""