Hello!
I am trying to handle a host crash, and do what need to be done for a host migration.
My setup is very simple (for the sake of the test) and uses Lobby + Relay + Authentication + NGO:
For the host:
- Authenticate
- Use the Relay Service to create an allocation using CreateAllocationAsync
- Get the Relay join code
- Create a Lobby with important options (playerID, allocationID)
- Subscribe to lobby events callbacks
- Store the relay join code in the lobby data
- Set the HostRelayData on the UnityTransport
- Start Host
For the client:
- Authenticate
- Quick Join Lobby
- Subscribe to lobby events callbacks
- Create a JoinAllocation using the data from the lobby
- Update the player data with the joinAllocation allocationID
- Set ClientRelayData on the UnityTransport
- Start Client
With this flow, my host and my clients are connecting correctly to the lobby (the host receives a PlayerJoined callback).
The problems comes from when I “crash” my host. The client never receives a PlayerLeft callback from the lobby, it just disconnect and gets kicked from the lobby after a set amount of time.
According to the documentation :
If the host is removed due to inactivity, the underlying relay connection is destroyed. To continue receiving automatic disconnects, the new host must create a new relay, and then have the other players join it and set their Allocation ID/ Connection info per the instructions in the Relay integrations section.
However, from my tests, because the relay is destroyed, the clients never receive the PlayerLeft message, the just get kicked from the lobby for timing out, making it impossible to migrate the host.
What is the correct way to hand this scenario? Or am I missing an important piece of the puzzle?
Thank you!
Here is the code for my LObbyManager that handle 90% of the logic that I am talking about above. The rest is a RelayManager that just andle the CreateAllocation and JoinAllocation
> public class LobbyManager : Singleton<LobbyManager>
> {
> private Lobby _currentLobby;
>
> private const string k_keyJoinCode = "RelayJoinCode";
>
> private Coroutine heartbeatCoroutine;
>
> protected override void Awake()
> {
> base.Awake();
> }
>
> public async Task<bool> CreateLobby(int maxPlayer, string lobbyName)
> {
> Debug.Log("Create Lobby Called");
>
> if (!AuthenticationService.Instance.IsSignedIn)
> {
> Debug.Log("Auth not signed in");
> return false;
> }
> try
> {
> Debug.Log("Allocating");
> Allocation allocation = await RelayManager.Instance.AllocateRelay(maxPlayer);
>
> Debug.Log("Getting Relay join code");
> string relayJoinCode = await RelayManager.Instance.GetRelayJoinCode(allocation);
>
> CreateLobbyOptions options = new CreateLobbyOptions()
> {
> IsPrivate = false,
> Player = new Player(id: AuthenticationService.Instance.PlayerId,
> allocationId: allocation.AllocationId.ToString()),
>
> };
> Debug.Log("Creating lobby");
> _currentLobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName + Random.Range(0, 100), maxPlayer, options);
>
> await SubscribeToLobbyCallbacks();
>
> UpdateLobbyOptions updateOption = new UpdateLobbyOptions()
> {
> Data = new Dictionary<string, DataObject> {
> { k_keyJoinCode, new DataObject(DataObject.VisibilityOptions.Member,relayJoinCode)}
> }
> };
>
> try
> {
> await LobbyService.Instance.UpdateLobbyAsync(_currentLobby.Id, updateOption);
> }
> catch (LobbyServiceException e)
> {
> Debug.Log(e);
> }
>
>
> heartbeatCoroutine = StartCoroutine(HeartbeatCoroutine(_currentLobby.Id, 6.0f));
>
> NetworkManager.Singleton.GetComponent<UnityTransport>().SetHostRelayData(allocation.RelayServer.IpV4,
> (ushort)allocation.RelayServer.Port,
> allocation.AllocationIdBytes,
> allocation.Key,
> allocation.ConnectionData);
>
> NetworkManager.Singleton.StartHost();
>
>
> return true;
> }
> catch (LobbyServiceException e)
> {
> Debug.LogError("Failed to create lobby " + e.Message);
>
> return false;
> }
> }
>
> public async Task<bool> QuickJoinLobby()
> {
> if (!AuthenticationService.Instance.IsSignedIn)
> {
> return false;
> }
>
> try
> {
> _currentLobby = await LobbyService.Instance.QuickJoinLobbyAsync();
>
> await SubscribeToLobbyCallbacks();
>
> Debug.Log("CURRENT LOBBY = " + _currentLobby.Name);
>
> string relayJoinCode = _currentLobby.Data[k_keyJoinCode].Value;
>
> Debug.Log("RELAY JOIN CODE = " + relayJoinCode);
>
> JoinAllocation joinAllocation = await RelayManager.Instance.JoinRelay(relayJoinCode);
>
> await UpdatePlayerAllocationID(joinAllocation.AllocationId.ToString());
>
>
> NetworkManager.Singleton.GetComponent<UnityTransport>().SetClientRelayData(
> joinAllocation.RelayServer.IpV4, (ushort)joinAllocation.RelayServer.Port,
> joinAllocation.AllocationIdBytes, joinAllocation.Key, joinAllocation.ConnectionData, joinAllocation.HostConnectionData
> );
>
>
> NetworkManager.Singleton.StartClient();
>
> return true;
> }
> catch (LobbyServiceException e)
> {
> Debug.LogError("Failed to quick join lobby " + e.Message);
> return false;
> }
> }
>
> async Task UpdatePlayerAllocationID(string allocationId)
> {
> try
> {
> UpdatePlayerOptions options = new UpdatePlayerOptions();
> options.AllocationId = allocationId;
>
> string playerId = AuthenticationService.Instance.PlayerId;
>
> var lobby = await LobbyService.Instance.UpdatePlayerAsync(_currentLobby.Id, playerId, options);
> }
> catch (LobbyServiceException e)
> {
> Debug.LogError("Failed to update Player ALlocationID " + e);
> }
> }
>
> async Task SubscribeToLobbyCallbacks()
> {
> var callbacks = new LobbyEventCallbacks();
> callbacks.LobbyChanged += OnLobbyChanged;
> callbacks.LobbyEventConnectionStateChanged += OnLobbyConnexionStateChanged;
> callbacks.KickedFromLobby += OnKickedFromLobby;
> try
> {
> await LobbyService.Instance.SubscribeToLobbyEventsAsync(_currentLobby.Id, callbacks);
> }
> catch (LobbyServiceException e)
> {
> Debug.LogError("Failed to subscribe to lobby events " + e);
> }
> }
>
> private void OnLobbyChanged(ILobbyChanges changes)
> {
> if (changes.LobbyDeleted)
> {
>
> }
> else
> {
> changes.ApplyToLobby(_currentLobby);
> if (changes.PlayerJoined.Changed)
> {
> Debug.Log("Player Joined");
> }
> else if (changes.PlayerLeft.Changed)
> {
> Debug.Log("Player Left");
> }
> }
> }
>
> private void OnLobbyConnexionStateChanged(LobbyEventConnectionState state)
> {
> Debug.Log("Lobby Connexion State Changed: " + state.ToString());
> }
>
> private void OnKickedFromLobby()
> {
> Debug.Log("KICKED FROM LOBBY");
> }
>
>
> IEnumerator HeartbeatCoroutine(string lobbyID, float interval)
> {
> while (true)
> {
> Debug.Log("Heartbeat");
> LobbyService.Instance.SendHeartbeatPingAsync(lobbyID);
> yield return new WaitForSecondsRealtime(interval);
> }
> }
> }