Skip to content

perf!: reduce linq, cleanup connectedclients #1196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 66 commits into from
Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
2fa0f6d
FastBufferWriter implemented and tested (still need to add and update…
ShadauxCat Aug 17, 2021
c36b3af
A few additional tests to cover the last missed cases I can think of …
ShadauxCat Aug 17, 2021
5ac5ef9
FastBufferReader + tests
ShadauxCat Aug 18, 2021
4776737
- More tests
ShadauxCat Aug 19, 2021
f245150
- Removed NativeArray from FastBufferReader and FastBufferWriter, rep…
ShadauxCat Aug 19, 2021
fc944c4
Added utility ref struct "Ref<T>" to more generically support wrappin…
ShadauxCat Aug 19, 2021
cb95862
BufferSerializer and tests.
ShadauxCat Aug 23, 2021
a3e0abc
Removed unnecessary comment.
ShadauxCat Aug 23, 2021
ea31f10
XMLDocs + cleanup for PR
ShadauxCat Aug 24, 2021
057c1fc
Merge branch 'develop' into feature/fast_buffer_reader_writer
ShadauxCat Aug 24, 2021
63309e2
Replaced possibly unaligned memory access with UnsafeUtility.MemCpy..…
ShadauxCat Aug 24, 2021
e9b9305
Resurrected BytewiseUtil.FastCopyBytes as a faster alternative to Uns…
ShadauxCat Aug 24, 2021
aa56440
Reverting an accidental change.
ShadauxCat Aug 24, 2021
fabb3b8
Removed files that got accidentally duplicated from before the rename.
ShadauxCat Aug 24, 2021
3a2a50b
Standards fixes
ShadauxCat Aug 24, 2021
58db1bd
Removed accidentally added files.
ShadauxCat Aug 24, 2021
c827339
Added BuildInfo.json to the .gitignore so I stop accidentally checkin…
ShadauxCat Aug 24, 2021
f8bfb2f
Addressed most of the review feedback. Still need to do a little more…
ShadauxCat Aug 26, 2021
9816b50
standards.py --fix
ShadauxCat Aug 26, 2021
a576552
standards.py --fix
ShadauxCat Aug 26, 2021
5193e39
Fixed incorrect namespaces.
ShadauxCat Aug 26, 2021
119ee2f
-Fixed a couple of issues where growing a FastBufferWriter wouldn't w…
ShadauxCat Aug 26, 2021
7fb2d53
Fix a test failure and better implementation of large growths
ShadauxCat Aug 27, 2021
8ee4610
- Removed RefArray
ShadauxCat Aug 31, 2021
808f28a
First INetworkMessage WIP - not hooked up to anything yet.
ShadauxCat Aug 31, 2021
81c3dc3
Killed DelayUtil.
ShadauxCat Sep 1, 2021
8dfa959
Merge branch 'develop' into wip/inetworkmessage
ShadauxCat Sep 7, 2021
b24d1a7
Hooked up MessagingSystem and converted ConnectionRequestMessage and …
ShadauxCat Sep 8, 2021
441d933
More converted messages
ShadauxCat Sep 10, 2021
1436cf8
Finished converting all messages over and removed all the now-dead code.
ShadauxCat Sep 14, 2021
90f41be
Removed IMessageHandler as it's no longer needed after DelayUtil went…
ShadauxCat Sep 14, 2021
2957095
Fixed tests.
ShadauxCat Sep 14, 2021
7a21615
standards.py --fix
ShadauxCat Sep 14, 2021
1e8761b
Corrected some incorrect xmldocs.
ShadauxCat Sep 14, 2021
89e66e8
-Added some tests for serializing RPC parameters using FastBufferRead…
ShadauxCat Sep 15, 2021
7f95934
Fixed missing send queue information for the server on StartClient().
ShadauxCat Sep 15, 2021
c788567
Added ILPP compile-time check for INetworkMessage Receive() function.
ShadauxCat Sep 15, 2021
eb26475
Missed the meta file.
ShadauxCat Sep 15, 2021
d2a2635
Changed generic function lookup in ILPP to use Cecil instead of relyi…
ShadauxCat Sep 15, 2021
3d176ee
Changed scene management delivery type to ReliableFragmentedSequenced…
ShadauxCat Sep 15, 2021
b190a20
Merge branch 'develop' into wip/inetworkmessage
ShadauxCat Sep 15, 2021
fa848a4
Fixed DontDestroyWithOwner throwing an exception on disconnect, fixed…
ShadauxCat Sep 15, 2021
00c6f71
- Removed DynamicUnmanagedArray in favor of NativeList
ShadauxCat Sep 15, 2021
73a1e1f
standards.py --fix, plus some adjustments based on over-the-zoom-shou…
ShadauxCat Sep 15, 2021
e4db745
Not sure how standards.py --fix missed this the first time.
ShadauxCat Sep 16, 2021
73b2071
increasing timeout in hopes it fixes test failure.
ShadauxCat Sep 16, 2021
3b71334
Merge branch 'develop' into feature/inetworkmessage
ShadauxCat Sep 16, 2021
f23af57
Restored ClientConnected on the ServerClientId in StartClient... seem…
ShadauxCat Sep 16, 2021
d5f6436
perf: refactor networkmanger connected clients and reduce gc allocs/ …
LukeStampfli Sep 16, 2021
0d065be
Cherrypick from fast buffer reader/writer branch to fix accidentally …
ShadauxCat Sep 16, 2021
23b3039
use better capacity
LukeStampfli Sep 16, 2021
e8b59c9
Applied much review feedback.
ShadauxCat Sep 16, 2021
bdfc6e6
xml docs
LukeStampfli Sep 16, 2021
57b56c0
Merge remote-tracking branch 'origin/feature/inetworkmessage' into re…
LukeStampfli Sep 17, 2021
18a1d39
simplify readonlylist path
LukeStampfli Sep 17, 2021
ad5d90a
use not server exception instead of asserts
LukeStampfli Sep 17, 2021
b82e130
remove unused usings
LukeStampfli Sep 17, 2021
fcafdb3
make connectedlist fields private instead of internal.
LukeStampfli Sep 17, 2021
cd7229a
Merge remote-tracking branch 'origin/develop' into refactor/remove-linq
LukeStampfli Sep 20, 2021
1276dd6
Merge remote-tracking branch 'origin/develop' into refactor/remove-linq
LukeStampfli Sep 28, 2021
34e0e6d
merge conflicts
LukeStampfli Sep 28, 2021
4ce41eb
fix merge
LukeStampfli Sep 28, 2021
20c91bc
standards
LukeStampfli Sep 28, 2021
e5ec6aa
clear LocalClient on initialize
LukeStampfli Sep 28, 2021
4ec10a2
add tools usings back
LukeStampfli Sep 28, 2021
50cc9eb
fix metrics integration
LukeStampfli Sep 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions com.unity.netcode.adapter.utp/Runtime/UnityTransport.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent;
using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent;
using Unity.Collections.LowLevel.Unsafe;
Expand Down Expand Up @@ -472,12 +470,21 @@ public override void DisconnectRemoteClient(ulong clientId)
}

// we need to cleanup any SendQueues for this connectionID;
var keys = m_SendQueue.Keys.Where(k => k.ClientId == clientId).ToList();
var keys = new NativeList<SendTarget>(16, Allocator.Temp); // use nativelist and manual foreach to avoid allocations
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Cosmin-B what do you think should we do this or keep linq here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Also, is worth noting that this call would not happen frequently at all... so the performance benefit is super tiny.

foreach (var key in m_SendQueue.Keys)
{
if (key.ClientId == clientId)
{
keys.Add(key);
}
}

foreach (var queue in keys)
{
m_SendQueue[queue].Dispose();
m_SendQueue.Remove(queue);
}
keys.Dispose();
}
}

Expand Down
11 changes: 2 additions & 9 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still here because of profiler stuff (which is really broken and needs a separate pass). It's now at the right position at least 🙃

using UnityEngine;
using System.Reflection;
using System.Linq;
using Unity.Collections;

namespace Unity.Netcode
Expand Down Expand Up @@ -131,14 +131,7 @@ internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId,

if (rpcParams.Send.TargetClientIds != null)
{
// Copy into a localArray because SendMessage doesn't take IEnumerable, only IReadOnlyList
ulong* localArray = stackalloc ulong[rpcParams.Send.TargetClientIds.Count()];
var index = 0;
foreach (var clientId in rpcParams.Send.TargetClientIds)
{
localArray[index++] = clientId;
}
messageSize = NetworkManager.SendMessage(message, networkDelivery, localArray, index, true);
messageSize = NetworkManager.SendMessage(message, networkDelivery, in rpcParams.Send.TargetClientIds, true);
}
else if (rpcParams.Send.TargetClientIdsNativeArray != null)
{
Expand Down
78 changes: 65 additions & 13 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
Expand Down Expand Up @@ -251,20 +250,61 @@ internal set

private ulong m_LocalClientId;

private Dictionary<ulong, NetworkClient> m_ConnectedClients = new Dictionary<ulong, NetworkClient>();

private List<NetworkClient> m_ConnectedClientsList = new List<NetworkClient>();

private List<ulong> m_ConnectedClientIds = new List<ulong>();

/// <summary>
/// Gets a dictionary of connected clients and their clientId keys. This is only populated on the server.
/// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server.
/// </summary>
public readonly Dictionary<ulong, NetworkClient> ConnectedClients = new Dictionary<ulong, NetworkClient>();
public IReadOnlyDictionary<ulong, NetworkClient> ConnectedClients
{
get
{
if (IsServer == false)
{
throw new NotServerException($"{nameof(ConnectedClients)} should only be accessed on server.");
}
return m_ConnectedClients;
}
}

/// <summary>
/// Gets a list of connected clients. This is only populated on the server.
/// Gets a list of connected clients. This is only accessible on the server.
/// </summary>
public readonly List<NetworkClient> ConnectedClientsList = new List<NetworkClient>();
public IReadOnlyList<NetworkClient> ConnectedClientsList
{
get
{
if (IsServer == false)
{
throw new NotServerException($"{nameof(ConnectedClientsList)} should only be accessed on server.");
}
return m_ConnectedClientsList;
}
}

/// <summary>
/// Gets a list of just the IDs of all connected clients.
/// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
/// </summary>
public ulong[] ConnectedClientsIds => ConnectedClientsList.Select(c => c.ClientId).ToArray();
public IReadOnlyList<ulong> ConnectedClientsIds
{
get
{
if (IsServer == false)
{
throw new NotServerException($"{nameof(m_ConnectedClientIds)} should only be accessed on server.");
}
return m_ConnectedClientIds;
}
}

/// <summary>
/// Gets the local <see cref="NetworkClient"/> for this client.
/// </summary>
public NetworkClient LocalClient { get; internal set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this getting set in connection approval, but I don't ever see it unset?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this was already an issue before where ConnectedClients was not cleared until the next initialize. I added a line which clears LocalClient in initialize. So this will behave differently as before which is not ideal behavior but I think we decided to touch the initialization/shutdown flow it should be in a separate PR.


/// <summary>
/// Gets a dictionary of the clients that have been accepted by the transport but are still pending by the Netcode. This is only populated on the server.
Expand Down Expand Up @@ -468,8 +508,10 @@ private void Initialize(bool server)
LocalClientId = ulong.MaxValue;

PendingClients.Clear();
ConnectedClients.Clear();
ConnectedClientsList.Clear();
m_ConnectedClients.Clear();
m_ConnectedClientsList.Clear();
m_ConnectedClientIds.Clear();
LocalClient = null;
NetworkObject.OrphanChildren.Clear();

// Create spawn manager instance
Expand Down Expand Up @@ -1415,12 +1457,21 @@ private void OnClientDisconnectFromServer(ulong clientId)
{
if (ConnectedClientsList[i].ClientId == clientId)
{
ConnectedClientsList.RemoveAt(i);
m_ConnectedClientsList.RemoveAt(i);
break;
}
}

for (int i = 0; i < ConnectedClientsIds.Count; i++)
{
if (ConnectedClientsIds[i] == clientId)
{
m_ConnectedClientIds.RemoveAt(i);
break;
}
}

ConnectedClients.Remove(clientId);
m_ConnectedClients.Remove(clientId);
}
m_MessagingSystem.ClientDisconnected(clientId);
}
Expand Down Expand Up @@ -1462,8 +1513,9 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?
PendingClients.Remove(ownerClientId);

var client = new NetworkClient { ClientId = ownerClientId, };
ConnectedClients.Add(ownerClientId, client);
ConnectedClientsList.Add(client);
m_ConnectedClients.Add(ownerClientId, client);
m_ConnectedClientsList.Add(client);
m_ConnectedClientIds.Add(client.ClientId);

if (createPlayerObject)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ private void OnTransformParentChanged()

unsafe
{
var maxCount = NetworkManager.ConnectedClientsIds.Length;
var maxCount = NetworkManager.ConnectedClientsIds.Count;
ulong* clientIds = stackalloc ulong[maxCount];
int idx = 0;
foreach (var clientId in NetworkManager.ConnectedClientsIds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void Handle(FastBufferReader reader, ulong clientId, NetworkManager netwo
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport.

networkManager.ConnectedClients.Add(networkManager.LocalClientId, new NetworkClient { ClientId = networkManager.LocalClientId });
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };

// Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct ClientRpcSendParams
/// Note: Even if you provide a value type such as NativeArray, enumerating it will cause boxing.
/// If you want to avoid boxing, use TargetClientIdsNativeArray
/// </summary>
public IEnumerable<ulong> TargetClientIds;
public IReadOnlyList<ulong> TargetClientIds;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this becomes IReadOnlyList, then NetworkBehaviour.SendClientRpc() can be adjusted to not have to make a copy of it and just pass it directly to SendMessage() :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, simplified the code to use the SendMessage overload with IReadOnlyList.


/// <summary>
/// NativeArray version of target id list - use either this OR TargetClientIds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void TrackRpcReceived(

void TrackServerLogReceived(ulong senderClientId, uint logType, long bytesCount);

void TrackSceneEventSent(ulong[] receiverClientIds, uint sceneEventType, string sceneName, long bytesCount);
void TrackSceneEventSent(IReadOnlyList<ulong> receiverClientIds, uint sceneEventType, string sceneName, long bytesCount);

void TrackSceneEventSent(ulong receiverClientId, uint sceneEventType, string sceneName, long bytesCount);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public void TrackServerLogReceived(ulong senderClientId, uint logType, long byte
m_ServerLogReceivedEvent.Mark(new ServerLogEvent(new ConnectionInfo(senderClientId), (Unity.Multiplayer.Tools.MetricTypes.LogLevel)logType, bytesCount));
}

public void TrackSceneEventSent(ulong[] receiverClientIds, uint sceneEventType, string sceneName, long bytesCount)
public void TrackSceneEventSent(IReadOnlyList<ulong> receiverClientIds, uint sceneEventType, string sceneName, long bytesCount)
{
foreach (var receiverClientId in receiverClientIds)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void TrackServerLogReceived(ulong senderClientId, uint logType, long byte
{
}

public void TrackSceneEventSent(ulong[] receiverClientIds, uint sceneEventType, string sceneName, long bytesCount)
public void TrackSceneEventSent(IReadOnlyList<ulong> receiverClientIds, uint sceneEventType, string sceneName, long bytesCount)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public class NetworkSpawnManager
/// </summary>
public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>();


/// <summary>
/// Gets the NetworkManager associated with this SpawnManager.
/// </summary>
Expand Down Expand Up @@ -69,7 +68,7 @@ public NetworkObject GetPlayerNetworkObject(ulong clientId)
throw new NotServerException("Only the server can find player objects from other clients.");
}

if (NetworkManager.ConnectedClients.TryGetValue(clientId, out NetworkClient networkClient))
if (TryGetNetworkClient(clientId, out NetworkClient networkClient))
{
return networkClient.PlayerObject;
}
Expand Down Expand Up @@ -111,12 +110,37 @@ internal void RemoveOwnership(NetworkObject networkObject)
foreach (var client in NetworkManager.ConnectedClients)
{
var bytesReported = NetworkManager.LocalClientId == client.Key
? 0
: size;
? 0
: size;
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject.NetworkObjectId, networkObject.name, bytesReported);
}
}

/// <summary>
/// Helper function to get a network client for a clientId from the NetworkManager.
/// On the server this will check the <see cref="NetworkManager.ConnectedClients"/> list.
/// On a non-server this will check the <see cref="NetworkManager.LocalClient"/> only.
/// </summary>
/// <param name="clientId">The clientId for which to try getting the NetworkClient for.</param>
/// <param name="networkClient">The found NetworkClient. Null if no client was found.</param>
/// <returns>True if a NetworkClient with a matching id was found else false.</returns>
private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient)
{
if (NetworkManager.IsServer)
{
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient);
}

if (clientId == NetworkManager.LocalClient.ClientId)
{
networkClient = NetworkManager.LocalClient;
return true;
}

networkClient = null;
return false;
}

internal void ChangeOwnership(NetworkObject networkObject, ulong clientId)
{
if (!NetworkManager.IsServer)
Expand All @@ -129,7 +153,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId)
throw new SpawnStateException("Object is not spawned");
}

if (NetworkManager.ConnectedClients.TryGetValue(networkObject.OwnerClientId, out NetworkClient networkClient))
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
{
Expand Down Expand Up @@ -360,7 +384,7 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong
}
else if (playerObject && ownerClientId.Value == NetworkManager.LocalClientId)
{
NetworkManager.ConnectedClients[ownerClientId.Value].PlayerObject = networkObject;
NetworkManager.LocalClient.PlayerObject = networkObject;
}
}

Expand Down Expand Up @@ -524,7 +548,6 @@ internal void DestroySceneObjects()
}
}


internal void ServerSpawnSceneObjectsOnStartSweep()
{
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
Expand Down Expand Up @@ -577,7 +600,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
}
}

if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && NetworkManager.Singleton.ConnectedClients.TryGetValue(networkObject.OwnerClientId, out NetworkClient networkClient))
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
//Someone owns it.
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
Expand Down Expand Up @@ -641,6 +664,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
}
}
}

networkObject.IsSpawned = false;

if (SpawnedObjects.Remove(networkObject.NetworkObjectId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,10 @@ private void OnGUI()
{
if (GUILayout.Button("Random Teleport"))
{
// Get the local `NetworkClient` by `LocalClientId`
if (networkManager.ConnectedClients.TryGetValue(networkManager.LocalClientId, out var networkedClient))
if (networkManager.LocalClient != null)
{
// Get `BootstrapPlayer` component from the player's `PlayerObject`
if (networkedClient.PlayerObject.TryGetComponent(out BootstrapPlayer bootstrapPlayer))
if (networkManager.LocalClient.PlayerObject.TryGetComponent(out BootstrapPlayer bootstrapPlayer))
{
// Invoke a `ServerRpc` from client-side to teleport player to a random position on the server-side
bootstrapPlayer.RandomTeleportServerRpc();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ public static void ShutdownNetworkManager()

if (NetworkManagerGameObject != null)
{
NetworkManagerObject.ConnectedClientsList.Clear();
Debug.Log($"{nameof(NetworkManager)} shutdown.");

StopNetworkManagerMode();
Expand Down