Drift-compensating frame throttle, fix FPS startup spike
Replace per-frame sleep(remaining) with absolute next_frame_time tracking so asyncio.sleep() overshoots are recovered in subsequent frames, keeping average FPS on target. Skip first FPS sample to avoid ~2000+ spike from near-zero init interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -427,6 +427,8 @@ class WledTargetProcessor(TargetProcessor):
|
||||
f"(display={self._resolved_display_index}, fps={self._target_fps})"
|
||||
)
|
||||
|
||||
next_frame_time = time.perf_counter()
|
||||
|
||||
try:
|
||||
with high_resolution_timer():
|
||||
while self._is_running:
|
||||
@@ -516,9 +518,10 @@ class WledTargetProcessor(TargetProcessor):
|
||||
f"({len(send_colors)} LEDs) — send={send_ms:.1f}ms"
|
||||
)
|
||||
|
||||
# FPS tracking
|
||||
# FPS tracking (skip first sample — interval from loop init is near-zero)
|
||||
interval = now - prev_frame_time_stamp
|
||||
prev_frame_time_stamp = now
|
||||
if self._metrics.frames_processed > 1:
|
||||
fps_samples.append(1.0 / interval if interval > 0 else 0)
|
||||
self._metrics.fps_actual = sum(fps_samples) / len(fps_samples)
|
||||
|
||||
@@ -534,19 +537,24 @@ class WledTargetProcessor(TargetProcessor):
|
||||
self._metrics.last_error = str(e)
|
||||
logger.error(f"Processing error for target {self._target_id}: {e}", exc_info=True)
|
||||
|
||||
# Throttle to target FPS
|
||||
elapsed = now - loop_start
|
||||
remaining = frame_time - elapsed
|
||||
if remaining > 0:
|
||||
# Drift-compensating throttle: sleep until the absolute
|
||||
# next_frame_time so overshoots in one frame are recovered
|
||||
# in the next, keeping average FPS on target.
|
||||
next_frame_time += frame_time
|
||||
sleep_time = next_frame_time - time.perf_counter()
|
||||
if sleep_time > 0:
|
||||
t_sleep_start = time.perf_counter()
|
||||
await asyncio.sleep(remaining)
|
||||
await asyncio.sleep(sleep_time)
|
||||
t_sleep_end = time.perf_counter()
|
||||
actual_sleep = (t_sleep_end - t_sleep_start) * 1000
|
||||
requested_sleep = remaining * 1000
|
||||
requested_sleep = sleep_time * 1000
|
||||
jitter = actual_sleep - requested_sleep
|
||||
_diag_sleep_jitters.append((requested_sleep, actual_sleep))
|
||||
if jitter > 10.0: # >10ms overshoot
|
||||
_diag_slow_iters.append(((t_sleep_end - loop_start) * 1000, "sleep_jitter"))
|
||||
elif sleep_time < -frame_time:
|
||||
# Too far behind — reset to avoid burst catch-up
|
||||
next_frame_time = time.perf_counter()
|
||||
|
||||
# Track total iteration time
|
||||
iter_end = time.perf_counter()
|
||||
|
||||
Reference in New Issue
Block a user