Skip to content

Getting started

Install, run, then host — everything below is distilled from the runnable examples/counter in the repository.

rooms_sdk is consumed as git path dependencies (it is not on pub.dev). From a Dart or Flutter project next to a clone of the repository:

dependencies:
protocol:
path: ../path/to/rooms_sdk/packages/protocol
client:
path: ../path/to/rooms_sdk/packages/client
server_sdk:
path: ../path/to/rooms_sdk/packages/server_sdk

Then dart pub get (or flutter pub get). client runs on native and web; server_sdk is native-only (it binds real sockets).

The complete join → chat → shared-state round trip in one command:

Terminal window
cd examples/counter
dart run

It spawns a dedicated RoomServer in its own isolate on a loopback socket; two RoomClients join over real WebSockets, exchange chat through room social, and share a counter through a RoomAuthority whose snapshot is replicated to everyone.

Validation, simulation and snapshots. Networking never imports your game types and vice versa:

class CounterAuthority implements RoomAuthority {
final counters = <String, int>{};
@override
int capacity = 8, tick = 0;
@override
double elapsed = 0;
@override
void update(double seconds) { elapsed += seconds; tick++; }
@override
Object? validateJoin(Map<String, dynamic> message) => null;
@override
void addPlayer(String id, String name, Object? data) => counters[id] = 0;
@override
void removePlayer(String id) => counters.remove(id);
@override
Map<String, dynamic> welcome(String id) => {'sequence': 0};
@override
void command(String id, Map<String, dynamic> message) {
if (message['type'] == 'increment') counters[id] = counters[id]! + 1;
}
@override
Map<String, dynamic> snapshot() => {
'type': 'snapshot', 'epoch': 0, 'tick': tick, 'time': elapsed,
'players': {for (final e in counters.entries) e.key: {'count': e.value}},
};
@override
void resume(String id, Object? data) {}
@override
void disconnect(String id) {}
@override
void dispose() {}
}

Every method, when the hub calls it, and its invariants are specified in /spec/room-authority.md.

import 'package:server_sdk/server_sdk.dart';
final server = RoomServer(
CounterAuthority(),
protocolVersion: 1, // your choice; client and server must match
simulationHz: 60,
snapshotHz: 15,
roomKey: 'demo-room',
);
await server.start(port: 8790);
print('room at ${server.localUri}'); // ws://host:8790/play/demo-room

For in-process or player-hosted rooms, skip RoomServer and drive a RoomHub directly with a MemoryTransportPair or your own GameConnection adapter — see Concepts → hosting shapes.

import 'package:client/client.dart';
class CounterClient extends RoomClient<RoomSnapshot> {
CounterClient(Uri uri, String name)
: super(uri: uri, name: name, protocolVersion: 1);
@override
RoomSnapshot decodeSnapshot(Map<String, dynamic> data) => RoomSnapshot(data);
}
final alice = CounterClient(server.localUri, 'Alice');
await alice.connect(); // join/resume handshake, first snapshot
alice.social.sendChat('hello'); // chat via room social
alice.sendControl({'type': 'increment'}); // mutates authority state
// every peer's alice.latest.players now carries the new count
await alice.close();

Reconnects resume automatically from a one-time token inside a 15 s grace window. To host many rooms in one process behind an HTTP API instead, see Self-hosting.


Distilled from README.md and examples/counter; if the two drift, the repository wins.