using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEditor; using UnityEngine; namespace Unity.Netcode { /// /// Interface used by NetworkVariables to serialize them /// /// internal interface INetworkVariableSerializer { // Write has to be taken by ref here because of INetworkSerializable // Open Instance Delegates (pointers to methods without an instance attached to them) // require the first parameter passed to them (the instance) to be passed by ref. // So foo.Bar() becomes BarDelegate(ref foo); // Taking T as an in parameter like we do in other places would require making a copy // of it to pass it as a ref parameter. public void Write(FastBufferWriter writer, ref T value); public void Read(FastBufferReader reader, ref T value); } /// /// Packing serializer for shorts /// internal class ShortSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref short value) { BytePacker.WriteValueBitPacked(writer, value); } public void Read(FastBufferReader reader, ref short value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } } /// /// Packing serializer for shorts /// internal class UshortSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref ushort value) { BytePacker.WriteValueBitPacked(writer, value); } public void Read(FastBufferReader reader, ref ushort value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } } /// /// Packing serializer for ints /// internal class IntSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref int value) { BytePacker.WriteValueBitPacked(writer, value); } public void Read(FastBufferReader reader, ref int value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } } /// /// Packing serializer for ints /// internal class UintSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref uint value) { BytePacker.WriteValueBitPacked(writer, value); } public void Read(FastBufferReader reader, ref uint value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } } /// /// Packing serializer for longs /// internal class LongSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref long value) { BytePacker.WriteValueBitPacked(writer, value); } public void Read(FastBufferReader reader, ref long value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } } /// /// Packing serializer for longs /// internal class UlongSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref ulong value) { BytePacker.WriteValueBitPacked(writer, value); } public void Read(FastBufferReader reader, ref ulong value) { ByteUnpacker.ReadValueBitPacked(reader, out value); } } /// /// Basic serializer for unmanaged types. /// This covers primitives, built-in unity types, and IForceSerializeByMemcpy /// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things /// by calling that directly - thus preventing us from having to have a specific T that meets /// the specific constraints that the various generic WriteValue calls require. /// /// internal class UnmanagedTypeSerializer : INetworkVariableSerializer where T : unmanaged { public void Write(FastBufferWriter writer, ref T value) { writer.WriteUnmanagedSafe(value); } public void Read(FastBufferReader reader, ref T value) { reader.ReadUnmanagedSafe(out value); } } /// /// Serializer for FixedStrings /// /// internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes { public void Write(FastBufferWriter writer, ref T value) { writer.WriteValueSafe(value); } public void Read(FastBufferReader reader, ref T value) { reader.ReadValueSafeInPlace(ref value); } } /// /// Serializer for unmanaged INetworkSerializable types /// /// internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged, INetworkSerializable { public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); value.NetworkSerialize(bufferSerializer); } public void Read(FastBufferReader reader, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); value.NetworkSerialize(bufferSerializer); } } /// /// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it /// has to be null-aware /// internal class ManagedNetworkSerializableSerializer : INetworkVariableSerializer where T : class, INetworkSerializable, new() { public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); bool isNull = (value == null); bufferSerializer.SerializeValue(ref isNull); if (!isNull) { value.NetworkSerialize(bufferSerializer); } } public void Read(FastBufferReader reader, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); bool isNull = false; bufferSerializer.SerializeValue(ref isNull); if (isNull) { value = null; } else { if (value == null) { value = new T(); } value.NetworkSerialize(bufferSerializer); } } } /// /// This class is used to register user serialization with NetworkVariables for types /// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter /// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows /// users to tell NetworkVariable about those extension methods (or simply pass in a lambda) /// /// public class UserNetworkVariableSerialization { /// /// The write value delegate handler definition /// /// The to write the value of type `T` /// The value of type `T` to be written public delegate void WriteValueDelegate(FastBufferWriter writer, in T value); /// /// The read value delegate handler definition /// /// The to read the value of type `T` /// The value of type `T` to be read public delegate void ReadValueDelegate(FastBufferReader reader, out T value); /// /// The delegate handler declaration /// public static WriteValueDelegate WriteValue; /// /// The delegate handler declaration /// public static ReadValueDelegate ReadValue; } /// /// This class is instantiated for types that we can't determine ahead of time are serializable - types /// that don't meet any of the constraints for methods that are available on FastBufferReader and /// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure /// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence /// of user serialization isn't checked until it's used, so if no serialization is provided, this /// will throw an exception when an object containing the relevant NetworkVariable is spawned. /// /// internal class FallbackSerializer : INetworkVariableSerializer { public void Write(FastBufferWriter writer, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null) { throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)} and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); } UserNetworkVariableSerialization.WriteValue(writer, value); } public void Read(FastBufferReader reader, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null) { throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)} and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); } UserNetworkVariableSerialization.ReadValue(reader, out value); } } /// /// This class contains initialization functions for various different types used in NetworkVariables. /// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP) /// and do not need to be called manually. /// /// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker /// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without /// a serializer registered will fall back to using the delegates in . /// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt /// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't /// typically need to be performed manually.) /// public static class NetworkVariableSerializationTypes { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] #if UNITY_EDITOR [InitializeOnLoadMethod] #endif internal static void InitializeIntegerSerialization() { NetworkVariableSerialization.Serializer = new ShortSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; NetworkVariableSerialization.Serializer = new UshortSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; NetworkVariableSerialization.Serializer = new IntSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; NetworkVariableSerialization.Serializer = new UintSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; NetworkVariableSerialization.Serializer = new LongSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; NetworkVariableSerialization.Serializer = new UlongSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; } /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// /// public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged { NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); } /// /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize /// /// public static void InitializeSerializer_UnmanagedINetworkSerializable() where T : unmanaged, INetworkSerializable { NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer(); } /// /// Registers a managed type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize /// /// public static void InitializeSerializer_ManagedINetworkSerializable() where T : class, INetworkSerializable, new() { NetworkVariableSerialization.Serializer = new ManagedNetworkSerializableSerializer(); } /// /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString /// serializers /// /// public static void InitializeSerializer_FixedString() where T : unmanaged, INativeList, IUTF8Bytes { NetworkVariableSerialization.Serializer = new FixedStringSerializer(); } /// /// Registers a managed type that will be checked for equality using T.Equals() /// /// public static void InitializeEqualityChecker_ManagedIEquatable() where T : class, IEquatable { NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEqualsObject; } /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// /// public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : unmanaged, IEquatable { NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEquals; } /// /// Registers an unmanaged type that will be checked for equality using memcmp and only considered /// equal if they are bitwise equivalent in memory /// /// public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : unmanaged { NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; } /// /// Registers a managed type that will be checked for equality using the == operator /// /// public static void InitializeEqualityChecker_ManagedClassEquals() where T : class { NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ClassEquals; } } /// /// 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 sets up various read/write schemes /// based on which constraints are met by `T` using reflection, which is done at module load time. /// /// The type the associated NetworkVariable is templated on [Serializable] public static class NetworkVariableSerialization { internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); internal delegate bool EqualsDelegate(ref T a, ref T b); internal static EqualsDelegate AreEqual; // Compares two values of the same unmanaged type by underlying memory // Ignoring any overridden value checks // Size is fixed internal static unsafe bool ValueEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged { // get unmanaged pointers var aptr = UnsafeUtility.AddressOf(ref a); var bptr = UnsafeUtility.AddressOf(ref b); // compare addresses return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0; } internal static bool EqualityEqualsObject(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable { if (a == null) { return b == null; } if (b == null) { return false; } return a.Equals(b); } internal static bool EqualityEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable { return a.Equals(b); } internal static bool ClassEquals(ref TValueType a, ref TValueType b) where TValueType : class { return a == b; } internal static void Write(FastBufferWriter writer, ref T value) { Serializer.Write(writer, ref value); } internal static void Read(FastBufferReader reader, ref T value) { Serializer.Read(reader, ref value); } } }