using System; using UnityEngine; namespace Unity.Netcode { /// /// A variable that can be synchronized over the network. /// /// the unmanaged type for [Serializable] [GenerateSerializationForGenericParameter(0)] public class NetworkVariable : NetworkVariableBase { /// /// Delegate type for value changed event /// /// The value before the change /// The new value public delegate void OnValueChangedDelegate(T previousValue, T newValue); /// /// The callback to be invoked when the value gets changed /// public OnValueChangedDelegate OnValueChanged; public delegate bool CheckExceedsDirtinessThresholdDelegate(in T previousValue, in T newValue); public CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold; public override bool ExceedsDirtinessThreshold() { if (CheckExceedsDirtinessThreshold != null && m_HasPreviousValue) { return CheckExceedsDirtinessThreshold(m_PreviousValue, m_InternalValue); } return true; } public override void OnInitialize() { base.OnInitialize(); m_HasPreviousValue = true; NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); } internal override NetworkVariableType Type => NetworkVariableType.Value; /// /// Constructor for /// /// initial value set that is of type T /// the for this /// the for this public NetworkVariable(T value = default, NetworkVariableReadPermission readPerm = DefaultReadPerm, NetworkVariableWritePermission writePerm = DefaultWritePerm) : base(readPerm, writePerm) { m_InternalValue = value; // Since we start with IsDirty = true, this doesn't need to be duplicated // right away. It won't get read until after ResetDirty() is called, and // the duplicate will be made there. Avoiding calling // NetworkVariableSerialization.Duplicate() is important because calling // it in the constructor might not give users enough time to set the // DuplicateValue callback if they're using UserNetworkVariableSerialization m_PreviousValue = default; } /// /// Resets the NetworkVariable when the associated NetworkObject is not spawned /// /// the value to reset the NetworkVariable to (if none specified it resets to the default) public void Reset(T value = default) { if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned) { m_InternalValue = value; m_PreviousValue = default; } } /// /// The internal value of the NetworkVariable /// [SerializeField] private protected T m_InternalValue; private protected T m_PreviousValue; private bool m_HasPreviousValue; private bool m_IsDisposed; /// /// The value of the NetworkVariable container /// public virtual T Value { get => m_InternalValue; set { // Compare bitwise if (NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref value)) { return; } if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); } Set(value); m_IsDisposed = false; } } internal ref T RefValue() { return ref m_InternalValue; } public override void Dispose() { if (m_IsDisposed) { return; } m_IsDisposed = true; if (m_InternalValue is IDisposable internalValueDisposable) { internalValueDisposable.Dispose(); } m_InternalValue = default; if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable) { m_HasPreviousValue = false; previousValueDisposable.Dispose(); } m_PreviousValue = default; base.Dispose(); } ~NetworkVariable() { Dispose(); } /// /// Gets Whether or not the container is dirty /// /// Whether or not the container is dirty public override bool IsDirty() { // For most cases we can use the dirty flag. // This doesn't work for cases where we're wrapping more complex types // like INetworkSerializable, NativeList, NativeArray, etc. // Changes to the values in those types don't call the Value.set method, // so we can't catch those changes and need to compare the current value // against the previous one. if (base.IsDirty()) { return true; } // Cache the dirty value so we don't perform this again if we already know we're dirty // Unfortunately we can't cache the NOT dirty state, because that might change // in between to checks... but the DIRTY state won't change until ResetDirty() // is called. var dirty = !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue); SetDirty(dirty); return dirty; } /// /// Resets the dirty state and marks the variable as synced / clean /// public override void ResetDirty() { // Resetting the dirty value declares that the current value is not dirty // Therefore, we set the m_PreviousValue field to a duplicate of the current // field, so that our next dirty check is made against the current "not dirty" // value. if (IsDirty()) { m_HasPreviousValue = true; NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); } base.ResetDirty(); } /// /// Sets the , marks the dirty, and invokes the callback /// if there are subscribers to that event. /// /// the new value of type `T` to be set/> private protected void Set(T value) { SetDirty(true); T previousValue = m_InternalValue; m_InternalValue = value; OnValueChanged?.Invoke(previousValue, m_InternalValue); } /// /// Writes the variable to the writer /// /// The stream to write the value to public override void WriteDelta(FastBufferWriter writer) { NetworkVariableSerialization.WriteDelta(writer, ref m_InternalValue, ref m_PreviousValue); } /// /// Reads value from the reader and applies it /// /// The stream to read the value from /// Whether or not the container should keep the dirty delta, or mark the delta as consumed public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { // todo: // keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients // In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit // would be stored in different fields T previousValue = m_InternalValue; NetworkVariableSerialization.ReadDelta(reader, ref m_InternalValue); if (keepDirtyDelta) { SetDirty(true); } OnValueChanged?.Invoke(previousValue, m_InternalValue); } /// public override void ReadField(FastBufferReader reader) { NetworkVariableSerialization.Read(reader, ref m_InternalValue); } /// public override void WriteField(FastBufferWriter writer) { NetworkVariableSerialization.Write(writer, ref m_InternalValue); } } }