Host migration is the process of transferring ownership of the game from one host to another. This can happen in a variety of ways, that fall broadly into three categories:
CRYENGINE handles all three cases, with a degree of programmer control. In addition, the host migration process is monitored so that if the new host quits, crashes or suffers a power loss, another host is chosen until either there are no more clients in the game or the host migration time-limit is reached (known as a multi-migration event).
Host migration is session based, and each session can migrate independently (and concurrently). Sessions need to be created as migratable (or not) by supplying the CRYSESSION_CREATE_FLAG_MIGRATABLE flag to ICryMatchMaking::SessionCreate().
It is also a controllable feature of the lobby service; an individual lobby service can be configured to not support host migration (the default is to support it, via the eCLSF_SupportHostMigration flag) so that a game title can support host migration only on selected platforms if required (e.g. host migration only makes sense in a peer hosted environment; if one platform is using dedicated servers, then it can be prevented from migrating sessions). A lobby service can be queried for host migration support as follows:
bool hostMigrationSupported = gEnv->pNetwork->GetLobby()->GetLobbyServiceFlag(<lobby_service_type>, eCLSF_SupportHostMigration);
where <lobby_service_type> is eCLS_LAN or eCLS_Online.
The determination of the 'best' host is decided by hints that are synchronised between all clients in a game. The criteria for determining the best host are as follows:
The hint list is maintained by the current host for as long as the session is active (and supports host migration), and propagated to all clients.
There are a number of console variables that can be used both to control host migration, and to show information about the hint list and status of each machine:
The host migration process passes through a number of stages, and at each stage certain actions must be performed. To facilitate this, classes can register interest in host migration events by way of a listener interface pattern.
Firstly, your class must inherit from IHostMigrationEventListener (defined in INetwork.h):
struct IHostMigrationEventListener
{
virtual void OnInitiate(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnDisconnectClient(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnDemoteToClient(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnPromoteToServer(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnReconnectClient(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnFinalise(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnTerminate(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
virtual void OnReset(SHostMigrationInfo& hostMigrationInfo, uint32& state) = 0;
};
Secondly your class must register itself with the network layer. This is best done in the constructor of the class with the following:
gEnv->pNetwork->AddHostMigrationEventListener(this, "<your_class_name>");
where <your_class_name> is just a text string that's used to aid debugging listeners.
A corresponding call to unregister your class should go in its destructor:
gEnv->pNetwork->RemoveHostMigrationEventListener(this);
Once registered, your class will have the methods defined in IHostMigrationEventListener invoked during a host migration event. Each method takes two parameters (supplied by the network layer), namely a reference to a SHostMigrationInfo structure which contains information for this host migration event, and a reference to the internal state of the listener (see below). The methods are called in the order they are declared in the interface:
Each method returns a boolean, with true meaning 'all processing for this stage is complete', and false meaning 'more processing is required'. Only when all the registered listeners for a given state have returned true does the host migration process move onto the next state. This allows asynchronous tasks such as disconnecting or reconnecting to fully complete before moving onto the next state (the passed reference 'state' is guaranteed to be 0 the first time the listener is invoked, and will persist its value on subsequent invocations, until the listener completes - useful for controlling switch based state machines).
CRYENGINE handles most of the work required to migrate a session to a new host, but the game will have some responsibilities.
Firstly, after a client connects to the host there is a period of time where the newly connected client simply doesn't have enough information to assume host responsibilities, should the host leave or fail catastrophically. To mitigate this, all clients will initially report that they are unsuitable as a host of any session they connect to, by way of reporting undesirable host hint information to the current host. Once the game deems that the client has sufficient information to be able to assume host responsibilities for the session, the game must at least do the following:
ICryMatchMaking* pMatchMaking = gEnv->pNetwork->GetLobby()->GetLobbyService()->GetMatchMaking();
pMatchMaking->SessionSetLocalFlags(<game_session_handle>, CRYSESSION_LOCAL_FLAG_HOST_MIGRATION_CAN_BE_HOST, NULL, NULL, NULL);
where <game_session_handle> is the session handle that was returned in the callback from SessionCreate(). The NULL parameters in the example code are the normal lobby/matchmaking callback parameters, namely a CryLobbyTaskID*, CryMatchmakingFlagsCallback and void* callback argument, and are omitted here for brevity. It is important to note that this must be done for each session that the game controls and wishes this client to be considered as host if a host migration event is triggered.
During a migration event for a game that is in progress, clients go through several of the context establishment tasks that are required when connecting to a new server, however they skip the tear-down stages. It is therefore up to the game to decide what is no longer needed, and to throw it away as necessary. This should be done by creating a list of entities that will no longer be needed post-migration in the OnDemoteToClient() listener, and then removing these in the OnFinalise() listener. Note that with a migration event, it is possible for a client to start migrating to a new host (remaining a client) but for that host to leave (either in a controlled or catastrophic manner) and for this client to subsequently become the host (a multi-migrate event). This is an important point to remember as it means that during a multi-migrate event, the list of entities created in the OnDemoteToClient() listener mustn't be deleted until the OnFinalise() listener (which is the point when host migration has essentially completed) otherwise the client who is becoming the host will have no knowledge of the entities which it is supposed to assume control of. This also means that the entity list on any client who remains a client during a multi-migrate event might gain duplicate entries as the OnDemoteToClient() listener will be called more than once. In this situation, it is safe to immediately discard the duplicates.
The game is also responsible for making sure that reconnecting clients are given the correct actor. This is done inside CGameRules::SetChannelForMigratingPlayer() and CGameRules::OnClientConnect():
During a migration event on Xbox LIVE, the SessionID is changed, and this will cause the .m_SID component of the SConnectionUID for all lobby connections to be changed. In order to match an update to it's associated player, SConnectionUIDs should only use the .m_UID component for comparison purposes.