diff --git a/Assets/Mirror/Core/NetworkClient.cs b/Assets/Mirror/Core/NetworkClient.cs index fc77d7941a..8b0284a6d8 100644 --- a/Assets/Mirror/Core/NetworkClient.cs +++ b/Assets/Mirror/Core/NetworkClient.cs @@ -29,10 +29,23 @@ public static partial class NetworkClient // otherwise it's too easy to accidentally cause interpolation issues if // a component sends with client.interval but interpolates with // server.interval, etc. - public static int sendRate => NetworkServer.sendRate; - public static float sendInterval => sendRate < int.MaxValue ? 1f / sendRate : 0; // for 30 Hz, that's 33ms + public static int sendRate = 30; + public static float sendInterval => sendRate > 0 ? 1f / sendRate : 0f; // 0 = disabled static double lastSendTime; + /// Client Update frequency, 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. + // overwritten by NetworkManager (if any) + public static int tickRate = 30; + + // tick rate is in Hz. + // convert to interval in seconds for convenience where needed. + // + // tick interval is 1 / tickRate. + // but for tests we need a way to set it to exactly 0. + // 1 / int.max would not be exactly 0, so handel that manually. + public static float tickInterval => tickRate > 0 ? 1f / tickRate : 0f; // 0 = disabled + static double lastTickTime; + // ocassionally send a full reliable state for unreliable components to delta compress against. // this only applies to Components with SyncMethod=Unreliable. public static int unreliableBaselineRate => NetworkServer.unreliableBaselineRate; @@ -1726,8 +1739,8 @@ internal static void NetworkLateUpdate() // snapshots _but_ not every single tick. // // Unity 2019 doesn't have Time.timeAsDouble yet - bool sendIntervalElapsed = AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime); - bool unreliableBaselineElapsed = AccurateInterval.Elapsed(NetworkTime.localTime, unreliableBaselineInterval, ref lastUnreliableBaselineTime); + bool sendIntervalElapsed = sendInterval > 0 && AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime); + bool unreliableBaselineElapsed = unreliableBaselineInterval > 0 && AccurateInterval.Elapsed(NetworkTime.localTime, unreliableBaselineInterval, ref lastUnreliableBaselineTime); if (!Application.isPlaying || sendIntervalElapsed) { Broadcast(unreliableBaselineElapsed); @@ -1805,7 +1818,9 @@ static void Broadcast(bool unreliableBaselineElapsed) if (NetworkServer.active) return; // send time snapshot every sendInterval. - Send(new TimeSnapshotMessage(), Channels.Unreliable); + bool tickIntervalElapsed = tickInterval > 0 && AccurateInterval.Elapsed(NetworkTime.localTime, tickInterval, ref lastTickTime); + if (tickIntervalElapsed) + Send(new TimeSnapshotMessage(), Channels.Unreliable); // broadcast client state to server BroadcastToServer(unreliableBaselineElapsed); diff --git a/Assets/Mirror/Core/NetworkConnectionToClient.cs b/Assets/Mirror/Core/NetworkConnectionToClient.cs index df82ef9110..6c24bf3719 100644 --- a/Assets/Mirror/Core/NetworkConnectionToClient.cs +++ b/Assets/Mirror/Core/NetworkConnectionToClient.cs @@ -134,7 +134,7 @@ protected override void SendToTransport(ArraySegment segment, int channelI protected virtual void UpdatePing() { // localTime (double) instead of Time.time for accuracy over days - if (NetworkTime.localTime >= lastPingTime + NetworkTime.PingInterval) + if (NetworkTime.PingInterval > 0 && NetworkTime.localTime >= lastPingTime + NetworkTime.PingInterval) { // TODO it would be safer for the server to store the last N // messages' timestamp and only send a message number. diff --git a/Assets/Mirror/Core/NetworkManager.cs b/Assets/Mirror/Core/NetworkManager.cs index 80c588bab5..d87f3ed34c 100644 --- a/Assets/Mirror/Core/NetworkManager.cs +++ b/Assets/Mirror/Core/NetworkManager.cs @@ -38,10 +38,21 @@ public class NetworkManager : MonoBehaviour public bool editorAutoStart; [Header("Sync Settings")] - /// Server Update frequency, 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. - [Tooltip("Server / Client send rate per second.\nUse 60-100Hz for fast paced games like Counter-Strike to minimize latency.\nUse around 30Hz for games like WoW to minimize computations.\nUse around 1-10Hz for slow paced games like EVE.")] + /// Send frequency in Hz for network snapshots/messages. + [Tooltip("Send rate in Hz for server/client snapshots and messages.")] + [Range(1, 60)] + public int sendRate = 30; + + /// Server simulation frequency in Hz. [FormerlySerializedAs("serverTickRate")] - public int sendRate = 60; + [Tooltip("Tick rate in Hz for server simulation.\nSet this to match Send Rate, or set to 0 if not using NetworkTransform.")] + [Range (0, 60)] + public int tickRate = 30; + + /// Ping/Pong frequency in Hz for RTT/prediction updates. + [Tooltip("Ping rate in Hz for RTT/prediction updates.\nSet to 0 to disable ping.\nCan be lower than Send Rate for games not using NetworkTransform.")] + [Range(1, 60)] + public int pingRate = 10; /// [Tooltip("Ocassionally send a full reliable state for unreliable components to delta compress against. This only applies to Components with SyncMethod=Unreliable.")] @@ -176,10 +187,16 @@ public class NetworkManager : MonoBehaviour // virtual so that inheriting classes' OnValidate() can call base.OnValidate() too public virtual void OnValidate() { - // unreliable full send rate needs to be >= 0. - // we need to have something to delta compress against. - // it should also be <= sendRate otherwise there's no point. - unreliableBaselineRate = Mathf.Clamp(unreliableBaselineRate, 1, sendRate); + sendRate = Mathf.Max(sendRate, 0); + tickRate = Mathf.Max(tickRate, 0); + pingRate = Mathf.Max(pingRate, 0); + + // tick rate should either match send rate or be disabled. + if (tickRate > 0) tickRate = sendRate; + + // unreliable baseline depends on send rate. + // if send is disabled, baseline must be disabled too. + unreliableBaselineRate = sendRate == 0 ? 0 : Mathf.Clamp(unreliableBaselineRate, 1, sendRate); // always >= 0 maxConnections = Mathf.Max(maxConnections, 0); @@ -289,9 +306,15 @@ bool IsServerOnlineSceneChangeNeeded() => // => all exposed settings should be applied at all times if NM exists. void ApplyConfiguration() { - NetworkServer.tickRate = sendRate; + NetworkServer.tickRate = tickRate; + NetworkServer.sendRate = sendRate; + NetworkClient.sendRate = sendRate; + NetworkServer.unreliableBaselineRate = unreliableBaselineRate; NetworkServer.unreliableRedundancy = unreliableRedundancy; + + NetworkTime.PingInterval = pingRate > 0 ? 1f / pingRate : 0f; + NetworkClient.snapshotSettings = snapshotSettings; NetworkClient.connectionQualityInterval = evaluationInterval; NetworkClient.connectionQualityMethod = evaluationMethod; @@ -686,7 +709,7 @@ public virtual void ConfigureHeadlessFrameRate() { if (Utils.IsHeadless()) { - Application.targetFrameRate = sendRate; + Application.targetFrameRate = sendRate > 0 ? sendRate : -1; // Debug.Log($"Server Tick Rate set to {Application.targetFrameRate} Hz."); } } diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs index e35dca6431..47f49ae72a 100644 --- a/Assets/Mirror/Core/NetworkServer.cs +++ b/Assets/Mirror/Core/NetworkServer.cs @@ -36,28 +36,29 @@ public static partial class NetworkServer /// Server Update frequency, 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. // overwritten by NetworkManager (if any) - public static int tickRate = 60; + public static int tickRate = 30; // tick rate is in Hz. // convert to interval in seconds for convenience where needed. // - // send interval is 1 / sendRate. + // tick interval is 1 / tickRate. // but for tests we need a way to set it to exactly 0. // 1 / int.max would not be exactly 0, so handel that manually. - public static float tickInterval => tickRate < int.MaxValue ? 1f / tickRate : 0; // for 30 Hz, that's 33ms + public static float tickInterval => tickRate > 0 ? 1f / tickRate : 0f; // 0 = disabled + static double lastTickTime; // time & value snapshot interpolation are separate. // -> time is interpolated globally on NetworkClient / NetworkConnection // -> value is interpolated per-component, i.e. NetworkTransform. // however, both need to be on the same send interval. - public static int sendRate => tickRate; - public static float sendInterval => sendRate < int.MaxValue ? 1f / sendRate : 0; // for 30 Hz, that's 33ms + public static int sendRate = 30; + public static float sendInterval => sendRate > 0 ? 1f / sendRate : 0f; // 0 = disabled static double lastSendTime; // ocassionally send a full reliable state for unreliable components to delta compress against. // this only applies to Components with SyncMethod=Unreliable. public static int unreliableBaselineRate = 1; - public static float unreliableBaselineInterval => unreliableBaselineRate < int.MaxValue ? 1f / unreliableBaselineRate : 0; // for 1 Hz, that's 1000ms + public static float unreliableBaselineInterval => unreliableBaselineRate > 0 ? 1f / unreliableBaselineRate : 0f; static double lastUnreliableBaselineTime; // quake sends unreliable messages twice to make up for message drops. @@ -216,9 +217,10 @@ static void Initialize() initialized = true; // profiling - earlyUpdateDuration = new TimeSample(sendRate); - lateUpdateDuration = new TimeSample(sendRate); - fullUpdateDuration = new TimeSample(sendRate); + int profilingSampleSize = Mathf.Max(1, sendRate); + earlyUpdateDuration = new TimeSample(profilingSampleSize); + lateUpdateDuration = new TimeSample(profilingSampleSize); + fullUpdateDuration = new TimeSample(profilingSampleSize); } static void AddTransportHandlers() @@ -2272,6 +2274,8 @@ static void Broadcast(bool unreliableBaselineElapsed) connectionsCopy.Clear(); connections.Values.CopyTo(connectionsCopy); + bool tickIntervalElapsed = tickInterval > 0 && AccurateInterval.Elapsed(NetworkTime.localTime, tickInterval, ref lastTickTime); + // go through all connections foreach (NetworkConnectionToClient connection in connectionsCopy) { @@ -2293,7 +2297,8 @@ static void Broadcast(bool unreliableBaselineElapsed) // make sure Broadcast() is only called every sendInterval, // even if targetFrameRate isn't set in host mode (!) // (done via AccurateInterval) - connection.Send(new TimeSnapshotMessage(), Channels.Unreliable); + if (tickIntervalElapsed) + connection.Send(new TimeSnapshotMessage(), Channels.Unreliable); // broadcast world state to this connection BroadcastToConnection(connection, unreliableBaselineElapsed); @@ -2349,8 +2354,8 @@ internal static void NetworkLateUpdate() // NetworkTransform, so they can sync on same interval as time // snapshots _but_ not every single tick. // Unity 2019 doesn't have Time.timeAsDouble yet - bool sendIntervalElapsed = AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime); - bool unreliableBaselineElapsed = AccurateInterval.Elapsed(NetworkTime.localTime, unreliableBaselineInterval, ref lastUnreliableBaselineTime); + bool sendIntervalElapsed = sendInterval > 0 && AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime); + bool unreliableBaselineElapsed = unreliableBaselineInterval > 0 && AccurateInterval.Elapsed(NetworkTime.localTime, unreliableBaselineInterval, ref lastUnreliableBaselineTime); if (!Application.isPlaying || sendIntervalElapsed) Broadcast(unreliableBaselineElapsed); } diff --git a/Assets/Mirror/Core/NetworkTime.cs b/Assets/Mirror/Core/NetworkTime.cs index 6319970c45..42afeeea60 100644 --- a/Assets/Mirror/Core/NetworkTime.cs +++ b/Assets/Mirror/Core/NetworkTime.cs @@ -140,7 +140,7 @@ public static void ResetStatics() internal static void UpdateClient() { // localTime (double) instead of Time.time for accuracy over days - if (localTime >= lastPingTime + PingInterval) + if (PingInterval > 0 && localTime >= lastPingTime + PingInterval) SendPing(); } diff --git a/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs b/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs index d20edec5e5..3ea20e2dae 100644 --- a/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs +++ b/Assets/Mirror/Tests/Editor/NetworkConnection/NetworkConnectionToClientTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NUnit.Framework; using UnityEngine; +using System.Reflection; namespace Mirror.Tests.NetworkConnections { @@ -136,13 +137,17 @@ public void IsAlive_ReturnsFalseWhenTimedOut() [Test] public void UpdatePing_SendsPingWhenIntervalElapsed() { - // PingInterval = -1f ensures localTime >= lastPingTime + (-1) is always true float savedPingInterval = NetworkTime.PingInterval; try { - NetworkTime.PingInterval = -1f; + NetworkTime.PingInterval = 0.1f; NetworkConnectionToClient connection = new NetworkConnectionToClient(1); + // force "interval elapsed" deterministically + FieldInfo lastPingTimeField = typeof(NetworkConnectionToClient) + .GetField("lastPingTime", BindingFlags.Instance | BindingFlags.NonPublic); + lastPingTimeField.SetValue(connection, -1d); + // Update() calls UpdatePing (fires ping) then flushes the unreliable batcher connection.Update(); UpdateTransport();