com.unity.netcode.gameobjects@1.0.0-pre.10

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.10] - 2022-06-21

### Added

- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)

### Removed

### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
This commit is contained in:
Unity Technologies
2022-06-21 00:00:00 +00:00
parent 5b1fc203ed
commit 0f7a30d285
62 changed files with 3763 additions and 1286 deletions

View File

@@ -8,7 +8,7 @@ namespace Unity.Netcode
/// Event based NetworkVariable container for syncing Lists
/// </summary>
/// <typeparam name="T">The type for the list</typeparam>
public class NetworkList<T> : NetworkVariableSerialization<T> where T : unmanaged, IEquatable<T>
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
{
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
@@ -72,34 +72,35 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
for (int i = 0; i < m_DirtyEvents.Length; i++)
{
writer.WriteValueSafe(m_DirtyEvents[i].Type);
switch (m_DirtyEvents[i].Type)
var element = m_DirtyEvents.ElementAt(i);
writer.WriteValueSafe(element.Type);
switch (element.Type)
{
case NetworkListEvent<T>.EventType.Add:
{
Write(writer, m_DirtyEvents[i].Value);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.Insert:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
Write(writer, m_DirtyEvents[i].Value);
writer.WriteValueSafe(element.Index);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.Remove:
{
Write(writer, m_DirtyEvents[i].Value);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.RemoveAt:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
writer.WriteValueSafe(element.Index);
}
break;
case NetworkListEvent<T>.EventType.Value:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
Write(writer, m_DirtyEvents[i].Value);
writer.WriteValueSafe(element.Index);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.Clear:
@@ -117,7 +118,7 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
for (int i = 0; i < m_ListAtLastReset.Length; i++)
{
Write(writer, m_ListAtLastReset[i]);
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
}
}
@@ -128,7 +129,7 @@ namespace Unity.Netcode
reader.ReadValueSafe(out ushort count);
for (int i = 0; i < count; i++)
{
Read(reader, out T value);
NetworkVariableSerialization<T>.Read(reader, out T value);
m_List.Add(value);
}
}
@@ -144,7 +145,7 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
Read(reader, out T value);
NetworkVariableSerialization<T>.Read(reader, out T value);
m_List.Add(value);
if (OnListChanged != null)
@@ -171,7 +172,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Insert:
{
reader.ReadValueSafe(out int index);
Read(reader, out T value);
NetworkVariableSerialization<T>.Read(reader, out T value);
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
@@ -198,7 +199,7 @@ namespace Unity.Netcode
break;
case NetworkListEvent<T>.EventType.Remove:
{
Read(reader, out T value);
NetworkVariableSerialization<T>.Read(reader, out T value);
int index = m_List.IndexOf(value);
if (index == -1)
{
@@ -258,7 +259,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value:
{
reader.ReadValueSafe(out int index);
Read(reader, out T value);
NetworkVariableSerialization<T>.Read(reader, out T value);
if (index >= m_List.Length)
{
throw new Exception("Shouldn't be here, index is higher than list length");

View File

@@ -9,7 +9,7 @@ namespace Unity.Netcode
/// A variable that can be synchronized over the network.
/// </summary>
[Serializable]
public class NetworkVariable<T> : NetworkVariableSerialization<T> where T : unmanaged
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
{
/// <summary>
/// Delegate type for value changed event
@@ -102,7 +102,7 @@ namespace Unity.Netcode
// would be stored in different fields
T previousValue = m_InternalValue;
Read(reader, out m_InternalValue);
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
if (keepDirtyDelta)
{
@@ -115,13 +115,13 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void ReadField(FastBufferReader reader)
{
Read(reader, out m_InternalValue);
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
}
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
Write(writer, m_InternalValue);
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
}
}
}

View File

@@ -1,77 +0,0 @@
using System;
using UnityEngine;
namespace Unity.Netcode
{
public class NetworkVariableHelper
{
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
//
// 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.
//
// RuntimeAccessModifiersILPP will make this `public`
internal static void InitializeDelegatesNetworkSerializable<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteNetworkSerializable);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadNetworkSerializable);
}
internal static void InitializeDelegatesStruct<T>() where T : unmanaged, INetworkSerializeByMemcpy
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteStruct);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadStruct);
}
internal static void InitializeDelegatesEnum<T>() where T : unmanaged, Enum
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteEnum);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadEnum);
}
internal static void InitializeDelegatesPrimitive<T>() where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WritePrimitive);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadPrimitive);
}
internal static void InitializeAllBaseDelegates()
{
// Built-in C# types, serialized through a generic method
InitializeDelegatesPrimitive<bool>();
InitializeDelegatesPrimitive<byte>();
InitializeDelegatesPrimitive<sbyte>();
InitializeDelegatesPrimitive<char>();
InitializeDelegatesPrimitive<decimal>();
InitializeDelegatesPrimitive<float>();
InitializeDelegatesPrimitive<double>();
InitializeDelegatesPrimitive<short>();
InitializeDelegatesPrimitive<ushort>();
InitializeDelegatesPrimitive<int>();
InitializeDelegatesPrimitive<uint>();
InitializeDelegatesPrimitive<long>();
InitializeDelegatesPrimitive<ulong>();
// Built-in Unity types, serialized with specific overloads because they're structs without ISerializeByMemcpy attached
NetworkVariableSerialization<Vector2>.SetWriteDelegate((FastBufferWriter writer, in Vector2 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector3>.SetWriteDelegate((FastBufferWriter writer, in Vector3 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector4>.SetWriteDelegate((FastBufferWriter writer, in Vector4 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Quaternion>.SetWriteDelegate((FastBufferWriter writer, in Quaternion value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Color>.SetWriteDelegate((FastBufferWriter writer, in Color value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Color32>.SetWriteDelegate((FastBufferWriter writer, in Color32 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Ray>.SetWriteDelegate((FastBufferWriter writer, in Ray value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Ray2D>.SetWriteDelegate((FastBufferWriter writer, in Ray2D value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector2>.SetReadDelegate((FastBufferReader reader, out Vector2 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Vector3>.SetReadDelegate((FastBufferReader reader, out Vector3 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Vector4>.SetReadDelegate((FastBufferReader reader, out Vector4 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Quaternion>.SetReadDelegate((FastBufferReader reader, out Quaternion value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Color>.SetReadDelegate((FastBufferReader reader, out Color value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Color32>.SetReadDelegate((FastBufferReader reader, out Color32 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Ray>.SetReadDelegate((FastBufferReader reader, out Ray value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Ray2D>.SetReadDelegate((FastBufferReader reader, out Ray2D value) => { reader.ReadValueSafe(out value); });
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: e54b65208bd3bbe4eaf62ca0384ae21f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,169 +1,248 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// Interface used by NetworkVariables to serialize them
/// </summary>
/// <typeparam name="T"></typeparam>
internal interface INetworkVariableSerializer<T>
{
// 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, out T value);
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
{
public void Write(FastBufferWriter writer, ref T value)
{
writer.WriteUnmanagedSafe(value);
}
public void Read(FastBufferReader reader, out T value)
{
reader.ReadUnmanagedSafe(out value);
}
}
/// <summary>
/// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do,
/// but is implemented to get the data it needs using open instance delegates that are passed in
/// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable
/// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates
/// circumvent that.)
///
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
/// aren't known to actually contain those methods.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
{
internal delegate int GetLengthDelegate(ref T value);
internal delegate void SetLengthDelegate(ref T value, int length);
internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value);
internal GetLengthDelegate GetLength;
internal SetLengthDelegate SetLength;
internal GetUnsafePtrDelegate GetUnsafePtr;
public unsafe void Write(FastBufferWriter writer, ref T value)
{
int length = GetLength(ref value);
byte* data = GetUnsafePtr(ref value);
writer.WriteUnmanagedSafe(length);
writer.WriteBytesSafe(data, length);
}
public unsafe void Read(FastBufferReader reader, out T value)
{
value = new T();
reader.ReadValueSafe(out int length);
SetLength(ref value, length);
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
}
}
/// <summary>
/// Serializer for INetworkSerializable types, which does the same thing
/// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method
/// via open instance delegates passed in via reflection. This prevents needing T to meet any interface
/// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read -
/// reflection + Open Instance Delegates circumvent that.)
///
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
/// aren't known to actually contain those methods.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
{
internal delegate void WriteValueDelegate(ref T value, BufferSerializer<BufferSerializerWriter> serializer);
internal delegate void ReadValueDelegate(ref T value, BufferSerializer<BufferSerializerReader> serializer);
internal WriteValueDelegate WriteValue;
internal ReadValueDelegate ReadValue;
public void Write(FastBufferWriter writer, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
WriteValue(ref value, bufferSerializer);
}
public void Read(FastBufferReader reader, out T value)
{
value = new T();
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
ReadValue(ref value, bufferSerializer);
}
}
/// <summary>
/// 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)
/// </summary>
/// <typeparam name="T"></typeparam>
public class UserNetworkVariableSerialization<T>
{
public delegate void WriteValueDelegate(FastBufferWriter writer, in T value);
public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
public static WriteValueDelegate WriteValue;
public static ReadValueDelegate ReadValue;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
{
public void Write(FastBufferWriter writer, ref T value)
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.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<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
}
public void Read(FastBufferReader reader, out T value)
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.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<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
}
}
internal static class NetworkVariableSerializationTypes
{
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type>
{
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(float),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
typeof(Vector2),
typeof(Vector3),
typeof(Vector2Int),
typeof(Vector3Int),
typeof(Vector4),
typeof(Quaternion),
typeof(Color),
typeof(Color32),
typeof(Ray),
typeof(Ray2D)
};
}
/// <summary>
/// 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<T>` 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.
/// 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.
/// </summary>
[Serializable]
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
public static class NetworkVariableSerialization<T> where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
writer.WriteNetworkSerializable(value);
}
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer();
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
private static INetworkVariableSerializer<T> GetSerializer()
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize structs
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
writer.WriteValueSafe(value);
}
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
reader.ReadValueSafe(out value);
}
// Functions that serialize enums
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, Enum
{
writer.WriteValueSafe(value);
}
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, Enum
{
reader.ReadValueSafe(out value);
}
// Functions that serialize other types
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
writer.WriteValueSafe(value);
}
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
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<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged
{
if (value is INetworkSerializable)
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
return new UnmanagedTypeSerializer<T>();
}
else if (value is INetworkSerializeByMemcpy)
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
return new UnmanagedTypeSerializer<T>();
}
else if (value is Enum)
if (typeof(Enum).IsAssignableFrom(typeof(T)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
return new UnmanagedTypeSerializer<T>();
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T)))
{
// Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods -
// one for an instance of the generic method taking BufferSerializerWriter as T,
// one for an instance of the generic method taking BufferSerializerReader as T
var writeMethod = (NetworkSerializableSerializer<T>.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter)));
var readMethod = (NetworkSerializableSerializer<T>.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader)));
return new NetworkSerializableSerializer<T> { WriteValue = writeMethod, ReadValue = readMethod };
}
NetworkVariableSerialization<TForMethod>.Write(writer, value);
if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList<byte>).IsAssignableFrom(typeof(T)))
{
// Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed
// with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr()
var getLength = (FixedStringSerializer<T>.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList<byte>.Length)));
var setLength = (FixedStringSerializer<T>.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList<byte>.Length)));
var getUnsafePtr = (FixedStringSerializer<T>.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr)));
return new FixedStringSerializer<T> { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr };
}
return new FallbackSerializer<T>();
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
internal static void Write(FastBufferWriter writer, ref T value)
{
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<TForMethod>.Read(reader, out value);
s_Serializer.Write(writer, ref value);
}
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
protected internal delegate void ReadDelegate<TForMethod>(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<T> s_Write = WriteValue;
private static ReadDelegate<T> 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<T> write)
{
s_Write = write;
}
internal static void SetReadDelegate(ReadDelegate<T> read)
{
s_Read = read;
}
protected NetworkVariableSerialization(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
internal static void Read(FastBufferReader reader, out T value)
{
s_Serializer.Read(reader, out value);
}
}
}