Social

Lobbies

Create/join/list lobbies with slots and metadata.

Server
services.AddCrossplayLobbies();
Unity package
com.crossplay.lobbies
Depends on
core

Seams you implement 1

Crossplay calls these; your game supplies them. Each ships an inert or permissive default, so register yours before services.AddCrossplayLobbies(); and it wins.

ILobbyStartSink game SPI

The start seam this piece OWNS (the IMatchFormedSink idiom): called inside TryStartMatch after the lobby's state flips to InProgress, BEFORE the MatchStarting broadcast, with the lobby id, its game-defined mode string, and every member — cross-node aware. The piece defaults it to NullLobbyStartSink (a TryAdd seam), so with nothing registered ahead of it both lobby twins behave byte-identically to before the seam existed. The composition layer (Hosting/game) registers a bridge FIRST to route lobby starts somewhere (e.g. the Director allocates an instance) — Lobbies itself never references what listens. MatchStarting is still broadcast either way.

sealed class DirectorLobbyBridge : ILobbyStartSink
{
    public void LobbyStarted(long lobbyId, string mode, IReadOnlyList<LobbyStartMember> members)
    {
        var instance = _director.Allocate(mode, members.Count);
        foreach (var m in members)
            _push.InstanceAssignment(m, instance); // local via session, remote via account+node
    }
}
services.AddSingleton<ILobbyStartSink, DirectorLobbyBridge>(); // before AddCrossplayLobbies
  • void LobbyStarted(long lobbyId, string mode, IReadOnlyList<LobbyStartMember> members)

    The host just started lobbyId with these members.

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

ILobbyService

Genre-neutral lobby directory: browsable, joinable game sessions with metadata + a host who can start the match. A session may be in at most one lobby at a time. Roster/state changes are pushed to members through the Core IRoomRegistry. Depends only on Core primitives (sessions + identity + rooms). Crossplay never inspects what a "match" means to the game.

  • IReadOnlyList<LobbyInfo> Browse()

    Snapshot of the browsable lobbies (open, public lobbies only), ordered by id.

  • bool TryCastVote(ISession session, byte optionIndex, out ushort reason)

    Casts (or changes) the caller's pick for the lobby's active vote.

  • bool TryCreate(ISession session, string name, string mode, int maxPlayers, bool isPrivate, string password, out long lobbyId, out ushort reason)

    Creates a lobby owned by session and adds it as the host. On success the new id is returned and the host is sent the initial roster. Returns false with reason set to a notification code.

  • bool TryJoin(ISession session, long lobbyId, string password, out ushort reason)

    Adds session to lobbyId (checking password/capacity/state). On success every member is sent the updated roster. Returns false with reason.

  • bool TryLeave(ISession session, out ushort reason)

    Removes session from whatever lobby it is in, reassigning the host or closing the lobby when it empties, and broadcasting the updated roster. Returns false with reason when the session is in no lobby.

  • bool TryStartMatch(ISession session, out ushort reason)

    Host-only: moves the caller's lobby to InProgress and broadcasts MatchStarting + the updated roster. Returns false with reason (not in a lobby / not host / already in progress).

  • bool TryStartVote(ISession session, string[] options, out ushort reason)

    Host opens a vote among opaque options — map names, mode keys, anything (a new vote replaces a stale one). Default: unsupported — the single-node service implements it; cross-node lobby votes are a future Redis-twin addition.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

LobbyOptions

Configurable lobby rules (defaults live here, never inline in the service/handlers).

  • int DefaultMaxPlayers { get; set; }

    Max-players used when a create request does not specify one (0 or less).

  • int MaxModeLength { get; set; }

    Maximum length of the game-defined mode tag.

  • int MaxNameLength { get; set; }

    Maximum lobby name length.

  • int MaxPasswordLength { get; set; }

    Maximum password length.

  • int MaxPlayersCap { get; set; }

    Largest max-players any lobby may be created with (requests are clamped down to this).

  • int MaxVoteOptionLength { get; set; }

    Longest allowed vote-option text.

  • int MaxVoteOptions { get; set; }

    Most options a single vote may offer.

  • int MinMaxPlayers { get; set; }

    Smallest max-players a lobby may be created with.

  • int MinNameLength { get; set; }

    Minimum lobby name length.

Wire messages 17

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

1401 LobbyCreateRequest

Client -> server: create a new lobby owned by the sender.

1402 LobbyCreateResponse

Server -> client: result of a LobbyCreateRequest.

1403 LobbyListRequest

Client -> server: request the browsable list of open, public lobbies.

1404 LobbyListResponse

Server -> client: the browsable lobby list (open, public lobbies only).

1405 LobbyJoinRequest

Client -> server: join the given lobby (with an optional password).

1406 LobbyJoinResponse

Server -> client: result of a LobbyJoinRequest.

1407 LobbyLeaveRequest

Client -> server: leave the lobby the sender is currently in.

1408 LobbyLeaveResponse

Server -> client: result of a LobbyLeaveRequest.

1409 LobbyStartMatchRequest

Client -> server: the host asks to start the match for their lobby.

1410 LobbyStartMatchResponse

Server -> client: result of a LobbyStartMatchRequest (host only).

1411 LobbyRoster

Server -> client: the current roster + metadata of a lobby, pushed to every member whenever membership, host, or state changes.

1412 MatchStarting

Server -> client: the host started the match; broadcast to every lobby member.

1413 LobbyVoteStartRequest

Client → server: the host opens a vote among OPAQUE options — map names, mode keys, anything the game encodes as strings. The framework only tallies; a new vote replaces a stale one.

1414 LobbyVoteStartResponse

Server → client: vote-start result.

1415 LobbyVote

Server → client: a vote began (broadcast to the lobby with the options).

1416 LobbyVoteCast

Client → server: my pick.

1417 LobbyVoteState

Server → client: tallies after every cast; when every member voted, the round is complete and WinnerIndex holds the result (ties break to the lowest index).

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

ILobbyClient

Data-layer lobby service (no UI): async create/list/join/leave/start RPCs plus events for the server-pushed roster/state and match-starting broadcasts. Self-registers under the Crossplay client context. Depends only on Crossplay Core.

  • event Action<LobbyRoster> RosterChanged

    Raised whenever the server pushes the roster/state of the lobby this client is in.

  • event Action<MatchStarting> MatchStarting

    Raised when the host starts the match for the lobby this client is in.

  • UniTask<LobbyCreateResponse> CreateAsync(string name, string mode, int maxPlayers, bool isPrivate, string password, CancellationToken ct = default)

    Creates a lobby owned by this client (public unless isPrivate).

  • UniTask<LobbyListResponse> ListAsync(CancellationToken ct = default)

    Requests the browsable list of open, public lobbies.

  • UniTask<LobbyJoinResponse> JoinAsync(long lobbyId, string password, CancellationToken ct = default)

    Joins a lobby by id (with an optional password).

  • UniTask<LobbyLeaveResponse> LeaveAsync(CancellationToken ct = default)

    Leaves the current lobby.

  • UniTask<LobbyStartMatchResponse> StartMatchAsync(CancellationToken ct = default)

    Host-only: starts the match for the current lobby.

  • UniTask<LobbyVoteStartResponse> StartVoteAsync(string[] options, CancellationToken ct = default)

    Host opens a vote among opaque options (map names, mode keys, anything).

  • void CastVote(byte optionIndex)

    Casts (or changes) my pick. Fire-and-forget — the broadcast state is the feedback.

  • event Action<LobbyVote> VoteStarted

    Raised when a vote begins.

  • event Action<LobbyVoteState> VoteStateChanged

    Raised after every cast (Complete carries the winner).

UI views you can replace 1

Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.

ICrossplayLobbiesView

Narrow contract the presenter drives. UI-only: render calls in, user intents out.