using System; using Unity.Mathematics; using UnityEngine; namespace Unity.Netcode { public enum StaleDataHandling { Ignore, Reanticipate } #pragma warning disable IDE0001 /// /// A variable that can be synchronized over the network. /// This version supports basic client anticipation - the client can set a value on the belief that the server /// will update it to reflect the same value in a future update (i.e., as the result of an RPC call). /// This value can then be adjusted as new updates from the server come in, in three basic modes: /// /// /// /// Snap: In this mode (with set to /// and no callback), /// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value, /// resulting in a "snap" to the new value if it is different from the anticipated value. /// /// Smooth: In this mode (with set to /// and an callback that calls /// from the anticipated value to the authority value with an appropriate /// -style smooth function), when a more up-to-date value is received from the authority, /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. /// /// Constant Reanticipation: In this mode (with set to /// and an that calculates a /// new anticipated value based on the current authoritative value), when a more up-to-date value is received from /// the authority, user code calculates a new anticipated value, possibly calling to interpolate /// between the previous anticipation and the new anticipation. This is useful for values that change frequently and /// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply /// need a one-time anticipation when the user performs that action. /// /// /// /// Note that these three modes may be combined. For example, if an callback /// does not call either or , the result will be a snap to the /// authoritative value, enabling for a callback that may conditionally call when the /// difference between the anticipated and authoritative values is within some threshold, but fall back to /// snap behavior if the difference is too large. /// /// the unmanaged type for #pragma warning restore IDE0001 [Serializable] [GenerateSerializationForGenericParameter(0)] [GenerateSerializationForType(typeof(byte))] public class AnticipatedNetworkVariable : NetworkVariableBase { [SerializeField] private NetworkVariable m_AuthoritativeValue; private T m_AnticipatedValue; private T m_PreviousAnticipatedValue; private ulong m_LastAuthorityUpdateCounter = 0; private ulong m_LastAnticipationCounter = 0; private bool m_IsDisposed = false; private bool m_SettingAuthoritativeValue = false; private T m_SmoothFrom; private T m_SmoothTo; private float m_SmoothDuration; private float m_CurrentSmoothTime; private bool m_HasSmoothValues; #pragma warning disable IDE0001 /// /// Defines what the behavior should be if we receive a value from the server with an earlier associated /// time value than the anticipation time value. ///

/// If this is , the stale data will be ignored and the authoritative /// value will not replace the anticipated value until the anticipation time is reached. /// and will also not be invoked for this stale data. ///

/// If this is , the stale data will replace the anticipated data and /// and will be invoked. /// In this case, the authoritativeTime value passed to will be lower than /// the anticipationTime value, and that callback can be used to calculate a new anticipated value. ///
#pragma warning restore IDE0001 public StaleDataHandling StaleDataHandling; public delegate void OnAuthoritativeValueChangedDelegate(AnticipatedNetworkVariable variable, in T previousValue, in T newValue); /// /// Invoked any time the authoritative value changes, even when the data is stale or has been changed locally. /// public OnAuthoritativeValueChangedDelegate OnAuthoritativeValueChanged = null; /// /// Determines if the difference between the last serialized value and the current value is large enough /// to serialize it again. /// public event NetworkVariable.CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold { add => m_AuthoritativeValue.CheckExceedsDirtinessThreshold += value; remove => m_AuthoritativeValue.CheckExceedsDirtinessThreshold -= value; } private class AnticipatedObject : IAnticipatedObject { public AnticipatedNetworkVariable Variable; public void Update() { Variable.Update(); } public void ResetAnticipation() { Variable.ShouldReanticipate = false; } public NetworkObject OwnerObject => Variable.m_NetworkBehaviour.NetworkObject; } private AnticipatedObject m_AnticipatedObject; public override void OnInitialize() { m_AuthoritativeValue.Initialize(m_NetworkBehaviour); NetworkVariableSerialization.Duplicate(m_AuthoritativeValue.Value, ref m_AnticipatedValue); NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); if (m_NetworkBehaviour != null && m_NetworkBehaviour.NetworkManager != null && m_NetworkBehaviour.NetworkManager.AnticipationSystem != null) { m_AnticipatedObject = new AnticipatedObject { Variable = this }; m_NetworkBehaviour.NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); } } public override bool ExceedsDirtinessThreshold() { return m_AuthoritativeValue.ExceedsDirtinessThreshold(); } /// /// Retrieves the current value for the variable. /// This is the "display value" for this variable, and is affected by and /// , as well as by updates from the authority, depending on /// and the behavior of any callbacks. ///

/// When a server update arrives, this value will be overwritten /// by the new server value (unless stale data handling is set /// to "Ignore" and the update is determined to be stale). /// This value will be duplicated in /// , which /// will NOT be overwritten in server updates. ///
public T Value => m_AnticipatedValue; /// /// Indicates whether this variable currently needs /// reanticipation. If this is true, the anticipated value /// has been overwritten by the authoritative value from the /// server; the previous anticipated value is stored in /// public bool ShouldReanticipate { get; private set; } /// /// Holds the most recent anticipated value, whatever was /// most recently set using . Unlike /// , this does not get overwritten /// when a server update arrives. /// public T PreviousAnticipatedValue => m_PreviousAnticipatedValue; /// /// Sets the current value of the variable on the expectation that the authority will set the variable /// to the same value within one network round trip (i.e., in response to an RPC). /// /// public void Anticipate(T value) { if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress || !m_NetworkBehaviour.NetworkManager.IsListening) { return; } m_SmoothDuration = 0; m_CurrentSmoothTime = 0; m_LastAnticipationCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.AnticipationCounter; m_AnticipatedValue = value; NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); if (CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) { AuthoritativeValue = value; } } #pragma warning disable IDE0001 /// /// Retrieves or sets the underlying authoritative value. /// Note that only a client or server with write permissions to this variable may set this value. /// When this variable has been anticipated, this value will alawys return the most recent authoritative /// state, which is updated even if is . /// #pragma warning restore IDE0001 public T AuthoritativeValue { get => m_AuthoritativeValue.Value; set { m_SettingAuthoritativeValue = true; try { m_AuthoritativeValue.Value = value; m_AnticipatedValue = value; NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); } finally { m_SettingAuthoritativeValue = false; } } } /// /// A function to interpolate between two values based on a percentage. /// See , , , and so on /// for examples. /// public delegate T SmoothDelegate(T authoritativeValue, T anticipatedValue, float amount); private SmoothDelegate m_SmoothDelegate = null; public AnticipatedNetworkVariable(T value = default, StaleDataHandling staleDataHandling = StaleDataHandling.Ignore) : base() { StaleDataHandling = staleDataHandling; m_AuthoritativeValue = new NetworkVariable(value) { OnValueChanged = OnValueChangedInternal }; } public void Update() { if (m_CurrentSmoothTime < m_SmoothDuration) { m_CurrentSmoothTime += m_NetworkBehaviour.NetworkManager.RealTimeProvider.DeltaTime; var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f); m_AnticipatedValue = m_SmoothDelegate(m_SmoothFrom, m_SmoothTo, pct); NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); } } public override void Dispose() { if (m_IsDisposed) { return; } if (m_NetworkBehaviour != null && m_NetworkBehaviour.NetworkManager != null && m_NetworkBehaviour.NetworkManager.AnticipationSystem != null) { if (m_AnticipatedObject != null) { m_NetworkBehaviour.NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); m_NetworkBehaviour.NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); m_AnticipatedObject = null; } } m_IsDisposed = true; m_AuthoritativeValue.Dispose(); if (m_AnticipatedValue is IDisposable anticipatedValueDisposable) { anticipatedValueDisposable.Dispose(); } m_AnticipatedValue = default; if (m_PreviousAnticipatedValue is IDisposable previousValueDisposable) { previousValueDisposable.Dispose(); m_PreviousAnticipatedValue = default; } if (m_HasSmoothValues) { if (m_SmoothFrom is IDisposable smoothFromDisposable) { smoothFromDisposable.Dispose(); m_SmoothFrom = default; } if (m_SmoothTo is IDisposable smoothToDisposable) { smoothToDisposable.Dispose(); m_SmoothTo = default; } m_HasSmoothValues = false; } } ~AnticipatedNetworkVariable() { Dispose(); } private void OnValueChangedInternal(T previousValue, T newValue) { if (!m_SettingAuthoritativeValue) { m_LastAuthorityUpdateCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.LastAnticipationAck; if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipationCounter > m_LastAuthorityUpdateCounter) { // Keep the anticipated value unchanged because it is more recent than the authoritative one. return; } ShouldReanticipate = true; m_NetworkBehaviour.NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject); } NetworkVariableSerialization.Duplicate(AuthoritativeValue, ref m_AnticipatedValue); m_SmoothDuration = 0; m_CurrentSmoothTime = 0; OnAuthoritativeValueChanged?.Invoke(this, previousValue, newValue); } /// /// Interpolate this variable from to over of /// real time. The duration uses , so it is affected by . /// /// /// /// /// public void Smooth(in T from, in T to, float durationSeconds, SmoothDelegate how) { if (durationSeconds <= 0) { NetworkVariableSerialization.Duplicate(to, ref m_AnticipatedValue); m_SmoothDuration = 0; m_CurrentSmoothTime = 0; m_SmoothDelegate = null; return; } NetworkVariableSerialization.Duplicate(from, ref m_AnticipatedValue); NetworkVariableSerialization.Duplicate(from, ref m_SmoothFrom); NetworkVariableSerialization.Duplicate(to, ref m_SmoothTo); m_SmoothDuration = durationSeconds; m_CurrentSmoothTime = 0; m_SmoothDelegate = how; m_HasSmoothValues = true; } public override bool IsDirty() { return m_AuthoritativeValue.IsDirty(); } public override void ResetDirty() { m_AuthoritativeValue.ResetDirty(); } public override void WriteDelta(FastBufferWriter writer) { m_AuthoritativeValue.WriteDelta(writer); } public override void WriteField(FastBufferWriter writer) { m_AuthoritativeValue.WriteField(writer); } public override void ReadField(FastBufferReader reader) { m_AuthoritativeValue.ReadField(reader); NetworkVariableSerialization.Duplicate(m_AuthoritativeValue.Value, ref m_AnticipatedValue); NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); } public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { m_AuthoritativeValue.ReadDelta(reader, keepDirtyDelta); } } }