using UnityEngine;
using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
///
/// A variable that can be synchronized over the network.
///
[Serializable]
public class NetworkVariable : NetworkVariableSerialization where T : unmanaged
{
///
/// 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 NetworkVariable(T value = default,
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
m_InternalValue = value;
}
[SerializeField]
private protected T m_InternalValue;
///
/// The value of the NetworkVariable container
///
public virtual T Value
{
get => m_InternalValue;
set
{
// Compare bitwise
if (ValueEquals(ref m_InternalValue, ref value))
{
return;
}
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
}
Set(value);
}
}
// Compares two values of the same unmanaged type by underlying memory
// Ignoring any overriden value checks
// Size is fixed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe bool ValueEquals(ref T a, ref T b)
{
// get unmanaged pointers
var aptr = UnsafeUtility.AddressOf(ref a);
var bptr = UnsafeUtility.AddressOf(ref b);
// compare addresses
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
}
private protected void Set(T value)
{
m_IsDirty = 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)
{
WriteField(writer);
}
///
/// 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;
Read(reader, out m_InternalValue);
if (keepDirtyDelta)
{
m_IsDirty = true;
}
OnValueChanged?.Invoke(previousValue, m_InternalValue);
}
///
public override void ReadField(FastBufferReader reader)
{
Read(reader, out m_InternalValue);
}
///
public override void WriteField(FastBufferWriter writer)
{
Write(writer, m_InternalValue);
}
}
}