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)