using System;
namespace Unity.Netcode
{
///
/// Support methods for reading/writing NetworkVariables
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
/// but there's no way to achieve the same thing with a class, this includes various read/write delegates
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
/// which is invoked by code generated by ILPP during module load.
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
///
/// Any type that inherits from `NetworkVariableSerialization` will implicitly result in any `T`
/// passed to it being picked up and initialized by ILPP.
///
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
///
[Serializable]
public abstract class NetworkVariableSerialization : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize structs
internal static void WriteStruct(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
writer.WriteValueSafe(value);
}
internal static void ReadStruct(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
reader.ReadValueSafe(out value);
}
// Functions that serialize enums
internal static void WriteEnum(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, Enum
{
writer.WriteValueSafe(value);
}
internal static void ReadEnum(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, Enum
{
reader.ReadValueSafe(out value);
}
// Functions that serialize other types
internal static void WritePrimitive(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable, IEquatable
{
writer.WriteValueSafe(value);
}
internal static void ReadPrimitive(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable, IEquatable
{
reader.ReadValueSafe(out value);
}
// Should never be reachable at runtime. All calls to this should be replaced with the correct
// call above by ILPP.
private static void WriteValue(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged
{
if (value is INetworkSerializable)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is INetworkSerializeByMemcpy)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is Enum)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization.Write(writer, value);
}
private static void ReadValue(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization.Read(reader, out value);
}
protected internal delegate void WriteDelegate(FastBufferWriter writer, in TForMethod value);
protected 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.
private static WriteDelegate s_Write = WriteValue;
private static ReadDelegate s_Read = ReadValue;
protected static void Write(FastBufferWriter writer, in T value)
{
s_Write(writer, value);
}
protected static void Read(FastBufferReader reader, out T value)
{
s_Read(reader, out value);
}
internal static void SetWriteDelegate(WriteDelegate write)
{
s_Write = write;
}
internal static void SetReadDelegate(ReadDelegate read)
{
s_Read = read;
}
protected NetworkVariableSerialization(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
}
}
}