Skip to content

Events Reference

Every message from the Signal Fish server — plus two synthetic transport-layer signals — is delivered as a [SignalFishEvent] variant through the event receiver returned by [SignalFishClient::start].

This page documents all 26 variants grouped by category, with field descriptions and usage examples.

Type aliases used throughout

PlayerId is an alias for uuid::Uuid. RoomId is an alias for uuid::Uuid.


Connection Events

Synthetic events generated by the transport layer rather than the server. Use these to track the raw connection lifecycle.

Variant Fields Description
Connected The transport handshake is complete and the client is ready to communicate. Synthetic — see Connection timing for details.
Disconnected reason: Option<String> The transport connection was closed or errored.

Best-effort delivery

Disconnected uses a blocking send so it will not be dropped due to channel backpressure, but it may be missed if the event receiver is dropped or if shutdown() times out and aborts the transport task.

Rust
match event {
    SignalFishEvent::Connected => {
        println!("Transport connected — waiting for authentication…");
    }
    SignalFishEvent::Disconnected { reason } => {
        println!("Disconnected: {}", reason.as_deref().unwrap_or("unknown"));
    }
    _ => {}
}

Authentication Events

Received in response to the automatic Authenticate message sent when the client starts. You must wait for Authenticated before sending any other commands (e.g., joining a room).

Variant Key Fields Description
Authenticated app_name: String, organization: Option<String>, rate_limits: RateLimitInfo Authentication succeeded.
ProtocolInfo ProtocolInfoPayload (wrapped) SDK/protocol compatibility details advertised after authentication.
AuthenticationError error: String, error_code: ErrorCode Authentication failed.

Authenticated

Field Type Description
app_name String Application name confirmed by the server.
organization Option<String> Organization the app belongs to, if any.
rate_limits RateLimitInfo Rate limits enforced for this application (per_minute, per_hour, per_day).

ProtocolInfo(ProtocolInfoPayload)

The payload is wrapped as a single struct rather than flattened. Important fields include platform, sdk_version, capabilities, game_data_formats, and player_name_rules.

AuthenticationError

Field Type Description
error String Human-readable error description.
error_code ErrorCode Structured error code for programmatic handling.
Rust
match event {
    SignalFishEvent::Authenticated { app_name, rate_limits, .. } => {
        println!("Authenticated as {app_name}");
        println!("Rate limits: {}/min", rate_limits.per_minute);
    }
    SignalFishEvent::ProtocolInfo(info) => {
        println!("Capabilities: {:?}", info.capabilities);
    }
    SignalFishEvent::AuthenticationError { error, error_code } => {
        eprintln!("Auth failed [{error_code}]: {error}");
    }
    _ => {}
}

Room Events

Events related to joining, failing to join, or leaving a room.

Variant Key Fields Description
RoomJoined room_id, room_code, player_id, current_players, … Successfully joined a room.
RoomJoinFailed reason: String, error_code: Option<ErrorCode> Failed to join a room.
RoomLeft Successfully left the current room.

RoomJoined

Field Type Description
room_id RoomId Unique room identifier.
room_code String Human-readable room code.
player_id PlayerId The local player's identifier.
game_name String Name of the game this room is for.
max_players u8 Maximum number of players allowed.
supports_authority bool Whether the room supports authority delegation.
current_players Vec<PlayerInfo> Players already present in the room.
is_authority bool Whether the local player is the authority.
lobby_state LobbyState Current lobby readiness state (Waiting, Lobby, or Finalized).
ready_players Vec<PlayerId> Players that have signaled readiness.
relay_type String Relay transport type label (e.g., "auto", "tcp").
current_spectators Vec<SpectatorInfo> Spectators currently watching.

RoomJoinFailed

Field Type Description
reason String Human-readable failure reason.
error_code Option<ErrorCode> Structured error code, if provided.
Rust
match event {
    SignalFishEvent::RoomJoined { room_code, player_id, current_players, .. } => {
        println!("Joined room {room_code} as {player_id}");
        println!("{} player(s) already here", current_players.len());
    }
    SignalFishEvent::RoomJoinFailed { reason, error_code } => {
        eprintln!("Join failed: {reason} ({error_code:?})");
    }
    SignalFishEvent::RoomLeft => {
        println!("Left the room");
    }
    _ => {}
}

Player Events

Notifications about other players joining or leaving the room you are in.

Variant Fields Description
PlayerJoined player: PlayerInfo Another player joined the room.
PlayerLeft player_id: PlayerId Another player left the room.

PlayerInfo contains id, name, is_authority, is_ready, connected_at, and an optional connection_info.

Rust
match event {
    SignalFishEvent::PlayerJoined { player } => {
        println!("{} joined (id: {})", player.name, player.id);
    }
    SignalFishEvent::PlayerLeft { player_id } => {
        println!("Player {player_id} left");
    }
    _ => {}
}

Game Data Events

Carry arbitrary payloads between players. JSON payloads arrive as GameData; binary-encoded payloads (MessagePack, Rkyv) arrive as GameDataBinary.

Variant Fields Description
GameData from_player: PlayerId, data: serde_json::Value JSON game data from another player.
GameDataBinary from_player: PlayerId, encoding: GameDataEncoding, payload: Vec<u8> Binary game data from another player.

GameDataEncoding is one of Json, MessagePack, or Rkyv.

Rust
match event {
    SignalFishEvent::GameData { from_player, data } => {
        println!("JSON data from {from_player}: {data}");
    }
    SignalFishEvent::GameDataBinary { from_player, encoding, payload } => {
        println!(
            "Binary data from {from_player}: {encoding:?}, {} bytes",
            payload.len()
        );
    }
    _ => {}
}

Authority Events

Authority delegation lets one player act as the game host. These events report changes and responses to authority requests.

Variant Key Fields Description
AuthorityChanged authority_player: Option<PlayerId>, you_are_authority: bool The room's authority assignment changed.
AuthorityResponse granted: bool, reason: Option<String>, error_code: Option<ErrorCode> Response to an authority request.
Rust
match event {
    SignalFishEvent::AuthorityChanged { authority_player, you_are_authority } => {
        if you_are_authority {
            println!("You are now the authority");
        } else if let Some(id) = authority_player {
            println!("Authority is now player {id}");
        }
    }
    SignalFishEvent::AuthorityResponse { granted, reason, .. } => {
        if granted {
            println!("Authority request granted");
        } else {
            println!("Authority denied: {}", reason.as_deref().unwrap_or("no reason"));
        }
    }
    _ => {}
}

Lobby Events

Lobby state tracks player readiness. Once all players are ready the server can finalize the lobby and emit GameStarting with peer connection details.

Variant Key Fields Description
LobbyStateChanged lobby_state: LobbyState, ready_players: Vec<PlayerId>, all_ready: bool The lobby readiness state changed.
GameStarting peer_connections: Vec<PeerConnectionInfo> The game is starting with peer connection info.

LobbyState is one of Waiting, Lobby, or Finalized.

PeerConnectionInfo contains player_id, player_name, is_authority, relay_type, and an optional connection_info.

Rust
match event {
    SignalFishEvent::LobbyStateChanged { lobby_state, ready_players, all_ready } => {
        println!("Lobby: {lobby_state:?}, {}/{} ready",
            ready_players.len(),
            ready_players.len() + if all_ready { 0 } else { 1 }
        );
    }
    SignalFishEvent::GameStarting { peer_connections } => {
        println!("Game starting with {} peers", peer_connections.len());
        for peer in &peer_connections {
            println!("  {} (authority={})", peer.player_name, peer.is_authority);
        }
    }
    _ => {}
}

Heartbeat Events

Call client.ping() to send a heartbeat message that keeps the connection alive. The server replies with a Pong event confirming receipt.

Variant Fields Description
Pong Pong response to a ping.
Rust
match event {
    SignalFishEvent::Pong => {
        // Connection is healthy — usually no action needed.
    }
    _ => {}
}

Reconnection Events

If a player's connection drops, the SDK can attempt to rejoin the same room. On success the server replays any events that were missed.

Variant Key Fields Description
Reconnected room_id, room_code, player_id, missed_events, … Reconnection succeeded; state is restored.
ReconnectionFailed reason: String, error_code: ErrorCode Reconnection failed.
PlayerReconnected player_id: PlayerId Another player reconnected to the room.

Reconnected

Carries the same room-state fields as RoomJoined plus:

Field Type Description
missed_events Vec<SignalFishEvent> Events that occurred while the client was disconnected.

ReconnectionFailed

Field Type Description
reason String Human-readable failure reason.
error_code ErrorCode Structured error code.
Rust
match event {
    SignalFishEvent::Reconnected { room_code, missed_events, .. } => {
        println!("Reconnected to {room_code}");
        println!("Replaying {} missed events", missed_events.len());
        for missed in &missed_events {
            println!("  missed: {missed:?}");
        }
    }
    SignalFishEvent::ReconnectionFailed { reason, error_code } => {
        eprintln!("Reconnection failed [{error_code}]: {reason}");
    }
    SignalFishEvent::PlayerReconnected { player_id } => {
        println!("Player {player_id} reconnected");
    }
    _ => {}
}

Spectator Events

Spectators can watch a room without participating. These events cover the full spectator lifecycle.

Variant Key Fields Description
SpectatorJoined room_id, spectator_id, current_players, current_spectators, … Successfully joined a room as a spectator.
SpectatorJoinFailed reason: String, error_code: Option<ErrorCode> Failed to join as a spectator.
SpectatorLeft room_id: Option<RoomId>, room_code: Option<String>, reason, current_spectators Successfully left spectator mode.
NewSpectatorJoined spectator: SpectatorInfo, current_spectators, reason Another spectator joined the room.
SpectatorDisconnected spectator_id: PlayerId, reason, current_spectators Another spectator disconnected.

SpectatorJoined

Field Type Description
room_id RoomId Unique room identifier.
room_code String Human-readable room code.
spectator_id PlayerId The local spectator's identifier.
game_name String Name of the game this room is for.
current_players Vec<PlayerInfo> Players currently in the room.
current_spectators Vec<SpectatorInfo> Spectators currently watching.
lobby_state LobbyState Current lobby readiness state.
reason Option<SpectatorStateChangeReason> Reason the spectator state changed, if applicable.

SpectatorLeft

Field Type Description
room_id Option<RoomId> Room identifier, if available.
room_code Option<String> Room code, if available.
reason Option<SpectatorStateChangeReason> Reason for leaving, if available.
current_spectators Vec<SpectatorInfo> Remaining spectators in the room.

SpectatorStateChangeReason is one of Joined, VoluntaryLeave, Disconnected, Removed, or RoomClosed.

Rust
match event {
    SignalFishEvent::SpectatorJoined { room_code, spectator_id, current_players, .. } => {
        println!("Spectating room {room_code} as {spectator_id}");
        println!("{} player(s) in game", current_players.len());
    }
    SignalFishEvent::SpectatorJoinFailed { reason, error_code } => {
        eprintln!("Spectator join failed: {reason} ({error_code:?})");
    }
    SignalFishEvent::SpectatorLeft { reason, .. } => {
        println!("Left spectator mode: {reason:?}");
    }
    SignalFishEvent::NewSpectatorJoined { spectator, current_spectators, .. } => {
        println!("{} started spectating ({} total)",
            spectator.name, current_spectators.len());
    }
    SignalFishEvent::SpectatorDisconnected { spectator_id, current_spectators, .. } => {
        println!("Spectator {spectator_id} left ({} remaining)",
            current_spectators.len());
    }
    _ => {}
}

Error Events

Catch-all for server-side errors that don't fit a more specific variant.

Variant Fields Description
Error message: String, error_code: Option<ErrorCode> A generic server error.
Rust
match event {
    SignalFishEvent::Error { message, error_code } => {
        eprintln!("Server error: {message} ({error_code:?})");
    }
    _ => {}
}

Complete Event Loop Pattern

A realistic event loop handling the most important variants. Use this as a starting template and expand as needed for your game.

Rust
use signal_fish_client::{
    JoinRoomParams, SignalFishClient, SignalFishConfig,
    SignalFishEvent, WebSocketTransport,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = std::env::var("SIGNAL_FISH_URL")
        .unwrap_or_else(|_| "ws://localhost:3536/ws".to_string());

    let transport = WebSocketTransport::connect(&url).await?;
    let config = SignalFishConfig::new("your-app-id");
    let (mut client, mut event_rx) = SignalFishClient::start(transport, config);

    while let Some(event) = event_rx.recv().await {
        match event {
            // ── Connection ──────────────────────────────────────
            SignalFishEvent::Connected => {
                println!("Connected — authenticating…");
            }

            // ── Authentication ──────────────────────────────────
            SignalFishEvent::Authenticated { app_name, .. } => {
                println!("Authenticated as {app_name}");
                client.join_room(JoinRoomParams::new("my-game", "Alice"))?;
            }
            SignalFishEvent::AuthenticationError { error, error_code } => {
                eprintln!("Auth failed [{error_code}]: {error}");
                break;
            }

            // ── Room lifecycle ──────────────────────────────────
            SignalFishEvent::RoomJoined { room_code, current_players, .. } => {
                println!("Joined room {room_code}");
                println!("{} player(s) in room", current_players.len());
            }

            // ── Player presence ─────────────────────────────────
            SignalFishEvent::PlayerJoined { player } => {
                println!("{} joined the room", player.name);
            }
            SignalFishEvent::PlayerLeft { player_id } => {
                println!("Player {player_id} left");
            }

            // ── Game data ───────────────────────────────────────
            SignalFishEvent::GameData { from_player, data } => {
                println!("Data from {from_player}: {data}");
            }

            // ── Lobby ───────────────────────────────────────────
            SignalFishEvent::LobbyStateChanged { all_ready, .. } => {
                if all_ready {
                    println!("All players ready!");
                }
            }
            SignalFishEvent::GameStarting { peer_connections } => {
                println!("Game starting with {} peers!", peer_connections.len());
            }

            // ── Errors ──────────────────────────────────────────
            SignalFishEvent::Error { message, error_code } => {
                eprintln!("Server error: {message} ({error_code:?})");
            }

            // ── Disconnection ───────────────────────────────────
            SignalFishEvent::Disconnected { reason } => {
                println!("Disconnected: {}",
                    reason.as_deref().unwrap_or("unknown"));
                break;
            }

            // ── Everything else ─────────────────────────────────
            _ => {}
        }
    }

    client.shutdown().await;
    Ok(())
}

Tip

All public enums in this crate are exhaustive. Match event enums with explicit variant arms and avoid _ => {} catch-all arms so compiler errors surface unhandled variants during upgrades.