Skip to content

Commit 8b2e771

Browse files
authored
fix: unregister NetworkPlayerLoop systems from PlayerLoop (#588)
* uninject NetworkPlayerLoop systems from the PlayerLoop when exiting PlayMode in the Editor so multiplayer systems don't continue to run afterward * implement tests for NetworkUpdateLoop InjectSystems() and UninjectSystems()
1 parent 7346a2f commit 8b2e771

File tree

2 files changed

+207
-106
lines changed

2 files changed

+207
-106
lines changed

com.unity.multiplayer.mlapi/Runtime/Core/NetworkUpdateLoop.cs

Lines changed: 138 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Linq;
32
using System.Collections.Generic;
43
using UnityEngine;
54
using UnityEngine.LowLevel;
@@ -234,131 +233,166 @@ public static PlayerLoopSystem CreateLoopSystem()
234233
}
235234
}
236235

237-
[RuntimeInitializeOnLoadMethod]
236+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
238237
private static void Initialize()
239238
{
240-
var customPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
239+
UnregisterLoopSystems();
240+
RegisterLoopSystems();
241+
}
241242

242-
for (int i = 0; i < customPlayerLoop.subSystemList.Length; i++)
243-
{
244-
var playerLoopSystem = customPlayerLoop.subSystemList[i];
243+
private enum LoopSystemPosition
244+
{
245+
After,
246+
Before
247+
}
245248

246-
if (playerLoopSystem.type == typeof(Initialization))
249+
private static bool TryAddLoopSystem(ref PlayerLoopSystem parentLoopSystem, PlayerLoopSystem childLoopSystem, Type anchorSystemType, LoopSystemPosition loopSystemPosition)
250+
{
251+
int systemPosition = -1;
252+
if (anchorSystemType != null)
253+
{
254+
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
247255
{
248-
var subsystems = playerLoopSystem.subSystemList.ToList();
256+
var subsystem = parentLoopSystem.subSystemList[i];
257+
if (subsystem.type == anchorSystemType)
249258
{
250-
// insert at the bottom of `Initialization`
251-
subsystems.Add(NetworkInitialization.CreateLoopSystem());
259+
systemPosition = loopSystemPosition == LoopSystemPosition.After ? i + 1 : i;
260+
break;
252261
}
253-
playerLoopSystem.subSystemList = subsystems.ToArray();
254262
}
255-
else if (playerLoopSystem.type == typeof(EarlyUpdate))
263+
}
264+
else
265+
{
266+
systemPosition = loopSystemPosition == LoopSystemPosition.After ? parentLoopSystem.subSystemList.Length : 0;
267+
}
268+
269+
if (systemPosition == -1) return false;
270+
271+
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length + 1];
272+
273+
// begin = systemsBefore + systemsAfter
274+
// + systemsBefore
275+
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
276+
// + childSystem
277+
newSubsystemList[systemPosition] = childLoopSystem;
278+
// + systemsAfter
279+
if (systemPosition < parentLoopSystem.subSystemList.Length) Array.Copy(parentLoopSystem.subSystemList, systemPosition, newSubsystemList, systemPosition + 1, parentLoopSystem.subSystemList.Length - systemPosition);
280+
// end = systemsBefore + childSystem + systemsAfter
281+
282+
parentLoopSystem.subSystemList = newSubsystemList;
283+
284+
return true;
285+
}
286+
287+
private static bool TryRemoveLoopSystem(ref PlayerLoopSystem parentLoopSystem, Type childSystemType)
288+
{
289+
int systemPosition = -1;
290+
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
291+
{
292+
var subsystem = parentLoopSystem.subSystemList[i];
293+
if (subsystem.type == childSystemType)
256294
{
257-
var subsystems = playerLoopSystem.subSystemList.ToList();
258-
{
259-
int subsystemCount = subsystems.Count;
260-
for (int k = 0; k < subsystemCount; k++)
261-
{
262-
if (subsystems[k].type == typeof(EarlyUpdate.ScriptRunDelayedStartupFrame))
263-
{
264-
// insert before `EarlyUpdate.ScriptRunDelayedStartupFrame`
265-
subsystems.Insert(k, NetworkEarlyUpdate.CreateLoopSystem());
266-
break;
267-
}
268-
}
269-
}
270-
playerLoopSystem.subSystemList = subsystems.ToArray();
295+
systemPosition = i;
296+
break;
271297
}
272-
else if (playerLoopSystem.type == typeof(FixedUpdate))
298+
}
299+
300+
if (systemPosition == -1) return false;
301+
302+
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length - 1];
303+
304+
// begin = systemsBefore + childSystem + systemsAfter
305+
// + systemsBefore
306+
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
307+
// + systemsAfter
308+
if (systemPosition < parentLoopSystem.subSystemList.Length - 1) Array.Copy(parentLoopSystem.subSystemList, systemPosition + 1, newSubsystemList, systemPosition, parentLoopSystem.subSystemList.Length - systemPosition - 1);
309+
// end = systemsBefore + systemsAfter
310+
311+
parentLoopSystem.subSystemList = newSubsystemList;
312+
313+
return true;
314+
}
315+
316+
internal static void RegisterLoopSystems()
317+
{
318+
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
319+
320+
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
321+
{
322+
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
323+
324+
if (currentSystem.type == typeof(Initialization))
273325
{
274-
var subsystems = playerLoopSystem.subSystemList.ToList();
275-
{
276-
int subsystemCount = subsystems.Count;
277-
for (int k = 0; k < subsystemCount; k++)
278-
{
279-
if (subsystems[k].type == typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate))
280-
{
281-
// insert before `FixedUpdate.ScriptRunBehaviourFixedUpdate`
282-
subsystems.Insert(k, NetworkFixedUpdate.CreateLoopSystem());
283-
break;
284-
}
285-
}
286-
}
287-
playerLoopSystem.subSystemList = subsystems.ToArray();
326+
TryAddLoopSystem(ref currentSystem, NetworkInitialization.CreateLoopSystem(), null, LoopSystemPosition.After);
288327
}
289-
else if (playerLoopSystem.type == typeof(PreUpdate))
328+
else if (currentSystem.type == typeof(EarlyUpdate))
290329
{
291-
var subsystems = playerLoopSystem.subSystemList.ToList();
292-
{
293-
int subsystemCount = subsystems.Count;
294-
for (int k = 0; k < subsystemCount; k++)
295-
{
296-
if (subsystems[k].type == typeof(PreUpdate.PhysicsUpdate))
297-
{
298-
// insert before `PreUpdate.PhysicsUpdate`
299-
subsystems.Insert(k, NetworkPreUpdate.CreateLoopSystem());
300-
break;
301-
}
302-
}
303-
}
304-
playerLoopSystem.subSystemList = subsystems.ToArray();
330+
TryAddLoopSystem(ref currentSystem, NetworkEarlyUpdate.CreateLoopSystem(), typeof(EarlyUpdate.ScriptRunDelayedStartupFrame), LoopSystemPosition.Before);
305331
}
306-
else if (playerLoopSystem.type == typeof(Update))
332+
else if (currentSystem.type == typeof(FixedUpdate))
307333
{
308-
var subsystems = playerLoopSystem.subSystemList.ToList();
309-
{
310-
int subsystemCount = subsystems.Count;
311-
for (int k = 0; k < subsystemCount; k++)
312-
{
313-
if (subsystems[k].type == typeof(Update.ScriptRunBehaviourUpdate))
314-
{
315-
// insert before `Update.ScriptRunBehaviourUpdate`
316-
subsystems.Insert(k, NetworkUpdate.CreateLoopSystem());
317-
break;
318-
}
319-
}
320-
}
321-
playerLoopSystem.subSystemList = subsystems.ToArray();
334+
TryAddLoopSystem(ref currentSystem, NetworkFixedUpdate.CreateLoopSystem(), typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate), LoopSystemPosition.Before);
322335
}
323-
else if (playerLoopSystem.type == typeof(PreLateUpdate))
336+
else if (currentSystem.type == typeof(PreUpdate))
324337
{
325-
var subsystems = playerLoopSystem.subSystemList.ToList();
326-
{
327-
int subsystemCount = subsystems.Count;
328-
for (int k = 0; k < subsystemCount; k++)
329-
{
330-
if (subsystems[k].type == typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate))
331-
{
332-
// insert before `PreLateUpdate.ScriptRunBehaviourLateUpdate`
333-
subsystems.Insert(k, NetworkPreLateUpdate.CreateLoopSystem());
334-
break;
335-
}
336-
}
337-
}
338-
playerLoopSystem.subSystemList = subsystems.ToArray();
338+
TryAddLoopSystem(ref currentSystem, NetworkPreUpdate.CreateLoopSystem(), typeof(PreUpdate.PhysicsUpdate), LoopSystemPosition.Before);
339339
}
340-
else if (playerLoopSystem.type == typeof(PostLateUpdate))
340+
else if (currentSystem.type == typeof(Update))
341341
{
342-
var subsystems = playerLoopSystem.subSystemList.ToList();
343-
{
344-
int subsystemCount = subsystems.Count;
345-
for (int k = 0; k < subsystemCount; k++)
346-
{
347-
if (subsystems[k].type == typeof(PostLateUpdate.PlayerSendFrameComplete))
348-
{
349-
// insert after `PostLateUpdate.PlayerSendFrameComplete`
350-
subsystems.Insert(k + 1, NetworkPostLateUpdate.CreateLoopSystem());
351-
break;
352-
}
353-
}
354-
}
355-
playerLoopSystem.subSystemList = subsystems.ToArray();
342+
TryAddLoopSystem(ref currentSystem, NetworkUpdate.CreateLoopSystem(), typeof(Update.ScriptRunBehaviourUpdate), LoopSystemPosition.Before);
356343
}
344+
else if (currentSystem.type == typeof(PreLateUpdate))
345+
{
346+
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
347+
}
348+
else if (currentSystem.type == typeof(PostLateUpdate))
349+
{
350+
TryAddLoopSystem(ref currentSystem, NetworkPostLateUpdate.CreateLoopSystem(), typeof(PostLateUpdate.PlayerSendFrameComplete), LoopSystemPosition.After);
351+
}
352+
}
353+
354+
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
355+
}
356+
357+
internal static void UnregisterLoopSystems()
358+
{
359+
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
360+
361+
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
362+
{
363+
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
357364

358-
customPlayerLoop.subSystemList[i] = playerLoopSystem;
365+
if (currentSystem.type == typeof(Initialization))
366+
{
367+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkInitialization));
368+
}
369+
else if (currentSystem.type == typeof(EarlyUpdate))
370+
{
371+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkEarlyUpdate));
372+
}
373+
else if (currentSystem.type == typeof(FixedUpdate))
374+
{
375+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkFixedUpdate));
376+
}
377+
else if (currentSystem.type == typeof(PreUpdate))
378+
{
379+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreUpdate));
380+
}
381+
else if (currentSystem.type == typeof(Update))
382+
{
383+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkUpdate));
384+
}
385+
else if (currentSystem.type == typeof(PreLateUpdate))
386+
{
387+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
388+
}
389+
else if (currentSystem.type == typeof(PostLateUpdate))
390+
{
391+
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostLateUpdate));
392+
}
359393
}
360394

361-
PlayerLoop.SetPlayerLoop(customPlayerLoop);
395+
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
362396
}
363397
}
364-
}
398+
}

com.unity.multiplayer.mlapi/Tests/Runtime/NetworkUpdateLoopTests.cs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,74 @@ namespace MLAPI.RuntimeTests
1212
public class NetworkUpdateLoopTests
1313
{
1414
[Test]
15-
public void UpdateStageInjection()
15+
public void RegisterCustomLoopInTheMiddle()
16+
{
17+
// caching the current PlayerLoop (to prevent side-effects on other tests)
18+
var cachedPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
19+
{
20+
// since current PlayerLoop already took NetworkUpdateLoop systems inside,
21+
// we are going to swap it with the default PlayerLoop temporarily for testing
22+
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
23+
24+
NetworkUpdateLoop.RegisterLoopSystems();
25+
26+
var curPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
27+
int initSubsystemCount = curPlayerLoop.subSystemList[0].subSystemList.Length;
28+
var newInitSubsystems = new PlayerLoopSystem[initSubsystemCount + 1];
29+
Array.Copy(curPlayerLoop.subSystemList[0].subSystemList, newInitSubsystems, initSubsystemCount);
30+
newInitSubsystems[initSubsystemCount] = new PlayerLoopSystem { type = typeof(NetworkUpdateLoopTests) };
31+
curPlayerLoop.subSystemList[0].subSystemList = newInitSubsystems;
32+
PlayerLoop.SetPlayerLoop(curPlayerLoop);
33+
34+
NetworkUpdateLoop.UnregisterLoopSystems();
35+
36+
// our custom `PlayerLoopSystem` with the type of `NetworkUpdateLoopTests` should still exist
37+
Assert.AreEqual(typeof(NetworkUpdateLoopTests), PlayerLoop.GetCurrentPlayerLoop().subSystemList[0].subSystemList.Last().type);
38+
}
39+
// replace the current PlayerLoop with the cached PlayerLoop after the test
40+
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
41+
}
42+
43+
[UnityTest]
44+
public IEnumerator RegisterAndUnregisterSystems()
45+
{
46+
// caching the current PlayerLoop (it will have NetworkUpdateLoop systems registered)
47+
var cachedPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
48+
{
49+
// since current PlayerLoop already took NetworkUpdateLoop systems inside,
50+
// we are going to swap it with the default PlayerLoop temporarily for testing
51+
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
52+
53+
var oldPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
54+
55+
NetworkUpdateLoop.RegisterLoopSystems();
56+
57+
int nextFrameNumber = Time.frameCount + 1;
58+
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
59+
60+
NetworkUpdateLoop.UnregisterLoopSystems();
61+
62+
var newPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
63+
64+
// recursively compare old and new PlayerLoop systems and their subsystems
65+
AssertAreEqualPlayerLoopSystems(newPlayerLoop, oldPlayerLoop);
66+
}
67+
// replace the current PlayerLoop with the cached PlayerLoop after the test
68+
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
69+
}
70+
71+
private void AssertAreEqualPlayerLoopSystems(PlayerLoopSystem leftPlayerLoop, PlayerLoopSystem rightPlayerLoop)
72+
{
73+
Assert.AreEqual(leftPlayerLoop.type, rightPlayerLoop.type);
74+
Assert.AreEqual(leftPlayerLoop.subSystemList?.Length ?? 0, rightPlayerLoop.subSystemList?.Length ?? 0);
75+
for (int i = 0; i < (leftPlayerLoop.subSystemList?.Length ?? 0); i++)
76+
{
77+
AssertAreEqualPlayerLoopSystems(leftPlayerLoop.subSystemList[i], rightPlayerLoop.subSystemList[i]);
78+
}
79+
}
80+
81+
[Test]
82+
public void UpdateStageSystems()
1683
{
1784
var currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
1885
for (int i = 0; i < currentPlayerLoop.subSystemList.Length; i++)
@@ -369,4 +436,4 @@ public IEnumerator UpdateStagesMixed()
369436
}
370437
}
371438
}
372-
}
439+
}

0 commit comments

Comments
 (0)