> For the complete documentation index, see [llms.txt](https://docs.coherence.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.coherence.io/hosting/coherence-cloud/coherence-cloud-apis/lobbies.md).

# Lobbies

### Requirements

To use Lobbies, you must have at least one Region enabled in the Project Settings section of your [Online Dashboard](/hosting/coherence-cloud/online-dashboard.md).

{% hint style="info" %}
[Simulators](/manual/simulation-server.md) cannot currently use lobbies because a valid player account is required.
{% endhint %}

### Lobbies Service API

Accessible from **CoherenceBridge.CloudService.CloudRooms**, the Lobbies Service API has access to every endpoint you need to interface with the Lobbies feature.

### Lobby and Player Attributes

Attributes are one of the main avenues available for customizing the Lobbies logic and fitting it to the needs of your game project.

Every Player in the Lobby and the Lobby itself can have custom Attributes associated to them.

An Attribute is defined by:

* Key: Identifier for the Attribute.
* Value: Value of the Attribute, can be either an integer number or a string.
* Index: A predetermined value that will allow the Attribute to be used in Filter Expressions.
* Aggregate: This only applies to the lobby indexes and can be: “sum”, “avg”, “min”, “max” or “owner” for the integer indexes or “owner” for the string indexes. When a player joins/leaves the lobby their indexes are aggregated into the lobby indexes.
* Public: Public Attributes will be returned and visible to all Players.

{% hint style="warning" %}
Player or Lobby Attributes cannot be deleted, you can only add new ones or update existing ones.

If you wish to stop using an Attribute, we recommend setting a default value that you can interpret as such, like an empty string for a string Attribute, or a zero value for an integer Attribute.
{% endhint %}

#### Indexes and Aggregators

Indexed Attributes are aggregated from the Player Attributes into a Lobby Attribute.

For example, let's say every Player in my Lobby has a Matchmaking integer rating value Attribute associated to him, and we index this Attribute to n1.

We want Players to be able to do Matchmaking with Lobbies using the average Matchmaking rating of all the players present in the Lobby. The Lobby will have a "Matchmaking rating average" integer Attribute associated with it, that is also indexed to n1, with an aggregator of type "avg" to do the average of every Player present in the Lobby.

Every time a Player joins or leaves the Lobby, the Lobby Attribute indexed to n1 will be updated accordingly.

When you're entering the Matchmaking phase, you can use a Lobby Filter Expression to search for specific Lobbies that match the current rating of the Player that is initiating the Matchmaking phase.

### Matchmaking

In order to start the Matchmaking process, you can use the `FindOrCreateLobby` method. Based on the given parameters to find a Lobby, the response of this method will be either an existing Lobby or a fresh new Lobby where your player will be the owner.

A Lobby created this way is named after `CreateLobbyOptions.Name`. Lobby names are unique, so creating one with a name that's already taken fails. Leave the name empty to have one generated.

The bread and butter of the Matchmaking logic can be done through our powerful filter expression building system. The `FindOrCreateLobby` method accepts up to three different filters.

{% hint style="info" %}
[CreateLobbyOptions.ForRoom](https://unityapi.coherence.io/docs/v2.3.0/api/Coherence.Cloud.CreateLobbyOptions.html) should be used when creating a lobby for [a room](/hosting/coherence-cloud/coherence-cloud-apis/rooms.md).

To create a lobby for inter-room communication, use the CreateLobbyOptions constructor without passing a room argument.
{% endhint %}

#### Lobby Filter Expressions

This is similar to the WHERE clause in an SQL-like expression. You can build filter expressions using Lobby Attribute indexes and most of the Lobby properties.

The player can use up to three filter expressions to filter out Lobbies. The expressions are tried in order until one of them succeeds. This can be used to increasingly expand the search criteria and can be very efficient if the amount of players is low.

Currently, these are the properties that you can use to customize your Matchmaking process:

* Room Regions
* Tag
* Max Players
* Number of Players
* Created At
* Available Slots
* Simulator Slug
* Is Private Lobby
* Integer Attribute Index
* String Attribute Index

For example:

* “region in \[‘us’, ‘eu’] and (s1 = ‘map1’ or s1 = ‘map2’)”

{% hint style="info" %}
`Tag` in the context of Lobbies is different than `tags` in the context of Rooms. `Tag` here is meant as a single top-level grouping of lobbies, like `ranked` or `casual` . Use attributes as the query surface for more complex queries.
{% endhint %}

In Unity, you will build this filter expression using the builder pattern, if we wanted to match using the expression from the example, we would do it like this:

```csharp
async void Start()
{
    PlayerAccount playerAccount = await PlayerAccount.GetMainAsync();
    var cloudService = playerAccount.Services;

    var regionsInfo = await cloudService.Regions.FetchRegionsInfoAsync();
    var regions = regionsInfo.Regions;

    // Build filter for regions and either map1 or map2
    var lobbyFilter = new LobbyFilter()
        .WithAnd()
        .WithRegion(FilterOperator.Any, new List<string>(regions))
        .WithOr()
        .WithStringAttribute(FilterOperator.Equals, StringAttributeIndex.s1, "map1")
        .WithStringAttribute(FilterOperator.Equals, StringAttributeIndex.s1, "map2")
        .End();

    // You can use the ToString method to debug what your filters look like
    // This outputs: “region in [‘us’, ‘eu’] and (s1 = ‘map1’ or s1 = ‘map2’)”
    Debug.Log($"{lobbyFilter.ToString()}");

    var findOptions = new FindLobbyOptions { Limit = 20, LobbyFilters = new() { lobbyFilter } };

    // If we don't find a suitable Lobby, we will create one with Lobby Attribute map1 and the first available region
    var mapAttribute = new CloudAttribute("map", "map1", StringAttributeIndex.s1, StringAggregator.None, true);

    var createOptions = new CreateLobbyOptions { LobbyAttributes = new() { mapAttribute }, Region = regions[0] };

    var lobbies = cloudService.Rooms.LobbyService;

    // We start the Matchmaking process with the coherence Cloud
    var lobbySession = await lobbies.FindOrCreateLobbyAsync(findOptions, createOptions);
    var lobby = lobbySession.LobbyData;

    Debug.Log($"Lobby found or created: {lobby.Name}.");

    // All players in a lobby should join the room for a play session once it has been started.
    // This step can be skipped if a CoherenceBridge's 'Player Account' option has been set to 'Main User'.
    lobbies.OnPlaySessionStarted += OnLobbyPlaySessionStarted;

    // When ready (e.g. the lobby is full), the play session can be started using LobbyOwnerActions.StartGameSessionAsync:
    await lobbySession.LobbyOwnerActions.StartGameSessionAsync();
}

void OnLobbyPlaySessionStarted(string lobbyId, RoomData room)
{
    if (CoherenceBridgeStore.TryGetBridge(gameObject.scene, out var bridge))
    {
        Debug.Log($"Lobby play session has started. Joining the room {room.RoomName}.");
        bridge.JoinRoom(room);
    }
}
```

{% hint style="warning" %}
Entries returned by `FindLobbies` (and similar functions) do not include Lobbies that user is already part of. See the [#handling-lobbies-when-logging-in](#handling-lobbies-when-logging-in "mention") for more information on how to retrieve user's active Lobby sessions.
{% endhint %}

### Lobby Session

When you successfully join a Lobby, an instance of type LobbySession will be returned via the Lobbies API.

You can use this LobbySession instance to interface with the Lobby itself, you will find methods to leave the Lobby, update your Player Attributes or send a message to other Players. You will also be able to query all the information about the Lobby itself, like every Player present, their Attributes, maximum Players and other Lobby properties.

Each Player in `LobbyData.Players` comes with their Player Account's `Username`, `DisplayName` and `AvatarUrl`, and `LobbyData.CreatedAt` tells when the Lobby was created.

The Lobbies a player is in are shown to their [Friends](/hosting/coherence-cloud/game-services/friends.md#online-presence). To keep a Lobby out of its members' online presence, create it with `CreateLobbyOptions.HideFromPresence`.

Additionally, if you're the owner of the Lobby, you will have an instance of type LobbyOwnerActions available to you (for non-owners this instance will be null referenced). As an owner of the Lobby, you will be able to kick other Players, update the Attributes of the Lobby, or start the Game Session.

LobbySession instance will have a number of callbacks you can subscribe to that will inform you of any updates that happen to the Lobby, like a Player joining or leaving, or Attributes being updated.

If a Room associated with a Lobby is closed, Players will also receive a callback.

### Invites

Besides matchmaking, players can join a Lobby they've been invited to:

* **Invites**: `LobbySession.InviteAsync` invites a specific player, such as one of their [Friends](/hosting/coherence-cloud/game-services/friends.md). The invited player receives it through `LobbiesService.OnLobbyInviteReceived`, and joins with `LobbiesService.AcceptLobbyInviteAsync`. Invites are only delivered to players who are connected at the time.
* **Invite tokens**: `LobbySession.CreateInviteTokenAsync` creates a token you can share through your own channels, such as a link. A token can be used by any number of players until it expires, and stops working when the Lobby closes. Players join with `LobbiesService.JoinLobbyWithTokenAsync`, and `LobbiesService.ResolveLobbyFromInviteTokenAsync` looks up the Lobby without joining it.
* **Join codes**: a code set in `CreateLobbyOptions.JoinCode` that players can type in to join with `LobbiesService.JoinLobbyByCodeAsync`. It's unique among the project's open Lobbies and lasts as long as the Lobby does. Pair it with `CreateLobbyOptions.Unlisted` for a Lobby only players with the code can find.

Joining through any of these skips the Lobby's password.

By default, only the Lobby owner can invite players and create invite tokens. Set `CreateLobbyOptions.InvitePermissions` to `LobbyInvitePermissions.AllMembers` to allow every member. It can't be changed after the Lobby is created.

```csharp
using System;
using System.Threading.Tasks;
using Coherence.Cloud;
using UnityEngine;

public class LobbyInvitesExample : MonoBehaviour
{
    public string region = "eu";
    public string joinCode = "Q7K2X9";

    LobbiesService lobbies;
    LobbySession lobbySession;

    async void Start()
    {
        // Wait until a player account has logged in to coherence Cloud.
        // The CoherenceCloudLogin component can be used for this.
        PlayerAccount playerAccount = await PlayerAccount.GetMainAsync();
        lobbies = playerAccount.Services.Lobbies;
        lobbies.OnLobbyInviteReceived += OnLobbyInviteReceived;

        // Create an unlisted Lobby that every member can invite to, and that can be joined with a code.
        lobbySession = await lobbies.CreateLobbyAsync(new CreateLobbyOptions
        {
            Region = region,
            Unlisted = true,
            InvitePermissions = LobbyInvitePermissions.AllMembers,
            JoinCode = joinCode,
        });
    }

    void OnDestroy()
    {
        if (lobbies != null)
        {
            lobbies.OnLobbyInviteReceived -= OnLobbyInviteReceived;
        }
    }

    // Invite a player directly, for example one of the player's friends.
    public Task InviteAsync(PlayerAccountId playerAccountId) => lobbySession.InviteAsync(playerAccountId);

    // Create a token to share through your own channels, such as a link.
    public Task<string> CreateInviteTokenAsync() => lobbySession.CreateInviteTokenAsync(TimeSpan.FromMinutes(30));

    public Task<LobbySession> JoinWithTokenAsync(string inviteToken) => lobbies.JoinLobbyWithTokenAsync(inviteToken);

    public Task<LobbySession> JoinWithCodeAsync(string code) => lobbies.JoinLobbyByCodeAsync(code);

    async void OnLobbyInviteReceived(LobbyInvite invite)
    {
        Debug.Log($"{invite.Inviter.DisplayName} invited you to {invite.Lobby.Name}.");
        await lobbies.AcceptLobbyInviteAsync(invite);
    }
}
```

{% hint style="info" %}
coherence Cloud currently accepts join codes of 6 to 32 characters, and doesn't restrict which characters they use, so pick a length and alphabet that make codes hard to guess. Invite tokens currently last an hour by default, and up to a day.
{% endhint %}

### Reservations

Reservations keep Lobby slots free for specific players, such as the other members of a party, so a party can play together. Reserved slots count against the Lobby's maximum players, and are kept free until the reserved player joins, or the reservation times out and `LobbySession.OnReservationExpired` is raised.

A reservation only keeps a slot free: reserved players still join on their own, for example through an [invite](#invites), and still need the Lobby's password if it has one.

Describe reservations with `LobbyReservations`: the reserved players, and optionally how long the reservations last. You can reserve slots:

* **When creating a Lobby**, through `CreateLobbyOptions.Reservations`.
* **When finding or joining a Lobby**, through `CreateLobbyOptions.Reservations` with `FindOrCreateLobbyAsync`, or the `reservations` parameter of `JoinLobbyAsync`. The player joins and the slots are reserved in a single step: if the Lobby can't fit the whole party, nobody joins. `FindOrCreateLobbyAsync` skips Lobbies that can't fit the party, and creates one when none does.
* **From inside a Lobby**, through `LobbySession.ReserveSlotsAsync`.

Once a Lobby exists, it only accepts new reservations if it was created with `CreateLobbyOptions.MaxReservationsPerRequest` above 0 (it defaults to 0), which sets how many new slots a single request can reserve. `CreateLobbyOptions.MaxReservationTimeout` caps how long those reservations last. Neither can be changed after the Lobby is created.

Players already in the Lobby, or already holding a reservation, are skipped. This way, every member of a party can send the same reservations, or retry, without reserving extra slots.

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Coherence.Cloud;
using UnityEngine;

public class LobbyReservationsExample : MonoBehaviour
{
    public string region = "eu";

    // Find a Lobby with room for the whole party, or create one.
    // partyMembers are the other members of the player's party, such as some of their friends.
    public async Task<LobbySession> FindOrCreateLobbyForPartyAsync(IReadOnlyList<PlayerAccountId> partyMembers)
    {
        PlayerAccount playerAccount = await PlayerAccount.GetMainAsync();
        var lobbies = playerAccount.Services.Lobbies;

        var createOptions = new CreateLobbyOptions
        {
            Region = region,
            MaxPlayers = 8,
            // Keep a slot free for each party member for 5 minutes.
            Reservations = new LobbyReservations(partyMembers, TimeSpan.FromMinutes(5)),
            // Once the Lobby exists, let other players join with parties of up to 4.
            MaxReservationsPerRequest = 3,
        };

        var lobbySession = await lobbies.FindOrCreateLobbyAsync(FindLobbyOptions.Default, createOptions);

        lobbySession.OnReservationExpired += (session, reservation) =>
            Debug.Log($"{reservation.PlayerAccountId} didn't join in time. Their slot is free again.");

        return lobbySession;
    }

    // Reserve a slot for a player once already in the Lobby.
    public Task ReserveSlotAsync(LobbySession lobbySession, PlayerAccountId playerAccountId) =>
        lobbySession.ReserveSlotsAsync(new LobbyReservations(new[] { playerAccountId }));
}
```

Reservations of a Lobby are listed in `LobbyData.Reservations`.

{% hint style="info" %}
coherence Cloud currently reserves slots for 2 minutes by default, accepts timeouts from 1 to 30 minutes, and up to 99 players per request.
{% endhint %}

### Game Session

When the owner of the Lobby starts the Game Session, every Player will receive a callback with the Room endpoint data that can be used to connect to the Replication Server using the associated **CoherenceBridge** instance.

If you need to perform custom logic before actually joining the Room, you can subscribe a custom listener through the LobbiesService instance in **CoherenceBridge.CloudService.Lobbies.OnPlaySessionStarted**. If no custom listener is registered, **coherence** will automatically connect to the Replication Server.

### Handling Lobbies when logging in

If the player has closed the game abruptly without leaving the Lobby, when the game is restarted and the Player authenticates with the coherence Cloud, in the Login response you will receive the ID of the Lobbies that the Player is currently a part of. You can use these Lobby IDs to fetch again the LobbySession instance from the Lobbies Service API.

Additionally, the Lobby might have a current Game Session active, so you can use the Room data that is available to reconnect to the Replication Server Room.

Here is an example of how to handle this scenario:

```csharp
using Coherence.Cloud;
using Coherence.Toolkit;
using UnityEngine;

class LobbyLoginExample : MonoBehaviour
{
    async void Start()
    {
        // Log in to coherence CLoud using a guest player account.
        var loginOperation = await CoherenceCloud.LoginAsGuest();

        // We can acquire ids of the lobbies that the player is in
        // from the completed login operation
        var lobbyIds = loginOperation.LobbyIds;
        if (lobbyIds.Count == 0)
        {
            return;
        }

        var lobbyId = lobbyIds[0];
        var lobbies = loginOperation.Result.Services.Lobbies;

        // We fetch a LobbySession instance from the Lobby Service API
        var lobbySession = await lobbies.GetActiveLobbySessionForLobbyId(lobbyId);
        if (lobbySession is null)
        {
            return;
        }

        // We can determine if a play session is active by checking if the LobbyData contains any RoomData.
        if (lobbySession.LobbyData.RoomData is { } room && CoherenceBridgeStore.TryGetBridge(gameObject.scene, out var bridge))
        {
            // The Lobby has an ongoing Game Session, so we reconnect to the Room using the CoherenceBridge
            bridge.JoinRoom(room);
        }
        else
        {
            // The Lobby doesn't have an ongoing Game Session, here we can update our UI accordingly
        }
    }
}
```
