Matchmaking
Every join call from the SDK goes through the matchmaker. It picks the room a
client lands in, and answers with a seat reservation the SDK consumes to
establish the room connection. joinOrCreate() finds a compatible room or
spawns one, join() requires one to exist, and joinById() targets a
specific room. See Joining Rooms for the frontend.
Which rooms are eligible, and in what order, is up to you:
How the locked, private, and unlisted flags control who can find and join a room.
A live listing of available rooms, the backend for a room browser UI.
A matchmaking queue that groups players over time and spawns a match room when a group is ready.
Run matchmaking on a dedicated process, separate from your game servers.
Matchmaking Options
Room types accept matchmaking options at definition time: which client
options distinguish rooms from each other, and which candidate fills first.
Filter By (.filterBy())
When a room is created by create() or joinOrCreate(), only the options declared in filterBy() are stored internally. Those stored options then filter candidate rooms in later join() or joinOrCreate() calls.
Parameters
options: string[]- a list of option names
Examples
Filter matchmaking by mode option.
const server = defineServer({
rooms: {
battle: defineRoom(BattleRoom).filterBy(['mode'])
}
})Whenever the room is created, the mode option is going to be stored internally.
client.joinOrCreate("battle", { mode: "duo" }).then(room => {/* ... */});You can handle the provided option in the onCreate() and/or onJoin() to implement the requested feature inside your room implementation.
class BattleRoom extends Room {
onCreate(options) {
if (options.mode === "duo") {
// do something!
}
}
onJoin(client, options) {
if (options.mode === "duo") {
// put this player into a team!
}
}
}Sort By (.sortBy())
You can also give a different priority for joining rooms depending on their information upon creation.
The options parameter is a key-value object containing the field name in the left, and the sorting direction in the right. Sorting direction can be one of these values: -1, "desc", "descending", 1, "asc" or "ascending".
Example
The clients is an internal variable stored for matchmaking, which contains the current number of connected clients. On the example below, the rooms with the highest amount of clients connected will have priority. Use -1, "desc" or "descending" for descending order:
const server = defineServer({
rooms: {
battle: defineRoom(BattleRoom).sortBy({ clients: -1 })
}
})To sort by the fewest amount of players, you can do the opposite. Use 1, "asc" or "ascending" for ascending order:
const server = defineServer({
rooms: {
battle: defineRoom(BattleRoom).sortBy({ clients: 1 })
}
})Realtime Listing for Lobby
To allow the LobbyRoom to receive updates from a specific room type, you should define them with realtime listing enabled:
const server = defineServer({
rooms: {
battle: defineRoom(BattleRoom).enableRealtimeListing()
}
})See Lobby Room for more details.
Matchmaker API
The Matchmaker API gives you server-side control over room management and player matchmaking in Colyseus.
From your server code, you can create rooms, join players to existing rooms, query available rooms, and reserve seats. You can also monitor statistics across processes: connected users (CCU) and room counts. You can call methods on rooms running in a different process, too. None of it depends on a client request.
import { matchMaker } from "colyseus";Stats
Colyseus internally keeps track of statistics for the matchmaker.
Fetch All
Fetch all stats from all processes.
Returns an array of objects with the following properties:
processId: the id of the process.roomCount: the number of rooms on the process.ccu: the number of connected clients on the process.
import { matchMaker } from "colyseus";
const stats = await matchMaker.stats.fetchAll();
console.log(stats);
// => [
// { processId: "xxx", roomCount: 10, ccu: 100 },
// { processId: "yyy", roomCount: 9, ccu: 90 },
// ]Get Global CCU
Get the total number of connected clients across all processes.
import { matchMaker } from "colyseus";
const globalCCU = await matchMaker.stats.getGlobalCCU();
console.log(globalCCU);
// => 190Get Local CCU
Get the number of connected clients on the current process.
matchMaker.stats.local.ccuGet Local Room Count
Get the number of rooms on the current process.
matchMaker.stats.local.roomCountMethods
Create a Room
Creates a new room and return its cached data.
Parameters:
roomName: the identifier you defined in theroomsconfiguration ofdefineServer().options: options foronCreate.
const room = await matchMaker.createRoom("battle", { mode: "duo" });
console.log(room);
/*
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
*/The Room may be created on a different process. The processId to create the room is selected via selectProcessIdToCreateRoom method.
Join or Create a Room
Join or create a room and return a client seat reservation.
Parameters:
roomName: the identifier you defined in theroomsconfiguration ofdefineServer().options: options for the client seat reservation (foronJoin/onAuth).
const reservation = await matchMaker.joinOrCreate("battle", { mode: "duo" });
console.log(reservation);
/*
{
"name": "battle",
"sessionId": "zzzzzzzzz",
"roomId": "xxxxxxxxx",
"processId": "yyyyyyyyy"
}
*/Consuming the seat reservation - You can use consumeSeatReservation() from the frontend to join the room by its reserved seat.
Reserve A Seat For
Creates a seat reservation in a specific room.
Parameters:
room: room data (result fromcreateRoom(), etc).clientOptions: options foronJoin.authData: authentication data (available duringonJoinasclient.auth)
const room = await matchMaker.findOneRoomAvailable("battle", { mode: "duo" });
const reservation = await matchMaker.reserveSeatFor(room, {});
console.log(reservation);
/*
{
"name": "battle",
"sessionId": "zzzzzzzzz",
"roomId": "xxxxxxxxx",
"processId": "yyyyyyyyy"
}
*/Consuming the seat reservation - You can use consumeSeatReservation() from the frontend to join the room by its reserved seat.
Join existing Room
Join a room and return seat reservation. An exception is thrown if there are no rooms available for roomName.
Parameters:
roomName: the identifier you defined in theroomsconfiguration ofdefineServer().options: options for the client seat reservation (foronJoin/onAuth).
const reservation = await matchMaker.join("battle", { mode: "duo" });
console.log(reservation);
/*
{
"name": "battle",
"sessionId": "zzzzzzzzz",
"roomId": "xxxxxxxxx",
"processId": "yyyyyyyyy"
}
*/Consuming the seat reservation - You can use consumeSeatReservation() from the frontend to join the room by its reserved seat.
Join Room by ID
Join a room by ID and return client seat reservation. An exception is thrown if a room is not found for roomId.
Parameters:
roomId: the id of a specific room instance.options: options for the client seat reservation (foronJoin/onAuth).
const reservation = await matchMaker.joinById("xxxxxxxxx", {});
console.log(reservation);
/*
{
"name": "battle",
"sessionId": "zzzzzzzzz",
"roomId": "xxxxxxxxx",
"processId": "yyyyyyyyy"
}
*/Consuming the seat reservation - You can use consumeSeatReservation() from the frontend to join the room by its reserved seat.
Create and Reserve a Seat
Create a new room and return client seat reservation.
Parameters:
roomName: the identifier you defined in theroomsconfiguration ofdefineServer().options: options for the client seat reservation (foronJoin/onAuth).
const reservation = await matchMaker.create("battle", { mode: "duo" });
console.log(reservation);
/*
{
"name": "battle",
"sessionId": "zzzzzzzzz",
"roomId": "xxxxxxxxx",
"processId": "yyyyyyyyy"
}
*/Consuming the seat reservation - You can use consumeSeatReservation() from the frontend to join the room by its reserved seat.
Search for Rooms
Perform a query against cached rooms.
Parameters:
conditions: key-value conditions object.sortOptions: key-value sort object.
Example querying with conditions:
const rooms = await matchMaker.query({ name: "battle", mode: "duo" });
console.log(rooms);
/*
[
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false },
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false },
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
]
*/Find One Room Available
Find an available room the same way join()/joinOrCreate() do: rooms flagged locked or private are skipped (unlisted rooms still match).
Parameters:
roomName: the identifier you defined in theroomsconfiguration ofdefineServer().filterOptions: options matched against the room type’sfilterBy()fields.additionalSortOptions: (optional) extra sort criteria applied on top of the room type’ssortBy().
const room = await matchMaker.findOneRoomAvailable("battle", { mode: "duo" });
console.log(room);
/*
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
*/Get Room by ID
Get room data by ID. This method returns the cached room data, and is safe to call from any process.
Parameters:
roomId: the id of a specific room instance.
const room = await matchMaker.getRoomById("xxxxxxxxx");
console.log(room);
/*
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
*/Get Local Room by ID
Get the actual Room instance by ID. This method only returns the room instance if it’s available in the current process, otherwise it will return undefined.
Parameters:
roomId: the id of a specific room instance.
const room = await matchMaker.getLocalRoomById("xxxxxxxxx");
room.clients[0].send("hello", "world");Batch Lookup by Room Id
Resolve a set of known roomIds in a single backend round trip. Use this when you already have a list of roomIds (for example, from a reverse-index lookup) and want their current matchmaker records. It’s bounded at one wire op for any K, where query() would scan the whole room cache.
Parameters:
roomIds: array of room ids to look up.
const rooms = await matchMaker.findRoomsByIds(["roomId-a", "roomId-b", "roomId-missing"]);
console.log(rooms);
/*
Map {
"roomId-a" => { "roomId": "roomId-a", "processId": "...", "name": "battle", ... },
"roomId-b" => { "roomId": "roomId-b", "processId": "...", "name": "battle", ... }
}
*/Missing roomIds are absent from the returned Map. Prefer this over matchMaker.query({}) whenever you know the exact roomIds. The cost stays the same as your cluster grows.
Remote Room Call
Call a method or return a property on a remote room.
Parameters:
roomId: the id of a specific room instance.method: method or attribute to call or retrieve.args: array of arguments.
// call lock() on a remote room by id
await matchMaker.remoteRoomCall("xxxxxxxxx", "lock");Restricting the frontend from creating rooms
You can restrict the frontend to be allowed only to call specific matchmaking methods.
Example: by exposing only join, joinById, and reconnect methods, the frontend is
not going to be able to perform create or joinOrCreate calls.
import { matchMaker } from "colyseus";
matchMaker.controller.exposedMethods = ['join', 'joinById', 'reconnect'];Possible values for exposedMethods are:
'create''join''joinById''joinOrCreate''reconnect'
Next Steps
- Authentication - Gate room joins with the
onAuthhook - Scalability - Run matchmaking across multiple processes and machines
- Driver - Where matchmaking data is stored and queried