Lobby System

From HEWIKI
Jump to: navigation, search
Example Lobby Directory View

Contents

Overview

One of the most difficult balancing acts to carry out in an online game is the management and distribution of players that belong to conceptual game systems like parties, guilds, phases, and chat.  Membership often depends upon several factors that vary over time (the location of the player, the affiliation of the player with social groups, the player's current progression through game content, or a player's immediate functional needs) and, as a result, the population and state of these instances fluctuate wildly.  It's useful, then, to have a system in which players - regardless of where they are in the game world - can subscribe, unsubscribe, and interact as members of a given conceptual group quickly, efficiently and in a load-balanced manner.

We call these conceptual groups Lobbies and - with customization - they can be used to facilitate a variety of game mechanics which require tracking player membership and interaction.  Support is included to allow direct interaction with lobbies (via replication) from any arbitrary area server or player's client; this is useful in cases (like the traditional 'pre-game lobby') in which players are expected to immediately and directly interact with the lobby and each-other.

Behind the scenes, the Lobby System's logic is housed in a System Area.  This area is responsible for storing data on all currently instantiated Lobbies as well as facilitating communication (either directly through remote calls or indirectly through replication) between clients of the Lobby System and the system itself. The system is load-balanced to prevent over-crowding of any one lobby system worker instance and allows many sparsely-populated areas/systems' lobbies to be combined into a single area instance.

Possible use cases include:

What problems does this solve?

What problems does this not solve?

Organization

Lobby System Overall diagram

Areas of Responsibility

The Lobby System is comprised of four areas of responsibility, each of which is carries out its own piece of the overall logic:

The Control Instance is responsible for maintaining knowledge of all Worker Instances that exist as well as the contents of each of the worker instances.  This is accomplished by responding to spin-up and spin-down events for the worker instances as well as maintaining a set of lightweight objects containing the identity of its heavyweight object as well as a means of communicating with its heavyweight object (this is accomplished, as will be explained below, via replication of lightweight objects from the worker instance to which it belongs). All requests to discover or forget a lobby system entity are directed to the control instance, which - having knowledge of the identities and locations of entities - forwards the request to the appropriate worker instance.

The Worker Instance is responsible for creating, destroying and maintaining all lobby system entities.  When an entity is requested to be created, the creation is delegated from the control instance to an appropriate worker instance based upon the load balancing rules defined. Discovery requests made to the control instance are routed to the worker instances, which respond to these requests by 'revealing' (i.e. replicating) the entity to the requester. Likewise, requests to forget, delete, subscribe to, unsubscribe from, or modify an entity are routed through the control instance to the appropriate worker instance, which passes the request to the appropriate lobby system entity for processing.

The Game Area is defined as any area server instance that wants to act as a client of the lobby system by making requests of it.  This means the game area might be another system area, a communal hub area, or any other game area.  This area is responsible for making requests of the lobby system to create, destroy, subscribe players to, unsubscribe players from, or modify lobby system entities.  This is usually accomplished by - after creating an entity - discovering it and then responding to the discovery by performing specific actions upon it.  Additionally, the game area may make lobby system entities available to clients who also wish to interact with the entity.

The Player Client is involved only in situations in which the game area offers an interface to the client to interact with a lobby system entity.  Situations in which this would happen might include the joining of a player to a specific pre-game lobby (the appropriate response to which might be the revealing of the lobby to the client and the presentation of a lobby GUI) or the joining of a player to a specific chat channel (the appropriate response to which might be the updating of the player's chat GUI to reflect the new channel membership).

Entities

All objects within the Lobby System are assigned a unique identifier at the moment of creation. These IDs are used by external clients of the Lobby System to identify lobbies, directories or logical directories to which commands or requests should be directed. The public interface to the Lobby System uses these IDs exclusively as handles for objects and - as a result - it is the responsibility of clients of the Lobby System to remember the IDs of objects it is interested in.

The Lobby System is comprised of three basic types of objects:

Lobbies are contained within worker instances of the lobby system. They are responsible for tracking player membership as well as the state of the lobby. Additionally, they process all logic associated with interactions between players in the lobby and interactions between players and the lobby itself. Lobbies are spec-derived objects and provide hooks for extending or overriding their default behavior. Their expected usage includes handling external requests to add/remove players, modify the state of the lobby, and get replicated to other area servers or player clients. Finally, it should be noted that lobbies may be members of any number of Lobby Directories (commonly, a lobby will be a member of both a specific lobby directory as well as the master 'All Lobbies' directory).

Lobby Directories are responsible for tracking and maintaining Lobby objects. They offer a means of indexing and looking-up member lobbies during situations in which messages (e.g. requests for subscription or discovery) need to be delivered to a specific lobby. These directories may contain any number of lobbies, and each directory may itself be part of a collection associated with a Logical Lobby Directory.

Logical Lobby Directories are objects which associate themselves with one or more Lobby Directories. Due to the load-balanced nature of the Lobby System, lobby directories representing the same 'logical' directory may be spread across multiple worker instances; as a result, accessing a conceptual directory in its entirety requires bringing these lobby directories 'out' of their worker instances (via replication) and providing an interface to access the lobbies their merged collection contains. The Logical Lobby Directory provides this interface for clients of discovered directories.

Communication

Communication System Request

Communications between outside systems and the lobby system take one of two forms: System Requests or Object Requests

System Requests are requests that are made without direct access to any particular lobby system entity. They are always routed through the $_LOBBY system node and are always asynchronous due to the distributed nature of objects within the lobby system. They often involve the discovery or creation of objects in order to make them available locally.

Object Requests, in contrast, are requests that are made of an object once it has been made local(through creation and then discovery). Lobby system entities that have been localized may be communicated with directly and any information queried for will be returned synchronously. Requests to perform actions (such as 'Start the game' or 'Lock the lobby'), however, will still be performed asynchronously due to the fact that the authoritative object resides in the system area.

System Requests

Object Requests

Usage

The lobby system is comprised of several discrete pieces of functionality, one or more of which may be used in isolation or conjunction.

These are:

For a full 'game lobby' experience, all three pieces can be combined to provide both player tracking and client interaction capabilities.

For system-level logic (e.g. tracking and responding to live event participation, tracking regional chat, etc), discovery and client logic may be omitted.

Example Sequence of Operations

The basic sequence of operation for a traditional 'game lobby' experience might be:

  1. Create a lobby directory to serve as a directory of 'all games'
  2. Reveal this directory to a 'Pre-game Lobby' game area
  3. When players enter this area, reveal this directory to player clients (and display a GUI to them)
  4. When a player clicks 'New Game' in their GUI, instruct the lobby system to generate a new game lobby and join the player to it
  5. When other players click 'Join Game' in their GUI, instruct the lobby system to join the players to this lobby
  6. When players join a lobby, reveal the lobby to the player's client and open a new GUI representing the lobby
  7. When all players click 'Ready' in their lobby GUI, perform logic to 'start the match'

System Node Interface

The following operations may be requested of the lobby system from an external object:

Create/Destroy

Methods

To request the creation of a lobby system entity, the following methods are used:

//Requests the creation of the given lobby with the given sped key and creation data.
unique method _RequestCreateLobby(lobbySpecKey as ID, lobbyCreationData copies NodeRef of Class _LobbyCreationData, listener as NodeRef of Class ObsListener) as ID
 
//Requests the creation of a lobby directory from the given spec key with the provided initialization data. Upon success, the listener node
//will receive a message indicating either success or failure. The return value is the requestID used to identify the request upon callback.
unique method _RequestCreateLobbyDirectory(logicalLobbyDirectoryID as ID, lobbyDirectorySpecKey as ID, lobbyDirectoryCreationData copies NodeRef of Class _LobbyDirectoryCreationData, listener as NodeRef of Class ObsListener) as ID
 
//Requests the creation of a logical lobby directory from the given spec key with the provided initialization data. Upon success, the listener node
//will receive a message indicating either success or failure. The return value is the requestID used to identify the request upon callback.
unique method _RequestCreateLogicalLobbyDirectory(logicalLobbyDirectorySpecKey as ID, logicalLobbyDirectoryCreationData copies NodeRef of Class _LogicalLobbyDirectoryCreationData, listener as NodeRef of Class ObsListener) as ID

These methods will accept a 'specID' to determine the spec from which Lobbies, Lobby Directories, and Logical Lobby Directories are factoried. These methods will accept a 'creationData' node to specify additional properties with which a lobby system entity can use to initialize itself at the time of creation. Default versions of these structures may be created using the appropriate method on the lobby system node (e.g. $_LOBBY._GetLogicalLobbyDirectoryDefaultCreationData(...)) These methods will accept a 'listener' which will receive a notification callback when the request succeeds or fails These methods will return a 'requestID' which may be tracked and used by listeners to respond to specific lobby system requests.

To request the destruction of a lobby system entity, the following methods are used:

//Requests the destruction of the specified lobby. Upon success, the lobby will no longer be replicated to any areas or clients,
//and the lobby will be destroyed. A callback will also be issued to the requesting area, and this message will be propagated to
//all interested listeners.
unique method _RequestDestroyLobby(lobbyID as ID, listener as NodeRef of Class ObsListener) as ID
 
//Requests the destruction of the given logical lobby directory (and all child directories). Upon success, the directory will no longer be replicated
//to any areas or clients and it will be destroyed. A callback will be issued to the original requesting area which will be propagated to all interested
//listeners.
unique method _RequestDestroyLogicalLobbyDirectory(uniqueLogicalLobbyDirectoryIdentifier as ID, listener as NodeRef of Class ObsListener) as ID

These methods will accept a 'lobbyEntityID' to uniquely identify the lobby system entity to destroy These methods will accept a 'listener' which will receive a notification callback when the request succeeds or fails These methods will return a 'requestID' which may be tracked and used by listeners to respond to specific lobby system requests.

Example

The following HSL will create a new logical lobby directory, then create a new lobby beneath it.

method MyMainMethod()
  //Sequence of events is as follows:
  // 1. call _MyCreateLogicalLobbyDirectory to request the creation of a logical lobby directory
  // 2. wait for a callback in EventRaised of type 'LLD_CREATE_SUCCEEDED'; respond by calling _MyCreateLobby
  // 3. wait for a callback in EventRaised of type 'L_CREATE_SUCCEEDED'; respond by printing "Success!"
  me._MyCreateLogicalLobbyDirectory("My Logical Lobby Directory")
.
 
method MyCreateLogicalLobbyDirectory(lldName as string)
  requestID as ID = $_LOBBY._RequestCreateLogicalLobbyDirectory(1, $_LOBBY._GetLogicalLobbyDirectoryDefaultCreationData(lldName), me)
  println("We just made a request to create a Logical Lobby Directory with name " + lldName + "! We'll wait for a callback containing requestID " + requestID + " and respond accordingly!")
.
 
method MyCreateLobby(lldID as ID, lName as string)
  requestID as ID = $_LOBBY._RequestCreateLobby(1, $_LOBBY._GetLobbyDefaultCreationData(lName, lldID) , me)
  println("We just made a request to create a Lobby with name " + lName + "! We'll wait for a callback containing requestID " + requestID + " and respond accordingly!")
.
 
method EventRaised( obs as NodeRef of Class ObsSubject, data as NodeRef )
  where data is kindof eventObject
    lobbySystemEntityID as ID = data.eventAffectsID
    when data.EventType
      is "LLD_CREATE_SUCCEEDED"
        me.MyCreateLobby(lobbySystemEntityID, "My Lobby")
      .
      is "L_CREATE_SUCCEEDED"
        println("Success!")
      .
      is "L_CREATE_FAILED"
        println("Uh oh!")
      .
    .
  .
.

Subscribe/Unsubscribe

Methods

To request the joining of a a player to a lobby, the following methods are used:

//Requests the joining of the specified player to the designated lobby. Upon success, the listener node will receive a callback containing the original
//request ID as well as a success or failure message.
unique method _RequestPlayerJoinLobby(playerID as ID, lobbyID as ID, listener as NodeRef of Class ObsListener) as ID
 
//Requests the leaving of the specified player from the designated lobby. Upon success, the listener node will receive a callback containing the original
//request ID as well as a success or failure message.
unique method _RequestPlayerLeaveLobby(playerID as ID, lobbyID as ID, listener as NodeRef of Class ObsListener) as ID
Example

The following will join a player to - and then immediately leave - a lobby.

method MyMainMethod()
  //Sequence of events is as follows:
  // 1. call _MyPlayerJoinLobby to request the joining of the player to the lobby
  // 2. wait for a callback in EventRaised of type 'L_PLAYER_JOIN_SUCCEEDED'; respond by calling _MyPlayerLeaveLobby 
  // 3. wait for a callback in EventRaised of type 'L_CREATE_SUCCEEDED'; respond by printing "Success!"
  theLobbyID as ID = 8675309
  thePlayerID as ID = 112358132134
  me._MyPlayerJoinLobby(theLobbyID, thePlayerID)
.
 
method _MyPlayerJoinLobby(lobbyID as ID, playerID as ID)
  requestID as ID = $_LOBBY._RequestPlayerJoinLobby(playerID, lobbyID , me)
  println("We just made a request to cause player " + playerID + " to join lobby " + lobbyID + "! We'll wait for a callback containing requestID " + requestID + " and respond accordingly!")
.
 
method _MyPlayerLeaveLobby(lldID as ID, playerID as ID)
  requestID as ID = $_LOBBY._RequestPlayerLeaveLobby(playerID, lobbyID , me)
  println("We just made a request to cause player " + playerID + " to leave lobby " + lobbyID + "! We'll wait for a callback containing requestID " + requestID + " and respond accordingly!")
.
 
method EventRaised( obs as NodeRef of Class ObsSubject, data as NodeRef )
  where data is kindof eventObject
    lobbySystemEntityID as ID = data.eventAffectsID
    when data.EventType
      is "L_PLAYER_JOIN_SUCCEEDED"
       thePlayerID as ID = 112358132134
        me._MyPlayerLeaveLobby(lobbySystemEntityID, thePlayerID)
      .
      is "L_PLAYER_LEAVE_SUCCEEDED"
        println("Success!")
      .
      is "L_PLAYER_JOIN_FAILED"
        println("Uh oh!")
      .
      is "L_PLAYER_LEAVE_FAILED"
        println("Uh oh!")
      .
    .
  .
.

Discover/Forget

Methods

To request the discovery of lobby system area (either for an area server or for a client), the following methods are used:

//Requests the discovery of the given lobby; upon success, the lobby will be replicated to the area from which this request originated.
unique method _RequestDiscoverLobby(lobbyID as ID, listener as NodeRef of Class ObsListener) as ID
 
//Requests the discovery of the given logical lobby directory; upon success, the logical lobby directory (and all of its child lobby directories)
//will be replicated to the area from which this request originated.
unique method _RequestDiscoverLogicalLobbyDirectory(logicalLobbyDirectoryID as ID, listener as NodeRef of Class ObsListener) as ID
 
//Requests the forgetting of the given lobby; upon success, the lobby will no longer be replicated to the area from which this request originated.
unique method _RequestForgetLobby(lobbyID as ID, listener as NodeRef of Class ObsListener) as ID
 
//Requests the forgetting of the given logical lobby directory; upon success, the logical lobby directory (and all of its child lobby directories)
//will no longer be replicated to the area from which this request originated.
unique method _RequestForgetLogicalLobbyDirectory(logicalLobbyDirectoryID as ID, listener as NodeRef of Class ObsListener) as ID
 
//On logical lobby directory object
method _RevealLogicalLobbyDirectoryToClient(clientID as ID)
method _HideLogicalLobbyDirectoryFromClient(clientID as ID)
 
//On lobby object
proxyLocal method _RevealLobbyToClient(clientID as ID)
proxyLocal method _HideLobbyFromClient(clientID as ID)
Example

The following will discover the master 'All Lobbies' directory and reveal it to the area server, then reveal the directory to a client.

method MyMainMethod()
  //Sequence of events is as follows:
  // 1. call _MyDiscoverMasterDirectory to request the discovery of the 'All Lobbies' directory
  // 2. wait for a callback in EventRaised of type 'LLD_DISCOVER_SUCCEEDED'; respond by calling _MyClientDiscoverMasterDirectory 
  // 3. let the client do it's thing
  theLldID as ID = 1
  thePlayerID as ID = 112358132134
  me._MyDiscoverMasterDirectory(theLldID)
.
 
method _MyDiscoverMasterDirectory(lldID as ID)
  requestID as ID = $_LOBBY._RequestDiscoverLogicalLobbyDirectory(lldID , me)
  println("We just made a request to discover logical lobby directory " + lldID + "! We'll wait for a callback containing requestID " + requestID + " and respond accordingly!")
.
 
method _MyClientDiscoverMasterDirectory(lldID as ID, playerID as ID)
  logicalLobbyDirectory as noderef of class _LogicalLobbyDirectory = $_LOBBY._GetLogicalLobbyDirectory(lldID)
  logicalLobbyDirectory._RevealLogicalLobbyDirectoryToClient(thePlayerID)
.
 
method EventRaised( obs as NodeRef of Class ObsSubject, data as NodeRef )
  where data is kindof eventObject
    lobbySystemEntityID as ID = data.eventAffectsID
    when data.EventType
      is "LLD_DISCOVER_SUCCEEDED"
       thePlayerID as ID = 112358132134
        me._MyClientDiscoverMasterDirectory(lobbySystemEntityID, thePlayerID)
      .
      is "LLD_DISCOVER_FAILED"
        println("Uh oh!")
      .
    .
  .
.

Local Object Access

Once a lobby system entity - such as a lobby - has been made local via discovery, it may be queried synchronously and requests may be made directly. Note that the local entity is a replicated proxy of the authoritative node and that any request that must be processed in the system area (such as one that would require changing properties on the node) will be carried out asynchronously.

Example

method MyMainMethod(mySimpleGameLobby as NodeRef of Class _SimpleGameLobby, thePlayerID as ID)
  isReady as boolean
 
  //Get readiness state
  isReady = mySimpleGameLobby._GetSimpleGameLobbyPlayerReadyState(thePlayerID)
  println("It is " + isReady + " that the player is ready")
 
  //Set readiness state (note: asynchronous!)
  mySimpleGameLobby._SetSimpleGameLobbyPlayerReadyState(thePlayerID, not isReady)
 
  //Get readiness state again
  isReady = mySimpleGameLobby._GetSimpleGameLobbyPlayerReadyState(thePlayerID)
  println("It is STILL " + isReady + " that the player is ready, because our request to change the readiness state is asynchronous!")
.

Extending the Lobby System

Procedure

Extending the lobby system can be accomplished in one of several ways:

Script and Class References

Lobby System:

Lobby System Entities:

Examples of Extensions:

References

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox