diff --git a/Assets/Mirror/Core/NetworkIdentity.cs b/Assets/Mirror/Core/NetworkIdentity.cs index 92fbae17a..2b496422a 100644 --- a/Assets/Mirror/Core/NetworkIdentity.cs +++ b/Assets/Mirror/Core/NetworkIdentity.cs @@ -269,17 +269,52 @@ internal static void ResetClientStatics() internal static void ResetServerStatics() { + reuseNetworkIds = true; + reuseDelay = 1; + + netIdQueue.Clear(); nextNetworkId = 1; } /// Gets the NetworkIdentity from the sceneIds dictionary with the corresponding id public static NetworkIdentity GetSceneIdentity(ulong id) => sceneIds[id]; + #region Network ID Reuse + + internal static bool reuseNetworkIds = true; + internal static byte reuseDelay = 1; + + internal struct ReusableNetworkId + { + public uint reusableNetId; + public double timeAvailable; + } + + // pool of NetworkIds that can be reused + internal static readonly Queue netIdQueue = new Queue(); static uint nextNetworkId = 1; - internal static uint GetNextNetworkId() => nextNetworkId++; + + internal static uint GetNextNetworkId() + { + // Older Unity versions don't have TryPeek. + if (reuseNetworkIds && netIdQueue.Count > 0 && netIdQueue.Peek().timeAvailable < NetworkTime.time) + { + ReusableNetworkId nextNetId = netIdQueue.Dequeue(); + //Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, $"[GetNextNetworkId] Reusing NetworkId {nextNetId.reusableNetId}."); + return nextNetId.reusableNetId; + } + + return nextNetworkId++; + } /// Resets nextNetworkId = 1 - public static void ResetNextNetworkId() => nextNetworkId = 1; + public static void ResetNextNetworkId() + { + netIdQueue.Clear(); + nextNetworkId = 1; + } + + #endregion /// The delegate type for the clientAuthorityCallback. public delegate void ClientAuthorityCallback(NetworkConnectionToClient conn, NetworkIdentity identity, bool authorityState); @@ -633,6 +668,9 @@ void OnDestroy() NetworkServer.Destroy(gameObject); } + if (isServer && reuseNetworkIds && netId > 0) + netIdQueue.Enqueue(new ReusableNetworkId { reusableNetId = netId, timeAvailable = NetworkTime.time + reuseDelay }); + if (isLocalPlayer) { // previously there was a bug where isLocalPlayer was @@ -1327,7 +1365,14 @@ internal void ResetState() isOwned = false; NotifyAuthority(); - netId = 0; + if (netId > 0) + { + if (reuseNetworkIds) + netIdQueue.Enqueue(new ReusableNetworkId { reusableNetId = netId, timeAvailable = NetworkTime.time + reuseDelay }); + + netId = 0; + } + connectionToServer = null; connectionToClient = null; diff --git a/Assets/Mirror/Core/NetworkManager.cs b/Assets/Mirror/Core/NetworkManager.cs index b0445ac34..f48c29732 100644 --- a/Assets/Mirror/Core/NetworkManager.cs +++ b/Assets/Mirror/Core/NetworkManager.cs @@ -47,6 +47,13 @@ public class NetworkManager : MonoBehaviour // [Tooltip("Client broadcasts 'sendRate' times per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")] // public int clientSendRate = 30; // 33 ms + [Header("Network ID Reuse")] + [Tooltip("Reuse network IDs when objects are unspawned or destroyed?")] + public bool reuseNetworkIds = true; + [Range(1, 60)] + [Tooltip("Delay in seconds before network IDs are reused")] + public byte reuseDelay = 1; + /// Automatically switch to this scene upon going offline (on start / on disconnect / on shutdown). [Header("Scene Management")] [Scene] @@ -290,6 +297,10 @@ void SetupServer() NetworkServer.disconnectInactiveTimeout = disconnectInactiveTimeout; NetworkServer.exceptionsDisconnect = exceptionsDisconnect; + // Setup reuseable network IDs + NetworkIdentity.reuseNetworkIds = reuseNetworkIds; + NetworkIdentity.reuseDelay = reuseDelay; + if (runInBackground) Application.runInBackground = true; diff --git a/Assets/Mirror/Editor/NetworkInformationPreview.cs b/Assets/Mirror/Editor/NetworkInformationPreview.cs index b4dbe5ea9..0e1e6368f 100644 --- a/Assets/Mirror/Editor/NetworkInformationPreview.cs +++ b/Assets/Mirror/Editor/NetworkInformationPreview.cs @@ -113,6 +113,8 @@ public override void OnPreviewGUI(Rect r, GUIStyle background) Y = DrawObservers(identity, initialX, Y); + Y = DrawNetworkIDQueue(initialX, Y); + _ = DrawOwner(identity, initialX, Y); } @@ -193,6 +195,25 @@ float DrawObservers(NetworkIdentity identity, float initialX, float Y) return Y; } + float DrawNetworkIDQueue(float initialX, float Y) + { + if (NetworkIdentity.netIdQueue.Count > 0) + { + Rect netIdRect = new Rect(initialX, Y + 10, 200, 20); + GUI.Label(netIdRect, new GUIContent("Network ID Queue"), styles.labelStyle); + netIdRect.x += 20; + netIdRect.y += netIdRect.height; + foreach (var entry in NetworkIdentity.netIdQueue) + { + GUI.Label(netIdRect, $"[{entry.reusableNetId}] {entry.timeAvailable:0.000}", styles.componentName); + netIdRect.y += netIdRect.height; + Y = netIdRect.y; + } + } + + return Y; + } + float DrawOwner(NetworkIdentity identity, float initialX, float Y) { if (identity.connectionToClient != null)