I am using NetworkList to store a list of player scores. I initially ran into a memory leak when I initialized the list in its declaration, as is expected based on the docs. However, even after moving the initialization into Awake(), I am still getting memory leaks.
After doing some searching, it seems like calling Dispose() on the list at some point might be a solution. I tried this but then ran into other issues regarding “accessing a disposed object”.
Is this the proper solution, and I just need to rework my code to work with this? Or is there a better solution?
using UnityEngine;
using Unity.Netcode;
using System.Collections.Generic;
using TMPro;
public class PlayerScoreManager : NetworkBehaviour
{
public NetworkList<int> playerScores;
[SerializeField] private List<TMP_Text> playerScoreTexts;
private void Awake()
{
playerScores = new NetworkList<int>();
}
public override void OnNetworkSpawn()
{
NetworkManager.Singleton.OnConnectionEvent += OnClientConnected;
base.OnNetworkSpawn();
}
public override void OnNetworkDespawn()
{
NetworkManager.Singleton.OnConnectionEvent -= OnClientConnected;
base.OnNetworkDespawn();
}
public override void OnDestroy()
{
//playerScores.Dispose(); // I have been playing around with using Dispose
base.OnDestroy();
}
private void OnClientConnected(NetworkManager networkManager, ConnectionEventData connectionData)
{
if (connectionData.EventType != ConnectionEvent.ClientConnected) return;
if (IsServer)
{
Debug.Log("Adding a player score");
playerScores.Add(0);
}
}
// Player index starts at 0
public void IncrementPlayerScore(ulong clientId, int step = 1)
{
if (!IsServer) return;
int playerIndex = (int)clientId;
if (playerIndex > playerScores.Count - 1)
{
Debug.LogWarning("Attempt to add score to non-existent player");
return;
}
Debug.Log("Incrementing player score");
playerScores[playerIndex] += step;
}
private void UpdateScoreUi()
{
for (int i = 0; i < playerScores.Count; i++)
{
TMP_Text playerScoreText = playerScoreTexts[i];
playerScoreText.text = playerScores[i].ToString();
}
}
private void Update()
{
if (IsClient && IsSpawned)
{
UpdateScoreUi();
}
}
}
I’ve been playing with this and I think in kjowak’s case it has to do with the network list being public. It looks like as part of the serialisation for the inspector the network list is auto instantiated and is then instantiated again in Awake and the first is never disposed. Without the second instantiation the list is usable if Initialise is called on it and added values do appear in the inspector but it still causes a memory leak.
Incidentally public network variables are also auto instantiated but much sooner than the list so I’m not 100% on the above.
public class CommandController : NetworkBehaviour
{
public NetworkList<LogOutput> LogOutputs = new();
public RoomController RoomController;
public void InputCommand(string command)
{
InputCommandRpc(command);
}
[Rpc(SendTo.Server)]
private void InputCommandRpc(FixedString64Bytes command, RpcParams rpcParams = default)
{
if (rpcParams.Receive.SenderClientId != RoomController.ownerClientId.Value)
{
Log.Warning($"Client {rpcParams.Receive.SenderClientId} attempts to input command without owner perm");
return;
}
var logOutputs = CommandService.ProcessCommand(command.ToString(), rpcParams.Receive.SenderClientId);
foreach (var logOutput in logOutputs)
{
LogOutputs.Add(logOutput);
}
}
}
And yes! Seems if I use private NetworkList declare and remove =new() construction I no longer get memory leak warning outputs:
private readonly NetworkList<LogOutput> _logOutputs;
public NetworkList<LogOutput> LogOutputs => _logOutputs;
Or should say this is because explicit =new() construction (or use public for inspector serialization) caused NetworkListinstance creation in Editor domain and not properly Disposed because NGO does not source generate dispose() calls for NetwokrList on Editor domain reload event?
If true, I’m not quite sure if this would actually cause memory leak in runtime because NGO should source generated dispose() calls on NetworkBehavior OnDestroy
I’m not sure what’s going on under the hood with this. Testing it today it’s a lot more intermittent on whether it does auto instantiate or not.
I don’t think it would cause an issue at runtime either. I should probably create an issue for this although it does appear to have been mentioned before and not picked up.
Is there any particular reason you need the list to be public?
Yes in my case because the elements in the list need to be read by ui script.
btw it’s better choice to expose a IReadOnlyList property as api, but NetworkList did not implement this😂
This appears to be specific to in-scene placed NetworkObjects that have one or more NetworkBehaviour components with one or more NetworkList properties. However, it looks like the two NativeLists within NetworkList were being instantiated when the scene was loaded within the editor but never would be disposed due to the in-editor instance not being considered “spawned” and the NetworkBehaviour’s dispose method was never being invoked.