Skip to content

Commit c4067da

Browse files
committed
Add message receive allocations test
1 parent 3c4c6b1 commit c4067da

5 files changed

Lines changed: 121 additions & 6 deletions

File tree

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage
4747

4848
private Dictionary<ulong, List<int>> m_ForwardUpdates;
4949

50-
private List<int> m_UpdatedNetworkVariables;
50+
private NativeList<int> m_UpdatedNetworkVariables;
5151

5252
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5353
private void WriteNetworkVariable(ref FastBufferWriter writer, ref NetworkVariableBase networkVariable, bool ensureNetworkVariableLengthSafety, int nonfragmentedSize, int fragmentedSize)
@@ -217,7 +217,7 @@ public void Handle(ref NetworkContext context)
217217
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
218218
var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer;
219219
var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer;
220-
m_UpdatedNetworkVariables = new List<int>();
220+
m_UpdatedNetworkVariables = new NativeList<int>(Allocator.Temp);
221221

222222
if (networkBehaviour == null)
223223
{
@@ -396,9 +396,9 @@ public void Handle(ref NetworkContext context)
396396
// When a server forwards delta updates to connected clients, it needs to preserve the previous value
397397
// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This
398398
// is invoked after it is done forwarding the deltas.
399-
foreach (var fieldIndex in m_UpdatedNetworkVariables)
399+
for (int i = 0; i < m_UpdatedNetworkVariables.Length; i++)
400400
{
401-
networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead();
401+
networkBehaviour.NetworkVariableFields[m_UpdatedNetworkVariables[i]].PostDeltaRead();
402402
}
403403
}
404404
}

com.unity.netcode.gameobjects/Tests/Runtime/Helpers/MessageCatcher.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using Unity.Collections;
4+
using UnityEngine;
45

56
namespace Unity.Netcode.RuntimeTests
67
{
@@ -25,14 +26,20 @@ private struct TriggerData
2526

2627
public void ReleaseMessages()
2728
{
28-
2929
foreach (var caughtSpawn in m_CaughtMessages)
3030
{
3131
// Reader will be disposed within HandleMessage
3232
m_OwnerNetworkManager.ConnectionManager.MessageManager.HandleMessage(caughtSpawn.Header, caughtSpawn.Reader, caughtSpawn.SenderId, caughtSpawn.Timestamp, caughtSpawn.SerializedHeaderSize);
3333
}
3434
}
3535

36+
public void HandleCaughtMessage(int index)
37+
{
38+
var caughtMessage = m_CaughtMessages[index];
39+
// Reader will be disposed within HandleMessage
40+
m_OwnerNetworkManager.ConnectionManager.MessageManager.HandleMessage(caughtMessage.Header, caughtMessage.Reader, caughtMessage.SenderId, caughtMessage.Timestamp, caughtMessage.SerializedHeaderSize);
41+
}
42+
3643
public int CaughtMessageCount => m_CaughtMessages.Count;
3744

3845
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Collections;
2+
using NUnit.Framework;
3+
using Unity.Netcode.TestHelpers.Runtime;
4+
using UnityEngine;
5+
using UnityEngine.TestTools;
6+
using UnityEngine.TestTools.Constraints;
7+
using Is = NUnit.Framework.Is;
8+
9+
namespace Unity.Netcode.RuntimeTests
10+
{
11+
internal class AllocationTestBehaviour : NetworkBehaviour
12+
{
13+
internal int RpcReceivedCount;
14+
15+
public NetworkVariable<int> TestVariable = new();
16+
17+
[Rpc(SendTo.NotMe)]
18+
public void NotMeRpc()
19+
{
20+
RpcReceivedCount++;
21+
}
22+
}
23+
24+
[TestFixture(HostOrServer.Host)]
25+
[TestFixture(HostOrServer.DAHost)]
26+
internal class MessageReceiveAllocationTests : NetcodeIntegrationTest
27+
{
28+
protected override int NumberOfClients => 1;
29+
30+
public MessageReceiveAllocationTests(HostOrServer hostOrServer) : base(hostOrServer) {}
31+
32+
private GameObject m_Prefab;
33+
34+
protected override void OnServerAndClientsCreated()
35+
{
36+
m_Prefab = CreateNetworkObjectPrefab(nameof(AllocationTestBehaviour));
37+
m_Prefab.AddComponent<AllocationTestBehaviour>();
38+
base.OnServerAndClientsCreated();
39+
}
40+
41+
[UnityTest]
42+
public IEnumerator NoAllocationsOnMessageReceive()
43+
{
44+
var authority = GetAuthorityNetworkManager();
45+
var nonAuthority = GetNonAuthorityNetworkManager();
46+
47+
var authorityInstance = SpawnObject(m_Prefab, authority);
48+
var authorityComponent = authorityInstance.GetComponent<AllocationTestBehaviour>();
49+
yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance);
50+
AssertOnTimeout("Timed out waiting for objects to spawn");
51+
52+
var nonAuthorityComponent = nonAuthority.SpawnManager.SpawnedObjects[authorityComponent.NetworkObjectId].GetComponent<AllocationTestBehaviour>();
53+
54+
/*
55+
* RpcMessage
56+
*/
57+
var rpcCatcher = new MessageCatcher<RpcMessage>(nonAuthority);
58+
nonAuthority.ConnectionManager.MessageManager.Hook(rpcCatcher);
59+
60+
// Send the same message twice: the first is replayed as a warm-up.
61+
// The second will follow the identical code path and is checked for allocations.
62+
authorityComponent.NotMeRpc();
63+
authorityComponent.NotMeRpc();
64+
yield return WaitForConditionOrTimeOut(() => rpcCatcher.CaughtMessageCount == 2);
65+
AssertOnTimeout($"Timed out waiting to catch all expected {nameof(RpcMessage)} messages. Expected: 2, Actual: {rpcCatcher.CaughtMessageCount}");
66+
67+
// Unhook first so the replayed messages are handled instead of being caught again
68+
nonAuthority.ConnectionManager.MessageManager.Unhook(rpcCatcher);
69+
70+
rpcCatcher.HandleCaughtMessage(0);
71+
Assert.AreEqual(1, nonAuthorityComponent.RpcReceivedCount);
72+
73+
Assert.That(() =>
74+
{
75+
rpcCatcher.HandleCaughtMessage(1);
76+
}, Is.Not.AllocatingGCMemory());
77+
Assert.AreEqual(2, nonAuthorityComponent.RpcReceivedCount);
78+
79+
/*
80+
* NetworkVariableDeltaMessage
81+
*/
82+
var deltaCatcher = new MessageCatcher<NetworkVariableDeltaMessage>(nonAuthority);
83+
nonAuthority.ConnectionManager.MessageManager.Hook(deltaCatcher);
84+
85+
authorityComponent.TestVariable.Value = 1;
86+
// Wait for the first change to be received client-side before sending the second change
87+
yield return WaitForConditionOrTimeOut(() => deltaCatcher.CaughtMessageCount == 1);
88+
authorityComponent.TestVariable.Value = 2;
89+
yield return WaitForConditionOrTimeOut(() => deltaCatcher.CaughtMessageCount == 2);
90+
AssertOnTimeout($"Timed out waiting to catch all expected {nameof(NetworkVariableDeltaMessage)} messages. Expected: 2, Actual: {deltaCatcher.CaughtMessageCount}");
91+
92+
// Unhook first so the replayed messages are handled instead of being caught again
93+
nonAuthority.ConnectionManager.MessageManager.Unhook(deltaCatcher);
94+
95+
deltaCatcher.HandleCaughtMessage(0);
96+
Assert.AreEqual(1, nonAuthorityComponent.TestVariable.Value);
97+
98+
Assert.That(() =>
99+
{
100+
deltaCatcher.HandleCaughtMessage(1);
101+
}, Is.Not.AllocatingGCMemory());
102+
Assert.AreEqual(2, nonAuthorityComponent.TestVariable.Value);
103+
}
104+
105+
}
106+
}

com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
using Unity.Netcode.TestHelpers.Runtime;
99
using UnityEngine;
1010
using UnityEngine.TestTools;
11-
using Random = UnityEngine.Random;
1211
using UnityEngine.TestTools.Constraints;
1312
using Is = NUnit.Framework.Is;
13+
using Random = UnityEngine.Random;
1414

1515
namespace Unity.Netcode.RuntimeTests
1616
{

0 commit comments

Comments
 (0)