GamendWeb.ChannelUpdates (gamend_web v1.0.1216)

Copy Markdown View Source

Outbound state-update pushes for channels: drop no-op updates, and optionally coalesce bursts into one message.

State events re-send the current state of an object the subscriber already has, so two things are worth doing before pushing:

  • Deduplicate. If nothing changed since the last push on this connection, send nothing at all.
  • Debounce. When REALTIME_DEBOUNCE_MS is set, hold updates for that long and push only the latest state per object when the timer fires. A burst of ten writes to one lobby becomes one message.

Debouncing is off by default (0), which pushes immediately and only deduplicates.

Coalescing beats shrinking here: each WebSocket message costs roughly 76 bytes of framing, TLS, TCP and IP headers before any payload, and the server runs with TCP_NODELAY, so every message is its own packet. Removing a message saves more than compressing one ever can.

Usage

def handle_info({:lobby_updated, lobby}, socket) do
  payload = Serializers.serialize_lobby(lobby)
  {:noreply, ChannelUpdates.push(socket, "lobby_updated", payload.id, payload)}
end

# required once per channel that pushes updates
def handle_info({:channel_updates_flush, _}, socket),
  do: {:noreply, ChannelUpdates.flush(socket)}

key scopes the dedupe/coalesce slot within the channel. Channels that track one object can pass any constant; channels that multiplex (lobbies, groups, member lists) must pass the object id.

Summary

Functions

Milliseconds to hold updates before pushing. 0 disables debouncing.

Pushes every pending update. Call from the channel's handler for :channel_updates_flush messages.

Forgets everything remembered for event/key, so the next push is treated as the first one. Use when the object is deleted or the client leaves it.

The last payload pushed for event/key, or nil.

Pushes payload for event/key, unless it is identical to the last one pushed on this socket. Returns the updated socket.

Records payload as already delivered without pushing it, so a later identical update is suppressed. Use when the payload went out under a different event name (a create that doubles as the first update).

Functions

debounce_ms()

@spec debounce_ms() :: non_neg_integer()

Milliseconds to hold updates before pushing. 0 disables debouncing.

flush(socket)

@spec flush(Phoenix.Socket.t()) :: Phoenix.Socket.t()

Pushes every pending update. Call from the channel's handler for :channel_updates_flush messages.

forget(socket, event, key)

@spec forget(Phoenix.Socket.t(), String.t(), term()) :: Phoenix.Socket.t()

Forgets everything remembered for event/key, so the next push is treated as the first one. Use when the object is deleted or the client leaves it.

last(socket, event, key)

@spec last(Phoenix.Socket.t(), String.t(), term()) :: map() | nil

The last payload pushed for event/key, or nil.

push(socket, event, key, payload, wrap \\ & &1)

@spec push(Phoenix.Socket.t(), String.t(), term(), map(), (map() -> map())) ::
  Phoenix.Socket.t()

Pushes payload for event/key, unless it is identical to the last one pushed on this socket. Returns the updated socket.

wrap builds the message body from the payload, for events that nest it (friend_updated sends %{friends: %{id => payload}}). Deduplication always compares the unwrapped payload.

remember(socket, event, key, payload)

@spec remember(Phoenix.Socket.t(), String.t(), term(), map()) :: Phoenix.Socket.t()

Records payload as already delivered without pushing it, so a later identical update is suppressed. Use when the payload went out under a different event name (a create that doubles as the first update).