Skip to content

Commit c4bed49

Browse files
fix: dispatch metrics once per frame instead of once per tick (MTT-1391) (#1261)
* fix: dispatch metrics once per frame instead of once per tick (MTT-1391) * fixed encoding * fixed bug where a network manager which has executed a single tick would not dispatch the first frame after it was reset * Updated to use a dirty flag in the metrics instead of a tick check in the network manager Co-authored-by: Matt Walsh <[email protected]>
1 parent 9c61c51 commit c4bed49

File tree

4 files changed

+197
-3
lines changed

4 files changed

+197
-3
lines changed

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,7 @@ private void OnNetworkPreUpdate()
11761176
private void OnNetworkPostLateUpdate()
11771177
{
11781178
m_MessagingSystem.ProcessSendQueues();
1179+
NetworkMetrics.DispatchFrame();
11791180
}
11801181

11811182
/// <summary>
@@ -1194,8 +1195,6 @@ private void OnNetworkManagerTick()
11941195
{
11951196
SyncTime();
11961197
}
1197-
1198-
NetworkMetrics.DispatchFrame();
11991198
}
12001199

12011200
private void SendConnectionRequest()

com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ internal class NetworkMetrics : INetworkMetrics
3737
readonly EventMetric<ServerLogEvent> m_ServerLogReceivedEvent = new EventMetric<ServerLogEvent>(NetworkMetricTypes.ServerLogReceived.Id);
3838
readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id);
3939
readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id);
40+
private bool m_Dirty;
4041

4142
readonly Dictionary<ulong, NetworkObjectIdentifier> m_NetworkGameObjects = new Dictionary<ulong, NetworkObjectIdentifier>();
4243

@@ -87,16 +88,19 @@ public void TrackNetworkObject(NetworkObject networkObject)
8788
public void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount)
8889
{
8990
m_NetworkMessageSentEvent.Mark(new NetworkMessageEvent(new ConnectionInfo(receivedClientId), messageType, bytesCount));
91+
MarkDirty();
9092
}
9193

9294
public void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount)
9395
{
9496
m_NetworkMessageReceivedEvent.Mark(new NetworkMessageEvent(new ConnectionInfo(senderClientId), messageType, bytesCount));
97+
MarkDirty();
9598
}
9699

97100
public void TrackNamedMessageSent(ulong receiverClientId, string messageName, long bytesCount)
98101
{
99102
m_NamedMessageSentEvent.Mark(new NamedMessageEvent(new ConnectionInfo(receiverClientId), messageName, bytesCount));
103+
MarkDirty();
100104
}
101105

102106
public void TrackNamedMessageSent(IReadOnlyCollection<ulong> receiverClientIds, string messageName, long bytesCount)
@@ -110,11 +114,13 @@ public void TrackNamedMessageSent(IReadOnlyCollection<ulong> receiverClientIds,
110114
public void TrackNamedMessageReceived(ulong senderClientId, string messageName, long bytesCount)
111115
{
112116
m_NamedMessageReceivedEvent.Mark(new NamedMessageEvent(new ConnectionInfo(senderClientId), messageName, bytesCount));
117+
MarkDirty();
113118
}
114119

115120
public void TrackUnnamedMessageSent(ulong receiverClientId, long bytesCount)
116121
{
117122
m_UnnamedMessageSentEvent.Mark(new UnnamedMessageEvent(new ConnectionInfo(receiverClientId), bytesCount));
123+
MarkDirty();
118124
}
119125

120126
public void TrackUnnamedMessageSent(IReadOnlyCollection<ulong> receiverClientIds, long bytesCount)
@@ -128,6 +134,7 @@ public void TrackUnnamedMessageSent(IReadOnlyCollection<ulong> receiverClientIds
128134
public void TrackUnnamedMessageReceived(ulong senderClientId, long bytesCount)
129135
{
130136
m_UnnamedMessageReceivedEvent.Mark(new UnnamedMessageEvent(new ConnectionInfo(senderClientId), bytesCount));
137+
MarkDirty();
131138
}
132139

133140
public void TrackNetworkVariableDeltaSent(
@@ -145,6 +152,7 @@ public void TrackNetworkVariableDeltaSent(
145152
variableName,
146153
networkBehaviourName,
147154
bytesCount));
155+
MarkDirty();
148156
}
149157

150158
public void TrackNetworkVariableDeltaReceived(
@@ -162,37 +170,44 @@ public void TrackNetworkVariableDeltaReceived(
162170
variableName,
163171
networkBehaviourName,
164172
bytesCount));
173+
MarkDirty();
165174
}
166175

167176
public void TrackOwnershipChangeSent(ulong receiverClientId, ulong networkObjectId, string gameObjectName, long bytesCount)
168177
{
169178
m_OwnershipChangeSentEvent.Mark(new OwnershipChangeEvent(new ConnectionInfo(receiverClientId), new NetworkObjectIdentifier(gameObjectName, networkObjectId), bytesCount));
179+
MarkDirty();
170180
}
171181

172182
public void TrackOwnershipChangeReceived(ulong senderClientId, ulong networkObjectId, string gameObjectName, long bytesCount)
173183
{
174184
m_OwnershipChangeReceivedEvent.Mark(new OwnershipChangeEvent(new ConnectionInfo(senderClientId),
175185
new NetworkObjectIdentifier(gameObjectName, networkObjectId), bytesCount));
186+
MarkDirty();
176187
}
177188

178189
public void TrackObjectSpawnSent(ulong receiverClientId, ulong networkObjectId, string gameObjectName, long bytesCount)
179190
{
180191
m_ObjectSpawnSentEvent.Mark(new ObjectSpawnedEvent(new ConnectionInfo(receiverClientId), new NetworkObjectIdentifier(gameObjectName, networkObjectId), bytesCount));
192+
MarkDirty();
181193
}
182194

183195
public void TrackObjectSpawnReceived(ulong senderClientId, ulong networkObjectId, string gameObjectName, long bytesCount)
184196
{
185197
m_ObjectSpawnReceivedEvent.Mark(new ObjectSpawnedEvent(new ConnectionInfo(senderClientId), new NetworkObjectIdentifier(gameObjectName, networkObjectId), bytesCount));
198+
MarkDirty();
186199
}
187200

188201
public void TrackObjectDestroySent(ulong receiverClientId, ulong networkObjectId, string gameObjectName, long bytesCount)
189202
{
190203
m_ObjectDestroySentEvent.Mark(new ObjectDestroyedEvent(new ConnectionInfo(receiverClientId), new NetworkObjectIdentifier(gameObjectName, networkObjectId), bytesCount));
204+
MarkDirty();
191205
}
192206

193207
public void TrackObjectDestroyReceived(ulong senderClientId, ulong networkObjectId, string gameObjectName, long bytesCount)
194208
{
195209
m_ObjectDestroyReceivedEvent.Mark(new ObjectDestroyedEvent(new ConnectionInfo(senderClientId), new NetworkObjectIdentifier(gameObjectName, networkObjectId), bytesCount));
210+
MarkDirty();
196211
}
197212

198213
public void TrackRpcSent(
@@ -214,6 +229,7 @@ public void TrackRpcSent(
214229
rpcName,
215230
networkBehaviourName,
216231
bytesCount));
232+
MarkDirty();
217233
}
218234

219235
public void TrackRpcSent(
@@ -247,16 +263,19 @@ public void TrackRpcReceived(
247263
rpcName,
248264
networkBehaviourName,
249265
bytesCount));
266+
MarkDirty();
250267
}
251268

252269
public void TrackServerLogSent(ulong receiverClientId, uint logType, long bytesCount)
253270
{
254271
m_ServerLogSentEvent.Mark(new ServerLogEvent(new ConnectionInfo(receiverClientId), (Unity.Multiplayer.Tools.MetricTypes.LogLevel)logType, bytesCount));
272+
MarkDirty();
255273
}
256274

257275
public void TrackServerLogReceived(ulong senderClientId, uint logType, long bytesCount)
258276
{
259277
m_ServerLogReceivedEvent.Mark(new ServerLogEvent(new ConnectionInfo(senderClientId), (Unity.Multiplayer.Tools.MetricTypes.LogLevel)logType, bytesCount));
278+
MarkDirty();
260279
}
261280

262281
public void TrackSceneEventSent(IReadOnlyList<ulong> receiverClientIds, uint sceneEventType, string sceneName, long bytesCount)
@@ -270,16 +289,27 @@ public void TrackSceneEventSent(IReadOnlyList<ulong> receiverClientIds, uint sce
270289
public void TrackSceneEventSent(ulong receiverClientId, uint sceneEventType, string sceneName, long bytesCount)
271290
{
272291
m_SceneEventSentEvent.Mark(new SceneEventMetric(new ConnectionInfo(receiverClientId), (SceneEventType)sceneEventType, sceneName, bytesCount));
292+
MarkDirty();
273293
}
274294

275295
public void TrackSceneEventReceived(ulong senderClientId, uint sceneEventType, string sceneName, long bytesCount)
276296
{
277297
m_SceneEventReceivedEvent.Mark(new SceneEventMetric(new ConnectionInfo(senderClientId), (SceneEventType)sceneEventType, sceneName, bytesCount));
298+
MarkDirty();
278299
}
279300

280301
public void DispatchFrame()
281302
{
282-
Dispatcher.Dispatch();
303+
if (m_Dirty)
304+
{
305+
Dispatcher.Dispatch();
306+
m_Dirty = false;
307+
}
308+
}
309+
310+
private void MarkDirty()
311+
{
312+
m_Dirty = true;
283313
}
284314
}
285315

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#if MULTIPLAYER_TOOLS
2+
using System;
3+
using System.Collections;
4+
using NUnit.Framework;
5+
using Unity.Collections;
6+
using Unity.Multiplayer.Tools.NetStats;
7+
using UnityEngine;
8+
using UnityEngine.TestTools;
9+
10+
namespace Unity.Netcode.RuntimeTests.Metrics
11+
{
12+
public class MetricsDispatchTests
13+
{
14+
private class MockMetricsObserver : IMetricObserver
15+
{
16+
public Action OnObserve;
17+
public void Observe(MetricCollection collection) => OnObserve?.Invoke();
18+
}
19+
20+
private int m_NumTicks;
21+
private int m_NumDispatches;
22+
23+
private NetworkManager m_NetworkManager;
24+
private NetworkTimeSystem timeSystem => m_NetworkManager.NetworkTimeSystem;
25+
private NetworkTickSystem tickSystem => m_NetworkManager.NetworkTickSystem;
26+
private uint m_TickRate = 1;
27+
28+
[SetUp]
29+
public void SetUp()
30+
{
31+
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(
32+
out m_NetworkManager,
33+
NetworkManagerHelper.NetworkManagerOperatingMode.Host,
34+
new NetworkConfig()
35+
{
36+
TickRate = m_TickRate
37+
}));
38+
39+
InitNetworkManager();
40+
41+
var networkMetrics = m_NetworkManager.NetworkMetrics as NetworkMetrics;
42+
networkMetrics.Dispatcher.RegisterObserver(new MockMetricsObserver()
43+
{
44+
OnObserve = ()=> m_NumDispatches++
45+
});
46+
}
47+
48+
private void InitNetworkManager()
49+
{
50+
tickSystem.Tick += ()=> m_NumTicks++;
51+
}
52+
53+
[TearDown]
54+
public void TearDown()
55+
{
56+
NetworkManagerHelper.ShutdownNetworkManager();
57+
m_NumTicks = default;
58+
m_NumDispatches = default;
59+
m_NetworkManager = default;
60+
}
61+
62+
[UnityTest]
63+
public IEnumerator GivenMetricsTracked_MultipleTicksPass_OneDispatchOccurs()
64+
{
65+
SendMetric();
66+
AdvanceTicks(2);
67+
68+
// Wait one frame so dispatch occurs
69+
yield return null;
70+
71+
Assert.AreEqual(2, m_NumTicks);
72+
Assert.AreEqual(1, m_NumDispatches);
73+
}
74+
75+
[UnityTest]
76+
public IEnumerator GivenMetricsTracked_OneTickPasses_OneDispatchOccurs()
77+
{
78+
SendMetric();
79+
AdvanceTicks(1);
80+
81+
// Wait one frame so dispatch occurs
82+
yield return null;
83+
84+
Assert.AreEqual(1, m_NumTicks);
85+
Assert.AreEqual(1, m_NumDispatches);
86+
}
87+
88+
[UnityTest]
89+
public IEnumerator GivenMetricsTracked_ZeroTicksPass_OneDispatchOccurs()
90+
{
91+
SendMetric();
92+
93+
yield return null;
94+
95+
Assert.AreEqual(0, m_NumTicks);
96+
Assert.AreEqual(1, m_NumDispatches);
97+
}
98+
99+
[UnityTest]
100+
public IEnumerator GivenNoMetricsTracked_ZeroTicksPass_NoDispatchOccurs()
101+
{
102+
yield return null;
103+
104+
Assert.AreEqual(0, m_NumTicks);
105+
Assert.AreEqual(0, m_NumDispatches);
106+
}
107+
108+
[UnityTest]
109+
public IEnumerator GivenNoMetricsTracked_MultipleTicksPass_NoDispatchOccurs()
110+
{
111+
AdvanceTicks(2);
112+
113+
yield return null;
114+
115+
Assert.AreEqual(2, m_NumTicks);
116+
Assert.AreEqual(0, m_NumDispatches);
117+
}
118+
119+
[UnityTest]
120+
// This tests a regression where dispatches would not occur when the network manager was reset
121+
// after a single tick and then started again. The first frame would not dispatch even though it should
122+
// This is likely not an edge case after refactoring to use dirty flags in the dispatcher
123+
// but the test should still pass
124+
public IEnumerator GivenReinitializedNetworkManagerAfterOneTickExecuted_WhenFirstTickExecuted_MetricsDispatch()
125+
{
126+
SendMetric();
127+
AdvanceTicks(1);
128+
yield return null;
129+
130+
Assert.AreEqual(1, m_NumTicks);
131+
Assert.AreEqual(1, m_NumDispatches);
132+
133+
m_NetworkManager.Shutdown();
134+
m_NetworkManager.StartHost();
135+
InitNetworkManager();
136+
137+
SendMetric();
138+
AdvanceTicks(1);
139+
yield return null;
140+
141+
Assert.AreEqual(2, m_NumTicks);
142+
Assert.AreEqual(2, m_NumDispatches);
143+
}
144+
145+
private void AdvanceTicks(int numTicks)
146+
{
147+
timeSystem.Advance(1f / m_TickRate * (numTicks + 0.1f));
148+
tickSystem.UpdateTick(timeSystem.LocalTime, timeSystem.ServerTime);
149+
}
150+
151+
private void SendMetric()
152+
{
153+
var writer = new FastBufferWriter(1300, Allocator.Temp);
154+
using (writer)
155+
{
156+
m_NetworkManager.CustomMessagingManager.SendNamedMessage("FakeMetric", m_NetworkManager.LocalClientId, writer);
157+
}
158+
}
159+
160+
}
161+
}
162+
#endif

com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs.meta

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

0 commit comments

Comments
 (0)