Error when trying to add a struct to a NetworkList

Hello, I am working on adding a scoreboard to my game, so I made a struct that holds player info, made it serializable, and want to add them to a list that syncs across the network.

When someone joins the server, they are added to that list, however im getting a lengthy NullReferenceException that doesn’t quite give me enough information.

I’ve tried looking through the error many times and tracking down where the real problem is, but I cant seem to find it. Can anyone help?

I believe these are the relevant code blocks:

This is the class which is meant to store and manage the list of players:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using System;

public unsafe class ActiveNetwork : NetworkBehaviour
{
    public NetworkList<NetPlayer> players;
    public int size;

    public struct NetPlayer : INetworkSerializable,IEquatable<NetPlayer>
    {
        public ulong id;
        public int kills;
        public int damage;
        public int deaths;

        public fixed char username[32];

        public NetPlayer(ulong id, string username, int kills, int damage, int deaths)
        {
            this.id = id;
            this.kills = kills;
            this.damage = damage;
            this.deaths = deaths;

            SetUsername(username);
        }

        public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
        {
            serializer.SerializeValue(ref id);
            serializer.SerializeValue(ref kills);
            serializer.SerializeValue(ref damage);
            serializer.SerializeValue(ref deaths);

            for (int i = 0; i < 32; ++i)
            {
                serializer.SerializeValue(ref username[i]);
            }
        }

        public void SetUsername(string user)
        {
            for (int i = 0; i < Math.Min(user.Length, 32); ++i)
            {
                username[i] = user[i];
            }
        }

        public string GetUsername()
        {
            fixed (char*p = username) { return new string(new ReadOnlySpan<char>(p, 32)); }
        }

        public bool Equals(NetPlayer other)
        {
            return false;
        }
    }

    void Awake()
    {
        players = new NetworkList<NetPlayer>();
    }

    public void Populate()
    {
        Debug.Log("Populated Player List");

        foreach (GameObject player in FindObjectsOfType<GameObject>())
        {
            if (player.GetComponent<Player>() != null)
            {
                Player activePlayer = player.GetComponent<Player>();
                players.Add(new NetPlayer(activePlayer.OwnerClientId, activePlayer.username, 0, 0, 0));
                Debug.Log("Found " + activePlayer.username + " On The Network!");
                size++;
            }
        }
    }

   
    public void Refresh()
    {
        players.Clear();
        size = 0;

        Populate();
    }

    public void AddPlayer(NetPlayer player)
    {
        Debug.Log("Added " + player.GetUsername() + " To The Network!");
        players.Add(player);
        size++;
    }
}

This is where the player attempts to join the server and have itself added to the list:

private ActiveNetwork network;
    private Scoreboard scoreboard;

    public override void OnNetworkSpawn()
    {
        network = (ActiveNetwork)FindObjectOfType(typeof(ActiveNetwork));
        scoreboard = (Scoreboard)FindObjectOfType(typeof(Scoreboard));

        network.players.OnListChanged += TestOnOnListChanged;

        if (IsOwner)
        {
            username = Profile.username;

            Debug.Log("Id Is: " + OwnerClientId + " Username Is: " + username);

            SendUserServerRpc(OwnerClientId, username);
        }
    }

[ServerRpc]
    void SendUserServerRpc(ulong id, string user)
    {
        Debug.Log("Client: " + user + " Joined! ");

        this.username = user;

        Debug.Log(network.players == null ? "No Network List Found" : "Network List Found");

        network.AddPlayer(new ActiveNetwork.NetPlayer(id, user, 0, 0, 0));
    }

And Here is the error in question:

NullReferenceException: Object reference not set to an instance of an object
Unity.Netcode.NetworkList`1[T].MarkNetworkObjectDirty () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/NetworkVariable/Collections/NetworkList.cs:68)
Unity.Netcode.NetworkList`1[T].HandleAddListEvent (Unity.Netcode.NetworkListEvent`1[T] listEvent) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/NetworkVariable/Collections/NetworkList.cs:499)
Unity.Netcode.NetworkList`1[T].Add (T item) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/NetworkVariable/Collections/NetworkList.cs:386)
ActiveNetwork.AddPlayer (ActiveNetwork+NetPlayer player) (at Assets/Networking/ActiveNetwork.cs:118)
Player.SendUserServerRpc (System.UInt64 id, System.String user) (at Assets/Player/Player.cs:54)
Player.__rpc_handler_4236175673 (Unity.Netcode.NetworkBehaviour target, Unity.Netcode.FastBufferReader reader, Unity.Netcode.__RpcParams rpcParams) (at <38f293a5a37544e3beccfc416df1918a>:0)
Unity.Netcode.RpcMessageHelpers.Handle (Unity.Netcode.NetworkContext& context, Unity.Netcode.RpcMetadata& metadata, Unity.Netcode.FastBufferReader& payload, Unity.Netcode.__RpcParams& rpcParams) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Messaging/Messages/RpcMessages.cs:77)
Rethrow as Exception: Unhandled RPC exception!
UnityEngine.Debug:LogException(Exception)
Unity.Netcode.RpcMessageHelpers:Handle(NetworkContext&, RpcMetadata&, FastBufferReader&, __RpcParams&) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Messaging/Messages/RpcMessages.cs:81)
Unity.Netcode.ServerRpcMessage:Handle(NetworkContext&) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Messaging/Messages/RpcMessages.cs:122)
Unity.Netcode.NetworkBehaviour:__endSendServerRpc(FastBufferWriter&, UInt32, ServerRpcParams, RpcDelivery) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Core/NetworkBehaviour.cs:93)
Player:SendUserServerRpc(UInt64, String) (at Assets/Player/Player.cs:48)
Player:OnNetworkSpawn() (at Assets/Player/Player.cs:30)
Unity.Netcode.NetworkBehaviour:InternalOnNetworkSpawn() (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Core/NetworkBehaviour.cs:439)
Unity.Netcode.NetworkObject:InvokeBehaviourNetworkSpawn() (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Core/NetworkObject.cs:817)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocallyCommon(NetworkObject, UInt64, Boolean, Boolean, UInt64, Boolean) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Spawning/NetworkSpawnManager.cs:552)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocally(NetworkObject, UInt64, Boolean, Boolean, UInt64, Boolean) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Spawning/NetworkSpawnManager.cs:453)
Unity.Netcode.NetworkManager:HandleConnectionApproval(UInt64, ConnectionApprovalResponse) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Core/NetworkManager.cs:2044)
Unity.Netcode.NetworkManager:StartHost() (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.2/Runtime/Core/NetworkManager.cs:1140)
NetworkManagerUI:<Awake>b__3_0() (at Assets/Networking/NetworkManagerUI.cs:17)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:517)

Everything above is irrelevant. It is still ALWAYS the same three steps, ALWAYS.

How to fix a NullReferenceException error

https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

Three steps to success:

  • Identify what is null ← any other action taken before this step is WASTED TIME
  • Identify why it is null
  • Fix that

ALSO: are you aware of what making this a struct means?

Value Types vs Reference Types:

https://discussions.unity.com/t/826396/4

The problem is not that I don’t know how to fix a generic null reference. Its the most common error and often one of the easiest to fix; The problem is that despite the hours I’ve spent searching, looking over the error as well as the code, I cant identify what is null in this specific instance. The structs in the network list are all non-nullable types. I logged to make sure the object where the list is stored is not null. I have made sure the list itself does not return null before trying to add anything to it. Its only when adding something to the list that I get this error. I’m sure that it’s something, I just know I’ve spent more than enough time trying to figure it out myself without making any progress.

EDIT: It may also be worth noting that the Debug.Log message prior to adding a player to the list does not completely print before the error is thrown. It is meant to say “Added (username) to the Network” but only prints “Added username” then crashes. I assumed this meant that somehow, part of the char array that the username string was copied from was null. It appears this is not the case. I’m at my wit’s end.

Try replacing char[ ] with a FixedString32Bytes

2 Likes

I see two random unguarded gets on line 6 and 7 of the second code block.

Those are my first suspects.

Also, line 9 in the top blurb marks players as public, which means that if you drop this in a scene/prefab, and Unity has data to deserialize over it, then it WILL potentially wipe out anything you initialize in Awake().

It seems only professional to eliminate all the obvious possibilities first, and I don’t see you mentioning or guarding those.

I did a test with your code and the RPC worked fine, I wasn’t using unsafe though, I used FixedString32Bytes as MaskedMouse suggested.

Thanks for the idea. I did try implementing this but I’m getting the same error. The full debug message does print as intended now though.

I am quite new to c# honestly, I’m not sure what this really means or how it would go about solving that issue. Does this mean that those two gameObjects could potentially not exist? If so, I know that before I started using a networkList both the list and the scoreboard were operational.

This is something I was not aware of. I actually did want to make that list private, but I also needed the players to subscribe to the OnListChanged event so that they would be notified when a player joins. I don’t know another way to make that possible without the list being public.

There’s a potential issue with these lines as Kurt pointed out:

network = (ActiveNetwork)FindObjectOfType(typeof(ActiveNetwork));
scoreboard = (Scoreboard)FindObjectOfType(typeof(Scoreboard));

How do you know those objects are spawned before the player?

Another related issue is the RPC maybe being called too early, try delaying it or putting it on a button click.

1 Like

Absolutely ANYTHING that is a reference type can be null.

You are getting a null.

At a bare minimum, check the things that could be null.

“Were” is not now. This is debugging. Eliminate the possibilities. Once they’re eliminated, move on!

Traditionally one does not expose fields randomly. Yes, the Unity way has people doing this commonly, but the intended purpose of that is only for Unity to serialize things into, and it can run cross-purpose to global public accesses.

A more robust approach is to keep it private and make your own public API to interact with the list, such as add items to it, iterate it, etc.

Here’s the underlying process involved:

Serialized properties in Unity are initialized as a cascade of possible values, each subsequent value (if present) overwriting the previous value:

  • what the class constructor makes (either default(T) or else field initializers)

  • what is saved with the prefab

  • what is saved with the prefab override(s)/variant(s)

  • what is saved in the scene and not applied to the prefab

  • what is changed in Awake(), Start(), or even later etc.

Make sure you only initialize things at ONE of the above levels, or if necessary, at levels that you specifically understand in your use case. Otherwise errors will seem very mysterious.

Field initializers versus using Reset() function and Unity serialization:

https://discussions.unity.com/t/829681/2

https://discussions.unity.com/t/846251/8

Putting some thought into this I determined maybe the fact that I was using OnNetworkSpawn was causing an issue with the order of things. I must have misunderstood when this gets called. Upon changing it to Start, everything works as intended. Players can join the network and their information is synced. Thanks for your help!

1 Like

I had the same problem. And, acosionally, discovered the Initialize(NetworkBehaviour) method. Try call when construct de NetworkList. Like.
8889132--1215555--upload_2023-3-20_11-26-53.png

2 Likes