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