InvalidOperationException: Only one ConnectionApprovalCallback can be registered at a time.

I’m trying to disconnect by using this code:

var netMan = NetworkManager.Singleton;
            if (netMan != null && netMan.ShutdownInProgress == false)
            {
                netMan.Shutdown();
            }

SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex - 1);

And apparently, it disconnects the player successfully. However, upon loading the scene, it throws the following error:
“InvalidOperationException: Only one ConnectionApprovalCallback can be registered at a time.”

It points to a script attached to the NetworkManager, on its Start() method, where I subscribe to the callbacks and events:

private void Start() => AddNetworkManagerCallbacks();

I would understand the error if the function “AddNetworkManagerCallbacks()” would register to the ConnectionAprovalCallback twice, but it doesn’t seem possible since I automatically unsubscribe before subscribing:

private void AddNetworkManagerCallbacks()
        {
            var netMan = NetworkManager.Singleton;
            if (netMan != null)
            {
                // ensure we never register callbacks twice
                RemoveNetworkManagerCallbacks();

                netMan.ConnectionApprovalCallback += OnConnectionApproval;
                netMan.OnServerStarted += OnServerStarted;
                netMan.OnClientConnectedCallback += OnClientConnected;
                netMan.OnClientDisconnectCallback += OnClientDisconnect;
                netMan.OnTransportFailure += OnTransportFailure;
            }
        }

        private void RemoveNetworkManagerCallbacks()
        {

            var netMan = NetworkManager.Singleton;
            if (netMan != null)
            {
                netMan.ConnectionApprovalCallback -= OnConnectionApproval;
                netMan.OnServerStarted -= OnServerStarted;
                netMan.OnClientConnectedCallback -= OnClientConnected;
                netMan.OnClientDisconnectCallback -= OnClientDisconnect;
                netMan.OnTransportFailure -= OnTransportFailure;

            }
        }

Also, if I ignore the error and try to reconnect to the game, I get this error:
“Exception: NetManager tried to registered with ScenePlacedObjects which already contains the same GlobalObjectIdHash value 1232765018 for NetManager!”
This doesn’t happen if I stop the game, then play the game and ultimately reconnect (works correctly).

Any help is highly appreciated.

Are you sure the netMan is not null in this case?

I’ve added some Debugs. Apparently, the netMan is not null. After shutting down, the AddNetwork gets called, it says it’s not null ( netMan: NetworkManager (Unity.Netcode.NetworkManager)), and then it calls the RemoveNetwork, which isn’t null either.

I noticed that the NetworkManager is being duplicated, along with other objects that I have with Don’t Destroy On Load (namely a network object called “NetManager” and a regular object which is the AudioManager). Why are these being duplicated?

Edit: Actually it makes sense, they are objects living in that scene (the lobby). If I go back to the lobby I will then have these duplicated, triplicated if I repeat it again, and so on.

What would be the best practice in this scenario? I think one solution would be detecting in the “Don’t Destroy On Load” script if another object of that kind exists and destroy it if so. But I would like to know what people’s practices are.

I found another issue. Now I’m destroying duplicates, so everything seems to work fine. However, I’m getting a null reference exception when destroying the duplicates.

Specifically, it’s pointing to this function:

private void RemoveNetworkManagerCallbacks()
        {
            var netMan = NetworkManager.Singleton;
            if (netMan != null)
            {
                netMan.ConnectionApprovalCallback -= OnConnectionApproval;
                netMan.OnServerStarted -= OnServerStarted;
                netMan.OnClientConnectedCallback -= OnClientConnected;
                netMan.OnClientDisconnectCallback -= OnClientDisconnect;
                netMan.OnTransportFailure -= OnTransportFailure;
                netMan.SceneManager.OnSynchronize -= UpdateHostVariablesStruct; //it points to this line
            }
        }

EDIT: So, a simple null check fixes the error:

if (netMan.SceneManager != null)
            {
                netMan.SceneManager.OnSynchronize -= UpdateHostVariablesStruct;
            }

However, I don’t know the implications of not unsubscribing to the NetworkManager.Singleton.SceneManager’s callbacks…

Any help is appreciated, as always.

This has come up before and the recommendation is to not return to the scene containing the network manager. Create an initial scene that contains the network manager and then load your first game scene (that you return to on disconnect) from there.

2 Likes

What would be the best way to do it? Would it be the first scene all in black, with the Network objects with Don’t Destroy On Load, and then, on the Network Manager’s Awake function, load the real scene?

I’m not sure about Awake, I tend to only do things there that relate directly to that game object that need to be done early. In all scenes I have a game object with a scene controller script which kicks things off in Start. One example for starting the server:

    public class ServerStartSceneController : MonoBehaviour
    {
        void Start()
        {
            ServerNetworkManager.Instance().Initialise();

            ServerNetworkManager.Instance().StartServer();

            SceneManager.LoadScene(Constants.SCENE_SERVER, LoadSceneMode.Single);
        }
    }

I have my own scene management though so you’ll probably need to use the NetworkSceneManager to load the next scene.

I should mention you don’t need to start the server/client here as you just use the scene to get the network manager up and running. You can just make the call to load the next scene.

1 Like

Sorry to bring this up again. Is there any way I can fix this issue if everything is in the same scene? Would I just have to reload the scene as a whole or…?

What issue are you seeing, as it may be a simpler fix if everything is in the same scene. Saying that further down the line that scene may get a little busy and you might want to separate things out into different scenes.

You might just subscribe to NetworkManager.OnServerStarted and create an alternate MonoBehaviour derived class that will help you in managing your NetworkManager instance like the below pseudo code:

public class NetworkManagerSessionFlow: MonoBehaviour
{
    public static NetworkManagerSessionFlow Instance;
#if UNITY_EDITOR
    public SceneAsset EndSessionScene;
    private void OnValidate()
    {
        if (EndSessionScene != null)
        {
            m_EndSessionSceneName = EndSessionScene.name;
        }
    }
#endif
    [HideInInspector]
    [SerializeField]
    private string m_EndSessionSceneName;
    private NetworkManager m_NetworkManager;
    public enum NetworkManagerModes
    {
        Client,
        Host,
        Server
    }
    private void Awake()
    {
        if (Instance != null)
        {
            throw new System.Exception("You have more than one NetworkManager instance instantiated!");
        }
        Instance = this;
        m_NetworkManager = GetComponent<NetworkManager>();
        m_NetworkManager.OnServerStarted += OnStarted;
        m_NetworkManager.OnClientStarted += OnStarted;
        m_NetworkManager.OnServerStopped += OnStopped;
        m_NetworkManager.OnClientStopped += OnStopped;
    }
    private void OnStopped(bool obj)
    {
        m_NetworkManager.OnServerStarted -= OnStarted;
        m_NetworkManager.OnClientStarted -= OnStarted;
        m_NetworkManager.OnServerStopped -= OnStopped;
        m_NetworkManager.OnClientStopped -= OnStopped;
        if (!string.IsNullOrEmpty(m_EndSessionSceneName))
        {
            // Once shutdown, then load into the "end session scene".
            SceneManager.LoadSceneAsync(m_EndSessionSceneName);
        }
        else
        {
            Debug.LogWarning($"{nameof(m_EndSessionSceneName)} was null or empty! Did you forget to assign a scene to {nameof(EndSessionScene)}?");
        }
    }
    private void OnStarted()
    {
        AddNetworkManagerCallbacks();
    }
    private void AddNetworkManagerCallbacks()
    {
        if (m_NetworkManager == null)
        {
            // Optional to log a message here
            return;
        }
        // ensure we never register callbacks twice
        RemoveNetworkManagerCallbacks();
        // I didn't add all of the callbacks, but this is a general idea/approach
        m_NetworkManager.ConnectionApprovalCallback += OnConnectionApproval;
        m_NetworkManager.OnServerStarted += OnServerStarted;
        m_NetworkManager.OnClientConnectedCallback += OnClientConnected;
        m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnect;
        m_NetworkManager.OnTransportFailure += OnTransportFailure;
    }
    private void RemoveNetworkManagerCallbacks()
    {
        if (m_NetworkManager == null)
        {
            // Optional to log a message here
            return;
        }
        // I didn't add all of the callbacks, but this is a general idea/approach
        m_NetworkManager.ConnectionApprovalCallback -= OnConnectionApproval;
        m_NetworkManager.OnServerStarted -= OnServerStarted;
        m_NetworkManager.OnClientConnectedCallback -= OnClientConnected;
        m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnect;
        m_NetworkManager.OnTransportFailure -= OnTransportFailure;
    }
    public void StartSession(NetworkManagerModes networkManagerMode)
    {
        switch (networkManagerMode)
        {
            case NetworkManagerModes.Client:
                {
                    m_NetworkManager.StartClient();
                    break;
                }
            case NetworkManagerModes.Host:
                {
                    m_NetworkManager.StartHost();
                    break;
                }
            case NetworkManagerModes.Server:
                {
                    m_NetworkManager.StartServer();
                    break;
                }
        }
    }
    public void StopSession()
    {
        // Remove callbacks first
        RemoveNetworkManagerCallbacks();
        // Shutdown the NetworkManager
        m_NetworkManager.Shutdown();           
    }
}

You would place the above component on the same GameObject as your NetworkManager.
You would assign the EndSessionScene to the SceneAsset you want to load when you end your session.
You would use NetworkManagerSessionFlow.Instance.StartSession to start your NetworkManager.
You would use NetworkManagerSessionFlow.Instance.StopSession to stop your NetworkManager.
When the NetworkManager finishes its shutdown sequence it will then load into the EndSessionScene.

Something like the above example should help prevent these types of issues.

Let me know if this resolves your issue?