Error: A Native Collection has not been disposed, resulting in a memory leak

Hi.
Recently, I’ve been having issues with the following error message (the two below):

A Native Collection has not been disposed, resulting in a memory leak. Enable Full StackTraces to get more details. Leak tracking may be enabled via Unity.Collections.NativeLeakDetection.Mode or from the editor preferences menu Edit > Preferences > Jobs > Leak Detection Level.

and

A Native Collection has not been disposed, resulting in a memory leak. Allocated from:
Unity.Collections.NativeList`1:.ctor(Int32, AllocatorHandle) (at .\Library\PackageCache\com.unity.collections@1.2.4\Unity.Collections\NativeList.cs:116) Unity.Netcode.NetworkList`1:.ctor() (at .\Library\PackageCache\com.unity.netcode.gameobjects@1.10.0\Runtime\NetworkVariable\Collections\NetworkList.cs:15)
TurnBasedGameManager:.ctor() (at Assets\Scripts\TurnBasedGameManager.cs:21)

After several hours, I couldn’t find a solution. Among other things, I followed this approach, but was unsuccessful:

public override void OnDestroy()
{
    if (playerNames != null)
    {
        playerNames.Dispose();
    }
    base.OnDestroy();
}

Maybe someone can help me. Thanks in advance.
Here is the full code:

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

public class TurnBasedGameManager : NetworkBehaviour
{
    public GameObject lobbyPanel;
    public Text lobbyCodeText;
    public Text playerListText;
    public Button startGameButton;
    public InputField playerNameInput;
    public InputField joinCodeInput;
    public Button hostButton;
    public Button joinButton;

    private string lobbyCode;
    private NetworkVariable<bool> isGameStarted = new NetworkVariable<bool>(false);
    private NetworkList<FixedString128Bytes> playerNames = new NetworkList<FixedString128Bytes>();

    private void Start()
    {
        hostButton.onClick.AddListener(OnHostGame);
        joinButton.onClick.AddListener(OnJoinGame);
        startGameButton.onClick.AddListener(OnStartGame);

        isGameStarted.OnValueChanged += (oldValue, newValue) =>
        {
            if (newValue)
            {
                SceneManager.LoadScene("GameScene");
            }
        };

        playerNames.OnListChanged += (NetworkListEvent<FixedString128Bytes> changeEvent) =>
        {
            UpdatePlayerListUI();
        };
    }
    

    private void UpdatePlayerListUI()
    {
        playerListText.text = "Players in Lobby:\n";
        foreach (var name in playerNames)
        {
            playerListText.text += $"- {name}\n";
        }
    }

    private void OnHostGame()
    {
        NetworkManager.Singleton.StartHost();
        lobbyCode = GenerateLobbyCode();
        lobbyCodeText.text = $"Lobby Code: {lobbyCode}";
        AddPlayerToLobbyServerRpc(playerNameInput.text);
        lobbyPanel.SetActive(true);
    }

    private void OnJoinGame()
    {
        NetworkManager.Singleton.StartClient();
        SubmitJoinRequestServerRpc(joinCodeInput.text, playerNameInput.text);
        lobbyPanel.SetActive(true);
    }

    private void OnStartGame()
    {
        if (IsHost)
        {
            isGameStarted.Value = true;
        }
    }

    [ServerRpc(RequireOwnership = false)]
    private void SubmitJoinRequestServerRpc(string submittedCode, string playerName, ServerRpcParams rpcParams = default)
    {
        if (submittedCode == lobbyCode)
        {
            AddPlayerToLobbyServerRpc(playerName);
            NotifyClientLobbyAcceptedClientRpc(rpcParams.Receive.SenderClientId);
        }
        else
        {
            NotifyClientLobbyRejectedClientRpc(rpcParams.Receive.SenderClientId);
        }
    }

    [ClientRpc]
    private void NotifyClientLobbyAcceptedClientRpc(ulong clientId)
    {
        if (NetworkManager.Singleton.LocalClientId == clientId)
        {
            // Successfully joined the lobby
        }
    }

    [ClientRpc]
    private void NotifyClientLobbyRejectedClientRpc(ulong clientId)
    {
        if (NetworkManager.Singleton.LocalClientId == clientId)
        {
            Debug.LogError("Failed to join lobby: Invalid code.");
        }
    }

    [ServerRpc(RequireOwnership = false)]
    private void AddPlayerToLobbyServerRpc(string playerName, ServerRpcParams rpcParams = default)
    {
        if (IsHost || NetworkManager.Singleton.IsServer)
        {
            playerNames.Add(new FixedString128Bytes(playerName));
        }
    }

    private string GenerateLobbyCode()
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        System.Random random = new System.Random();
        char[] code = new char[6];
        for (int i = 0; i < code.Length; i++)
        {
            code[i] = chars[random.Next(chars.Length)];
        }
        return new string(code);
    }
}

Try instantiating the network list in Awake rather than where it’s declared:

    private NetworkList<FixedString128Bytes> playerNames;

    private void Awake()
    {
        playerNames = new NetworkList<FixedString128Bytes>();
    }
1 Like

It is wasteful to maintain a network synchronized list of player names! Consider that names don’t often change, and strings can be quite large (128 bytes here) but every time any one’s name changes the whole list gets synchronized again (barring any optimizations).

A better way to share player names among players is to send an RPC with the player name as parameter every time a player’s name changes. If the player name cannot change while playing it is sufficient to provide the player name in the connection approval payload.

Then locally maintain a list of player names respectively a dictionary where the key is either the player object’s NetworkObjectId or the client id of the player.

2 Likes