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.9.1] - 2024-04-18 ### Added - Added AnticipatedNetworkVariable<T>, which adds support for client anticipation of NetworkVariable values, allowing for more responsive gameplay (#2820) - Added AnticipatedNetworkTransform, which adds support for client anticipation of NetworkTransforms (#2820) - Added NetworkVariableBase.ExceedsDirtinessThreshold to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in NetworkVariable<T> with the callback NetworkVariable<T>.CheckExceedsDirtinessThreshold) (#2820) - Added NetworkVariableUpdateTraits, which add additional throttling support: MinSecondsBetweenUpdates will prevent the NetworkVariable from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while MaxSecondsBetweenUpdates will force a dirty NetworkVariable to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2820) - Added virtual method NetworkVariableBase.OnInitialize() which can be used by NetworkVariable subclasses to add initialization code (#2820) - Added virtual method NetworkVariableBase.Update(), which is called once per frame to support behaviors such as interpolation between an anticipated value and an authoritative one. (#2820) - Added NetworkTime.TickWithPartial, which represents the current tick as a double that includes the fractional/partial tick value. (#2820) - Added NetworkTickSystem.AnticipationTick, which can be helpful with implementation of client anticipation. This value represents the tick the current local client was at at the beginning of the most recent network round trip, which enables it to correlate server update ticks with the client tick that may have triggered them. (#2820) - `NetworkVariable` now includes built-in support for `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, and `Dictionary` (#2813) - `NetworkVariable` now includes delta compression for collection values (`NativeList`, `NativeArray`, `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, `Dictionary`, and `FixedString` types) to save bandwidth by only sending the values that changed. (Note: For `NativeList`, `NativeArray`, and `List`, this algorithm works differently than that used in `NetworkList`. This algorithm will use less bandwidth for "set" and "add" operations, but `NetworkList` is more bandwidth-efficient if you are performing frequent "insert" operations.) (#2813) - `UserNetworkVariableSerialization` now has optional callbacks for `WriteDelta` and `ReadDelta`. If both are provided, they will be used for all serialization operations on NetworkVariables of that type except for the first one for each client. If either is missing, the existing `Write` and `Read` will always be used. (#2813) - Network variables wrapping `INetworkSerializable` types can perform delta serialization by setting `UserNetworkVariableSerialization<T>.WriteDelta` and `UserNetworkVariableSerialization<T>.ReadDelta` for those types. The built-in `INetworkSerializable` serializer will continue to be used for all other serialization operations, but if those callbacks are set, it will call into them on all but the initial serialization to perform delta serialization. (This could be useful if you have a large struct where most values do not change regularly and you want to send only the fields that did change.) (#2813) ### Fixed - Fixed issue where NetworkTransformEditor would throw and exception if you excluded the physics package. (#2871) - Fixed issue where `NetworkTransform` could not properly synchronize its base position when using half float precision. (#2845) - Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822) - Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807) - Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) ### Changed - Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2874) - Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2872) - Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
255 lines
11 KiB
C#
255 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 the server time
|
|
/// as last received from the server plus an offset based on the current RTT - in other words, it is a best-guess
|
|
/// effort at predicting what the server tick will be when a given network action is processed on the server.
|
|
/// </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;
|
|
}
|
|
}
|
|
}
|