Skip to content
242 changes: 138 additions & 104 deletions com.unity.multiplayer.mlapi/Runtime/Core/NetworkUpdateLoop.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.LowLevel;
Expand Down Expand Up @@ -234,131 +233,166 @@ public static PlayerLoopSystem CreateLoopSystem()
}
}

[RuntimeInitializeOnLoadMethod]
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Initialize()
{
var customPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
UnregisterLoopSystems();
RegisterLoopSystems();
}

for (int i = 0; i < customPlayerLoop.subSystemList.Length; i++)
{
var playerLoopSystem = customPlayerLoop.subSystemList[i];
private enum LoopSystemPosition
{
After,
Before
}

if (playerLoopSystem.type == typeof(Initialization))
private static bool TryAddLoopSystem(ref PlayerLoopSystem parentLoopSystem, PlayerLoopSystem childLoopSystem, Type anchorSystemType, LoopSystemPosition loopSystemPosition)
{
int systemPosition = -1;
if (anchorSystemType != null)
{
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
{
var subsystems = playerLoopSystem.subSystemList.ToList();
var subsystem = parentLoopSystem.subSystemList[i];
if (subsystem.type == anchorSystemType)
{
// insert at the bottom of `Initialization`
subsystems.Add(NetworkInitialization.CreateLoopSystem());
systemPosition = loopSystemPosition == LoopSystemPosition.After ? i + 1 : i;
break;
}
playerLoopSystem.subSystemList = subsystems.ToArray();
}
else if (playerLoopSystem.type == typeof(EarlyUpdate))
}
else
{
systemPosition = loopSystemPosition == LoopSystemPosition.After ? parentLoopSystem.subSystemList.Length : 0;
}

if (systemPosition == -1) return false;

var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length + 1];

// begin = systemsBefore + systemsAfter
// + systemsBefore
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
// + childSystem
newSubsystemList[systemPosition] = childLoopSystem;
// + systemsAfter
if (systemPosition < parentLoopSystem.subSystemList.Length) Array.Copy(parentLoopSystem.subSystemList, systemPosition, newSubsystemList, systemPosition + 1, parentLoopSystem.subSystemList.Length - systemPosition);
// end = systemsBefore + childSystem + systemsAfter

parentLoopSystem.subSystemList = newSubsystemList;

return true;
}

private static bool TryRemoveLoopSystem(ref PlayerLoopSystem parentLoopSystem, Type childSystemType)
{
int systemPosition = -1;
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
{
var subsystem = parentLoopSystem.subSystemList[i];
if (subsystem.type == childSystemType)
{
var subsystems = playerLoopSystem.subSystemList.ToList();
{
int subsystemCount = subsystems.Count;
for (int k = 0; k < subsystemCount; k++)
{
if (subsystems[k].type == typeof(EarlyUpdate.ScriptRunDelayedStartupFrame))
{
// insert before `EarlyUpdate.ScriptRunDelayedStartupFrame`
subsystems.Insert(k, NetworkEarlyUpdate.CreateLoopSystem());
break;
}
}
}
playerLoopSystem.subSystemList = subsystems.ToArray();
systemPosition = i;
break;
}
else if (playerLoopSystem.type == typeof(FixedUpdate))
}

if (systemPosition == -1) return false;

var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length - 1];

// begin = systemsBefore + childSystem + systemsAfter
// + systemsBefore
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
// + systemsAfter
if (systemPosition < parentLoopSystem.subSystemList.Length - 1) Array.Copy(parentLoopSystem.subSystemList, systemPosition + 1, newSubsystemList, systemPosition, parentLoopSystem.subSystemList.Length - systemPosition - 1);
// end = systemsBefore + systemsAfter

parentLoopSystem.subSystemList = newSubsystemList;

return true;
}

internal static void RegisterLoopSystems()
{
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();

for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
{
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];

if (currentSystem.type == typeof(Initialization))
{
var subsystems = playerLoopSystem.subSystemList.ToList();
{
int subsystemCount = subsystems.Count;
for (int k = 0; k < subsystemCount; k++)
{
if (subsystems[k].type == typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate))
{
// insert before `FixedUpdate.ScriptRunBehaviourFixedUpdate`
subsystems.Insert(k, NetworkFixedUpdate.CreateLoopSystem());
break;
}
}
}
playerLoopSystem.subSystemList = subsystems.ToArray();
TryAddLoopSystem(ref currentSystem, NetworkInitialization.CreateLoopSystem(), null, LoopSystemPosition.After);
}
else if (playerLoopSystem.type == typeof(PreUpdate))
else if (currentSystem.type == typeof(EarlyUpdate))
{
var subsystems = playerLoopSystem.subSystemList.ToList();
{
int subsystemCount = subsystems.Count;
for (int k = 0; k < subsystemCount; k++)
{
if (subsystems[k].type == typeof(PreUpdate.PhysicsUpdate))
{
// insert before `PreUpdate.PhysicsUpdate`
subsystems.Insert(k, NetworkPreUpdate.CreateLoopSystem());
break;
}
}
}
playerLoopSystem.subSystemList = subsystems.ToArray();
TryAddLoopSystem(ref currentSystem, NetworkEarlyUpdate.CreateLoopSystem(), typeof(EarlyUpdate.ScriptRunDelayedStartupFrame), LoopSystemPosition.Before);
}
else if (playerLoopSystem.type == typeof(Update))
else if (currentSystem.type == typeof(FixedUpdate))
{
var subsystems = playerLoopSystem.subSystemList.ToList();
{
int subsystemCount = subsystems.Count;
for (int k = 0; k < subsystemCount; k++)
{
if (subsystems[k].type == typeof(Update.ScriptRunBehaviourUpdate))
{
// insert before `Update.ScriptRunBehaviourUpdate`
subsystems.Insert(k, NetworkUpdate.CreateLoopSystem());
break;
}
}
}
playerLoopSystem.subSystemList = subsystems.ToArray();
TryAddLoopSystem(ref currentSystem, NetworkFixedUpdate.CreateLoopSystem(), typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate), LoopSystemPosition.Before);
}
else if (playerLoopSystem.type == typeof(PreLateUpdate))
else if (currentSystem.type == typeof(PreUpdate))
{
var subsystems = playerLoopSystem.subSystemList.ToList();
{
int subsystemCount = subsystems.Count;
for (int k = 0; k < subsystemCount; k++)
{
if (subsystems[k].type == typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate))
{
// insert before `PreLateUpdate.ScriptRunBehaviourLateUpdate`
subsystems.Insert(k, NetworkPreLateUpdate.CreateLoopSystem());
break;
}
}
}
playerLoopSystem.subSystemList = subsystems.ToArray();
TryAddLoopSystem(ref currentSystem, NetworkPreUpdate.CreateLoopSystem(), typeof(PreUpdate.PhysicsUpdate), LoopSystemPosition.Before);
}
else if (playerLoopSystem.type == typeof(PostLateUpdate))
else if (currentSystem.type == typeof(Update))
{
var subsystems = playerLoopSystem.subSystemList.ToList();
{
int subsystemCount = subsystems.Count;
for (int k = 0; k < subsystemCount; k++)
{
if (subsystems[k].type == typeof(PostLateUpdate.PlayerSendFrameComplete))
{
// insert after `PostLateUpdate.PlayerSendFrameComplete`
subsystems.Insert(k + 1, NetworkPostLateUpdate.CreateLoopSystem());
break;
}
}
}
playerLoopSystem.subSystemList = subsystems.ToArray();
TryAddLoopSystem(ref currentSystem, NetworkUpdate.CreateLoopSystem(), typeof(Update.ScriptRunBehaviourUpdate), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPostLateUpdate.CreateLoopSystem(), typeof(PostLateUpdate.PlayerSendFrameComplete), LoopSystemPosition.After);
}
}

PlayerLoop.SetPlayerLoop(rootPlayerLoop);
}

internal static void UnregisterLoopSystems()
{
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();

for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
{
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];

customPlayerLoop.subSystemList[i] = playerLoopSystem;
if (currentSystem.type == typeof(Initialization))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkInitialization));
}
else if (currentSystem.type == typeof(EarlyUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkEarlyUpdate));
}
else if (currentSystem.type == typeof(FixedUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkFixedUpdate));
}
else if (currentSystem.type == typeof(PreUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreUpdate));
}
else if (currentSystem.type == typeof(Update))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkUpdate));
}
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostLateUpdate));
}
}

PlayerLoop.SetPlayerLoop(customPlayerLoop);
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,74 @@ namespace MLAPI.RuntimeTests
public class NetworkUpdateLoopTests
{
[Test]
public void UpdateStageInjection()
public void RegisterCustomLoopInTheMiddle()
{
// caching the current PlayerLoop (to prevent side-effects on other tests)
var cachedPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
{
// since current PlayerLoop already took NetworkUpdateLoop systems inside,
// we are going to swap it with the default PlayerLoop temporarily for testing
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());

NetworkUpdateLoop.RegisterLoopSystems();

var curPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
int initSubsystemCount = curPlayerLoop.subSystemList[0].subSystemList.Length;
var newInitSubsystems = new PlayerLoopSystem[initSubsystemCount + 1];
Array.Copy(curPlayerLoop.subSystemList[0].subSystemList, newInitSubsystems, initSubsystemCount);
newInitSubsystems[initSubsystemCount] = new PlayerLoopSystem { type = typeof(NetworkUpdateLoopTests) };
curPlayerLoop.subSystemList[0].subSystemList = newInitSubsystems;
PlayerLoop.SetPlayerLoop(curPlayerLoop);

NetworkUpdateLoop.UnregisterLoopSystems();

// our custom `PlayerLoopSystem` with the type of `NetworkUpdateLoopTests` should still exist
Assert.AreEqual(typeof(NetworkUpdateLoopTests), PlayerLoop.GetCurrentPlayerLoop().subSystemList[0].subSystemList.Last().type);
}
// replace the current PlayerLoop with the cached PlayerLoop after the test
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
}

[UnityTest]
public IEnumerator RegisterAndUnregisterSystems()
{
// caching the current PlayerLoop (it will have NetworkUpdateLoop systems registered)
var cachedPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
{
Comment thread
JesseOlmer marked this conversation as resolved.
// since current PlayerLoop already took NetworkUpdateLoop systems inside,
// we are going to swap it with the default PlayerLoop temporarily for testing
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());

var oldPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();

NetworkUpdateLoop.RegisterLoopSystems();

int nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);

NetworkUpdateLoop.UnregisterLoopSystems();

var newPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();

// recursively compare old and new PlayerLoop systems and their subsystems
AssertAreEqualPlayerLoopSystems(newPlayerLoop, oldPlayerLoop);
}
// replace the current PlayerLoop with the cached PlayerLoop after the test
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
Comment thread
JesseOlmer marked this conversation as resolved.
}

Comment thread
JesseOlmer marked this conversation as resolved.
private void AssertAreEqualPlayerLoopSystems(PlayerLoopSystem leftPlayerLoop, PlayerLoopSystem rightPlayerLoop)
{
Assert.AreEqual(leftPlayerLoop.type, rightPlayerLoop.type);
Assert.AreEqual(leftPlayerLoop.subSystemList?.Length ?? 0, rightPlayerLoop.subSystemList?.Length ?? 0);
for (int i = 0; i < (leftPlayerLoop.subSystemList?.Length ?? 0); i++)
{
AssertAreEqualPlayerLoopSystems(leftPlayerLoop.subSystemList[i], rightPlayerLoop.subSystemList[i]);
}
}

[Test]
public void UpdateStageSystems()
{
var currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
for (int i = 0; i < currentPlayerLoop.subSystemList.Length; i++)
Expand Down Expand Up @@ -369,4 +436,4 @@ public IEnumerator UpdateStagesMixed()
}
}
}
}
}