feat: Support Fast Enter Playmode (#3028)

* feat: Support Fast Enter Playmode
- Uses [RuntimeInitializeOnLoadMethod] to reset statics

* fixed namespace

* Added comment

* Don't clear cmdHandlerDelegates

* Don't set aoi null

* renamed Init to Reset in static classes

* renamed method to ResetStatics

* renamed one too many

* marked NetworkServer.Shutdown with RuntimeInitializeOnLoadMethod

* Added RuntimeInitializeOnLoadMethod to NetworkClient.Shutdown

* renamed NetworkTime.Reset to ResetStatics

* reverted changes to Player
- Will be changing Chat example in master

* Renamed NetworkManager.Shutdown to ResetStatics

* fixed comment

* NetworkServer now calls NetworkIdentity.ResetStatics from its Shutdown

* Updated NetworkManagerTest::ShutdownTest

* Updated NetworkServerTest::ShutdownCleanup

* Updated NetworkServerTest::ShutdownCleanup

* Updated NetworkClientTest::ShutdownCleanup

* comments

* Call NetworkIdentity.ResetStatics from NetworkClient.Shutdown
This commit is contained in:
MrGadget 2021-12-16 12:37:20 -05:00 committed by GitHub
parent 82d42591b6
commit d92c1cbded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 117 additions and 74 deletions

View File

@ -0,0 +1 @@
// removed 2021-12-12

View File

@ -1,52 +0,0 @@
// Unity 2019.3 has an experimental 'disable domain reload on play'
// feature. keeping any global state between sessions will break
// Mirror and most of our user's projects. don't allow it for now.
// https://blogs.unity3d.com/2019/11/05/enter-play-mode-faster-in-unity-2019-3/
using UnityEditor;
using UnityEngine;
namespace Mirror
{
public class EnterPlayModeSettingsCheck : MonoBehaviour
{
[InitializeOnLoadMethod]
static void OnInitializeOnLoad()
{
#if UNITY_2019_3_OR_NEWER
// We can't support experimental "Enter Play Mode Options" mode
// Check immediately on load, and before entering play mode, and warn the user
CheckPlayModeOptions();
#endif
// Hook this event to see if we have a good weave every time
// user attempts to enter play mode or tries to do a build
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
static void OnPlayModeStateChanged(PlayModeStateChange state)
{
// Per Unity docs, this fires "when exiting edit mode before the Editor is in play mode".
// This doesn't fire when closing the editor.
if (state == PlayModeStateChange.ExitingEditMode)
{
#if UNITY_2019_3_OR_NEWER
// We can't support experimental "Enter Play Mode Options" mode
// Check and prevent entering play mode if enabled
CheckPlayModeOptions();
#endif
}
}
#if UNITY_2019_3_OR_NEWER
static void CheckPlayModeOptions()
{
// enabling the checkbox is enough. it controls all the other settings.
if (EditorSettings.enterPlayModeOptionsEnabled)
{
Debug.LogError("Enter Play Mode Options are not supported by Mirror. Please disable 'ProjectSettings -> Editor -> Enter Play Mode Settings (Experimental)'.");
EditorApplication.isPlaying = false;
}
}
#endif
}
}

View File

@ -67,6 +67,17 @@ public class CanvasController : MonoBehaviour
public RoomGUI roomGUI;
public ToggleGroup toggleGroup;
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[RuntimeInitializeOnLoadMethod]
static void ResetStatics()
{
playerMatches.Clear();
openMatches.Clear();
matchConnections.Clear();
playerInfos.Clear();
waitingConnections.Clear();
}
#region UI Functions
// Called from several places to ensure a clean reset

View File

@ -83,7 +83,8 @@ public static class NetworkClient
new Dictionary<Guid, UnSpawnDelegate>();
// spawning
static bool isSpawnFinished;
// internal for tests
internal static bool isSpawnFinished;
// Disabled scene objects that can be spawned again, by sceneId.
internal static readonly Dictionary<ulong, NetworkIdentity> spawnableObjects =
@ -249,7 +250,7 @@ static void OnTransportConnected()
if (connection != null)
{
// reset network time stats
NetworkTime.Reset();
NetworkTime.ResetStatics();
// reset unbatcher in case any batches from last session remain.
unbatcher = new Unbatcher();
@ -1426,6 +1427,8 @@ public static void DestroyAllClientObjects()
}
/// <summary>Shutdown the client.</summary>
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[RuntimeInitializeOnLoadMethod]
public static void Shutdown()
{
//Debug.Log("Shutting down client.");
@ -1442,6 +1445,11 @@ public static void Shutdown()
handlers.Clear();
spawnableObjects.Clear();
// sets nextNetworkId to 1
// sets clientAuthorityCallback to null
// sets previousLocalPlayer to null
NetworkIdentity.ResetStatics();
// disconnect the client connection.
// we do NOT call Transport.Shutdown, because someone only called
// NetworkClient.Shutdown. we can't assume that the server is
@ -1456,6 +1464,7 @@ public static void Shutdown()
ready = false;
isSpawnFinished = false;
isLoadingScene = false;
unbatcher = new Unbatcher();
// clear events. someone might have hooked into them before, but

View File

@ -29,6 +29,17 @@ internal MessageInfo(NetworkMessage message, int channel, int bytes, int count)
/// <summary>Event for when Mirror sends a message. Can be subscribed to.</summary>
public static event Action<MessageInfo> OutMessageEvent;
/// <summary>Event for when Mirror receives a message. Can be subscribed to.</summary>
public static event Action<MessageInfo> InMessageEvent;
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[UnityEngine.RuntimeInitializeOnLoadMethod]
static void ResetStatics()
{
InMessageEvent = null;
OutMessageEvent = null;
}
internal static void OnSend<T>(T message, int channel, int bytes, int count)
where T : struct, NetworkMessage
{
@ -39,9 +50,6 @@ internal static void OnSend<T>(T message, int channel, int bytes, int count)
}
}
/// <summary>Event for when Mirror receives a message. Can be subscribed to.</summary>
public static event Action<MessageInfo> InMessageEvent;
internal static void OnReceive<T>(T message, int channel, int bytes)
where T : struct, NetworkMessage
{

View File

@ -254,6 +254,16 @@ internal set
static readonly Dictionary<ulong, NetworkIdentity> sceneIds =
new Dictionary<ulong, NetworkIdentity>();
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
// internal so it can be called from NetworkServer & NetworkClient
[RuntimeInitializeOnLoadMethod]
internal static void ResetStatics()
{
nextNetworkId = 1;
clientAuthorityCallback = null;
previousLocalPlayer = null;
}
/// <summary>Gets the NetworkIdentity from the sceneIds dictionary with the corresponding id</summary>
public static NetworkIdentity GetSceneIdentity(ulong id) => sceneIds[id];

View File

@ -49,6 +49,14 @@ internal enum AddMode { Beginning, End }
public static Action OnEarlyUpdate;
public static Action OnLateUpdate;
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[RuntimeInitializeOnLoadMethod]
static void ResetStatics()
{
OnEarlyUpdate = null;
OnLateUpdate = null;
}
// helper function to find an update function's index in a player loop
// type. this is used for testing to guarantee our functions are added
// at the beginning/end properly.

View File

@ -106,7 +106,8 @@ public class NetworkManager : MonoBehaviour
public bool isNetworkActive => NetworkServer.active || NetworkClient.active;
// TODO remove this
static NetworkConnection clientReadyConnection;
// internal for tests
internal static NetworkConnection clientReadyConnection;
/// <summary>True if the client loaded a new scene when connecting to the server.</summary>
// This is set before OnClientConnect is called, so it can be checked
@ -631,6 +632,9 @@ public virtual void OnApplicationQuit()
StopServer();
//Debug.Log("OnApplicationQuit: stopped server");
}
// Call ResetStatics to reset statics and singleton
ResetStatics();
}
/// <summary>Set the frame rate for a headless builds. Override to disable or modify.</summary>
@ -708,16 +712,22 @@ void RegisterClientMessages()
}
// This is the only way to clear the singleton, so another instance can be created.
public static void Shutdown()
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[RuntimeInitializeOnLoadMethod]
public static void ResetStatics()
{
if (singleton == null)
return;
// call StopHost if we have a singleton
if (singleton)
singleton.StopHost();
// reset all statics
startPositions.Clear();
startPositionIndex = 0;
clientReadyConnection = null;
loadingSceneAsync = null;
networkSceneName = string.Empty;
singleton.StopHost();
// and finally (in case it isn't null already)...
singleton = null;
}

View File

@ -70,7 +70,7 @@ static void Initialize()
if (aoi != null) aoi.Reset();
// reset NetworkTime
NetworkTime.Reset();
NetworkTime.ResetStatics();
Debug.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkServer.Listen, If you are calling Listen manually then make sure to set 'Transport.activeTransport' first");
AddTransportHandlers();
@ -156,6 +156,8 @@ static void CleanupSpawned()
}
/// <summary>Shuts down the server and disconnects all clients</summary>
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[RuntimeInitializeOnLoadMethod]
public static void Shutdown()
{
if (initialized)
@ -175,21 +177,25 @@ public static void Shutdown()
initialized = false;
}
// Reset all statics here....
dontListen = false;
active = false;
isLoadingScene = false;
localConnection = null;
connections.Clear();
connectionsCopy.Clear();
handlers.Clear();
newObservers.Clear();
// this calls spawned.Clear()
CleanupSpawned();
// sets nextNetworkId = 1
NetworkIdentity.ResetNextNetworkId();
// sets nextNetworkId to 1
// sets clientAuthorityCallback to null
// sets previousLocalPlayer to null
NetworkIdentity.ResetStatics();
// clear events. someone might have hooked into them before, but
// we don't want to use those hooks after Shutdown anymore.
@ -1400,7 +1406,8 @@ internal static void AddAllReadyServerConnectionsToObservers(NetworkIdentity ide
}
// allocate newObservers helper HashSet only once
static readonly HashSet<NetworkConnection> newObservers = new HashSet<NetworkConnection>();
// internal for tests
internal static readonly HashSet<NetworkConnection> newObservers = new HashSet<NetworkConnection>();
// rebuild observers default method (no AOI) - adds all connections
static void RebuildObserversDefault(NetworkIdentity identity, bool initialize)
@ -1652,7 +1659,8 @@ static void BroadcastToConnection(NetworkConnectionToClient connection)
// NetworkLateUpdate called after any Update/FixedUpdate/LateUpdate
// (we add this to the UnityEngine in NetworkLoop)
static readonly List<NetworkConnectionToClient> connectionsCopy =
// internal for tests
internal static readonly List<NetworkConnectionToClient> connectionsCopy =
new List<NetworkConnectionToClient>();
static void Broadcast()

View File

@ -73,14 +73,18 @@ static NetworkTime()
// TODO does this need to be public? user should only need NetworkTime.time
public static double rttStandardDeviation => Math.Sqrt(rttVariance);
public static void Reset()
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
[UnityEngine.RuntimeInitializeOnLoadMethod]
public static void ResetStatics()
{
stopwatch.Restart();
PingFrequency = 2.0f;
PingWindowSize = 10;
lastPingTime = 0;
_rtt = new ExponentialMovingAverage(PingWindowSize);
_offset = new ExponentialMovingAverage(PingWindowSize);
offsetMin = double.MinValue;
offsetMax = double.MaxValue;
lastPingTime = 0;
stopwatch.Restart();
}
internal static void UpdateClient()

View File

@ -107,8 +107,22 @@ public void ShutdownCleanup()
NetworkClient.Shutdown();
Assert.That(NetworkClient.handlers.Count, Is.EqualTo(0));
Assert.That(NetworkClient.spawned.Count, Is.EqualTo(0));
Assert.That(NetworkClient.spawnableObjects.Count, Is.EqualTo(0));
Assert.That(NetworkClient.connectState, Is.EqualTo(ConnectState.None));
Assert.That(NetworkClient.connection, Is.Null);
Assert.That(NetworkClient.localPlayer, Is.Null);
Assert.That(NetworkClient.ready, Is.False);
Assert.That(NetworkClient.isSpawnFinished, Is.False);
Assert.That(NetworkClient.isLoadingScene, Is.False);
Assert.That(NetworkClient.OnConnectedEvent, Is.Null);
Assert.That(NetworkClient.OnDisconnectedEvent, Is.Null);
Assert.That(NetworkClient.OnErrorEvent, Is.Null);
}
}
}

View File

@ -82,12 +82,14 @@ public void StopHostTest()
public void ShutdownTest()
{
manager.StartClient();
NetworkManager.Shutdown();
NetworkManager.ResetStatics();
Assert.That(NetworkManager.startPositions.Count, Is.Zero);
Assert.That(NetworkManager.startPositionIndex, Is.Zero);
Assert.That(NetworkManager.startPositionIndex, Is.Zero);
Assert.That(NetworkManager.clientReadyConnection, Is.Null);
Assert.That(NetworkManager.loadingSceneAsync, Is.Null);
Assert.That(NetworkManager.singleton, Is.Null);
Assert.That(NetworkManager.networkSceneName, Is.Empty);
}
[Test]

View File

@ -1176,12 +1176,22 @@ public void ShutdownCleanup()
NetworkServer.Shutdown();
// state cleared?
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
Assert.That(NetworkServer.dontListen, Is.False);
Assert.That(NetworkServer.active, Is.False);
Assert.That(NetworkServer.isLoadingScene, Is.False);
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
Assert.That(NetworkServer.connectionsCopy.Count, Is.EqualTo(0));
Assert.That(NetworkServer.handlers.Count, Is.EqualTo(0));
Assert.That(NetworkServer.newObservers.Count, Is.EqualTo(0));
Assert.That(NetworkServer.spawned.Count, Is.EqualTo(0));
Assert.That(NetworkServer.localConnection, Is.Null);
Assert.That(NetworkServer.localClientActive, Is.False);
Assert.That(NetworkServer.OnConnectedEvent, Is.Null);
Assert.That(NetworkServer.OnDisconnectedEvent, Is.Null);
Assert.That(NetworkServer.OnErrorEvent, Is.Null);
}
[Test]