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.
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. |
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. |
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.
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.
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. |
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.
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. |
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. |
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.
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. |
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.
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.