Getting started
Install, run, then host — everything below is distilled from the runnable
examples/counter
in the repository.
1. Install
Section titled “1. Install”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_sdkThen dart pub get (or flutter pub get). client runs on native and web;
server_sdk is native-only (it binds real sockets).
2. Run the counter example
Section titled “2. Run the counter example”The complete join → chat → shared-state round trip in one command:
cd examples/counterdart runIt 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.
3. Host your first room
Section titled “3. Host your first room”The authority — your whole game
Section titled “The authority — your whole game”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.
The server — one room per process
Section titled “The server — one room per process”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-roomFor 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.
The client — typed per game
Section titled “The client — typed per game”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 socialalice.sendControl({'type': 'increment'}); // mutates authority state// every peer's alice.latest.players now carries the new countawait 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.