lobby

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 22, 2026 License: LGPL-3.0 Imports: 3 Imported by: 0

README

Lobby Plugin

A flexible lobby/party system for Cardinal worlds, handling player grouping and session management. Implemented as a Cardinal plugin.

Quick Start

import (
    "github.com/argus-labs/world-engine/pkg/cardinal"
    "github.com/argus-labs/world-engine/pkg/lobby"
)

func main() {
    world, _ := cardinal.NewWorld(cardinal.WorldOptions{...})

    cardinal.RegisterPlugin(world, lobby.NewPlugin(lobby.Config{
        LobbyWorld: cardinal.OtherWorld{
            Region:       "us-west",
            Organization: "myorg",
            Project:      "myproject",
            ShardID:      "lobby-shard-1",
        },
    }))

    world.StartGame()
}

Overview

  • PvE: Lobby acts as a persistent party that plays games together
  • PvP: Lobby acts as a temporary session where multiple teams gather

Message Categories

The lobby package uses 4 message categories:

1. Command (Client ↔ Shard)

Client sends a command to the shard. Shard processes it and returns a result to the sender.

Command Request Fields Response Fields
CreateLobby Teams []TeamConfig, GameWorld, PlayerPassthroughData?, SessionPassthroughData? IsSuccess, Message, Lobby
JoinLobby InviteCode, TeamName?, PlayerPassthroughData? IsSuccess, Message, Lobby
JoinTeam TeamName IsSuccess, Message
LeaveLobby - IsSuccess, Message
SetReady IsReady IsSuccess, Message
KickPlayer TargetPlayerID IsSuccess, Message
TransferLeader TargetPlayerID IsSuccess, Message
StartSession - IsSuccess, Message
GenerateInviteCode - IsSuccess, Message, InviteCode
UpdateSessionPassthrough PassthroughData IsSuccess, Message
UpdatePlayerPassthrough PassthroughData IsSuccess, Message
Heartbeat - - (no response)

All commands include RequestID for request/response correlation (except Heartbeat).

2. Event (Shard → All Clients, broadcast)

Shard broadcasts state changes to all subscribed clients.

Event Fields Description
LobbyCreatedEvent LobbyID, LeaderID, InviteCode Lobby created
PlayerJoinedEvent LobbyID, TeamName, Player (PlayerState) Player joined
PlayerLeftEvent LobbyID, PlayerID Player left
PlayerKickedEvent LobbyID, PlayerID, KickerID Player kicked
PlayerReadyEvent LobbyID, PlayerID, IsReady Ready status changed
PlayerChangedTeamEvent LobbyID, PlayerID, OldTeamName, NewTeamName Player changed team
LeaderChangedEvent LobbyID, OldLeaderID, NewLeaderID Leadership transferred
SessionStartedEvent LobbyID Session started
SessionEndedEvent LobbyID Session ended
InviteCodeGeneratedEvent LobbyID, InviteCode New code generated
LobbyDeletedEvent LobbyID Lobby deleted (empty)
PlayerTimedOutEvent LobbyID, PlayerID Player removed due to missed heartbeats
SessionPassthroughUpdatedEvent Lobby Session passthrough data updated
PlayerPassthroughUpdatedEvent Lobby, PlayerID Player passthrough data updated
3. CrossShardCommand (Shard → Shard)

One shard sends a command to another shard.

Command Direction Fields Description
NotifySessionStartCommand Lobby → Game Lobby, LobbyWorld Notify game shard to start session
NotifySessionEndCommand Game → Lobby LobbyID Notify lobby shard to end session
4. CrossShardResult (Shard → Shard, response)

Shard sends a response back to the originating shard.

Currently not needed. CrossShardCommands in this package don't require responses.

Data Structures

Lobby
Lobby
- ID string              // Unique identifier
- LeaderID string        // Player who controls the lobby
- Teams []Team           // List of teams
- InviteCode string      // Code for others to join
- GameWorld OtherWorld   // Target game shard address
- Session Session        // Current session state
- CreatedAt int64        // Unix timestamp
Team
Team
- TeamID string          // Unique team identifier
- Name string            // Team display name
- Players []PlayerState  // Players in team
- MaxPlayers int         // Max players (0 = unlimited)
PlayerState
PlayerState
- PlayerID string                 // Player identifier
- IsReady bool                    // Ready status
- PassthroughData map[string]any  // Forwarded to game shard
- JoinedAt int64                  // Unix timestamp when player joined
Session
Session
- State SessionState             // idle | in_session
- PassthroughData map[string]any // Forwarded to game shard

Configuration

Option Description Default
LobbyWorld This lobby shard's address (for game shard to send NotifySessionEndCommand back) required
Provider Custom provider (optional, default provided) DefaultProvider
HeartbeatTimeout Seconds before a player is removed for not sending heartbeats. Clients should send heartbeats more frequently (e.g., every timeout/3 seconds). 30

Cross-Shard Communication

When StartSessionCommand succeeds, NotifySessionStartCommand is sent to game shard:

type NotifySessionStartCommand struct {
    Lobby      LobbyComponent    // Full lobby data (includes GameWorld)
    LobbyWorld cardinal.OtherWorld // For NotifySessionEndCommand callback
}

Game shard should:

  1. Register a system that handles NotifySessionStartCommand
  2. Run the game
  3. Send NotifySessionEndCommand back to lobby when game ends
// In game shard
func GameSessionSystem(state *GameSessionSystemState) {
    for cmd := range state.SessionStartCmds.Iter() {
        payload := cmd.Payload
        // payload.Lobby contains full lobby data
        // payload.LobbyWorld for sending NotifySessionEndCommand back

        // When game ends:
        payload.LobbyWorld.SendCommand(&state.BaseSystemState, lobby.NotifySessionEndCommand{
            LobbyID: payload.Lobby.ID,
        })
    }
}

Custom Provider

Override invite code generation:

type MyProvider struct {
    lobby.DefaultProvider
}

func (p MyProvider) GenerateInviteCode(l *lobby.Component) string {
    return generateMyCustomCode(8)
}

cardinal.RegisterPlugin(world, lobby.NewPlugin(lobby.Config{
    LobbyWorld: cardinal.OtherWorld{...},
    Provider:   MyProvider{},
}))

Default: Hash(LobbyID + Timestamp) -> 6-char uppercase alphanumeric (excludes confusing chars: 0, O, I, L, 1).

Lifecycle

CreateLobby -> idle -> (players join, ready up) -> StartSession -> in_session -> EndSession -> idle
                                                                                      │
                                                                                      v
                                                                        (players can start again)

Key Behaviors

  • Invite code persists on leader transfer/leave (no auto-regeneration)
  • Leader-only actions: kick, transfer, start session, regenerate code
  • Ready check: All players (including leader) must be ready to start
  • Session lock: No join/team-change during in_session (leave is always allowed)
  • Auto-reset: After session ends, all players reset to not-ready
  • Auto-delete: Lobby deleted when last player leaves
  • Per-lobby game shard: Each lobby specifies its target game shard via GameWorld

Heartbeat System

The lobby package includes a heartbeat mechanism to detect and remove disconnected players. It uses a deadline-based (lease) approach where clients must continuously renew their presence.

How It Works
  1. Clients send HeartbeatCommand periodically (e.g., every HeartbeatTimeout/3 seconds)
  2. Server stores a deadline for each player: deadline = now + HeartbeatTimeout
  3. On each heartbeat, the deadline is extended: deadline = now + HeartbeatTimeout
  4. Server removes players when now >= deadline (player failed to renew their lease)
  5. Events emitted: PlayerTimedOutEvent when a player times out
Deadline Approach

When a player joins or sends a heartbeat:

deadline = currentTime + HeartbeatTimeout

When checking for timeouts (every tick):

if currentTime >= deadline {
    // Player timed out - remove from lobby
}

This approach is simpler than tracking "last heartbeat time" because:

  • No subtraction needed at check time (now >= deadline vs now - lastHeartbeat > timeout)
  • The deadline is the actual removal time, making debugging easier
Configuration
cardinal.RegisterPlugin(world, lobby.NewPlugin(lobby.Config{
    LobbyWorld:       cardinal.OtherWorld{...},
    HeartbeatTimeout: 30, // Remove player after 30 seconds without heartbeat
}))
Client Implementation

Clients should:

  1. Start a heartbeat timer immediately after creating or joining a lobby
  2. Send HeartbeatCommand at the configured interval
  3. Stop the timer after leaving the lobby
// Client-side pseudocode
ticker := time.NewTicker(10 * time.Second)
go func() {
    for range ticker.C {
        sendCommand(lobby.HeartbeatCommand{})
    }
}()

Important: Start heartbeats immediately after joining. The timeout clock starts the moment a player joins, not when they send their first heartbeat.

Timeout Behavior

When a player joins a lobby, their deadline is set to now + timeout. This means:

  • Players must send heartbeats before their deadline expires or they are removed
  • This prevents "zombie" players who join but never send heartbeats from occupying slots indefinitely

When a player times out:

  • Player is removed from the lobby
  • PlayerTimedOutEvent is emitted
  • If player was leader, leadership auto-transfers to another player
  • If lobby becomes empty, it is deleted

Documentation

Overview

Package lobby provides a flexible lobby/party system for Cardinal worlds.

This package handles player grouping and session management. Players can create lobbies, invite friends via invite codes, form teams, ready up, and start sessions.

Usage:

world := cardinal.NewWorld(cardinal.WorldOptions{...})
cardinal.RegisterPlugin(world, lobby.NewPlugin(lobby.Config{
	LobbyWorld: myLobbyWorld,
}))
world.StartGame()

The package registers the following systems:

  • InitSystem (Init hook): Creates singleton index and config entities
  • LobbySystem (Update hook): Processes lobby commands

Commands:

  • CreateLobby: Player creates a new lobby, becomes leader
  • JoinLobby: Player joins via invite code
  • JoinTeam: Player moves to a different team
  • LeaveLobby: Player leaves current lobby
  • SetReady: Player marks ready/unready
  • KickPlayer: Leader removes a player
  • TransferLeader: Leader gives leadership to another
  • StartSession: Leader starts the session
  • EndSession: End the current session
  • GenerateInviteCode: Leader generates new invite code

Index

Constants

View Source
const (
	SessionStateIdle      = component.SessionStateIdle
	SessionStateInSession = component.SessionStateInSession
)

Session states.

Variables

This section is empty.

Functions

This section is empty.

Types

type Component

type Component = component.LobbyComponent

Data structures.

type Config

type Config struct {
	// LobbyWorld is this lobby shard's address.
	// Included in NotifySessionStartCommand so game shard can send NotifySessionEndCommand back.
	LobbyWorld cardinal.OtherWorld

	// Provider is the customizable provider for the lobby system.
	// If nil, DefaultProvider is used.
	Provider Provider

	// HeartbeatTimeout is how long (in seconds) before a player is removed for not sending heartbeats.
	// Clients should send heartbeats more frequently than this (e.g., every timeout/3 seconds).
	// Default: 30 seconds.
	HeartbeatTimeout int64
}

Config holds configuration for the lobby package.

type ConfigComponent

type ConfigComponent = component.ConfigComponent

Re-export types for easier user access.

type CreateLobbyCommand

type CreateLobbyCommand = system.CreateLobbyCommand

Commands.

type CreateLobbyResult

type CreateLobbyResult = system.CreateLobbyResult

CommandResult (persona-prefixed responses).

type CreatedEvent

type CreatedEvent = system.LobbyCreatedEvent

Events (Broadcast).

type DefaultProvider

type DefaultProvider = system.DefaultProvider

Re-export types for easier user access.

type DeletedEvent

type DeletedEvent = system.LobbyDeletedEvent

Re-export types for easier user access.

type GameWorld

type GameWorld = cardinal.OtherWorld

Re-export types for easier user access.

type GenerateInviteCodeCommand

type GenerateInviteCodeCommand = system.GenerateInviteCodeCommand

Re-export types for easier user access.

type GenerateInviteCodeResult

type GenerateInviteCodeResult = system.GenerateInviteCodeResult

Re-export types for easier user access.

type GetAllPlayersCommand

type GetAllPlayersCommand = system.GetAllPlayersCommand

Re-export types for easier user access.

type GetAllPlayersResult

type GetAllPlayersResult = system.GetAllPlayersResult

Re-export types for easier user access.

type GetPlayerCommand

type GetPlayerCommand = system.GetPlayerCommand

Re-export types for easier user access.

type GetPlayerResult

type GetPlayerResult = system.GetPlayerResult

Re-export types for easier user access.

type HeartbeatCommand

type HeartbeatCommand = system.HeartbeatCommand

Re-export types for easier user access.

type IndexComponent

type IndexComponent = component.LobbyIndexComponent

Re-export types for easier user access.

type InviteCodeGeneratedEvent

type InviteCodeGeneratedEvent = system.InviteCodeGeneratedEvent

Re-export types for easier user access.

type JoinLobbyCommand

type JoinLobbyCommand = system.JoinLobbyCommand

Re-export types for easier user access.

type JoinLobbyResult

type JoinLobbyResult = system.JoinLobbyResult

Re-export types for easier user access.

type JoinTeamCommand

type JoinTeamCommand = system.JoinTeamCommand

Re-export types for easier user access.

type JoinTeamResult

type JoinTeamResult = system.JoinTeamResult

Re-export types for easier user access.

type KickPlayerCommand

type KickPlayerCommand = system.KickPlayerCommand

Re-export types for easier user access.

type KickPlayerResult

type KickPlayerResult = system.KickPlayerResult

Re-export types for easier user access.

type LeaderChangedEvent

type LeaderChangedEvent = system.LeaderChangedEvent

Re-export types for easier user access.

type LeaveLobbyCommand

type LeaveLobbyCommand = system.LeaveLobbyCommand

Re-export types for easier user access.

type LeaveLobbyResult

type LeaveLobbyResult = system.LeaveLobbyResult

Re-export types for easier user access.

type NotifySessionEndCommand

type NotifySessionEndCommand = system.NotifySessionEndCommand

Re-export types for easier user access.

type NotifySessionStartCommand

type NotifySessionStartCommand = system.NotifySessionStartCommand

Cross-Shard Commands.

type PlayerChangedTeamEvent

type PlayerChangedTeamEvent = system.PlayerChangedTeamEvent

Re-export types for easier user access.

type PlayerComponent

type PlayerComponent = component.PlayerComponent

Re-export types for easier user access.

type PlayerJoinedEvent

type PlayerJoinedEvent = system.PlayerJoinedEvent

Re-export types for easier user access.

type PlayerKickedEvent

type PlayerKickedEvent = system.PlayerKickedEvent

Re-export types for easier user access.

type PlayerLeftEvent

type PlayerLeftEvent = system.PlayerLeftEvent

Re-export types for easier user access.

type PlayerPassthroughUpdatedEvent

type PlayerPassthroughUpdatedEvent = system.PlayerPassthroughUpdatedEvent

Re-export types for easier user access.

type PlayerReadyEvent

type PlayerReadyEvent = system.PlayerReadyEvent

Re-export types for easier user access.

type PlayerTimedOutEvent

type PlayerTimedOutEvent = system.PlayerTimedOutEvent

Re-export types for easier user access.

type Plugin added in v0.11.0

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin implements cardinal.Plugin for the lobby system.

func NewPlugin added in v0.11.0

func NewPlugin(config Config) *Plugin

NewPlugin creates a new lobby plugin with the given configuration.

func (*Plugin) Register added in v0.11.0

func (p *Plugin) Register(world *cardinal.World)

Register implements cardinal.Plugin.

type Provider

type Provider = system.LobbyProvider

Provider.

type Session

type Session = component.Session

Re-export types for easier user access.

type SessionEndedEvent

type SessionEndedEvent = system.SessionEndedEvent

Re-export types for easier user access.

type SessionPassthroughUpdatedEvent

type SessionPassthroughUpdatedEvent = system.SessionPassthroughUpdatedEvent

Re-export types for easier user access.

type SessionStartedEvent

type SessionStartedEvent = system.SessionStartedEvent

Re-export types for easier user access.

type SessionState

type SessionState = component.SessionState

Re-export types for easier user access.

type SetReadyCommand

type SetReadyCommand = system.SetReadyCommand

Re-export types for easier user access.

type SetReadyResult

type SetReadyResult = system.SetReadyResult

Re-export types for easier user access.

type StartSessionCommand

type StartSessionCommand = system.StartSessionCommand

Re-export types for easier user access.

type StartSessionPayload

type StartSessionPayload = system.StartSessionPayload // Alias for NotifySessionStartCommand

Re-export types for easier user access.

type StartSessionResult

type StartSessionResult = system.StartSessionResult

Re-export types for easier user access.

type Team

type Team = component.Team

Re-export types for easier user access.

type TeamConfig

type TeamConfig = system.TeamConfig

Re-export types for easier user access.

type TransferLeaderCommand

type TransferLeaderCommand = system.TransferLeaderCommand

Re-export types for easier user access.

type TransferLeaderResult

type TransferLeaderResult = system.TransferLeaderResult

Re-export types for easier user access.

type UpdatePlayerPassthroughCommand

type UpdatePlayerPassthroughCommand = system.UpdatePlayerPassthroughCommand

Re-export types for easier user access.

type UpdatePlayerPassthroughResult

type UpdatePlayerPassthroughResult = system.UpdatePlayerPassthroughResult

Re-export types for easier user access.

type UpdateSessionPassthroughCommand

type UpdateSessionPassthroughCommand = system.UpdateSessionPassthroughCommand

Re-export types for easier user access.

type UpdateSessionPassthroughResult

type UpdateSessionPassthroughResult = system.UpdateSessionPassthroughResult

Re-export types for easier user access.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL