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). ## [2.0.0-exp.2] - 2024-04-02 ### Added - Added updates to all internal messages to account for a distributed authority network session connection. (#2863) - Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) - For a customized `NetworkRigidbodyBase` class: - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to: - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`. - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use. - Provides additional public methods: - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.MovePosition` to move to the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.MoveRotation` to move to the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.SetPosition` to set the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.SetRotation` to set the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.ApplyCurrentTransform` to set the position and rotation of the `Rigidbody` or `Rigidbody2d` based on the associated `GameObject` transform (depending upon its initialized setting). - `NetworkRigidbodyBase.WakeIfSleeping` to wake up the rigid body if sleeping. - `NetworkRigidbodyBase.SleepRigidbody` to put the rigid body to sleep. - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic. - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state. - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized. - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863) - Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863) - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority. - Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863) - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation. - Added distributed authority mode support to `NetworkAnimator`. (#2863) - Added session mode selection to `NetworkManager` inspector view. (#2863) - Added distributed authority permissions feature. (#2863) - Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) - Added distributed authority mode specific `NetworkObject.RemoveOwnershipStatus` method that removes one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) - Added distributed authority mode specific `NetworkObject.HasOwnershipStatus` method that will return (true or false) whether one or more ownership flags is set. (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipLock` method that locks ownership of a `NetworkObject` to prevent ownership from changing until the current owner releases the lock. (#2863) - Added distributed authority mode specific `NetworkObject.RequestOwnership` method that sends an ownership request to the current owner of a spawned `NetworkObject` instance. (#2863) - Added distributed authority mode specific `NetworkObject.OnOwnershipRequested` callback handler that is invoked on the owner/authoritative side when a non-owner requests ownership. Depending upon the boolean returned value depends upon whether the request is approved or denied. (#2863) - Added distributed authority mode specific `NetworkObject.OnOwnershipRequestResponse` callback handler that is invoked when a non-owner's request has been processed. This callback includes a `NetworkObjet.OwnershipRequestResponseStatus` response parameter that describes whether the request was approved or the reason why it was not approved. (#2863) - Added distributed authority mode specific `NetworkObject.DeferDespawn` method that defers the despawning of `NetworkObject` instances on non-authoritative clients based on the tick offset parameter. (#2863) - Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863) - Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863) - Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863) - Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863) - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example). - Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863) - Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863) - Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863) - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true. - Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863) - Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863) - Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863) ### Fixed - Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822) - Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807) - Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) ### Changed - Changed client side awareness of other clients is now the same as a server or host. (#2863) - Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863) - Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863) - Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863) - Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions). (#2863) - Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863) - Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863) - Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
2066 lines
81 KiB
C#
2066 lines
81 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using Unity.Mathematics;
|
|
using UnityEditor;
|
|
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, ref T value);
|
|
public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue);
|
|
public void ReadDelta(FastBufferReader reader, ref T value);
|
|
internal void ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator);
|
|
public void Duplicate(in T value, ref T duplicatedValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packing serializer for shorts
|
|
/// </summary>
|
|
internal class ShortSerializer : INetworkVariableSerializer<short>
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref short value, ref short previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref short value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<short>.ReadWithAllocator(FastBufferReader reader, out short value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in short value, ref short duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packing serializer for shorts
|
|
/// </summary>
|
|
internal class UshortSerializer : INetworkVariableSerializer<ushort>
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref ushort value, ref ushort previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref ushort value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<ushort>.ReadWithAllocator(FastBufferReader reader, out ushort value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in ushort value, ref ushort duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packing serializer for ints
|
|
/// </summary>
|
|
internal class IntSerializer : INetworkVariableSerializer<int>
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref int value, ref int previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref int value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<int>.ReadWithAllocator(FastBufferReader reader, out int value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in int value, ref int duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packing serializer for ints
|
|
/// </summary>
|
|
internal class UintSerializer : INetworkVariableSerializer<uint>
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref uint value, ref uint previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref uint value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<uint>.ReadWithAllocator(FastBufferReader reader, out uint value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in uint value, ref uint duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packing serializer for longs
|
|
/// </summary>
|
|
internal class LongSerializer : INetworkVariableSerializer<long>
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref long value, ref long previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref long value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<long>.ReadWithAllocator(FastBufferReader reader, out long value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in long value, ref long duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packing serializer for longs
|
|
/// </summary>
|
|
internal class UlongSerializer : INetworkVariableSerializer<ulong>
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref ulong value, ref ulong previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref ulong value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<ulong>.ReadWithAllocator(FastBufferReader reader, out ulong value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in ulong value, ref ulong duplicatedValue)
|
|
{
|
|
duplicatedValue = 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, ref T value)
|
|
{
|
|
reader.ReadUnmanagedSafe(out value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
|
|
{
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref T value)
|
|
{
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<T>.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in T value, ref T duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
internal class ListSerializer<T> : INetworkVariableSerializer<List<T>>
|
|
{
|
|
public void Write(FastBufferWriter writer, ref List<T> value)
|
|
{
|
|
bool isNull = value == null;
|
|
writer.WriteValueSafe(isNull);
|
|
if (!isNull)
|
|
{
|
|
BytePacker.WriteValuePacked(writer, value.Count);
|
|
foreach (var item in value)
|
|
{
|
|
var reffable = item;
|
|
NetworkVariableSerialization<T>.Write(writer, ref reffable);
|
|
}
|
|
}
|
|
}
|
|
public void Read(FastBufferReader reader, ref List<T> value)
|
|
{
|
|
reader.ReadValueSafe(out bool isNull);
|
|
if (isNull)
|
|
{
|
|
value = null;
|
|
}
|
|
else
|
|
{
|
|
if (value == null)
|
|
{
|
|
value = new List<T>();
|
|
}
|
|
|
|
ByteUnpacker.ReadValuePacked(reader, out int len);
|
|
if (len < value.Count)
|
|
{
|
|
value.RemoveRange(len, value.Count - len);
|
|
}
|
|
for (var i = 0; i < len; ++i)
|
|
{
|
|
// Read in place where possible
|
|
if (i < value.Count)
|
|
{
|
|
T item = value[i];
|
|
NetworkVariableSerialization<T>.Read(reader, ref item);
|
|
value[i] = item;
|
|
}
|
|
else
|
|
{
|
|
T item = default;
|
|
NetworkVariableSerialization<T>.Read(reader, ref item);
|
|
value.Add(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref List<T> value, ref List<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteListDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref List<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadListDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<List<T>>.ReadWithAllocator(FastBufferReader reader, out List<T> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in List<T> value, ref List<T> duplicatedValue)
|
|
{
|
|
if (duplicatedValue == null)
|
|
{
|
|
duplicatedValue = new List<T>();
|
|
}
|
|
|
|
duplicatedValue.Clear();
|
|
foreach (var item in value)
|
|
{
|
|
duplicatedValue.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class HashSetSerializer<T> : INetworkVariableSerializer<HashSet<T>> where T : IEquatable<T>
|
|
{
|
|
public void Write(FastBufferWriter writer, ref HashSet<T> value)
|
|
{
|
|
bool isNull = value == null;
|
|
writer.WriteValueSafe(isNull);
|
|
if (!isNull)
|
|
{
|
|
writer.WriteValueSafe(value.Count);
|
|
foreach (var item in value)
|
|
{
|
|
var reffable = item;
|
|
NetworkVariableSerialization<T>.Write(writer, ref reffable);
|
|
}
|
|
}
|
|
}
|
|
public void Read(FastBufferReader reader, ref HashSet<T> value)
|
|
{
|
|
reader.ReadValueSafe(out bool isNull);
|
|
if (isNull)
|
|
{
|
|
value = null;
|
|
}
|
|
else
|
|
{
|
|
if (value == null)
|
|
{
|
|
value = new HashSet<T>();
|
|
}
|
|
else
|
|
{
|
|
value.Clear();
|
|
}
|
|
reader.ReadValueSafe(out int len);
|
|
for (var i = 0; i < len; ++i)
|
|
{
|
|
T item = default;
|
|
NetworkVariableSerialization<T>.Read(reader, ref item);
|
|
value.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref HashSet<T> value, ref HashSet<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteHashSetDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref HashSet<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadHashSetDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<HashSet<T>>.ReadWithAllocator(FastBufferReader reader, out HashSet<T> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in HashSet<T> value, ref HashSet<T> duplicatedValue)
|
|
{
|
|
if (duplicatedValue == null)
|
|
{
|
|
duplicatedValue = new HashSet<T>();
|
|
}
|
|
|
|
duplicatedValue.Clear();
|
|
foreach (var item in value)
|
|
{
|
|
duplicatedValue.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal class DictionarySerializer<TKey, TVal> : INetworkVariableSerializer<Dictionary<TKey, TVal>>
|
|
where TKey : IEquatable<TKey>
|
|
{
|
|
public void Write(FastBufferWriter writer, ref Dictionary<TKey, TVal> value)
|
|
{
|
|
bool isNull = value == null;
|
|
writer.WriteValueSafe(isNull);
|
|
if (!isNull)
|
|
{
|
|
writer.WriteValueSafe(value.Count);
|
|
foreach (var item in value)
|
|
{
|
|
(var key, var val) = (item.Key, item.Value);
|
|
NetworkVariableSerialization<TKey>.Write(writer, ref key);
|
|
NetworkVariableSerialization<TVal>.Write(writer, ref val);
|
|
}
|
|
}
|
|
}
|
|
public void Read(FastBufferReader reader, ref Dictionary<TKey, TVal> value)
|
|
{
|
|
reader.ReadValueSafe(out bool isNull);
|
|
if (isNull)
|
|
{
|
|
value = null;
|
|
}
|
|
else
|
|
{
|
|
if (value == null)
|
|
{
|
|
value = new Dictionary<TKey, TVal>();
|
|
}
|
|
else
|
|
{
|
|
value.Clear();
|
|
}
|
|
reader.ReadValueSafe(out int len);
|
|
for (var i = 0; i < len; ++i)
|
|
{
|
|
(TKey key, TVal val) = (default, default);
|
|
NetworkVariableSerialization<TKey>.Read(reader, ref key);
|
|
NetworkVariableSerialization<TVal>.Read(reader, ref val);
|
|
value.Add(key, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref Dictionary<TKey, TVal> value, ref Dictionary<TKey, TVal> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteDictionaryDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref Dictionary<TKey, TVal> value)
|
|
{
|
|
CollectionSerializationUtility.ReadDictionaryDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<Dictionary<TKey, TVal>>.ReadWithAllocator(FastBufferReader reader, out Dictionary<TKey, TVal> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in Dictionary<TKey, TVal> value, ref Dictionary<TKey, TVal> duplicatedValue)
|
|
{
|
|
if (duplicatedValue == null)
|
|
{
|
|
duplicatedValue = new Dictionary<TKey, TVal>();
|
|
}
|
|
|
|
duplicatedValue.Clear();
|
|
foreach (var item in value)
|
|
{
|
|
duplicatedValue.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class UnmanagedArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeArray<T> value)
|
|
{
|
|
writer.WriteUnmanagedSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeArray<T> value)
|
|
{
|
|
value.Dispose();
|
|
reader.ReadUnmanagedSafe(out value, Allocator.Persistent);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeArray<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeArray<T>>.ReadWithAllocator(FastBufferReader reader, out NativeArray<T> value, Allocator allocator)
|
|
{
|
|
reader.ReadUnmanagedSafe(out value, allocator);
|
|
}
|
|
|
|
public void Duplicate(in NativeArray<T> value, ref NativeArray<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length)
|
|
{
|
|
if (duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue.Dispose();
|
|
}
|
|
|
|
duplicatedValue = new NativeArray<T>(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
|
}
|
|
|
|
duplicatedValue.CopyFrom(value);
|
|
}
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
internal class UnmanagedListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeList<T> value)
|
|
{
|
|
writer.WriteUnmanagedSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeList<T> value)
|
|
{
|
|
reader.ReadUnmanagedSafeInPlace(ref value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeList<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeListDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeList<T>>.ReadWithAllocator(FastBufferReader reader, out NativeList<T> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in NativeList<T> value, ref NativeList<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue = new NativeList<T>(value.Length, Allocator.Persistent);
|
|
}
|
|
else if (value.Length != duplicatedValue.Length)
|
|
{
|
|
duplicatedValue.ResizeUninitialized(value.Length);
|
|
}
|
|
|
|
duplicatedValue.CopyFrom(value);
|
|
}
|
|
}
|
|
|
|
|
|
internal class NativeHashSetSerializer<T> : INetworkVariableSerializer<NativeHashSet<T>> where T : unmanaged, IEquatable<T>
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeHashSet<T> value)
|
|
{
|
|
writer.WriteValueSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeHashSet<T> value)
|
|
{
|
|
reader.ReadValueSafeInPlace(ref value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeHashSet<T> value, ref NativeHashSet<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeHashSetDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeHashSet<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeHashSetDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeHashSet<T>>.ReadWithAllocator(FastBufferReader reader, out NativeHashSet<T> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in NativeHashSet<T> value, ref NativeHashSet<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue = new NativeHashSet<T>(value.Capacity, Allocator.Persistent);
|
|
}
|
|
|
|
duplicatedValue.Clear();
|
|
foreach (var item in value)
|
|
{
|
|
duplicatedValue.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal class NativeHashMapSerializer<TKey, TVal> : INetworkVariableSerializer<NativeHashMap<TKey, TVal>>
|
|
where TKey : unmanaged, IEquatable<TKey>
|
|
where TVal : unmanaged
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value)
|
|
{
|
|
writer.WriteValueSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeHashMap<TKey, TVal> value)
|
|
{
|
|
reader.ReadValueSafeInPlace(ref value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value, ref NativeHashMap<TKey, TVal> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeHashMapDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeHashMap<TKey, TVal> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeHashMapDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeHashMap<TKey, TVal>>.ReadWithAllocator(FastBufferReader reader, out NativeHashMap<TKey, TVal> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in NativeHashMap<TKey, TVal> value, ref NativeHashMap<TKey, TVal> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue = new NativeHashMap<TKey, TVal>(value.Capacity, Allocator.Persistent);
|
|
}
|
|
|
|
duplicatedValue.Clear();
|
|
foreach (var item in value)
|
|
{
|
|
duplicatedValue.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Serializer for FixedStrings
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
|
{
|
|
public void Write(FastBufferWriter writer, ref T value)
|
|
{
|
|
writer.WriteValueSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref T value)
|
|
{
|
|
reader.ReadValueSafeInPlace(ref value);
|
|
}
|
|
|
|
// Because of how strings are generally used, it is likely that most strings will still write as full strings
|
|
// instead of deltas. This actually adds one byte to the data to encode that it was serialized in full.
|
|
// But the potential savings from a small change to a large string are valuable enough to be worth that extra
|
|
// byte.
|
|
public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
|
|
{
|
|
using var changes = new ResizableBitVector(Allocator.Temp);
|
|
int minLength = math.min(value.Length, previousValue.Length);
|
|
var numChanges = 0;
|
|
for (var i = 0; i < minLength; ++i)
|
|
{
|
|
var val = value[i];
|
|
var prevVal = previousValue[i];
|
|
if (!NetworkVariableSerialization<byte>.AreEqual(ref val, ref prevVal))
|
|
{
|
|
++numChanges;
|
|
changes.Set(i);
|
|
}
|
|
}
|
|
|
|
for (var i = previousValue.Length; i < value.Length; ++i)
|
|
{
|
|
++numChanges;
|
|
changes.Set(i);
|
|
}
|
|
|
|
if (changes.GetSerializedSize() + FastBufferWriter.GetWriteSize<byte>() * numChanges > FastBufferWriter.GetWriteSize<byte>() * value.Length)
|
|
{
|
|
writer.WriteByteSafe(1);
|
|
writer.WriteValueSafe(value);
|
|
return;
|
|
}
|
|
writer.WriteByte(0);
|
|
BytePacker.WriteValuePacked(writer, value.Length);
|
|
writer.WriteValueSafe(changes);
|
|
unsafe
|
|
{
|
|
byte* ptr = value.GetUnsafePtr();
|
|
byte* prevPtr = previousValue.GetUnsafePtr();
|
|
for (int i = 0; i < value.Length; ++i)
|
|
{
|
|
if (changes.IsSet(i))
|
|
{
|
|
if (i < previousValue.Length)
|
|
{
|
|
NetworkVariableSerialization<byte>.WriteDelta(writer, ref ptr[i], ref prevPtr[i]);
|
|
}
|
|
else
|
|
{
|
|
NetworkVariableSerialization<byte>.Write(writer, ref ptr[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public unsafe void ReadDelta(FastBufferReader reader, ref T value)
|
|
{
|
|
// Writing can use the NativeArray logic as it is, but reading is a little different.
|
|
// Using the NativeArray logic for reading would result in length changes allocating a new NativeArray,
|
|
// which is not what we want for FixedString. With FixedString, the actual size of the data does not change,
|
|
// only an in-memory "length" value - so if the length changes, the only thing we want to do is change
|
|
// that value, and otherwise read everything in-place.
|
|
reader.ReadByteSafe(out byte full);
|
|
if (full == 1)
|
|
{
|
|
reader.ReadValueSafeInPlace(ref value);
|
|
return;
|
|
}
|
|
ByteUnpacker.ReadValuePacked(reader, out int length);
|
|
var changes = new ResizableBitVector(Allocator.Temp);
|
|
using var toDispose = changes;
|
|
{
|
|
reader.ReadNetworkSerializableInPlace(ref changes);
|
|
|
|
value.Length = length;
|
|
|
|
byte* ptr = value.GetUnsafePtr();
|
|
for (var i = 0; i < value.Length; ++i)
|
|
{
|
|
if (changes.IsSet(i))
|
|
{
|
|
reader.ReadByte(out ptr[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void INetworkVariableSerializer<T>.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in T value, ref T duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializer for FixedStrings
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class FixedStringArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeArray<T> value)
|
|
{
|
|
writer.WriteValueSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeArray<T> value)
|
|
{
|
|
value.Dispose();
|
|
reader.ReadValueSafe(out value, Allocator.Persistent);
|
|
}
|
|
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeArray<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeArray<T>>.ReadWithAllocator(FastBufferReader reader, out NativeArray<T> value, Allocator allocator)
|
|
{
|
|
reader.ReadValueSafe(out value, allocator);
|
|
}
|
|
|
|
public void Duplicate(in NativeArray<T> value, ref NativeArray<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length)
|
|
{
|
|
if (duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue.Dispose();
|
|
}
|
|
|
|
duplicatedValue = new NativeArray<T>(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
|
}
|
|
|
|
duplicatedValue.CopyFrom(value);
|
|
}
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Serializer for FixedStrings
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class FixedStringListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeList<T> value)
|
|
{
|
|
writer.WriteValueSafe(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeList<T> value)
|
|
{
|
|
reader.ReadValueSafeInPlace(ref value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeList<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeListDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeList<T>>.ReadWithAllocator(FastBufferReader reader, out NativeList<T> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in NativeList<T> value, ref NativeList<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue = new NativeList<T>(value.Length, Allocator.Persistent);
|
|
}
|
|
else if (value.Length != duplicatedValue.Length)
|
|
{
|
|
duplicatedValue.ResizeUninitialized(value.Length);
|
|
}
|
|
|
|
duplicatedValue.CopyFrom(value);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Serializer for unmanaged INetworkSerializable types
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable
|
|
{
|
|
public void Write(FastBufferWriter writer, ref T value)
|
|
{
|
|
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
|
value.NetworkSerialize(bufferSerializer);
|
|
}
|
|
public void Read(FastBufferReader reader, ref T value)
|
|
{
|
|
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
|
value.NetworkSerialize(bufferSerializer);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.WriteDelta != null && UserNetworkVariableSerialization<T>.ReadDelta != null)
|
|
{
|
|
UserNetworkVariableSerialization<T>.WriteDelta(writer, value, previousValue);
|
|
return;
|
|
}
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref T value)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.WriteDelta != null && UserNetworkVariableSerialization<T>.ReadDelta != null)
|
|
{
|
|
UserNetworkVariableSerialization<T>.ReadDelta(reader, ref value);
|
|
return;
|
|
}
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<T>.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in T value, ref T duplicatedValue)
|
|
{
|
|
duplicatedValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializer for unmanaged INetworkSerializable types
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class UnmanagedNetworkSerializableArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged, INetworkSerializable
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeArray<T> value)
|
|
{
|
|
writer.WriteNetworkSerializable(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeArray<T> value)
|
|
{
|
|
value.Dispose();
|
|
reader.ReadNetworkSerializable(out value, Allocator.Persistent);
|
|
}
|
|
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeArray<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeArray<T>>.ReadWithAllocator(FastBufferReader reader, out NativeArray<T> value, Allocator allocator)
|
|
{
|
|
reader.ReadNetworkSerializable(out value, allocator);
|
|
}
|
|
|
|
public void Duplicate(in NativeArray<T> value, ref NativeArray<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length)
|
|
{
|
|
if (duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue.Dispose();
|
|
}
|
|
|
|
duplicatedValue = new NativeArray<T>(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
|
}
|
|
|
|
duplicatedValue.CopyFrom(value);
|
|
}
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Serializer for unmanaged INetworkSerializable types
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class UnmanagedNetworkSerializableListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged, INetworkSerializable
|
|
{
|
|
public void Write(FastBufferWriter writer, ref NativeList<T> value)
|
|
{
|
|
writer.WriteNetworkSerializable(value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref NativeList<T> value)
|
|
{
|
|
reader.ReadNetworkSerializableInPlace(ref value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue)
|
|
{
|
|
CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref NativeList<T> value)
|
|
{
|
|
CollectionSerializationUtility.ReadNativeListDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<NativeList<T>>.ReadWithAllocator(FastBufferReader reader, out NativeList<T> value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in NativeList<T> value, ref NativeList<T> duplicatedValue)
|
|
{
|
|
if (!duplicatedValue.IsCreated)
|
|
{
|
|
duplicatedValue = new NativeList<T>(value.Length, Allocator.Persistent);
|
|
}
|
|
else if (value.Length != duplicatedValue.Length)
|
|
{
|
|
duplicatedValue.ResizeUninitialized(value.Length);
|
|
}
|
|
|
|
duplicatedValue.CopyFrom(value);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it
|
|
/// has to be null-aware
|
|
/// <typeparam name="T"></typeparam>
|
|
internal class ManagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : class, INetworkSerializable, new()
|
|
{
|
|
public void Write(FastBufferWriter writer, ref T value)
|
|
{
|
|
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(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<BufferSerializerReader>(new BufferSerializerReader(reader));
|
|
bool isNull = false;
|
|
bufferSerializer.SerializeValue(ref isNull);
|
|
if (isNull)
|
|
{
|
|
value = null;
|
|
}
|
|
else
|
|
{
|
|
if (value == null)
|
|
{
|
|
value = new T();
|
|
}
|
|
value.NetworkSerialize(bufferSerializer);
|
|
}
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.WriteDelta != null && UserNetworkVariableSerialization<T>.ReadDelta != null)
|
|
{
|
|
UserNetworkVariableSerialization<T>.WriteDelta(writer, value, previousValue);
|
|
return;
|
|
}
|
|
Write(writer, ref value);
|
|
}
|
|
public void ReadDelta(FastBufferReader reader, ref T value)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.WriteDelta != null && UserNetworkVariableSerialization<T>.ReadDelta != null)
|
|
{
|
|
UserNetworkVariableSerialization<T>.ReadDelta(reader, ref value);
|
|
return;
|
|
}
|
|
Read(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<T>.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);
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
{
|
|
/// <summary>
|
|
/// The write value delegate handler definition
|
|
/// </summary>
|
|
/// <param name="writer">The <see cref="FastBufferWriter"/> to write the value of type `T`</param>
|
|
/// <param name="value">The value of type `T` to be written</param>
|
|
public delegate void WriteValueDelegate(FastBufferWriter writer, in T value);
|
|
|
|
/// <summary>
|
|
/// The write value delegate handler definition
|
|
/// </summary>
|
|
/// <param name="writer">The <see cref="FastBufferWriter"/> to write the value of type `T`</param>
|
|
/// <param name="value">The value of type `T` to be written</param>
|
|
public delegate void WriteDeltaDelegate(FastBufferWriter writer, in T value, in T previousValue);
|
|
|
|
/// <summary>
|
|
/// The read value delegate handler definition
|
|
/// </summary>
|
|
/// <param name="reader">The <see cref="FastBufferReader"/> to read the value of type `T`</param>
|
|
/// <param name="value">The value of type `T` to be read</param>
|
|
public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
|
|
|
|
/// <summary>
|
|
/// The read value delegate handler definition
|
|
/// </summary>
|
|
/// <param name="reader">The <see cref="FastBufferReader"/> to read the value of type `T`</param>
|
|
/// <param name="value">The value of type `T` to be read</param>
|
|
public delegate void ReadDeltaDelegate(FastBufferReader reader, ref T value);
|
|
|
|
/// <summary>
|
|
/// The read value delegate handler definition
|
|
/// </summary>
|
|
/// <param name="reader">The <see cref="FastBufferReader"/> to read the value of type `T`</param>
|
|
/// <param name="value">The value of type `T` to be read</param>
|
|
public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue);
|
|
|
|
/// <summary>
|
|
/// Callback to write a value
|
|
/// </summary>
|
|
public static WriteValueDelegate WriteValue;
|
|
|
|
/// <summary>
|
|
/// Callback to read a value
|
|
/// </summary>
|
|
public static ReadValueDelegate ReadValue;
|
|
|
|
/// <summary>
|
|
/// Callback to write a delta between two values, based on computing the difference between the previous and
|
|
/// current values.
|
|
/// </summary>
|
|
public static WriteDeltaDelegate WriteDelta;
|
|
|
|
/// <summary>
|
|
/// Callback to read a delta, applying only select changes to the current value.
|
|
/// </summary>
|
|
public static ReadDeltaDelegate ReadDelta;
|
|
|
|
/// <summary>
|
|
/// Callback to create a duplicate of a value, used to check for dirty status.
|
|
/// </summary>
|
|
public static DuplicateValueDelegate DuplicateValue;
|
|
}
|
|
|
|
/// <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>
|
|
{
|
|
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<T>)}. 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<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.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<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
|
{
|
|
ThrowArgumentError();
|
|
}
|
|
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
|
}
|
|
public void Read(FastBufferReader reader, ref T value)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
|
{
|
|
ThrowArgumentError();
|
|
}
|
|
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
|
}
|
|
|
|
public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
|
{
|
|
ThrowArgumentError();
|
|
}
|
|
|
|
if (UserNetworkVariableSerialization<T>.WriteDelta == null || UserNetworkVariableSerialization<T>.ReadDelta == null)
|
|
{
|
|
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
|
return;
|
|
}
|
|
UserNetworkVariableSerialization<T>.WriteDelta(writer, value, previousValue);
|
|
}
|
|
|
|
public void ReadDelta(FastBufferReader reader, ref T value)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
|
{
|
|
ThrowArgumentError();
|
|
}
|
|
|
|
if (UserNetworkVariableSerialization<T>.WriteDelta == null || UserNetworkVariableSerialization<T>.ReadDelta == null)
|
|
{
|
|
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
|
return;
|
|
}
|
|
UserNetworkVariableSerialization<T>.ReadDelta(reader, ref value);
|
|
}
|
|
|
|
void INetworkVariableSerializer<T>.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Duplicate(in T value, ref T duplicatedValue)
|
|
{
|
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
|
{
|
|
ThrowArgumentError();
|
|
}
|
|
UserNetworkVariableSerialization<T>.DuplicateValue(value, ref duplicatedValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="UserNetworkVariableSerialization{T}"/>.
|
|
/// 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.)
|
|
/// </summary>
|
|
public static class NetworkVariableSerializationTypes
|
|
{
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
|
#if UNITY_EDITOR
|
|
[InitializeOnLoadMethod]
|
|
#endif
|
|
internal static void InitializeIntegerSerialization()
|
|
{
|
|
NetworkVariableSerialization<short>.Serializer = new ShortSerializer();
|
|
NetworkVariableSerialization<short>.AreEqual = NetworkVariableSerialization<short>.ValueEquals;
|
|
NetworkVariableSerialization<ushort>.Serializer = new UshortSerializer();
|
|
NetworkVariableSerialization<ushort>.AreEqual = NetworkVariableSerialization<ushort>.ValueEquals;
|
|
NetworkVariableSerialization<int>.Serializer = new IntSerializer();
|
|
NetworkVariableSerialization<int>.AreEqual = NetworkVariableSerialization<int>.ValueEquals;
|
|
NetworkVariableSerialization<uint>.Serializer = new UintSerializer();
|
|
NetworkVariableSerialization<uint>.AreEqual = NetworkVariableSerialization<uint>.ValueEquals;
|
|
NetworkVariableSerialization<long>.Serializer = new LongSerializer();
|
|
NetworkVariableSerialization<long>.AreEqual = NetworkVariableSerialization<long>.ValueEquals;
|
|
NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer();
|
|
NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableSerialization<ulong>.ValueEquals;
|
|
|
|
// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server
|
|
NetworkVariableSerialization<short>.Type = CollectionItemType.Short;
|
|
NetworkVariableSerialization<ushort>.Type = CollectionItemType.UShort;
|
|
NetworkVariableSerialization<int>.Type = CollectionItemType.Int;
|
|
NetworkVariableSerialization<uint>.Type = CollectionItemType.UInt;
|
|
NetworkVariableSerialization<long>.Type = CollectionItemType.Long;
|
|
NetworkVariableSerialization<ulong>.Type = CollectionItemType.ULong;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged
|
|
{
|
|
NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>();
|
|
// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server
|
|
NetworkVariableSerialization<T>.Type = CollectionItemType.Unmanaged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_UnmanagedByMemcpyArray<T>() where T : unmanaged
|
|
{
|
|
NetworkVariableSerialization<NativeArray<T>>.Serializer = new UnmanagedArraySerializer<T>();
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_UnmanagedByMemcpyList<T>() where T : unmanaged
|
|
{
|
|
NetworkVariableSerialization<NativeList<T>>.Serializer = new UnmanagedListSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registeres a native hash set (this generic implementation works with all types)
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_NativeHashSet<T>() where T : unmanaged, IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<NativeHashSet<T>>.Serializer = new NativeHashSetSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registeres a native hash set (this generic implementation works with all types)
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_NativeHashMap<TKey, TVal>()
|
|
where TKey : unmanaged, IEquatable<TKey>
|
|
where TVal : unmanaged
|
|
{
|
|
NetworkVariableSerialization<NativeHashMap<TKey, TVal>>.Serializer = new NativeHashMapSerializer<TKey, TVal>();
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Registeres a native hash set (this generic implementation works with all types)
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_List<T>()
|
|
{
|
|
NetworkVariableSerialization<List<T>>.Serializer = new ListSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registeres a native hash set (this generic implementation works with all types)
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_HashSet<T>() where T : IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<HashSet<T>>.Serializer = new HashSetSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registeres a native hash set (this generic implementation works with all types)
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_Dictionary<TKey, TVal>() where TKey : IEquatable<TKey>
|
|
{
|
|
NetworkVariableSerialization<Dictionary<TKey, TVal>>.Serializer = new DictionarySerializer<TKey, TVal>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
|
|
/// NetworkSerialize
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_UnmanagedINetworkSerializable<T>() where T : unmanaged, INetworkSerializable
|
|
{
|
|
NetworkVariableSerialization<T>.Serializer = new UnmanagedNetworkSerializableSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
|
|
/// NetworkSerialize
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_UnmanagedINetworkSerializableArray<T>() where T : unmanaged, INetworkSerializable
|
|
{
|
|
NetworkVariableSerialization<NativeArray<T>>.Serializer = new UnmanagedNetworkSerializableArraySerializer<T>();
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
|
|
/// NetworkSerialize
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_UnmanagedINetworkSerializableList<T>() where T : unmanaged, INetworkSerializable
|
|
{
|
|
NetworkVariableSerialization<NativeList<T>>.Serializer = new UnmanagedNetworkSerializableListSerializer<T>();
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Registers a managed type that implements INetworkSerializable and will be serialized through a call to
|
|
/// NetworkSerialize
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_ManagedINetworkSerializable<T>() where T : class, INetworkSerializable, new()
|
|
{
|
|
NetworkVariableSerialization<T>.Serializer = new ManagedNetworkSerializableSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
|
|
/// serializers
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_FixedString<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
|
{
|
|
NetworkVariableSerialization<T>.Serializer = new FixedStringSerializer<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
|
|
/// serializers
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_FixedStringArray<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
|
{
|
|
NetworkVariableSerialization<NativeArray<T>>.Serializer = new FixedStringArraySerializer<T>();
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
|
|
/// serializers
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeSerializer_FixedStringList<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
|
{
|
|
NetworkVariableSerialization<NativeList<T>>.Serializer = new FixedStringListSerializer<T>();
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Registers a managed type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_ManagedIEquatable<T>() where T : class, IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsObject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_UnmanagedIEquatable<T>() where T : unmanaged, IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEquals;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_UnmanagedIEquatableArray<T>() where T : unmanaged, IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<NativeArray<T>>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsArray;
|
|
}
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_List<T>()
|
|
{
|
|
NetworkVariableSerialization<List<T>>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsList;
|
|
}
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_HashSet<T>() where T : IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<HashSet<T>>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsHashSet;
|
|
}
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_Dictionary<TKey, TVal>()
|
|
where TKey : IEquatable<TKey>
|
|
{
|
|
NetworkVariableSerialization<Dictionary<TKey, TVal>>.AreEqual = NetworkVariableDictionarySerialization<TKey, TVal>.GenericEqualsDictionary;
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_UnmanagedIEquatableList<T>() where T : unmanaged, IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<NativeList<T>>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsNativeList;
|
|
}
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_NativeHashSet<T>() where T : unmanaged, IEquatable<T>
|
|
{
|
|
NetworkVariableSerialization<NativeHashSet<T>>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsNativeHashSet;
|
|
}
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_NativeHashMap<TKey, TVal>()
|
|
where TKey : unmanaged, IEquatable<TKey>
|
|
where TVal : unmanaged
|
|
{
|
|
NetworkVariableSerialization<NativeHashMap<TKey, TVal>>.AreEqual = NetworkVariableMapSerialization<TKey, TVal>.GenericEqualsNativeHashMap;
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
|
|
/// equal if they are bitwise equivalent in memory
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_UnmanagedValueEquals<T>() where T : unmanaged
|
|
{
|
|
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ValueEquals;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
|
|
/// equal if they are bitwise equivalent in memory
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_UnmanagedValueEqualsArray<T>() where T : unmanaged
|
|
{
|
|
NetworkVariableSerialization<NativeArray<T>>.AreEqual = NetworkVariableSerialization<T>.ValueEqualsArray;
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
/// <summary>
|
|
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
|
|
/// equal if they are bitwise equivalent in memory
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_UnmanagedValueEqualsList<T>() where T : unmanaged
|
|
{
|
|
NetworkVariableSerialization<NativeList<T>>.AreEqual = NetworkVariableSerialization<T>.ValueEqualsList;
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Registers a managed type that will be checked for equality using the == operator
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public static void InitializeEqualityChecker_ManagedClassEquals<T>() where T : class
|
|
{
|
|
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ClassEquals;
|
|
}
|
|
}
|
|
|
|
/// <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 sets up various read/write schemes
|
|
/// based on which constraints are met by `T` using reflection, which is done at module load time.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
|
|
[Serializable]
|
|
public static class NetworkVariableSerialization<T>
|
|
{
|
|
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
|
|
|
|
/// <summary>
|
|
/// The collection item type tells the CMB server how to read the bytes of each item in the collection
|
|
/// </summary>
|
|
/// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server
|
|
internal static CollectionItemType Type = CollectionItemType.Unknown;
|
|
|
|
/// <summary>
|
|
/// A callback to check if two values are equal.
|
|
/// </summary>
|
|
public delegate bool EqualsDelegate(ref T a, ref T b);
|
|
|
|
/// <summary>
|
|
/// Uses the most efficient mechanism for a given type to determine if two values are equal.
|
|
/// For types that implement <see cref="IEquatable{T}"/>, it will call the Equals() method.
|
|
/// For unmanaged types, it will do a bytewise memory comparison.
|
|
/// For other types, it will call the == operator.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
|
/// generic, it is better to check their equality yourself.
|
|
/// </summary>
|
|
public static EqualsDelegate AreEqual { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
|
/// generic, it is better to use FastBufferWriter directly.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// If the codegen is unable to determine a serializer for a type,
|
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.WriteValue"/> is called, which, by default,
|
|
/// will throw an exception, unless you have assigned a user serialization callback to it at runtime.
|
|
/// </summary>
|
|
/// <param name="writer"></param>
|
|
/// <param name="value"></param>
|
|
public static void Write(FastBufferWriter writer, ref T value)
|
|
{
|
|
Serializer.Write(writer, ref value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
|
/// generic, it is better to use FastBufferReader directly.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// If the codegen is unable to determine a serializer for a type,
|
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.ReadValue"/> is called, which, by default,
|
|
/// will throw an exception, unless you have assigned a user deserialization callback to it at runtime.
|
|
/// </summary>
|
|
/// <param name="reader"></param>
|
|
/// <param name="value"></param>
|
|
public static void Read(FastBufferReader reader, ref T value)
|
|
{
|
|
Serializer.Read(reader, ref value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
|
/// generic, it is better to use FastBufferWriter directly.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// If the codegen is unable to determine a serializer for a type,
|
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.WriteValue"/> is called, which, by default,
|
|
/// will throw an exception, unless you have assigned a user serialization callback to it at runtime.
|
|
/// </summary>
|
|
/// <param name="writer"></param>
|
|
/// <param name="value"></param>
|
|
public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
|
|
{
|
|
Serializer.WriteDelta(writer, ref value, ref previousValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
|
/// generic, it is better to use FastBufferReader directly.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// If the codegen is unable to determine a serializer for a type,
|
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.ReadValue"/> is called, which, by default,
|
|
/// will throw an exception, unless you have assigned a user deserialization callback to it at runtime.
|
|
/// </summary>
|
|
/// <param name="reader"></param>
|
|
/// <param name="value"></param>
|
|
public static void ReadDelta(FastBufferReader reader, ref T value)
|
|
{
|
|
Serializer.ReadDelta(reader, ref value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
|
/// generic, it is better to duplicate it directly.
|
|
/// <br/>
|
|
/// <br/>
|
|
/// If the codegen is unable to determine a serializer for a type,
|
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.DuplicateValue"/> is called, which, by default,
|
|
/// will throw an exception, unless you have assigned a user duplication callback to it at runtime.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <param name="duplicatedValue"></param>
|
|
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<TValueType>(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<TValueType>(ref NativeList<TValueType> a, ref NativeList<TValueType> b) where TValueType : unmanaged
|
|
{
|
|
if (a.IsCreated != b.IsCreated)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!a.IsCreated)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (a.Length != b.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if UTP_TRANSPORT_2_0_ABOVE
|
|
var aptr = a.GetUnsafePtr();
|
|
var bptr = b.GetUnsafePtr();
|
|
#else
|
|
var aptr = (TValueType*)a.GetUnsafePtr();
|
|
var bptr = (TValueType*)b.GetUnsafePtr();
|
|
#endif
|
|
|
|
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<TValueType>(ref NativeArray<TValueType> a, ref NativeArray<TValueType> 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<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
|
|
{
|
|
if (a == null)
|
|
{
|
|
return b == null;
|
|
}
|
|
|
|
if (b == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return a.Equals(b);
|
|
}
|
|
|
|
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
|
|
{
|
|
return a.Equals(b);
|
|
}
|
|
|
|
internal static bool EqualityEqualsList<TValueType>(ref List<TValueType> a, ref List<TValueType> b)
|
|
{
|
|
if ((a == null) != (b == null))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (a == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (a.Count != b.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < a.Count; ++i)
|
|
{
|
|
var aItem = a[i];
|
|
var bItem = b[i];
|
|
if (!NetworkVariableSerialization<TValueType>.AreEqual(ref aItem, ref bItem))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool EqualityEqualsHashSet<TValueType>(ref HashSet<TValueType> a, ref HashSet<TValueType> b) where TValueType : IEquatable<TValueType>
|
|
{
|
|
if ((a == null) != (b == null))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (a == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (a.Count != b.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var item in a)
|
|
{
|
|
if (!b.Contains(item))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#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 EqualityEqualsNativeList<TValueType>(ref NativeList<TValueType> a, ref NativeList<TValueType> b) where TValueType : unmanaged, IEquatable<TValueType>
|
|
{
|
|
if (a.IsCreated != b.IsCreated)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!a.IsCreated)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (a.Length != b.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if UTP_TRANSPORT_2_0_ABOVE
|
|
var aptr = a.GetUnsafePtr();
|
|
var bptr = b.GetUnsafePtr();
|
|
#else
|
|
var aptr = (TValueType*)a.GetUnsafePtr();
|
|
var bptr = (TValueType*)b.GetUnsafePtr();
|
|
#endif
|
|
for (var i = 0; i < a.Length; ++i)
|
|
{
|
|
if (!EqualityEquals(ref aptr[i], ref bptr[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool EqualityEqualsNativeHashSet<TValueType>(ref NativeHashSet<TValueType> a, ref NativeHashSet<TValueType> b) where TValueType : unmanaged, IEquatable<TValueType>
|
|
{
|
|
if (a.IsCreated != b.IsCreated)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!a.IsCreated)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#if UTP_TRANSPORT_2_0_ABOVE
|
|
if (a.Count != b.Count)
|
|
#else
|
|
if (a.Count() != b.Count())
|
|
#endif
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var item in a)
|
|
{
|
|
if (!b.Contains(item))
|
|
{
|
|
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<TValueType>(ref NativeArray<TValueType> a, ref NativeArray<TValueType> b) where TValueType : unmanaged, IEquatable<TValueType>
|
|
{
|
|
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<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
|
|
{
|
|
return a == b;
|
|
}
|
|
}
|
|
internal class NetworkVariableDictionarySerialization<TKey, TVal>
|
|
where TKey : IEquatable<TKey>
|
|
{
|
|
|
|
internal static bool GenericEqualsDictionary(ref Dictionary<TKey, TVal> a, ref Dictionary<TKey, TVal> b)
|
|
{
|
|
if ((a == null) != (b == null))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (a == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (a.Count != b.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var item in a)
|
|
{
|
|
var hasKey = b.TryGetValue(item.Key, out var val);
|
|
if (!hasKey)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var bVal = item.Value;
|
|
if (!NetworkVariableSerialization<TVal>.AreEqual(ref bVal, ref val))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
|
|
internal class NetworkVariableMapSerialization<TKey, TVal>
|
|
where TKey : unmanaged, IEquatable<TKey>
|
|
where TVal : unmanaged
|
|
{
|
|
|
|
internal static bool GenericEqualsNativeHashMap(ref NativeHashMap<TKey, TVal> a, ref NativeHashMap<TKey, TVal> b)
|
|
{
|
|
if (a.IsCreated != b.IsCreated)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!a.IsCreated)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#if UTP_TRANSPORT_2_0_ABOVE
|
|
if (a.Count != b.Count)
|
|
#else
|
|
if (a.Count() != b.Count())
|
|
#endif
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var item in a)
|
|
{
|
|
var hasKey = b.TryGetValue(item.Key, out var val);
|
|
if (!hasKey || !NetworkVariableSerialization<TVal>.AreEqual(ref item.Value, ref val))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// RuntimeAccessModifiersILPP will make this `public`
|
|
// This is just pass-through to NetworkVariableSerialization<T> but is here becaues I could not get ILPP
|
|
// to generate code that would successfully call Type<T>.Method(T), but it has no problem calling Type.Method<T>(T)
|
|
internal class RpcFallbackSerialization
|
|
{
|
|
public static void Write<T>(FastBufferWriter writer, ref T value)
|
|
{
|
|
NetworkVariableSerialization<T>.Write(writer, ref value);
|
|
}
|
|
|
|
public static void Read<T>(FastBufferReader reader, ref T value)
|
|
{
|
|
NetworkVariableSerialization<T>.Read(reader, ref value);
|
|
}
|
|
}
|
|
}
|