Unity-Technologies / com.unity.netcode.gameobjects

Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.
MIT License
2.12k stars 430 forks source link

StartServer() throws exception "Allowed types is not equal to the number of message type indices!" #2986

Open CodeSmile-0000011110110111 opened 1 month ago

CodeSmile-0000011110110111 commented 1 month ago

Description

I call StartServer() when the NetworkManager.OnSingletonReady event is raised (yes, the internal event).

This immediately throws the following exception:

Exception: Allowed types is not equal to the number of message type indices! Allowed Count: 0 | Index Count: 25
Unity.Netcode.ILPPMessageProvider.GetMessages () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs:69)
Unity.Netcode.NetworkMessageManager..ctor (Unity.Netcode.INetworkMessageSender sender, System.Object owner, Unity.Netcode.INetworkMessageProvider provider) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs:170)
Unity.Netcode.NetworkManager.Initialize (System.Boolean server) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:1123)
Unity.Netcode.NetworkManager.StartServer () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:1243)
CodeSmile.BetterNetcode.Network.NetworkState.StartServer () (at Assets/Scripts/Runtime/Network/NetworkState.cs:67)
CodeSmile.BetterNetcode.Network.NetworkState.<Awake>b__5_0 (CodeSmile.BetterNetcode.Network.NetworkState+StateChangedEventArgs args) (at Assets/Scripts/Runtime/Network/NetworkState.cs:50)
CodeSmile.BetterNetcode.Network.NetworkState.ChangeState (CodeSmile.BetterNetcode.Network.NetworkState+State newState) (at Assets/Scripts/Runtime/Network/NetworkState.cs:87)
CodeSmile.BetterNetcode.Network.NetworkState.OnNetworkManagerSingletonReady () (at Assets/Scripts/Runtime/Network/NetworkState.cs:58)
CodeSmile.Netcode.Extensions.NetworkManagerExt.InvokeSingletonReadyCallbacksOnce () (at ./Packages/de.codesmile.netcode/Runtime/Extensions/NetworkManagerExt.cs:81)
Unity.Netcode.NetworkManager.SetSingleton () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:957)
Unity.Netcode.NetworkManager.OnEnable () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:979)

This issue occurs in Netcode 2.0.0-pre.2 but not in 1.9.1 in Unity 6000.0.11f1.

The issue does not occur when I delay calling StartServer() from Start() rather than directly from the OnSingletonReady event. The NetworkState component from which I call StartServer() is attached to the NetworkManager object.

It seems the NetworkManager singleton isn't actually "fully ready" when the OnSingletonReady event gets raised. Although this is an internal event, I wanted to flag this because a) it's nevertheless unexpected and b) this behaviour has changed between NGO 1.9 and 2.0. It may indicate a possible order of initialization issue in the NetworkMessageManager subsystem.

CodeSmile-0000011110110111 commented 1 month ago

I got this issue again after exiting playmode. I was testing quick successive Start/Shutdown of the server. The flow is like this:

StartServer() => OnServerStarted => Shutdown() => Coroutine yields WaitForEndOfFrame or null or several null => StartServer() => repeat indefinitely ...

Upon exiting playmode, when I yield WaitForEndOfFrame I always get this exception:

Exception: Allowed types is not equal to the number of message type indices! Allowed Count: 0 | Index Count: 25
Unity.Netcode.ILPPMessageProvider.GetMessages () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs:69)
Unity.Netcode.NetworkMessageManager..ctor (Unity.Netcode.INetworkMessageSender sender, System.Object owner, Unity.Netcode.INetworkMessageProvider provider) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs:170)
Unity.Netcode.NetworkManager.Initialize (System.Boolean server) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:1123)
Unity.Netcode.NetworkManager.StartServer () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:1243)
CodeSmile.BetterNetcode.Network.NetworkState.StartNetworking (CodeSmile.BetterNetcode.Network.NetworkState+Role role) (at Assets/Scripts/Runtime/Network/NetworkState.cs:148)
CodeSmile.BetterNetcode.Network.NetworkState.OnNetworkStateChanged (CodeSmile.BetterNetcode.Network.NetworkState+StateChangedEventArgs args) (at Assets/Scripts/Runtime/Network/NetworkState.cs:113)
CodeSmile.BetterNetcode.Network.NetworkState.ChangeState (CodeSmile.BetterNetcode.Network.NetworkState+State newState) (at Assets/Scripts/Runtime/Network/NetworkState.cs:230)
CodeSmile.BetterNetcode.Network.NetworkState+<GoOfflineAtEndOfFrameCoroutine>d__28.MoveNext () (at Assets/Scripts/Runtime/Network/NetworkState.cs:220)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <1aa1cb9cf6694c2497abc532d802d5d9>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)

Upon exiting playmode, when I yield null (next frame) I always get two exceptions:

Exception: Allowed types is not equal to the number of message type indices! Allowed Count: 0 | Index Count: 25
Unity.Netcode.ILPPMessageProvider.GetMessages () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs:69)
Unity.Netcode.NetworkMessageManager..ctor (Unity.Netcode.INetworkMessageSender sender, System.Object owner, Unity.Netcode.INetworkMessageProvider provider) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs:170)
Unity.Netcode.NetworkManager.Initialize (System.Boolean server) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:1123)
Unity.Netcode.NetworkManager.StartServer () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:1243)
CodeSmile.BetterNetcode.Network.NetworkState.StartNetworking (CodeSmile.BetterNetcode.Network.NetworkState+Role role) (at Assets/Scripts/Runtime/Network/NetworkState.cs:148)
CodeSmile.BetterNetcode.Network.NetworkState.OnNetworkStateChanged (CodeSmile.BetterNetcode.Network.NetworkState+StateChangedEventArgs args) (at Assets/Scripts/Runtime/Network/NetworkState.cs:113)
CodeSmile.BetterNetcode.Network.NetworkState.ChangeState (CodeSmile.BetterNetcode.Network.NetworkState+State newState) (at Assets/Scripts/Runtime/Network/NetworkState.cs:230)
CodeSmile.BetterNetcode.Network.NetworkState+<GoOfflineAtEndOfFrameCoroutine>d__28.MoveNext () (at Assets/Scripts/Runtime/Network/NetworkState.cs:220)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <1aa1cb9cf6694c2497abc532d802d5d9>:0)
NullReferenceException: Object reference not set to an instance of an object
Unity.Netcode.NetworkManager.NetworkUpdate (Unity.Netcode.NetworkUpdateStage updateStage) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs:311)
Unity.Netcode.NetworkUpdateLoop.RunNetworkUpdateStage (Unity.Netcode.NetworkUpdateStage updateStage) (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs:191)
Unity.Netcode.NetworkUpdateLoop+NetworkPostLateUpdate+<>c.<CreateLoopSystem>b__0_0 () (at ./Library/PackageCache/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs:286)

Increasing the number of yield return null makes the exceptions increasingly less likely to get thrown, with 6 times (for me) not showing any exception on exit playmode. 

The IsListening, ShutdownInProgress, IsServer, IsHost, IsClient flags were all false after a single yield null.

NoelStephensUnity commented 1 month ago

@CodeSmile-0000011110110111 So, the internal OnSingletonReady event is not intended to be used and is actually considered legacy script that most likely will be removed in the final release of NGO v2.0.0.

There is indeed a change in how we handle identifying messages. The exception you are seeing within ILPPMessageProvider is most likely happening because the ILPP hasn't finished processing (my assumption is you are running this in the editor) and NetworkManager.SetSingleton is invoked when NetworkManager.OnEnable is invoked (i.e. it is called before the Awake methods are called on components). I would recommend not using that method.

The OnServerStarted immediately invoking NetworkManager.Shutdown and then creating a coroutine that starts the NetworkManager at the end of the frame is going to be problematic since NetworkManager.Shutdown needs to make it to the next frame before it actually shutsdown (this is to allow any pending messages and the like a chance to be flushed).

So, I would highly recommend using the NetworkManager.OnServerStopped notification prior to trying to start the NetworkManager again or you will run into these kinds of issues.

CodeSmile-0000011110110111 commented 1 month ago

Thanks for the clarifications!

I did move the code to Start() in this case.
Also I made the coroutine check for every flag that may be involved as I noticed that one of them returns to false (I think ShutdownInProgress) while IsServer was still true.

This is the version I use now, with a WaitForEndOfFrame on top just to be on the safe side (also trying to avoid possible issues because FixedUpdate has run but Update did not).

private IEnumerator GoOfflineAtEndOfFrameCoroutine()
{
    yield return new WaitUntil(() =>
    {
        var net = NetworkManager.Singleton;
        return net == null ||
              (net.ShutdownInProgress || net.IsListening || net.IsServer || net.IsHost || net.IsClient) == false;
    });

    // then also wait until after Update, LateUpdate, Rendering of current frame
    yield return new WaitForEndOfFrame();

    ChangeState(State.Offline);
}

Should that suffice?

Note that this is and was called from OnServerStopped:
private void OnServerStopped(Boolean isHost) => GoOfflineAtEndOfFrame();

Lastly, the NetworkManager.OnSingletonReady would still be good to have around, and public. I made a component that will log all events for debugging purposes, but if I were to hook up the events in Start I cannot guarantee that some other component did not already start networking and thus miss OnXxxxStarted events.

NoelStephensUnity commented 1 month ago

This is the version I use now, with a WaitForEndOfFrame on top just to be on the safe side (also trying to avoid possible issues because FixedUpdate has run but Update did not). ... Should that suffice?

That works or alternatively you could just kick off a coroutine when the NetworkManager stops. Both should get you the same results with the caveat that the approach below assures that you are waiting until the end of the frame that the NetworkManager exited its internal shutdown method. It is really a matter of preference and what you feel most comfortable using.

/// <summary>
/// Attach to the same GameObject as the NetworkManager
/// </summary>
public class MyOfflineHandler : MonoBehaviour
{
    public NetworkManager NetworkManager;

    private void Awake()
    {
        NetworkManager = GetComponent<NetworkManager>();
        NetworkManager.OnClientStarted += OnClientStarted;
        NetworkManager.OnServerStarted += OnServerStarted;
    }

    private void OnClientStarted()
    {
        NetworkManager.OnClientStarted -= OnClientStarted;
        NetworkManager.OnClientStopped += OnClientStopped;
    }

    private void OnServerStarted()
    {
        NetworkManager.OnServerStarted -= OnServerStarted;
        NetworkManager.OnServerStopped += OnServerStopped;
    }

    private void OnClientStopped(bool isHost)
    {
        NetworkManager.OnClientStopped -= OnClientStopped;
        NetworkManager.OnClientStarted += OnClientStarted;
        if (!isHost)
        {
            StartCoroutine(WaitForEndOfFrame());
        }
    }

    private void OnServerStopped(bool isHost)
    {
        NetworkManager.OnServerStopped -= OnServerStopped;
        NetworkManager.OnServerStarted += OnServerStarted;
        StartCoroutine(WaitForEndOfFrame());
    }

    private IEnumerator WaitForEndOfFrame()
    {
        // then also wait until after Update, LateUpdate, Rendering of current frame
        yield return new WaitForEndOfFrame();

        ChangeState(State.Offline);
    }
}

Lastly, the NetworkManager.OnSingletonReady would still be good to have around, and public. I made a component that will log all events for debugging purposes, but if I were to hook up the events in Start I cannot guarantee that some other component did not already start networking and thus miss OnXxxxStarted events.

If your component is attached to the same GameObject that the NetworkManager is attached to, then you could hook up the NetworkManager events in Awake after getting the NetworkManager component.

CodeSmile-0000011110110111 commented 1 month ago

If your component is attached to the same GameObject that the NetworkManager is attached to, then you could hook up the NetworkManager events in Awake after getting the NetworkManager component.

Fair point. I can also enforce it easily without [RequireComponent].