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); internal void ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator); public void Duplicate(in T value, ref T duplicatedValue); } /// /// 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out short value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in short value, ref short duplicatedValue) { duplicatedValue = 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ushort value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in ushort value, ref ushort duplicatedValue) { duplicatedValue = 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out int value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in int value, ref int duplicatedValue) { duplicatedValue = 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out uint value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in uint value, ref uint duplicatedValue) { duplicatedValue = 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out long value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in long value, ref long duplicatedValue) { duplicatedValue = 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ulong value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in ulong value, ref ulong duplicatedValue) { duplicatedValue = 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in T value, ref T duplicatedValue) { duplicatedValue = value; } } internal class UnmanagedArraySerializer : INetworkVariableSerializer> where T : unmanaged { public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteUnmanagedSafe(value); } public void Read(FastBufferReader reader, ref NativeArray value) { value.Dispose(); reader.ReadUnmanagedSafe(out value, Allocator.Persistent); } void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) { reader.ReadUnmanagedSafe(out value, allocator); } public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) { if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) { if (duplicatedValue.IsCreated) { duplicatedValue.Dispose(); } duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); } duplicatedValue.CopyFrom(value); } } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged { public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteUnmanagedSafe(value); } public void Read(FastBufferReader reader, ref NativeList value) { reader.ReadUnmanagedSafeInPlace(ref value); } void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in NativeList value, ref NativeList duplicatedValue) { if (!duplicatedValue.IsCreated) { duplicatedValue = new NativeList(value.Length, Allocator.Persistent); } else if (value.Length != duplicatedValue.Length) { duplicatedValue.ResizeUninitialized(value.Length); } duplicatedValue.CopyFrom(value); } } #endif /// /// 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in T value, ref T duplicatedValue) { duplicatedValue = value; } } /// /// Serializer for FixedStrings /// /// internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes { public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteValueSafe(value); } public void Read(FastBufferReader reader, ref NativeArray value) { value.Dispose(); reader.ReadValueSafe(out value, Allocator.Persistent); } void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) { reader.ReadValueSafe(out value, allocator); } public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) { if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) { if (duplicatedValue.IsCreated) { duplicatedValue.Dispose(); } duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); } duplicatedValue.CopyFrom(value); } } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// Serializer for FixedStrings /// /// internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes { public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteValueSafe(value); } public void Read(FastBufferReader reader, ref NativeList value) { reader.ReadValueSafeInPlace(ref value); } void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in NativeList value, ref NativeList duplicatedValue) { if (!duplicatedValue.IsCreated) { duplicatedValue = new NativeList(value.Length, Allocator.Persistent); } else if (value.Length != duplicatedValue.Length) { duplicatedValue.ResizeUninitialized(value.Length); } duplicatedValue.CopyFrom(value); } } #endif /// /// 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); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in T value, ref T duplicatedValue) { duplicatedValue = value; } } /// /// Serializer for unmanaged INetworkSerializable types /// /// internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable { public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteNetworkSerializable(value); } public void Read(FastBufferReader reader, ref NativeArray value) { value.Dispose(); reader.ReadNetworkSerializable(out value, Allocator.Persistent); } void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) { reader.ReadNetworkSerializable(out value, allocator); } public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) { if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) { if (duplicatedValue.IsCreated) { duplicatedValue.Dispose(); } duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); } duplicatedValue.CopyFrom(value); } } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// Serializer for unmanaged INetworkSerializable types /// /// internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable { public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteNetworkSerializable(value); } public void Read(FastBufferReader reader, ref NativeList value) { reader.ReadNetworkSerializableInPlace(ref value); } void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in NativeList value, ref NativeList duplicatedValue) { if (!duplicatedValue.IsCreated) { duplicatedValue = new NativeList(value.Length, Allocator.Persistent); } else if (value.Length != duplicatedValue.Length) { duplicatedValue.ResizeUninitialized(value.Length); } duplicatedValue.CopyFrom(value); } } #endif /// /// 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); } } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in T value, ref T duplicatedValue) { using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); var refValue = value; Write(writer, ref refValue); using var reader = new FastBufferReader(writer, Allocator.None); Read(reader, ref duplicatedValue); } } /// /// 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 read value delegate handler definition /// /// The to read the value of type `T` /// The value of type `T` to be read public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue); /// /// Callback to write a value /// public static WriteValueDelegate WriteValue; /// /// Callback to read a value /// public static ReadValueDelegate ReadValue; /// /// Callback to create a duplicate of a value, used to check for dirty status. /// public static DuplicateValueDelegate DuplicateValue; } /// /// 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 { private void ThrowArgumentError() { throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable)}. If this error continues to appear after doing one of those things and 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 to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); } public void Write(FastBufferWriter writer, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { ThrowArgumentError(); } UserNetworkVariableSerialization.WriteValue(writer, value); } public void Read(FastBufferReader reader, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { ThrowArgumentError(); } UserNetworkVariableSerialization.ReadValue(reader, out value); } void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) { throw new NotImplementedException(); } public void Duplicate(in T value, ref T duplicatedValue) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { ThrowArgumentError(); } UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); } } /// /// 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(); } /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// /// public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : unmanaged { NetworkVariableSerialization>.Serializer = new UnmanagedArraySerializer(); } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// /// public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unmanaged { NetworkVariableSerialization>.Serializer = new UnmanagedListSerializer(); } #endif /// /// 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 an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize /// /// public static void InitializeSerializer_UnmanagedINetworkSerializableArray() where T : unmanaged, INetworkSerializable { NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableArraySerializer(); } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize /// /// public static void InitializeSerializer_UnmanagedINetworkSerializableList() where T : unmanaged, INetworkSerializable { NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableListSerializer(); } #endif /// /// 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 FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString /// serializers /// /// public static void InitializeSerializer_FixedStringArray() where T : unmanaged, INativeList, IUTF8Bytes { NetworkVariableSerialization>.Serializer = new FixedStringArraySerializer(); } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString /// serializers /// /// public static void InitializeSerializer_FixedStringList() where T : unmanaged, INativeList, IUTF8Bytes { NetworkVariableSerialization>.Serializer = new FixedStringListSerializer(); } #endif /// /// 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 T.Equals() /// /// public static void InitializeEqualityChecker_UnmanagedIEquatableArray() where T : unmanaged, IEquatable { NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsArray; } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// /// public static void InitializeEqualityChecker_UnmanagedIEquatableList() where T : unmanaged, IEquatable { NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsList; } #endif /// /// 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 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_UnmanagedValueEqualsArray() where T : unmanaged { NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.ValueEqualsArray; } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT /// /// 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_UnmanagedValueEqualsList() where T : unmanaged { NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.ValueEqualsList; } #endif /// /// 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(); /// /// A callback to check if two values are equal. /// public delegate bool EqualsDelegate(ref T a, ref T b); /// /// Uses the most efficient mechanism for a given type to determine if two values are equal. /// For types that implement , it will call the Equals() method. /// For unmanaged types, it will do a bytewise memory comparison. /// For other types, it will call the == operator. ///
///
/// Note: If you are using this in a custom generic class, please make sure your class is /// decorated with so that codegen can /// initialize the serialization mechanisms correctly. If your class is NOT /// generic, it is better to check their equality yourself. ///
public static EqualsDelegate AreEqual { get; internal set; } /// /// Serialize a value using the best-known serialization method for a generic value. /// Will reliably serialize any value that is passed to it correctly with no boxing. ///
///
/// Note: If you are using this in a custom generic class, please make sure your class is /// decorated with so that codegen can /// initialize the serialization mechanisms correctly. If your class is NOT /// generic, it is better to use FastBufferWriter directly. ///
///
/// If the codegen is unable to determine a serializer for a type, /// . is called, which, by default, /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. ///
/// /// public static void Write(FastBufferWriter writer, ref T value) { Serializer.Write(writer, ref value); } /// /// Deserialize a value using the best-known serialization method for a generic value. /// Will reliably deserialize any value that is passed to it correctly with no boxing. /// For types whose deserialization can be determined by codegen (which is most types), /// GC will only be incurred if the type is a managed type and the ref value passed in is `null`, /// in which case a new value is created; otherwise, it will be deserialized in-place. ///
///
/// Note: If you are using this in a custom generic class, please make sure your class is /// decorated with so that codegen can /// initialize the serialization mechanisms correctly. If your class is NOT /// generic, it is better to use FastBufferReader directly. ///
///
/// If the codegen is unable to determine a serializer for a type, /// . is called, which, by default, /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. ///
/// /// public static void Read(FastBufferReader reader, ref T value) { Serializer.Read(reader, ref value); } /// /// Duplicates a value using the most efficient means of creating a complete copy. /// For most types this is a simple assignment or memcpy. /// For managed types, this is will serialize and then deserialize the value to ensure /// a correct copy. ///
///
/// Note: If you are using this in a custom generic class, please make sure your class is /// decorated with so that codegen can /// initialize the serialization mechanisms correctly. If your class is NOT /// generic, it is better to duplicate it directly. ///
///
/// If the codegen is unable to determine a serializer for a type, /// . is called, which, by default, /// will throw an exception, unless you have assigned a user duplication callback to it at runtime. ///
/// /// public static void Duplicate(in T value, ref T duplicatedValue) { Serializer.Duplicate(value, ref duplicatedValue); } // 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; } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT // Compares two values of the same unmanaged type by underlying memory // Ignoring any overridden value checks // Size is fixed internal static unsafe bool ValueEqualsList(ref NativeList a, ref NativeList b) where TValueType : unmanaged { if (a.IsCreated != b.IsCreated) { return false; } if (!a.IsCreated) { return true; } if (a.Length != b.Length) { return false; } var aptr = (TValueType*)a.GetUnsafePtr(); var bptr = (TValueType*)b.GetUnsafePtr(); return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; } #endif // Compares two values of the same unmanaged type by underlying memory // Ignoring any overridden value checks // Size is fixed internal static unsafe bool ValueEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged { if (a.IsCreated != b.IsCreated) { return false; } if (!a.IsCreated) { return true; } if (a.Length != b.Length) { return false; } var aptr = (TValueType*)a.GetUnsafePtr(); var bptr = (TValueType*)b.GetUnsafePtr(); return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 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); } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT // Compares two values of the same unmanaged type by underlying memory // Ignoring any overridden value checks // Size is fixed internal static unsafe bool EqualityEqualsList(ref NativeList a, ref NativeList b) where TValueType : unmanaged, IEquatable { if (a.IsCreated != b.IsCreated) { return false; } if (!a.IsCreated) { return true; } if (a.Length != b.Length) { return false; } var aptr = (TValueType*)a.GetUnsafePtr(); var bptr = (TValueType*)b.GetUnsafePtr(); for (var i = 0; i < a.Length; ++i) { if (!EqualityEquals(ref aptr[i], ref bptr[i])) { return false; } } return true; } #endif // Compares two values of the same unmanaged type by underlying memory // Ignoring any overridden value checks // Size is fixed internal static unsafe bool EqualityEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged, IEquatable { if (a.IsCreated != b.IsCreated) { return false; } if (!a.IsCreated) { return true; } if (a.Length != b.Length) { return false; } var aptr = (TValueType*)a.GetUnsafePtr(); var bptr = (TValueType*)b.GetUnsafePtr(); for (var i = 0; i < a.Length; ++i) { if (!EqualityEquals(ref aptr[i], ref bptr[i])) { return false; } } return true; } internal static bool ClassEquals(ref TValueType a, ref TValueType b) where TValueType : class { return a == b; } } // RuntimeAccessModifiersILPP will make this `public` // This is just pass-through to NetworkVariableSerialization but is here becaues I could not get ILPP // to generate code that would successfully call Type.Method(T), but it has no problem calling Type.Method(T) internal class RpcFallbackSerialization { public static void Write(FastBufferWriter writer, ref T value) { NetworkVariableSerialization.Write(writer, ref value); } public static void Read(FastBufferReader reader, ref T value) { NetworkVariableSerialization.Read(reader, ref value); } } }