Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions Assets/Mirror/Core/NetworkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>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.</summary>
// 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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Assets/Mirror/Core/NetworkConnectionToClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ protected override void SendToTransport(ArraySegment<byte> 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.
Expand Down
41 changes: 32 additions & 9 deletions Assets/Mirror/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,21 @@ public class NetworkManager : MonoBehaviour
public bool editorAutoStart;

[Header("Sync Settings")]
/// <summary>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.</summary>
[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.")]
/// <summary>Send frequency in Hz for network snapshots/messages.</summary>
[Tooltip("Send rate in Hz for server/client snapshots and messages.")]
[Range(1, 60)]
public int sendRate = 30;

/// <summary>Server simulation frequency in Hz.</summary>
[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;

/// <summary>Ping/Pong frequency in Hz for RTT/prediction updates.</summary>
[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;

/// <summary> </summary>
[Tooltip("Ocassionally send a full reliable state for unreliable components to delta compress against. This only applies to Components with SyncMethod=Unreliable.")]
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
}
}
Expand Down
29 changes: 17 additions & 12 deletions Assets/Mirror/Core/NetworkServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,29 @@ public static partial class NetworkServer

/// <summary>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.</summary>
// 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.
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion Assets/Mirror/Core/NetworkTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using System.Reflection;

namespace Mirror.Tests.NetworkConnections
{
Expand Down Expand Up @@ -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();
Expand Down
Loading