Installation & Quick Start¶
Get up and running with the Signal Fish Client SDK in minutes.
Prerequisites¶
Before you begin, make sure you have:
- Rust 1.85.0 or newer (
rustup update stable) - A tokio async runtime (the SDK is async-first)
- A running Signal Fish server URL (e.g.,
ws://localhost:3536/ws) - An App ID registered with your Signal Fish server
Installation¶
Add the crate to your project:
Feature Flags¶
| Feature | Default | Description |
|---|---|---|
transport-websocket |
Yes | WebSocket transport via tokio-tungstenite |
transport-websocket-emscripten |
No | Emscripten WebSocket transport for wasm32-unknown-emscripten |
tokio-runtime |
Yes (via transport-websocket) |
Tokio runtime integration; disable for WASM targets |
With default features (includes WebSocket transport)¶
Without default features (bring your own transport)¶
Tip
If you only need the core Transport trait to implement a custom backend, disable default features to avoid pulling in tokio-tungstenite and futures-util.
For Emscripten / Godot web exports¶
[dependencies]
signal-fish-client = { version = "0.4.1", default-features = false, features = ["transport-websocket-emscripten"] }
Tip
The transport-websocket-emscripten feature provides EmscriptenWebSocketTransport and SignalFishPollingClient — a synchronous, game-loop-driven client that does not require an async runtime. See the WebAssembly Guide for complete setup instructions.
Minimal Example¶
Below is a complete working example that connects to a Signal Fish server, authenticates, joins a room, and shuts down gracefully.
use signal_fish_client::{
JoinRoomParams, SignalFishClient, SignalFishConfig, SignalFishEvent, WebSocketTransport,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Read the server URL from the environment, or fall back to localhost.
let url = std::env::var("SIGNAL_FISH_URL")
.unwrap_or_else(|_| "ws://localhost:3536/ws".to_string());
// 1. Connect a WebSocket transport to the signaling server.
let transport = WebSocketTransport::connect(&url).await?;
// 2. Build a client config with your application ID.
let config = SignalFishConfig::new("your-app-id");
// 3. Start the client — returns a handle and an event receiver.
let (mut client, mut event_rx) = SignalFishClient::start(transport, config);
// 4. Drive the event loop.
loop {
tokio::select! {
event = event_rx.recv() => {
let Some(event) = event else {
// Channel closed — background task exited.
break;
};
match event {
SignalFishEvent::Connected => {
println!("Transport connected");
}
SignalFishEvent::Authenticated { app_name, .. } => {
println!("Authenticated as {app_name}");
// Safe to join a room now.
client.join_room(JoinRoomParams::new("my-game", "Alice"))?;
}
SignalFishEvent::RoomJoined { room_code, player_id, .. } => {
println!("Joined room {room_code} as {player_id}");
}
SignalFishEvent::Disconnected { .. } => break,
_ => {}
}
}
// Graceful shutdown on Ctrl+C.
_ = tokio::signal::ctrl_c() => {
println!("Shutting down...");
break;
}
}
}
// 5. Shut down gracefully.
client.shutdown().await;
Ok(())
}
Note
WebSocketTransport requires the transport-websocket feature, which is enabled by default. If you disabled default features you will need to re-enable it explicitly:
What Happens Under the Hood¶
When you call SignalFishClient::start, the SDK:
- Spawns a background task that drives the transport — reading incoming messages and writing outgoing ones.
- Auto-authenticates by immediately sending an
Authenticatemessage with the App ID from yourSignalFishConfig. - Emits typed events on a bounded
tokio::sync::mpscchannel (default capacity 256, configurable viaSignalFishConfig::event_channel_capacity). Your application consumes these via theevent_rxreceiver returned fromstart.
You interact with the server by calling methods on the SignalFishClient handle (e.g., join_room, send_game_data). These enqueue outgoing messages that the background task sends over the transport.
Warning
If your event-processing loop cannot keep up with the server, events will be
dropped (with a warning logged) to avoid blocking the transport loop. The
Disconnected event uses a blocking send so it will not be dropped due to
backpressure, but it may be missed if the receiver is dropped or
shutdown() times out. Design your handler to stay
responsive.
You can increase the buffer by setting event_channel_capacity on your
SignalFishConfig.
Next Steps¶
- Core Concepts — rooms, players, relays, and the event model
- Client API — full reference for
SignalFishClientmethods - Events — every
SignalFishEventvariant explained - WebAssembly Guide — building for
wasm32-unknown-unknownandwasm32-unknown-emscripten, Godot integration