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();