Skip to content

improve: coalesce playerUpdate frames to cut websocket spam - #242

Open
snakkeeh wants to merge 1 commit into
PerformanC:devfrom
snakkeeh:feat/player-update-coalescing
Open

snakkeeh wants to merge 1 commit into
PerformanC:devfrom
snakkeeh:feat/player-update-coalescing

Conversation

@snakkeeh

Copy link
Copy Markdown
Contributor

Changes

  • Added playerUpdate coalescing in Player._sendUpdate: leading-edge send for the first frame in an idle window, trailing debounce (newest-only) for later frames in the same window, and duplicate suppression when position/connected/ping match the last delivered frame.
  • Lifecycle transitions bypass the debounce and send immediately (seek, pause, track, connection, recover), so clients always converge on state changes without delay.
  • Added Player.flushPlayerUpdate() for explicit convergence (track end, resume flush paths) and _clearPlayerUpdateCoalescer() on destroy so dead players never flush stale frames.
  • Added queuePlayerUpdate helper in utils.ts: paused, resumable sessions keep only the newest playerUpdate frame (newest-wins eviction) instead of appending every position; non-resumable sessions drop them.
  • Restructured the per-tick loops in serverMonitor.ts and the cluster playerUpdateTimer in workers/main.ts into two phases (zombie checks, then one update per player), so N players produce at most N frames per tick.
  • Extended the playback Session, worker player, and cluster snapshot types with the coalescer state and flushPlayerUpdate.

Why

  • Every player emitted a playerUpdate frame on each state transition plus once per tick, so flappy connections or sponsorblock-heavy tracks produced bursts of near-identical { position, connected, ping } payloads per player — pure websocket spam.
  • Paused sessions queued every position frame for resume replay, where only the newest position is useful; stale frames wasted queue space and replay bandwidth.

Checkmarks

  • I have tested the changes locally (burst coalescing, duplicate suppression, force bypass, destroy cleanup, and paused-session routing verified programmatically).
  • I have run the linter/formatter (code follows the existing style conventions).
  • I have tested the changes on a real bot (manual playback with pause/seek/track-change flow).

Additional info

  • The trailing-flush delay is derived from the configured playerUpdateInterval (a quarter of it, clamped to 50-500ms), so operator tuning is respected.
  • Behavior for state transitions is unchanged: they still send immediately, only steady-state position updates are coalesced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant