using UnityEngine;
using System;
namespace Unity.Netcode
{
///
/// A variable that can be synchronized over the network.
///
[Serializable]
public class NetworkVariable : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable(FastBufferWriter writer, ref TForMethod value)
where TForMethod : INetworkSerializable, new()
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable(FastBufferReader reader, out TForMethod value)
where TForMethod : INetworkSerializable, new()
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize other types
private static void WriteValue(FastBufferWriter writer, ref TForMethod value) where TForMethod : unmanaged
{
writer.WriteValueSafe(value);
}
private static void ReadValue(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
reader.ReadValueSafe(out value);
}
internal delegate void WriteDelegate(FastBufferWriter writer, ref TForMethod value);
internal delegate void ReadDelegate(FastBufferReader reader, out TForMethod value);
// These static delegates provide the right implementation for writing and reading a particular network variable
// type.
//
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
//
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
//
// In the future we may be able to use this to provide packing implementations for floats and integers to
// optimize bandwidth usage.
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
internal static WriteDelegate Write = WriteValue;
internal static ReadDelegate Read = ReadValue;
///
/// 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;
///
/// Creates a NetworkVariable with the default value and custom read permission
///
/// The read permission for the NetworkVariable
public NetworkVariable()
{
}
///
/// Creates a NetworkVariable with the default value and custom read permission
///
/// The read permission for the NetworkVariable
public NetworkVariable(NetworkVariableReadPermission readPerm) : base(readPerm)
{
}
///
/// Creates a NetworkVariable with a custom value and custom settings
///
/// The read permission for the NetworkVariable
/// The initial value to use for the NetworkVariable
public NetworkVariable(NetworkVariableReadPermission readPerm, T value) : base(readPerm)
{
m_InternalValue = value;
}
///
/// Creates a NetworkVariable with a custom value and the default read permission
///
/// The initial value to use for the NetworkVariable
public NetworkVariable(T value)
{
m_InternalValue = value;
}
[SerializeField]
private protected T m_InternalValue;
///
/// The value of the NetworkVariable container
///
public virtual T Value
{
get => m_InternalValue;
set
{
// this could be improved. The Networking Manager is not always initialized here
// Good place to decouple network manager from the network variable
// Also, note this is not really very water-tight, if you are running as a host
// we cannot tell if a NetworkVariable write is happening inside client-ish code
if (m_NetworkBehaviour && (m_NetworkBehaviour.NetworkManager.IsClient && !m_NetworkBehaviour.NetworkManager.IsHost))
{
throw new InvalidOperationException("Client can't write to NetworkVariables");
}
Set(value);
}
}
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)
{
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, ref m_InternalValue);
}
}
}