Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in 'Netcode for GameObjects' started by GuirieSanchez, Jan 16, 2023.

  1. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    405
    I'm trying to disconnect by using this code:

    Code (CSharp):
    1. var netMan = NetworkManager.Singleton;
    2.             if (netMan != null && netMan.ShutdownInProgress == false)
    3.             {
    4.                 netMan.Shutdown();
    5.             }
    6.  
    7. 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:
    Code (CSharp):
    1. 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:
    Code (CSharp):
    1. private void AddNetworkManagerCallbacks()
    2.         {
    3.             var netMan = NetworkManager.Singleton;
    4.             if (netMan != null)
    5.             {
    6.                 // ensure we never register callbacks twice
    7.                 RemoveNetworkManagerCallbacks();
    8.  
    9.                 netMan.ConnectionApprovalCallback += OnConnectionApproval;
    10.                 netMan.OnServerStarted += OnServerStarted;
    11.                 netMan.OnClientConnectedCallback += OnClientConnected;
    12.                 netMan.OnClientDisconnectCallback += OnClientDisconnect;
    13.                 netMan.OnTransportFailure += OnTransportFailure;
    14.             }
    15.         }
    16.  
    17.         private void RemoveNetworkManagerCallbacks()
    18.         {
    19.  
    20.             var netMan = NetworkManager.Singleton;
    21.             if (netMan != null)
    22.             {
    23.                 netMan.ConnectionApprovalCallback -= OnConnectionApproval;
    24.                 netMan.OnServerStarted -= OnServerStarted;
    25.                 netMan.OnClientConnectedCallback -= OnClientConnected;
    26.                 netMan.OnClientDisconnectCallback -= OnClientDisconnect;
    27.                 netMan.OnTransportFailure -= OnTransportFailure;
    28.  
    29.             }
    30.         }

    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.
     
  2. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    437
    Are you sure the netMan is not null in this case?
     
  3. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    405
    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.
     
    Last edited: Jan 16, 2023
  4. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    405
    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.
     
    Last edited: Jan 16, 2023
  5. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    405
    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:

    Code (CSharp):
    1. private void RemoveNetworkManagerCallbacks()
    2.         {
    3.             var netMan = NetworkManager.Singleton;
    4.             if (netMan != null)
    5.             {
    6.                 netMan.ConnectionApprovalCallback -= OnConnectionApproval;
    7.                 netMan.OnServerStarted -= OnServerStarted;
    8.                 netMan.OnClientConnectedCallback -= OnClientConnected;
    9.                 netMan.OnClientDisconnectCallback -= OnClientDisconnect;
    10.                 netMan.OnTransportFailure -= OnTransportFailure;
    11.                 netMan.SceneManager.OnSynchronize -= UpdateHostVariablesStruct; //it points to this line
    12.             }
    13.         }

    EDIT: So, a simple null check fixes the error:
    Code (CSharp):
    1. if (netMan.SceneManager != null)
    2.             {
    3.                 netMan.SceneManager.OnSynchronize -= UpdateHostVariablesStruct;
    4.             }
    However, I don't know the implications of not unsubscribing to the NetworkManager.Singleton.SceneManager's callbacks...

    Any help is appreciated, as always.
     
    Last edited: Jan 16, 2023
  6. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    639
    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.
     
    JohnnyConnor and GuirieSanchez like this.
  7. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    405
    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?
     
  8. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    639
    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:
    Code (CSharp):
    1.     public class ServerStartSceneController : MonoBehaviour
    2.     {
    3.         void Start()
    4.         {
    5.             ServerNetworkManager.Instance().Initialise();
    6.  
    7.             ServerNetworkManager.Instance().StartServer();
    8.  
    9.             SceneManager.LoadScene(Constants.SCENE_SERVER, LoadSceneMode.Single);
    10.         }
    11.     }
    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.
     
    Last edited: Jan 17, 2023
    GuirieSanchez likes this.
  9. Somiaz

    Somiaz

    Joined:
    Apr 1, 2019
    Posts:
    5
    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..?
     
  10. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    639
    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.
     
  11. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    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:
    Code (CSharp):
    1.  
    2. public class NetworkManagerSessionFlow: MonoBehaviour
    3. {
    4.     public static NetworkManagerSessionFlow Instance;
    5. #if UNITY_EDITOR
    6.     public SceneAsset EndSessionScene;
    7.     private void OnValidate()
    8.     {
    9.         if (EndSessionScene != null)
    10.         {
    11.             m_EndSessionSceneName = EndSessionScene.name;
    12.         }
    13.     }
    14. #endif
    15.     [HideInInspector]
    16.     [SerializeField]
    17.     private string m_EndSessionSceneName;
    18.     private NetworkManager m_NetworkManager;
    19.     public enum NetworkManagerModes
    20.     {
    21.         Client,
    22.         Host,
    23.         Server
    24.     }
    25.     private void Awake()
    26.     {
    27.         if (Instance != null)
    28.         {
    29.             throw new System.Exception("You have more than one NetworkManager instance instantiated!");
    30.         }
    31.         Instance = this;
    32.         m_NetworkManager = GetComponent<NetworkManager>();
    33.         m_NetworkManager.OnServerStarted += OnStarted;
    34.         m_NetworkManager.OnClientStarted += OnStarted;
    35.         m_NetworkManager.OnServerStopped += OnStopped;
    36.         m_NetworkManager.OnClientStopped += OnStopped;
    37.     }
    38.     private void OnStopped(bool obj)
    39.     {
    40.         m_NetworkManager.OnServerStarted -= OnStarted;
    41.         m_NetworkManager.OnClientStarted -= OnStarted;
    42.         m_NetworkManager.OnServerStopped -= OnStopped;
    43.         m_NetworkManager.OnClientStopped -= OnStopped;
    44.         if (!string.IsNullOrEmpty(m_EndSessionSceneName))
    45.         {
    46.             // Once shutdown, then load into the "end session scene".
    47.             SceneManager.LoadSceneAsync(m_EndSessionSceneName);
    48.         }
    49.         else
    50.         {
    51.             Debug.LogWarning($"{nameof(m_EndSessionSceneName)} was null or empty! Did you forget to assign a scene to {nameof(EndSessionScene)}?");
    52.         }
    53.     }
    54.     private void OnStarted()
    55.     {
    56.         AddNetworkManagerCallbacks();
    57.     }
    58.     private void AddNetworkManagerCallbacks()
    59.     {
    60.         if (m_NetworkManager == null)
    61.         {
    62.             // Optional to log a message here
    63.             return;
    64.         }
    65.         // ensure we never register callbacks twice
    66.         RemoveNetworkManagerCallbacks();
    67.         // I didn't add all of the callbacks, but this is a general idea/approach
    68.         m_NetworkManager.ConnectionApprovalCallback += OnConnectionApproval;
    69.         m_NetworkManager.OnServerStarted += OnServerStarted;
    70.         m_NetworkManager.OnClientConnectedCallback += OnClientConnected;
    71.         m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnect;
    72.         m_NetworkManager.OnTransportFailure += OnTransportFailure;
    73.     }
    74.     private void RemoveNetworkManagerCallbacks()
    75.     {
    76.         if (m_NetworkManager == null)
    77.         {
    78.             // Optional to log a message here
    79.             return;
    80.         }
    81.         // I didn't add all of the callbacks, but this is a general idea/approach
    82.         m_NetworkManager.ConnectionApprovalCallback -= OnConnectionApproval;
    83.         m_NetworkManager.OnServerStarted -= OnServerStarted;
    84.         m_NetworkManager.OnClientConnectedCallback -= OnClientConnected;
    85.         m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnect;
    86.         m_NetworkManager.OnTransportFailure -= OnTransportFailure;
    87.     }
    88.     public void StartSession(NetworkManagerModes networkManagerMode)
    89.     {
    90.         switch (networkManagerMode)
    91.         {
    92.             case NetworkManagerModes.Client:
    93.                 {
    94.                     m_NetworkManager.StartClient();
    95.                     break;
    96.                 }
    97.             case NetworkManagerModes.Host:
    98.                 {
    99.                     m_NetworkManager.StartHost();
    100.                     break;
    101.                 }
    102.             case NetworkManagerModes.Server:
    103.                 {
    104.                     m_NetworkManager.StartServer();
    105.                     break;
    106.                 }
    107.         }
    108.     }
    109.     public void StopSession()
    110.     {
    111.         // Remove callbacks first
    112.         RemoveNetworkManagerCallbacks();
    113.         // Shutdown the NetworkManager
    114.         m_NetworkManager.Shutdown();          
    115.     }
    116. }
    117.  
    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?
     
    Last edited: Nov 25, 2023