The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.5.2] - 2023-07-24 ### Added ### Fixed - Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631) - Fixed a crash when calling TrySetParent with a null Transform (#2625) - Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623) - Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622) - Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618) - Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614) - Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603) ### Changed
253 lines
11 KiB
C#
253 lines
11 KiB
C#
using System;
|
|
using Unity.Profiling;
|
|
|
|
namespace Unity.Netcode
|
|
{
|
|
/// <summary>
|
|
/// <see cref="NetworkTimeSystem"/> is a standalone system which can be used to run a network time simulation.
|
|
/// The network time system maintains both a local and a server time. The local time is based on
|
|
/// </summary>
|
|
public class NetworkTimeSystem
|
|
{
|
|
/// <remarks>
|
|
/// This was the original comment when it lived in NetworkManager:
|
|
/// todo talk with UX/Product, find good default value for this
|
|
/// </remarks>
|
|
private const float k_DefaultBufferSizeSec = 0.05f;
|
|
|
|
/// <summary>
|
|
/// Time synchronization frequency defaults to 1 synchronization message per second
|
|
/// </summary>
|
|
private const double k_TimeSyncFrequency = 1.0d;
|
|
|
|
/// <summary>
|
|
/// The threshold, in seconds, used to force a hard catchup of network time
|
|
/// </summary>
|
|
private const double k_HardResetThresholdSeconds = 0.2d;
|
|
|
|
/// <summary>
|
|
/// Default adjustment ratio
|
|
/// </summary>
|
|
private const double k_DefaultAdjustmentRatio = 0.01d;
|
|
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
private static ProfilerMarker s_SyncTime = new ProfilerMarker($"{nameof(NetworkManager)}.SyncTime");
|
|
#endif
|
|
|
|
private double m_TimeSec;
|
|
private double m_CurrentLocalTimeOffset;
|
|
private double m_DesiredLocalTimeOffset;
|
|
private double m_CurrentServerTimeOffset;
|
|
private double m_DesiredServerTimeOffset;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the amount of time in seconds the server should buffer incoming client messages.
|
|
/// This increases the difference between local and server time so that messages arrive earlier on the server.
|
|
/// </summary>
|
|
public double LocalBufferSec { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the amount of the time in seconds the client should buffer incoming messages from the server. This increases server time.
|
|
/// A higher value increases latency but makes the game look more smooth in bad networking conditions.
|
|
/// This value must be higher than the tick length client side.
|
|
/// </summary>
|
|
public double ServerBufferSec { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a threshold in seconds used to force a hard catchup of network time.
|
|
/// </summary>
|
|
public double HardResetThresholdSec { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the ratio at which the NetworkTimeSystem speeds up or slows down time.
|
|
/// </summary>
|
|
public double AdjustmentRatio { get; set; }
|
|
|
|
/// <summary>
|
|
/// The current local time with the local time offset applied
|
|
/// </summary>
|
|
public double LocalTime => m_TimeSec + m_CurrentLocalTimeOffset;
|
|
|
|
/// <summary>
|
|
/// The current server time with the server time offset applied
|
|
/// </summary>
|
|
public double ServerTime => m_TimeSec + m_CurrentServerTimeOffset;
|
|
|
|
internal double LastSyncedServerTimeSec { get; private set; }
|
|
internal double LastSyncedRttSec { get; private set; }
|
|
|
|
private NetworkConnectionManager m_ConnectionManager;
|
|
private NetworkTransport m_NetworkTransport;
|
|
private NetworkTickSystem m_NetworkTickSystem;
|
|
private NetworkManager m_NetworkManager;
|
|
|
|
/// <summary>
|
|
/// <see cref="k_TimeSyncFrequency"/>
|
|
/// </summary>
|
|
private int m_TimeSyncFrequencyTicks;
|
|
|
|
/// <summary>
|
|
/// The constructor class for <see cref="NetworkTickSystem"/>
|
|
/// </summary>
|
|
/// <param name="localBufferSec">The amount of time, in seconds, the server should buffer incoming client messages.</param>
|
|
/// <param name="serverBufferSec">The amount of the time in seconds the client should buffer incoming messages from the server.</param>
|
|
/// <param name="hardResetThresholdSec">The threshold, in seconds, used to force a hard catchup of network time.</param>
|
|
/// <param name="adjustmentRatio">The ratio at which the NetworkTimeSystem speeds up or slows down time.</param>
|
|
public NetworkTimeSystem(double localBufferSec, double serverBufferSec = k_DefaultBufferSizeSec, double hardResetThresholdSec = k_HardResetThresholdSeconds, double adjustmentRatio = k_DefaultAdjustmentRatio)
|
|
{
|
|
LocalBufferSec = localBufferSec;
|
|
ServerBufferSec = serverBufferSec;
|
|
HardResetThresholdSec = hardResetThresholdSec;
|
|
AdjustmentRatio = adjustmentRatio;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The primary time system is initialized when a server-host or client is started
|
|
/// </summary>
|
|
internal NetworkTickSystem Initialize(NetworkManager networkManager)
|
|
{
|
|
m_NetworkManager = networkManager;
|
|
m_ConnectionManager = networkManager.ConnectionManager;
|
|
m_NetworkTransport = networkManager.NetworkConfig.NetworkTransport;
|
|
m_TimeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * networkManager.NetworkConfig.TickRate);
|
|
m_NetworkTickSystem = new NetworkTickSystem(networkManager.NetworkConfig.TickRate, 0, 0);
|
|
// Only the server side needs to register for tick based time synchronization
|
|
if (m_ConnectionManager.LocalClient.IsServer)
|
|
{
|
|
m_NetworkTickSystem.Tick += OnTickSyncTime;
|
|
}
|
|
|
|
return m_NetworkTickSystem;
|
|
}
|
|
|
|
internal void UpdateTime()
|
|
{
|
|
// As a client wait to run the time system until we are connected.
|
|
// As a client or server don't worry about the time system if we are no longer processing messages
|
|
if (!m_ConnectionManager.LocalClient.IsServer && !m_ConnectionManager.LocalClient.IsConnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only update RTT here, server time is updated by time sync messages
|
|
var reset = Advance(m_NetworkManager.RealTimeProvider.UnscaledDeltaTime);
|
|
if (reset)
|
|
{
|
|
m_NetworkTickSystem.Reset(LocalTime, ServerTime);
|
|
}
|
|
|
|
m_NetworkTickSystem.UpdateTick(LocalTime, ServerTime);
|
|
|
|
if (!m_ConnectionManager.LocalClient.IsServer)
|
|
{
|
|
Sync(LastSyncedServerTimeSec + m_NetworkManager.RealTimeProvider.UnscaledDeltaTime, m_NetworkTransport.GetCurrentRtt(NetworkManager.ServerClientId) / 1000d);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server-Side:
|
|
/// Synchronizes time with clients based on the given <see cref="m_TimeSyncFrequencyTicks"/>.
|
|
/// Also: <see cref="k_TimeSyncFrequency"/>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default is to send 1 time synchronization message per second
|
|
/// </remarks>
|
|
private void OnTickSyncTime()
|
|
{
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
s_SyncTime.Begin();
|
|
#endif
|
|
|
|
// Check if we need to send a time synchronization message, and if so send it
|
|
if (m_ConnectionManager.LocalClient.IsServer && m_NetworkTickSystem.ServerTime.Tick % m_TimeSyncFrequencyTicks == 0)
|
|
{
|
|
var message = new TimeSyncMessage
|
|
{
|
|
Tick = m_NetworkTickSystem.ServerTime.Tick
|
|
};
|
|
m_ConnectionManager.SendMessage(ref message, NetworkDelivery.Unreliable, m_ConnectionManager.ConnectedClientIds);
|
|
}
|
|
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
s_SyncTime.End();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke when shutting down the NetworkManager
|
|
/// </summary>
|
|
internal void Shutdown()
|
|
{
|
|
if (m_ConnectionManager.LocalClient.IsServer)
|
|
{
|
|
m_NetworkTickSystem.Tick -= OnTickSyncTime;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="NetworkTimeSystem"/> class for a server instance.
|
|
/// The server will not apply any buffer values which ensures that local time equals server time.
|
|
/// </summary>
|
|
/// <returns>The instance.</returns>
|
|
public static NetworkTimeSystem ServerTimeSystem()
|
|
{
|
|
return new NetworkTimeSystem(0, 0, double.MaxValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar.
|
|
/// </summary>
|
|
/// <param name="deltaTimeSec">The amount of time to advance. The delta time which passed since Advance was last called.</param>
|
|
/// <returns></returns>
|
|
public bool Advance(double deltaTimeSec)
|
|
{
|
|
m_TimeSec += deltaTimeSec;
|
|
|
|
if (Math.Abs(m_DesiredLocalTimeOffset - m_CurrentLocalTimeOffset) > HardResetThresholdSec || Math.Abs(m_DesiredServerTimeOffset - m_CurrentServerTimeOffset) > HardResetThresholdSec)
|
|
{
|
|
m_TimeSec += m_DesiredServerTimeOffset;
|
|
|
|
m_DesiredLocalTimeOffset -= m_DesiredServerTimeOffset;
|
|
m_CurrentLocalTimeOffset = m_DesiredLocalTimeOffset;
|
|
|
|
m_DesiredServerTimeOffset = 0;
|
|
m_CurrentServerTimeOffset = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
m_CurrentLocalTimeOffset += deltaTimeSec * (m_DesiredLocalTimeOffset > m_CurrentLocalTimeOffset ? AdjustmentRatio : -AdjustmentRatio);
|
|
m_CurrentServerTimeOffset += deltaTimeSec * (m_DesiredServerTimeOffset > m_CurrentServerTimeOffset ? AdjustmentRatio : -AdjustmentRatio);
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the time system to a time based on the given network parameters.
|
|
/// </summary>
|
|
/// <param name="serverTimeSec">The most recent server time value received in seconds.</param>
|
|
/// <param name="rttSec">The current RTT in seconds. Can be an averaged or a raw value.</param>
|
|
public void Reset(double serverTimeSec, double rttSec)
|
|
{
|
|
Sync(serverTimeSec, rttSec);
|
|
Advance(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronizes the time system with up-to-date network statistics but does not change any time values or advance the time.
|
|
/// </summary>
|
|
/// <param name="serverTimeSec">The most recent server time value received in seconds.</param>
|
|
/// <param name="rttSec">The current RTT in seconds. Can be an averaged or a raw value.</param>
|
|
public void Sync(double serverTimeSec, double rttSec)
|
|
{
|
|
LastSyncedRttSec = rttSec;
|
|
LastSyncedServerTimeSec = serverTimeSec;
|
|
|
|
var timeDif = serverTimeSec - m_TimeSec;
|
|
|
|
m_DesiredServerTimeOffset = timeDif - ServerBufferSec;
|
|
m_DesiredLocalTimeOffset = timeDif + rttSec + LocalBufferSec;
|
|
}
|
|
}
|
|
}
|