1422 lines
47 KiB
C#
1422 lines
47 KiB
C#
|
using FishNet.Connection;
|
||
|
using FishNet.Documenting;
|
||
|
using FishNet.Managing;
|
||
|
using FishNet.Object;
|
||
|
using FishNet.Object.Prediction;
|
||
|
using FishNet.Serializing.Helping;
|
||
|
using FishNet.Transporting;
|
||
|
using FishNet.Utility.Constant;
|
||
|
using FishNet.Utility.Extension;
|
||
|
using FishNet.Utility.Performance;
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using UnityEngine;
|
||
|
|
||
|
|
||
|
[assembly: InternalsVisibleTo(UtilityConstants.GENERATED_ASSEMBLY_NAME)]
|
||
|
//Required for internal tests.
|
||
|
[assembly: InternalsVisibleTo(UtilityConstants.TEST_ASSEMBLY_NAME)]
|
||
|
namespace FishNet.Serializing
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Used for read references to generic types.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T"></typeparam>
|
||
|
[APIExclude]
|
||
|
public static class GenericReader<T>
|
||
|
{
|
||
|
public static Func<Reader, T> Read { internal get; set; }
|
||
|
public static Func<Reader, AutoPackType, T> ReadAutoPack { internal get; set; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads data from a buffer.
|
||
|
/// </summary>
|
||
|
public class Reader
|
||
|
{
|
||
|
#region Public.
|
||
|
/// <summary>
|
||
|
/// Capacity of the buffer.
|
||
|
/// </summary>
|
||
|
public int Capacity => _buffer.Length;
|
||
|
/// <summary>
|
||
|
/// NetworkManager for this reader. Used to lookup objects.
|
||
|
/// </summary>
|
||
|
public NetworkManager NetworkManager;
|
||
|
/// <summary>
|
||
|
/// Offset within the buffer when the reader was created.
|
||
|
/// </summary>
|
||
|
public int Offset { get; private set; }
|
||
|
/// <summary>
|
||
|
/// Position for the next read.
|
||
|
/// </summary>
|
||
|
public int Position;
|
||
|
/// <summary>
|
||
|
/// Total number of bytes available within the buffer.
|
||
|
/// </summary>
|
||
|
public int Length { get; private set; }
|
||
|
/// <summary>
|
||
|
/// Bytes remaining to be read. This value is Length - Position.
|
||
|
/// </summary>
|
||
|
public int Remaining => ((Length + Offset) - Position);
|
||
|
#endregion
|
||
|
|
||
|
#region Internal.
|
||
|
/// <summary>
|
||
|
/// NetworkConnection that this data came from.
|
||
|
/// Value may not always be set.
|
||
|
/// </summary>
|
||
|
public NetworkConnection NetworkConnection { get; private set; }
|
||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||
|
/// <summary>
|
||
|
/// Last NetworkObject parsed.
|
||
|
/// </summary>
|
||
|
public static NetworkObject LastNetworkObject { get; private set; }
|
||
|
/// <summary>
|
||
|
/// Last NetworkBehaviour parsed.
|
||
|
/// </summary>
|
||
|
public static NetworkBehaviour LastNetworkBehaviour { get; private set; }
|
||
|
#endif
|
||
|
#endregion
|
||
|
|
||
|
#region Private.
|
||
|
/// <summary>
|
||
|
/// Data being read.
|
||
|
/// </summary>
|
||
|
private byte[] _buffer;
|
||
|
#endregion
|
||
|
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Reader(byte[] bytes, NetworkManager networkManager, NetworkConnection networkConnection = null)
|
||
|
{
|
||
|
Initialize(bytes, networkManager, networkConnection);
|
||
|
}
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Reader(ArraySegment<byte> segment, NetworkManager networkManager, NetworkConnection networkConnection = null)
|
||
|
{
|
||
|
Initialize(segment, networkManager, networkConnection);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Outputs reader to string.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public override string ToString()
|
||
|
{
|
||
|
return $"Position: {Position}, Length: {Length}, Buffer: {BitConverter.ToString(_buffer, Offset, Length)}.";
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes this reader with data.
|
||
|
/// </summary>
|
||
|
/// <param name="bytes"></param>
|
||
|
/// <param name="networkManager"></param>
|
||
|
internal void Initialize(ArraySegment<byte> bytes, NetworkManager networkManager, NetworkConnection networkConnection = null)
|
||
|
{
|
||
|
if (bytes.Array == null)
|
||
|
{
|
||
|
if (_buffer == null)
|
||
|
_buffer = new byte[0];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_buffer = bytes.Array;
|
||
|
}
|
||
|
|
||
|
Position = bytes.Offset;
|
||
|
Offset = bytes.Offset;
|
||
|
Length = bytes.Count;
|
||
|
NetworkManager = networkManager;
|
||
|
NetworkConnection = networkConnection;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Initializes this reader with data.
|
||
|
/// </summary>
|
||
|
/// <param name="bytes"></param>
|
||
|
/// <param name="networkManager"></param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal void Initialize(byte[] bytes, NetworkManager networkManager, NetworkConnection networkConnection = null)
|
||
|
{
|
||
|
Initialize(new ArraySegment<byte>(bytes), networkManager, networkConnection);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a dictionary.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
[Obsolete("Use ReadDictionaryAllocated.")]
|
||
|
public Dictionary<TKey, TValue> ReadDictionary<TKey, TValue>()
|
||
|
{
|
||
|
return ReadDictionaryAllocated<TKey, TValue>();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a dictionary.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Dictionary<TKey, TValue> ReadDictionaryAllocated<TKey, TValue>()
|
||
|
{
|
||
|
bool isNull = ReadBoolean();
|
||
|
if (isNull)
|
||
|
return null;
|
||
|
|
||
|
int count = ReadInt32();
|
||
|
|
||
|
Dictionary<TKey, TValue> result = new Dictionary<TKey, TValue>(count);
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
TKey key = Read<TKey>();
|
||
|
TValue value = Read<TValue>();
|
||
|
result.Add(key, value);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads length. This method is used to make debugging easier.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal int ReadLength()
|
||
|
{
|
||
|
return ReadInt32();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a packetId.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal PacketId ReadPacketId()
|
||
|
{
|
||
|
return (PacketId)ReadUInt16();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a ushort without advancing the reader.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal PacketId PeekPacketId()
|
||
|
{
|
||
|
int currentPosition = Position;
|
||
|
PacketId result = ReadPacketId();
|
||
|
Position = currentPosition;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Skips a number of bytes in the reader.
|
||
|
/// </summary>
|
||
|
/// <param name="value">Number of bytes to skip.</param>
|
||
|
[CodegenExclude]
|
||
|
public void Skip(int value)
|
||
|
{
|
||
|
if (value < 1 || Remaining < value)
|
||
|
return;
|
||
|
|
||
|
Position += value;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Clears remaining bytes to be read.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public void Clear()
|
||
|
{
|
||
|
if (Remaining > 0)
|
||
|
Skip(Remaining);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the buffer as an ArraySegment.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public ArraySegment<byte> GetArraySegmentBuffer()
|
||
|
{
|
||
|
return new ArraySegment<byte>(_buffer, Offset, Length);
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Returns the buffer as bytes. This does not trim excessive bytes.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public byte[] GetByteBuffer()
|
||
|
{
|
||
|
return _buffer;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Returns the buffer as bytes and allocates into a new array.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public byte[] GetByteBufferAllocated()
|
||
|
{
|
||
|
byte[] result = new byte[Length];
|
||
|
Buffer.BlockCopy(_buffer, Offset, result, 0, Length);
|
||
|
return result;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// BlockCopies data from the reader to target and advances reader.
|
||
|
/// </summary>
|
||
|
/// <param name="target"></param>
|
||
|
/// <param name="targetOffset"></param>
|
||
|
/// <param name="count"></param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public void BlockCopy(ref byte[] target, int targetOffset, int count)
|
||
|
{
|
||
|
Buffer.BlockCopy(_buffer, Position, target, targetOffset, count);
|
||
|
Position += count;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a byte.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public byte ReadByte()
|
||
|
{
|
||
|
byte r = _buffer[Position];
|
||
|
Position += 1;
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Read bytes from position into target.
|
||
|
/// </summary>
|
||
|
/// <param name="buffer">Buffer to read bytes into.</param>
|
||
|
/// <param name="count">Number of bytes to read.</param>
|
||
|
[CodegenExclude]
|
||
|
public void ReadBytes(ref byte[] buffer, int count)
|
||
|
{
|
||
|
if (buffer == null)
|
||
|
throw new EndOfStreamException($"Target is null.");
|
||
|
//Target isn't large enough.
|
||
|
if (count > buffer.Length)
|
||
|
throw new EndOfStreamException($"Count of {count} exceeds target length of {buffer.Length}.");
|
||
|
|
||
|
BlockCopy(ref buffer, 0, count);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates an ArraySegment by reading a number of bytes from position.
|
||
|
/// </summary>
|
||
|
/// <param name="count"></param>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public ArraySegment<byte> ReadArraySegment(int count)
|
||
|
{
|
||
|
ArraySegment<byte> result = new ArraySegment<byte>(_buffer, Position, count);
|
||
|
Position += count;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a sbyte.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public sbyte ReadSByte()
|
||
|
{
|
||
|
return (sbyte)ReadByte();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a char.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public char ReadChar() => (char)ReadUInt16();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a boolean.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public bool ReadBoolean()
|
||
|
{
|
||
|
byte result = ReadByte();
|
||
|
return (result == 1) ? true : false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an int16.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public ushort ReadUInt16()
|
||
|
{
|
||
|
ushort result = 0;
|
||
|
result |= _buffer[Position++];
|
||
|
result |= (ushort)(_buffer[Position++] << 8);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a uint16.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public short ReadInt16() => (short)ReadUInt16();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an int32.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public uint ReadUInt32(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
if (packType == AutoPackType.Packed)
|
||
|
return (uint)ReadPackedWhole();
|
||
|
|
||
|
uint result = 0;
|
||
|
result |= _buffer[Position++];
|
||
|
result |= (uint)_buffer[Position++] << 8;
|
||
|
result |= (uint)_buffer[Position++] << 16;
|
||
|
result |= (uint)_buffer[Position++] << 24;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Reads a uint32.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadInt32(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
if (packType == AutoPackType.Packed)
|
||
|
return (int)(long)ZigZagDecode(ReadPackedWhole());
|
||
|
|
||
|
return (int)ReadUInt32(packType);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a uint64.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public long ReadInt64(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
if (packType == AutoPackType.Packed)
|
||
|
return (long)ZigZagDecode(ReadPackedWhole());
|
||
|
|
||
|
return (long)ReadUInt64(packType);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an int64.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public ulong ReadUInt64(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
if (packType == AutoPackType.Packed)
|
||
|
return (ulong)ReadPackedWhole();
|
||
|
|
||
|
ulong result = 0;
|
||
|
result |= _buffer[Position++];
|
||
|
result |= (ulong)_buffer[Position++] << 8;
|
||
|
result |= (ulong)_buffer[Position++] << 16;
|
||
|
result |= (ulong)_buffer[Position++] << 24;
|
||
|
result |= (ulong)_buffer[Position++] << 32;
|
||
|
result |= (ulong)_buffer[Position++] << 40;
|
||
|
result |= (ulong)_buffer[Position++] << 48;
|
||
|
result |= (ulong)_buffer[Position++] << 56;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a single.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public float ReadSingle(AutoPackType packType = AutoPackType.Unpacked)
|
||
|
{
|
||
|
if (packType == AutoPackType.Unpacked)
|
||
|
{
|
||
|
UIntFloat converter = new UIntFloat();
|
||
|
converter.UIntValue = ReadUInt32(AutoPackType.Unpacked);
|
||
|
return converter.FloatValue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
long converter = (long)ReadPackedWhole();
|
||
|
return (float)(converter / 100f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a double.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public double ReadDouble()
|
||
|
{
|
||
|
UIntDouble converter = new UIntDouble();
|
||
|
converter.LongValue = ReadUInt64(AutoPackType.Unpacked);
|
||
|
return converter.DoubleValue;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a decimal.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public decimal ReadDecimal()
|
||
|
{
|
||
|
UIntDecimal converter = new UIntDecimal();
|
||
|
converter.LongValue1 = ReadUInt64(AutoPackType.Unpacked);
|
||
|
converter.LongValue2 = ReadUInt64(AutoPackType.Unpacked);
|
||
|
return converter.DecimalValue;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a string.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public string ReadString()
|
||
|
{
|
||
|
int size = ReadInt32();
|
||
|
//Null string.
|
||
|
if (size == Writer.UNSET_COLLECTION_SIZE_VALUE)
|
||
|
return null;
|
||
|
else if (size == 0)
|
||
|
return string.Empty;
|
||
|
|
||
|
if (!CheckAllocationAttack(size))
|
||
|
return string.Empty;
|
||
|
ArraySegment<byte> data = ReadArraySegment(size);
|
||
|
return ReaderStatics.GetString(data);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a byte array and reads bytes and size into it.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public byte[] ReadBytesAndSizeAllocated()
|
||
|
{
|
||
|
int size = ReadInt32();
|
||
|
if (size == Writer.UNSET_COLLECTION_SIZE_VALUE)
|
||
|
return null;
|
||
|
else
|
||
|
return ReadBytesAllocated(size);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads bytes and size and copies results into target. Returns UNSET if null was written.
|
||
|
/// </summary>
|
||
|
/// <returns>Bytes read.</returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadBytesAndSize(ref byte[] target)
|
||
|
{
|
||
|
int size = ReadInt32();
|
||
|
if (size > 0)
|
||
|
ReadBytes(ref target, size);
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads bytes and size and returns as an ArraySegment.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public ArraySegment<byte> ReadArraySegmentAndSize()
|
||
|
{
|
||
|
int size = ReadInt32();
|
||
|
/* UNSET would be written for null. But since
|
||
|
* ArraySegments cannot be null return default if
|
||
|
* length is unset or 0. */
|
||
|
if (size == Writer.UNSET_COLLECTION_SIZE_VALUE || size == 0)
|
||
|
return default;
|
||
|
|
||
|
return ReadArraySegment(size);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Vector2.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Vector2 ReadVector2()
|
||
|
{
|
||
|
return new Vector2(ReadSingle(), ReadSingle());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Vector3.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Vector3 ReadVector3()
|
||
|
{
|
||
|
return new Vector3(ReadSingle(), ReadSingle(), ReadSingle());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Vector4.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Vector4 ReadVector4()
|
||
|
{
|
||
|
return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Vector2Int.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Vector2Int ReadVector2Int(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
return new Vector2Int(ReadInt32(packType), ReadInt32(packType));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Vector3Int.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Vector3Int ReadVector3Int(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
return new Vector3Int(ReadInt32(packType), ReadInt32(packType), ReadInt32(packType));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a color.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Color ReadColor(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
float r, g, b, a;
|
||
|
if (packType == AutoPackType.Unpacked)
|
||
|
{
|
||
|
r = ReadSingle();
|
||
|
g = ReadSingle();
|
||
|
b = ReadSingle();
|
||
|
a = ReadSingle();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
r = (float)(ReadByte() / 100f);
|
||
|
g = (float)(ReadByte() / 100f);
|
||
|
b = (float)(ReadByte() / 100f);
|
||
|
a = (float)(ReadByte() / 100f);
|
||
|
}
|
||
|
return new Color(r, g, b, a);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Color32.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Color32 ReadColor32()
|
||
|
{
|
||
|
return new Color32(ReadByte(), ReadByte(), ReadByte(), ReadByte());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Quaternion.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Quaternion ReadQuaternion(AutoPackType packType = AutoPackType.Packed)
|
||
|
{
|
||
|
if (packType == AutoPackType.Packed)
|
||
|
{
|
||
|
uint result = ReadUInt32(AutoPackType.Unpacked);
|
||
|
return Quaternion32Compression.Decompress(result);
|
||
|
}
|
||
|
else if (packType == AutoPackType.PackedLess)
|
||
|
{
|
||
|
ulong result = ReadUInt64(AutoPackType.Unpacked);
|
||
|
return Quaternion64Compression.Decompress(result);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return new Quaternion(
|
||
|
ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Rect.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Rect ReadRect()
|
||
|
{
|
||
|
return new Rect(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Plane.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Plane ReadPlane()
|
||
|
{
|
||
|
return new Plane(ReadVector3(), ReadSingle());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Ray.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Ray ReadRay()
|
||
|
{
|
||
|
Vector3 position = ReadVector3();
|
||
|
Vector3 direction = ReadVector3();
|
||
|
return new Ray(position, direction);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Ray.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Ray2D ReadRay2D()
|
||
|
{
|
||
|
Vector3 position = ReadVector2();
|
||
|
Vector2 direction = ReadVector2();
|
||
|
return new Ray2D(position, direction);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Matrix4x4.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Matrix4x4 ReadMatrix4x4()
|
||
|
{
|
||
|
Matrix4x4 result = new Matrix4x4
|
||
|
{
|
||
|
m00 = ReadSingle(),
|
||
|
m01 = ReadSingle(),
|
||
|
m02 = ReadSingle(),
|
||
|
m03 = ReadSingle(),
|
||
|
m10 = ReadSingle(),
|
||
|
m11 = ReadSingle(),
|
||
|
m12 = ReadSingle(),
|
||
|
m13 = ReadSingle(),
|
||
|
m20 = ReadSingle(),
|
||
|
m21 = ReadSingle(),
|
||
|
m22 = ReadSingle(),
|
||
|
m23 = ReadSingle(),
|
||
|
m30 = ReadSingle(),
|
||
|
m31 = ReadSingle(),
|
||
|
m32 = ReadSingle(),
|
||
|
m33 = ReadSingle()
|
||
|
};
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a new byte array and reads bytes into it.
|
||
|
/// </summary>
|
||
|
/// <param name="count"></param>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public byte[] ReadBytesAllocated(int count)
|
||
|
{
|
||
|
byte[] bytes = new byte[count];
|
||
|
ReadBytes(ref bytes, count);
|
||
|
return bytes;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Guid.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public System.Guid ReadGuid()
|
||
|
{
|
||
|
byte[] buffer = ReaderStatics.GetGuidBuffer();
|
||
|
ReadBytes(ref buffer, 16);
|
||
|
return new System.Guid(buffer);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a GameObject.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public GameObject ReadGameObject()
|
||
|
{
|
||
|
NetworkObject nob = ReadNetworkObject();
|
||
|
return (nob == null) ? null : nob.gameObject;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a Transform.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Transform ReadTransform()
|
||
|
{
|
||
|
NetworkObject nob = ReadNetworkObject();
|
||
|
return (nob == null) ? null : nob.transform;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a NetworkObject.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public NetworkObject ReadNetworkObject()
|
||
|
{
|
||
|
return ReadNetworkObject(out _);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a NetworkObject.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public NetworkObject ReadNetworkObject(out int objectOrPrefabId)
|
||
|
{
|
||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||
|
LastNetworkBehaviour = null;
|
||
|
#endif
|
||
|
objectOrPrefabId = ReadNetworkObjectId();
|
||
|
bool isSpawned;
|
||
|
/* UNSET indicates that the object
|
||
|
* is null or no PrefabId is set.
|
||
|
* PrefabIds are set in Awake within
|
||
|
* the NetworkManager so that should
|
||
|
* never happen so long as nob isn't null. */
|
||
|
if (objectOrPrefabId == NetworkObject.UNSET_OBJECTID_VALUE)
|
||
|
return null;
|
||
|
else
|
||
|
isSpawned = ReadBoolean();
|
||
|
|
||
|
bool isServer = NetworkManager.ServerManager.Started;
|
||
|
bool isClient = NetworkManager.ClientManager.Started;
|
||
|
|
||
|
NetworkObject result;
|
||
|
//Is spawned.
|
||
|
if (isSpawned)
|
||
|
{
|
||
|
result = null;
|
||
|
/* Try to get the object client side first if client
|
||
|
* is running. When acting as a host generally the object
|
||
|
* will be available in the server and client list
|
||
|
* but there can be occasions where the server side
|
||
|
* deinitializes the object, making it unavailable, while
|
||
|
* it is still available in the client side. Since FishNet doesn't
|
||
|
* use a fake host connection like some lesser solutions the client
|
||
|
* has to always be treated as it's own entity. */
|
||
|
if (isClient)
|
||
|
NetworkManager.ClientManager.Objects.Spawned.TryGetValueIL2CPP(objectOrPrefabId, out result);
|
||
|
//If not found on client and server is running then try server.
|
||
|
if (result == null && isServer)
|
||
|
NetworkManager.ServerManager.Objects.Spawned.TryGetValueIL2CPP(objectOrPrefabId, out result);
|
||
|
}
|
||
|
//Not spawned.
|
||
|
else
|
||
|
{
|
||
|
//Only look up asServer if not client, otherwise use client.
|
||
|
bool asServer = !isClient;
|
||
|
//Look up prefab.
|
||
|
result = NetworkManager.GetPrefab(objectOrPrefabId, asServer);
|
||
|
}
|
||
|
|
||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||
|
LastNetworkObject = result;
|
||
|
#endif
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a NetworkObjectId and nothing else.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadNetworkObjectId()
|
||
|
{
|
||
|
return ReadUInt16();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads the Id for a NetworkObject and outputs spawn settings.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal int ReadNetworkObjectForSpawn(out sbyte initializeOrder, out ushort collectionid, out bool spawned)
|
||
|
{
|
||
|
int objectId = ReadNetworkObjectId();
|
||
|
|
||
|
bool isNull = (objectId == NetworkObject.UNSET_OBJECTID_VALUE);
|
||
|
if (isNull)
|
||
|
{
|
||
|
initializeOrder = 0;
|
||
|
collectionid = 0;
|
||
|
spawned = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
collectionid = ReadUInt16();
|
||
|
initializeOrder = ReadSByte();
|
||
|
spawned = ReadBoolean();
|
||
|
}
|
||
|
|
||
|
return objectId;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads the Id for a NetworkObject and outputs despawn settings.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal int ReadNetworkObjectForDepawn(out DespawnType dt)
|
||
|
{
|
||
|
int objectId = ReadNetworkObjectId();
|
||
|
dt = (DespawnType)ReadByte();
|
||
|
return objectId;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a NetworkBehaviourId and ObjectId.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal byte ReadNetworkBehaviourId(out int objectId)
|
||
|
{
|
||
|
objectId = ReadNetworkObjectId();
|
||
|
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||
|
return ReadByte();
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a NetworkBehaviour.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public NetworkBehaviour ReadNetworkBehaviour(out int objectId, out byte componentIndex)
|
||
|
{
|
||
|
NetworkObject nob = ReadNetworkObject(out objectId);
|
||
|
componentIndex = ReadByte();
|
||
|
|
||
|
NetworkBehaviour result;
|
||
|
if (nob == null)
|
||
|
{
|
||
|
result = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (componentIndex >= nob.NetworkBehaviours.Length)
|
||
|
{
|
||
|
NetworkManager.LogError($"ComponentIndex of {componentIndex} is out of bounds on {nob.gameObject.name} [id {nob.ObjectId}]. This may occur if you have modified your gameObject/prefab without saving it, or the scene.");
|
||
|
result = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = nob.NetworkBehaviours[componentIndex];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||
|
LastNetworkBehaviour = result;
|
||
|
#endif
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a NetworkBehaviour.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public NetworkBehaviour ReadNetworkBehaviour()
|
||
|
{
|
||
|
return ReadNetworkBehaviour(out _, out _);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a DateTime.
|
||
|
/// </summary>
|
||
|
/// <param name="dt"></param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public DateTime ReadDateTime()
|
||
|
{
|
||
|
DateTime result = DateTime.FromBinary(ReadInt64());
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a transport channel.
|
||
|
/// </summary>
|
||
|
/// <param name="channel"></param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public Channel ReadChannel()
|
||
|
{
|
||
|
return (Channel)ReadByte();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads the Id for a NetworkConnection.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadNetworkConnectionId()
|
||
|
{
|
||
|
return ReadInt16();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a NetworkConnection.
|
||
|
/// </summary>
|
||
|
/// <param name="conn"></param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public NetworkConnection ReadNetworkConnection()
|
||
|
{
|
||
|
int value = ReadNetworkConnectionId();
|
||
|
if (value == NetworkConnection.UNSET_CLIENTID_VALUE)
|
||
|
{
|
||
|
return FishNet.Managing.NetworkManager.EmptyConnection;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//Prefer server.
|
||
|
if (NetworkManager.IsServer)
|
||
|
{
|
||
|
NetworkConnection result;
|
||
|
if (NetworkManager.ServerManager.Clients.TryGetValueIL2CPP(value, out result))
|
||
|
{
|
||
|
return result;
|
||
|
}
|
||
|
//If also client then try client side data.
|
||
|
else if (NetworkManager.IsClient)
|
||
|
{
|
||
|
//If found in client collection then return.
|
||
|
if (NetworkManager.ClientManager.Clients.TryGetValueIL2CPP(value, out result))
|
||
|
return result;
|
||
|
/* Otherwise make a new instance.
|
||
|
* We do not know if this is for the server or client so
|
||
|
* initialize it either way. Connections rarely come through
|
||
|
* without being in server/client side collection. */
|
||
|
else
|
||
|
return new NetworkConnection(NetworkManager, value, true);
|
||
|
|
||
|
}
|
||
|
//Only server and not found.
|
||
|
else
|
||
|
{
|
||
|
NetworkManager.LogWarning($"Unable to find connection for read Id {value}. An empty connection will be returned.");
|
||
|
return FishNet.Managing.NetworkManager.EmptyConnection;
|
||
|
}
|
||
|
}
|
||
|
//Try client side, will only be able to fetch against local connection.
|
||
|
else
|
||
|
{
|
||
|
//If value is self then return self.
|
||
|
if (value == NetworkManager.ClientManager.Connection.ClientId)
|
||
|
return NetworkManager.ClientManager.Connection;
|
||
|
//Try client side dictionary.
|
||
|
else if (NetworkManager.ClientManager.Clients.TryGetValueIL2CPP(value, out NetworkConnection result))
|
||
|
return result;
|
||
|
/* Otherwise make a new instance.
|
||
|
* We do not know if this is for the server or client so
|
||
|
* initialize it either way. Connections rarely come through
|
||
|
* without being in server/client side collection. */
|
||
|
else
|
||
|
return new NetworkConnection(NetworkManager, value, true);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Checks if the size could possibly be an allocation attack.
|
||
|
/// </summary>
|
||
|
/// <param name="size"></param>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
private bool CheckAllocationAttack(int size)
|
||
|
{
|
||
|
/* Possible attacks. Impossible size, or size indicates
|
||
|
* more elements in collection or more bytes needed
|
||
|
* than what bytes are available. */
|
||
|
if (size != Writer.UNSET_COLLECTION_SIZE_VALUE && size < 0)
|
||
|
{
|
||
|
NetworkManager.LogError($"Size of {size} is invalid.");
|
||
|
return false;
|
||
|
}
|
||
|
if (size > Remaining)
|
||
|
{
|
||
|
NetworkManager.LogError($"Read size of {size} is larger than remaining data of {Remaining}.");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Checks pass.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
#region Packed readers.
|
||
|
/// <summary>
|
||
|
/// ZigZag decode an integer. Move the sign bit back to the left.
|
||
|
/// </summary>
|
||
|
public ulong ZigZagDecode(ulong value)
|
||
|
{
|
||
|
ulong sign = value << 63;
|
||
|
if (sign > 0)
|
||
|
return ~(value >> 1) | sign;
|
||
|
return value >> 1;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Reads a packed whole number.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public ulong ReadPackedWhole()
|
||
|
{
|
||
|
byte data = ReadByte();
|
||
|
ulong result = (ulong)(data & 0x7F);
|
||
|
if ((data & 0x80) == 0) return result;
|
||
|
|
||
|
data = ReadByte();
|
||
|
result |= (ulong)(data & 0x7F) << 7;
|
||
|
if ((data & 0x80) == 0) return result;
|
||
|
|
||
|
data = ReadByte();
|
||
|
result |= (ulong)(data & 0x7F) << 14;
|
||
|
if ((data & 0x80) == 0) return result;
|
||
|
|
||
|
data = ReadByte();
|
||
|
result |= (ulong)(data & 0x7F) << 21;
|
||
|
if ((data & 0x80) == 0) return result;
|
||
|
|
||
|
data = ReadByte();
|
||
|
result |= (ulong)(data & 0x0F) << 28;
|
||
|
int extraBytes = data >> 4;
|
||
|
|
||
|
switch (extraBytes)
|
||
|
{
|
||
|
case 0:
|
||
|
break;
|
||
|
case 1:
|
||
|
result |= (ulong)ReadByte() << 32;
|
||
|
break;
|
||
|
case 2:
|
||
|
result |= (ulong)ReadByte() << 32;
|
||
|
result |= (ulong)ReadByte() << 40;
|
||
|
break;
|
||
|
case 3:
|
||
|
result |= (ulong)ReadByte() << 32;
|
||
|
result |= (ulong)ReadByte() << 40;
|
||
|
result |= (ulong)ReadByte() << 48;
|
||
|
break;
|
||
|
case 4:
|
||
|
result |= (ulong)ReadByte() << 32;
|
||
|
result |= (ulong)ReadByte() << 40;
|
||
|
result |= (ulong)ReadByte() << 48;
|
||
|
result |= (ulong)ReadByte() << 56;
|
||
|
break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region Generators.
|
||
|
/// <summary>
|
||
|
/// Reads a replicate into collection and returns item count read.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal int ReadReplicate<T>(ref T[] collection, uint tick) where T : IReplicateData
|
||
|
{
|
||
|
//Number of entries written.
|
||
|
int count = (int)ReadByte();
|
||
|
if (collection == null || collection.Length < count)
|
||
|
collection = new T[count];
|
||
|
|
||
|
/* Subtract count total minus 1
|
||
|
* from starting tick. This sets the tick to what the first entry would be.
|
||
|
* EG packet came in as tick 100, so that was passed as tick.
|
||
|
* if there are 3 replicates then 2 would be subtracted (count - 1).
|
||
|
* The new tick would be 98.
|
||
|
* Ticks would be assigned to read values from oldest to
|
||
|
* newest as 98, 99, 100. Which is the correct result. In order for this to
|
||
|
* work properly past replicates cannot skip ticks. This will be ensured
|
||
|
* in another part of the code. */
|
||
|
tick -= (uint)(count - 1);
|
||
|
|
||
|
int fullPackType = ReadByte();
|
||
|
//Read once and apply to all entries.
|
||
|
if (fullPackType > 0)
|
||
|
{
|
||
|
T value;
|
||
|
if (fullPackType == Writer.REPLICATE_ALL_DEFAULT_BYTE)
|
||
|
{
|
||
|
value = default(T);
|
||
|
}
|
||
|
else if (fullPackType == Writer.REPLICATE_REPEATING_BYTE)
|
||
|
{
|
||
|
value = Read<T>();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
value = default(T);
|
||
|
NetworkManager?.LogError($"Unhandled Replicate pack type {fullPackType}.");
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
collection[i] = value;
|
||
|
collection[i].SetTick(tick + (uint)i);
|
||
|
}
|
||
|
}
|
||
|
//Values vary, read each indicator.
|
||
|
else
|
||
|
{
|
||
|
T lastData = default;
|
||
|
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
T value = default;
|
||
|
byte indicatorB = ReadByte();
|
||
|
if (indicatorB == Writer.REPLICATE_DUPLICATE_BYTE)
|
||
|
{
|
||
|
value = lastData;
|
||
|
}
|
||
|
else if (indicatorB == Writer.REPLICATE_UNIQUE_BYTE)
|
||
|
{
|
||
|
value = Read<T>();
|
||
|
lastData = value;
|
||
|
}
|
||
|
else if (indicatorB == Writer.REPLICATE_DEFAULT_BYTE)
|
||
|
{
|
||
|
value = default(T);
|
||
|
}
|
||
|
|
||
|
//Apply tick.
|
||
|
value.SetTick(tick + (uint)i);
|
||
|
//Assign to collection.
|
||
|
collection[i] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a ListCache with allocations.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public ListCache<T> ReadListCacheAllocated<T>()
|
||
|
{
|
||
|
List<T> lst = ReadListAllocated<T>();
|
||
|
ListCache<T> lc = new ListCache<T>();
|
||
|
lc.Collection = lst;
|
||
|
return lc;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Reads a ListCache and returns the item count read.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadListCache<T>(ref ListCache<T> listCache)
|
||
|
{
|
||
|
listCache.Collection = ReadListAllocated<T>();
|
||
|
return listCache.Collection.Count;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Reads a list with allocations.
|
||
|
/// </summary>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public List<T> ReadListAllocated<T>()
|
||
|
{
|
||
|
List<T> result = null;
|
||
|
ReadList<T>(ref result);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads into collection and returns item count read.
|
||
|
/// </summary>
|
||
|
/// <param name="collection"></param>
|
||
|
/// <param name="allowNullification">True to allow the referenced collection to be nullified when receiving a null collection read.</param>
|
||
|
/// <returns>Number of values read into the collection. UNSET is returned if the collection were read as null.</returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadList<T>(ref List<T> collection, bool allowNullification = false)
|
||
|
{
|
||
|
int count = ReadInt32();
|
||
|
if (count == Writer.UNSET_COLLECTION_SIZE_VALUE)
|
||
|
{
|
||
|
if (allowNullification)
|
||
|
collection = null;
|
||
|
return Writer.UNSET_COLLECTION_SIZE_VALUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (collection == null)
|
||
|
collection = new List<T>(count);
|
||
|
else
|
||
|
collection.Clear();
|
||
|
|
||
|
|
||
|
for (int i = 0; i < count; i++)
|
||
|
collection.Add(Read<T>());
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Reads an array.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T"></typeparam>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public T[] ReadArrayAllocated<T>()
|
||
|
{
|
||
|
T[] result = null;
|
||
|
ReadArray<T>(ref result);
|
||
|
return result;
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Reads into collection and returns amount read.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T"></typeparam>
|
||
|
/// <param name="collection"></param>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public int ReadArray<T>(ref T[] collection)
|
||
|
{
|
||
|
int count = ReadInt32();
|
||
|
if (count == Writer.UNSET_COLLECTION_SIZE_VALUE)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
else if (count == 0)
|
||
|
{
|
||
|
if (collection == null)
|
||
|
collection = new T[0];
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//Initialize buffer if not already done.
|
||
|
if (collection == null)
|
||
|
collection = new T[count];
|
||
|
else if (collection.Length < count)
|
||
|
Array.Resize(ref collection, count);
|
||
|
|
||
|
for (int i = 0; i < count; i++)
|
||
|
collection[i] = Read<T>();
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads any supported type.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T"></typeparam>
|
||
|
/// <returns></returns>
|
||
|
[CodegenExclude]
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public T Read<T>()
|
||
|
{
|
||
|
System.Type type = typeof(T);
|
||
|
if (IsAutoPackType(type, out AutoPackType packType))
|
||
|
{
|
||
|
Func<Reader, AutoPackType, T> autopackDel = GenericReader<T>.ReadAutoPack;
|
||
|
if (autopackDel == null)
|
||
|
{
|
||
|
LogError(GetLogMessage());
|
||
|
return default;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return autopackDel.Invoke(this, packType);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Func<Reader, T> del = GenericReader<T>.Read;
|
||
|
if (del == null)
|
||
|
{
|
||
|
LogError(GetLogMessage());
|
||
|
return default;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return del.Invoke(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string GetLogMessage() => $"Read method not found for {type.FullName}. Use a supported type or create a custom serializer.";
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Logs an error.
|
||
|
/// </summary>
|
||
|
/// <param name="msg"></param>
|
||
|
private void LogError(string msg)
|
||
|
{
|
||
|
if (NetworkManager == null)
|
||
|
NetworkManager.StaticLogError(msg);
|
||
|
else
|
||
|
NetworkManager.LogError(msg);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns if T takes AutoPackType argument.
|
||
|
/// </summary>
|
||
|
/// <param name="packType">Outputs the default pack type for T.</param>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal bool IsAutoPackType<T>(out AutoPackType packType) => Writer.IsAutoPackType<T>(out packType);
|
||
|
/// <summary>
|
||
|
/// Returns if T takes AutoPackType argument.
|
||
|
/// </summary>
|
||
|
/// <param name="packType">Outputs the default pack type for T.</param>
|
||
|
/// <returns></returns>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
internal bool IsAutoPackType(Type type, out AutoPackType packType) => Writer.IsAutoPackType(type, out packType);
|
||
|
#endregion
|
||
|
}
|
||
|
}
|