mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
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:
parent
82d42591b6
commit
d92c1cbded
1
Assets/Mirror/Editor/Empty/EnterPlayModeSettingsCheck.cs
Normal file
1
Assets/Mirror/Editor/Empty/EnterPlayModeSettingsCheck.cs
Normal file
@ -0,0 +1 @@
|
||||
// removed 2021-12-12
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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];
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user