How to do host migration?

The docs in Host migration are very unhelpful. I tried to do host migration to the best of my understanding but the host does not migrate from the host that is leaving to the remaining client.

How does host migration actually works? I want one of the remaining players to be the new host and go on playing with the other players.

private IEnumerator Quit()
    {
        Debug.Log("Beginning");
        var getLobbyTask = Lobbies.Instance.GetLobbyAsync(State.Value.Lobby.Id);
        yield return new WaitWhile(() => getLobbyTask.IsCompleted == false);
        var updatedLobby = getLobbyTask.Result;
        updatedLobby.Players.ForEach(player =>
        {
            Debug.Log($"player {player.Id}, allocationId {player.AllocationId}");
        });
        if (NetworkManager.Singleton.IsHost)
        {
            Debug.Log("I am the host, have to migrate host to someone else");
            Player oldHost = updatedLobby.Players.Where(p => p.AllocationId != null && p.AllocationId != "").First();
            Player newHost = updatedLobby.Players.Where(p => p.AllocationId == null || p.AllocationId == "").First();
            UpdateLobbyOptions updateOptions = new UpdateLobbyOptions()
            {
                HostId=newHost.Id
            };
            var t = Lobbies.Instance.UpdateLobbyAsync(updatedLobby.Id, updateOptions);
            yield return new WaitWhile(() => t.IsCompleted == false);
            Debug.Log("Supposedly the other player is the host now.");
        }
        Debug.Log("Leaving the lobby");
        var t1 = Lobbies.Instance.RemovePlayerAsync(updatedLobby.Id, AuthenticationService.Instance.PlayerId);
        yield return new WaitWhile(() => t1.IsCompleted == false);
        Debug.Log("shutting down network");
        NetworkManager.Singleton.DisconnectClient(NetworkManager.Singleton.LocalClientId);
        NetworkManager.Singleton.Shutdown(true);
        yield return new WaitWhile(() => NetworkManager.Singleton.ShutdownInProgress);
        var isDone = false;
        Debug.Log("back to home scen");
        SceneManager.LoadSceneAsync("Scenes/Home").completed += (c) =>
        {
            isDone = c.isDone;
        };
        yield return new WaitWhile(() => isDone == false);
        Destroy(gameObject);
1 Like

Hi @geronimo_desenvolvimentos

Thank you for participating in the Lobby open beta.

I believe your code currently circumvents the host migration process here:

Debug.Log("I am the host, have to migrate host to someone else");
            Player oldHost = updatedLobby.Players.Where(p => p.AllocationId != null && p.AllocationId != "").First();
            Player newHost = updatedLobby.Players.Where(p => p.AllocationId == null || p.AllocationId == "").First();
            UpdateLobbyOptions updateOptions = new UpdateLobbyOptions()
            {
                HostId=newHost.Id
            };

Specifically, newHost is being set to the first Player in the Lobby that does not have an AllocationId on line 3. However, this would indicate that the Player is not currently connected to the Lobby. If you’re trying to follow the first option of the linked Host Migration doc then I believe you’ll want to set newHost to a value that matches up with current client. Perhaps something like this would work for you:

Debug.Log("I am the host, have to migrate host to someone else");
            Player oldHost = updatedLobby.HostId;
            Player newHost = updatedLobby.Players.Where(p => p.Id != oldHost.Id).First();
            UpdateLobbyOptions updateOptions = new UpdateLobbyOptions()
            {
                HostId=newHost.Id
            };

For the other options, you should see a host migration by calling something similar to the following on the host or by disconnecting from an associated Relay:

RemovePlayerAsync(updatedLobby.Id, updatedLobby.HostId);

Note that the automatic migrations are not instantaneous but should happen prior to the Lobby being deleted due to inactivity. In addition, please note the Relay migration caveat:

Let me know if you continue to see issues with the host migration process with the changes above.

Finally, I’d like to submit a feature request to the Lobby team to automatically migrate hosts when a Lobby’s HostId is set to an invalid value. Please let us know your opinion on this suggestion and if it would better meet your expectations for how the SDK should work.

Regards,
-Kip

Thank you for your time. I created a minimum example, to be used as component of a Netcode for Game Objects’ NetworkManager,of how I am using relay+lobby. In this example host migration does not happen.

There is also a strange, in my opinion, behavior. Alice sets the allocationId (line 109) when the host creates the lobby and relay (CreateMyRelayAndMyLobby). But when Bob quick joins Alice’s lobby (TryToJoinExistingLobbyOrCreateNewIfNoneFound) Alice’s allocationId is nowhere to be found (it didn’t appear on the logs) and the host migration fails when the button that calls OnMigrateClick is clicked.

From what I understood the allocationId is the link between the Relay and Lobby and if it isn’t there the lobby won’t play along with the relay. Is this correct?

What am I doing wrong?

I don’t know it it matters but I am doing the tests using two editor instances on the same machine, cloned using ParrelSync.
Relay is @ 1.0.1-pre.6,
Unity Transport for Netcode for Game Objects @ 1.0.0-pre.6
Netcode For Game Objects @ 1.0.0-pre.6
Lobby @ 1.0.0-pre.6
Authentication 1.0.0-pre.37

Operating System is Win10, Unity 2020.3.29f1.1603 Personal

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using UnityEngine;
using System.Linq;
using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using Unity.Netcode;
using System;

public class HostMigrationTest : MonoBehaviour
{
    /// <summary>
    /// It's value comes from either TryToQuickJoin or CreateMyRelayAndMyLobby
    /// </summary>
    private Lobby lobby;
    /// <summary>
    /// It's value comes from either CreateRelayAllocation
    /// </summary>
    private Allocation allocation;
    /// <summary>
    /// Comes from TryToJoinExistingLobbyOrCreateNewIfNoneFound
    /// </summary>
    private JoinAllocation joinAllocation;
    private string joinCode;
    public async void Start()
    {
        Debug.Log("??");
        await InitializeServicesAndSignInAsync();
        await TryToJoinExistingLobbyOrCreateNewIfNoneFound();
    }
    /// <summary>
    /// Is there a lobby that I can join using quick join? If there is such a lobby
    /// then join it, get it's allocation and join the relay. If not create the relay
    /// and create the lobby.
    /// </summary>
    /// <returns></returns>
    private async Task TryToJoinExistingLobbyOrCreateNewIfNoneFound()
    {
        try
        {
            await TryToQuickJoin();
            Debug.Log("If I arrived here I found an open lobby.");
            ///Maybe there is a issue here: when Alice creates a Lobby it sets an allocationId on herself.
            ///But when Bob gets the lobby created by Alice via quick join Alice's allocationId is gone.
            Debug.Log($"id:{lobby.Id}, hostId:{lobby.HostId}, numberOfPlayers:{lobby.Players.Count}, " +
                $"allocationId:{lobby.Players.Where(p=>p.Id==lobby.HostId).First().AllocationId}");
            var connectionInfo = lobby.Players.Where(p => p.Id == lobby.HostId).First().ConnectionInfo;
            joinAllocation = await Relay.Instance.JoinAllocationAsync(connectionInfo);
        }
        catch (LobbyServiceException e)
        {
            if (e.Reason == LobbyExceptionReason.NoOpenLobbies)
            {
                Debug.Log("no lobby found, creating my own lobby");
                await CreateMyRelayAndMyLobby();
            }
            else
            {
                throw e;
            }
        }
    }

    private async Task InitializeServicesAndSignInAsync()
    {
        try
        {
            await UnityServices.InitializeAsync();
            await AuthenticationService.Instance.SignInAnonymouslyAsync();
            Debug.Log("init done");
        }
        catch(Exception ex)
        {
            Debug.LogError(ex);
        }
    }
    /// <summary>
    /// Join an existing lobby. ***IS THIS CORRECT?***
    /// </summary>
    /// <returns></returns>
    private async Task TryToQuickJoin()
    {
        Debug.Log("Looking for existing lobby");
        QuickJoinLobbyOptions options = new QuickJoinLobbyOptions();
        options.Filter = new List<QueryFilter>()
            {
                new QueryFilter(
                field: QueryFilter.FieldOptions.MaxPlayers,
                op: QueryFilter.OpOptions.GE,
                value: "2"
                )
            };
        lobby = await Lobbies.Instance.QuickJoinLobbyAsync();
    }
    /// <summary>
    /// Create the allocation and then the lobby using the allocation data.
    /// </summary>
    /// <returns></returns>
    private async Task CreateMyRelayAndMyLobby()
    {
        await CreateRelayAllocation();
        CreateLobbyOptions o = new CreateLobbyOptions();
        o.IsPrivate = false;
        o.Player = new Player(
            allocationId: allocation.AllocationId.ToString(),//****Alice's allocationId: is this correct ?****
            connectionInfo: joinCode,//****is this correct ?****
            id: AuthenticationService.Instance.PlayerId
            );
        lobby = await Lobbies.Instance.CreateLobbyAsync("foobar", 2, o);
        Debug.Log($"Created my lobby:{lobby.Id}, host:{lobby.HostId}, number of" +
            $"players:{lobby.Players.Count}");
    }
    /// <summary>
    /// Creates the allocation, the values that matter go to the allocation and joinCode
    /// fields.
    /// </summary>
    /// <returns></returns>
    private async Task CreateRelayAllocation()
    {
        allocation = await Relay.Instance.CreateAllocationAsync(2);
        joinCode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId);
        Debug.Log($"Created allocation:{allocation.AllocationId}");
    }
    private bool createdHost = false;
    private bool createdClient = false;
    public void Update()
    {
        var nm = NetworkManager.Singleton;
        if (allocation!=null && createdHost == false)
        {
            createdHost = true;
            nm.StartHost();
        }
        if(joinAllocation!=null && createdClient == false)
        {
            createdClient = true;
            nm.StartClient();
        }
    }
    public void OnMigrateClick()
    {
        Debug.Log("clicou");
        doMigrate();
    }
    private async void doMigrate()
    {
        var updatedLobby = await Lobbies.Instance.GetLobbyAsync(lobby.Id);
        var nm = NetworkManager.Singleton;
        if (nm.IsHost)
        {
            Debug.Log("I am the host, have to migrate host to someone else");
            Lobby shouldHaveDifferentHost = await Lobbies.Instance.UpdateLobbyAsync(updatedLobby.Id, new UpdateLobbyOptions()
            {
                HostId = updatedLobby.Players.Where(p => p.Id != updatedLobby.HostId).First().Id
            });
            Debug.Log($"Has the host id changed? {shouldHaveDifferentHost.HostId != updatedLobby.HostId}");
            await Lobbies.Instance.RemovePlayerAsync(updatedLobby.Id, updatedLobby.HostId);
            await Task.Delay(5000);//Waiting because host migration is not instantaneous
            Debug.Log("Waited for migration, time do disconnect from the game");
            nm.DisconnectClient(nm.LocalClientId);
            nm.Shutdown(true);
            Debug.Log("I am dead. Did the host migrate?");
        }
    }
}

Hi @geronimo_desenvolvimentos

It took awhile to dig into this and respond. There are a few things happening that are likely confounding your migration tests.

The key takeaway is that Host Migration is not currently available when using NGO (Netcode for Game Objects). If Host Migration is critical to your game, I recommend using UTP (Unity Transport Protocol) [Relay Integration] as your transport instead of NGO.

An immense amount of details are being omitted here because the nuance could be completely irrelevant by the end of the beta and might be misleading. However, if you’d like additional information, please open a support ticket through your Unity Dashboard under Help & Support → File a Ticket → Multiplayer → Lobby and I can fill in the omissions.

As far as your code goes (ignoring NGO), there were a few things that you could adjust that will ensure Host Migration is working between Relay and Lobby:

  • When your client joins the Lobby, they should update their own Lobby.Player.AllocationId to match the current host and ensure Lobby automatically updates when Relay detects a player disconnect.

  • During the migration, the new host will need to create a new Relay Allocation and update the values in the Lobby so that other clients can connect and join.

  • Note: If you manually migrate a Lobby host (as in your code), you should still create a new Relay Allocation and update the information.

Outside of the above, your minimal example was properly migrating in my own project so I suspect you were just running into issues with NGO.

I do know that you’ve requested a better demonstration for these behaviors in another thread , but please let me know if you had any other requests or suggestions you have for the development team and I’ll forward them along.

-Kip

1 Like

There should be an ownership/host transfer option that allows for a seamless transition without disrupting the ongoing game session. In my current scenario, where five players are connected in a puzzle game, if the host decides to leave, the current system removes all remaining players from the session. It would be beneficial to implement a solution that ensures the departure of a host does not disturb the continuity of the game for the entire party. Is there a way to handle this, so that if a player leaves, the entire party does not get removed from the session?

Hi,
Did you find any solution?

Hi,
Did you find any solution?

Unity does not provide any way to migrate the host.

After Host quit:
Step 1 : make 2nd Client create a new Lobby and become new Host.
Step 2: All other Clients will join in new Lobby as Client.
Step 3: Old Host reconnect to new Lobby as Client.
Host mirate is successful !
I did it successfully and showed video on youtube.

1 Like

Where’s the video, please?

Search for Unity No Code : Unity How to make game online in Visual Scripting
It’s not easy to show how I made it … You have to code it by yourself ^ ^