Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -0,0 +1,57 @@
namespace MLAPI.Serialization
{
/// <summary>
/// Arithmetic helper class
/// </summary>
public static class Arithmetic
{
// Sign bits for different data types
internal const long SIGN_BIT_64 = -9223372036854775808;
internal const int SIGN_BIT_32 = -2147483648;
internal const short SIGN_BIT_16 = -32768;
internal const sbyte SIGN_BIT_8 = -128;
// Ceiling function that doesn't deal with floating point values
// these only work correctly with possitive numbers
internal static ulong CeilingExact(ulong u1, ulong u2) => (u1 + u2 - 1) / u2;
internal static long CeilingExact(long u1, long u2) => (u1 + u2 - 1) / u2;
internal static uint CeilingExact(uint u1, uint u2) => (u1 + u2 - 1) / u2;
internal static int CeilingExact(int u1, int u2) => (u1 + u2 - 1) / u2;
internal static ushort CeilingExact(ushort u1, ushort u2) => (ushort)((u1 + u2 - 1) / u2);
internal static short CeilingExact(short u1, short u2) => (short)((u1 + u2 - 1) / u2);
internal static byte CeilingExact(byte u1, byte u2) => (byte)((u1 + u2 - 1) / u2);
internal static sbyte CeilingExact(sbyte u1, sbyte u2) => (sbyte)((u1 + u2 - 1) / u2);
/// <summary>
/// ZigZag encodes a signed integer and maps it to a unsigned integer
/// </summary>
/// <param name="value">The signed integer to encode</param>
/// <returns>A ZigZag encoded version of the integer</returns>
public static ulong ZigZagEncode(long value) => (ulong)((value >> 63) ^ (value << 1));
/// <summary>
/// Decides a ZigZag encoded integer back to a signed integer
/// </summary>
/// <param name="value">The unsigned integer</param>
/// <returns>The signed version of the integer</returns>
public static long ZigZagDecode(ulong value) => (((long)(value >> 1) & 0x7FFFFFFFFFFFFFFFL) ^ ((long)(value << 63) >> 63));
/// <summary>
/// Gets the output size in bytes after VarInting a unsigned integer
/// </summary>
/// <param name="value">The unsigned integer whose length to get</param>
/// <returns>The amount of bytes</returns>
public static int VarIntSize(ulong value) =>
value <= 240 ? 1 :
value <= 2287 ? 2 :
value <= 67823 ? 3 :
value <= 16777215 ? 4 :
value <= 4294967295 ? 5 :
value <= 1099511627775 ? 6 :
value <= 281474976710655 ? 7 :
value <= 72057594037927935 ? 8 :
9;
internal static long Div8Ceil(ulong value) => (long)((value >> 3) + ((value & 1UL) | ((value >> 1) & 1UL) | ((value >> 2) & 1UL)));
}
}

View file

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

View file

@ -0,0 +1,38 @@
namespace MLAPI.Serialization
{
/// <summary>
/// AutoBitWritable implements INetworkSerializable and automatically serializes fields using reflection
/// </summary>
public abstract class AutoNetworkSerializable : INetworkSerializable
{
private void Write(NetworkWriter writer)
{
var fields = SerializationManager.GetFieldsForType(GetType());
for (int i = 0; i < fields.Length; i++)
{
writer.WriteObjectPacked(fields[i].GetValue(this));
}
}
private void Read(NetworkReader reader)
{
var fields = SerializationManager.GetFieldsForType(GetType());
for (int i = 0; i < fields.Length; i++)
{
fields[i].SetValue(this, reader.ReadObjectPacked(fields[i].FieldType));
}
}
public void NetworkSerialize(NetworkSerializer serializer)
{
if (serializer.IsReading)
{
Read(serializer.Reader);
}
else
{
Write(serializer.Writer);
}
}
}
}

View file

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

View file

@ -0,0 +1,7 @@
namespace MLAPI.Serialization
{
public interface INetworkSerializable
{
void NetworkSerialize(NetworkSerializer serializer);
}
}

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0a4a44dfad228154db7945948c115efa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
namespace MLAPI.Serialization
{
[StructLayout(LayoutKind.Explicit)]
internal struct ByteBool
{
[FieldOffset(0)]
public bool BoolValue;
[FieldOffset(0)]
public byte ByteValue;
public byte Collapse() =>
ByteValue = (byte)((
// Collapse all bits to position 1 and reassign as bit
(ByteValue >> 7) |
(ByteValue >> 6) |
(ByteValue >> 5) |
(ByteValue >> 4) |
(ByteValue >> 3) |
(ByteValue >> 2) |
(ByteValue >> 1) |
ByteValue
) & 1);
public byte Collapse(bool b)
{
BoolValue = b;
return Collapse();
}
}
}

View file

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

View file

@ -0,0 +1,26 @@
using System.Runtime.InteropServices;
namespace MLAPI.Serialization
{
/// <summary>
/// A struct with a explicit memory layout. The struct has 4 fields. float,uint,double and ulong.
/// Every field has the same starting point in memory. If you insert a float value, it can be extracted as a uint.
/// This is to allow for lockless and garbage free conversion from float to uint and double to ulong.
/// This allows for VarInt encoding and other integer encodings.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
internal struct UIntFloat
{
[FieldOffset(0)]
public float FloatValue;
[FieldOffset(0)]
public uint UIntValue;
[FieldOffset(0)]
public double DoubleValue;
[FieldOffset(0)]
public ulong ULongValue;
}
}

View file

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

View file

@ -0,0 +1,512 @@
using System;
using System.IO;
using static MLAPI.Serialization.Arithmetic;
namespace MLAPI.Serialization
{
/// <summary>
/// A buffer that can be used at the bit level
/// </summary>
public class NetworkBuffer : Stream
{
private const int k_InitialCapacity = 16;
private const float k_InitialGrowthFactor = 2.0f;
private byte[] m_Target;
/// <summary>
/// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data.
/// </summary>
/// <param name="capacity">Initial capacity of buffer in bytes.</param>
/// <param name="growthFactor">Factor by which buffer should grow when necessary.</param>
public NetworkBuffer(int capacity, float growthFactor)
{
m_Target = new byte[capacity];
GrowthFactor = growthFactor;
Resizable = true;
}
/// <summary>
/// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data.
/// </summary>
/// <param name="growthFactor">Factor by which buffer should grow when necessary.</param>
public NetworkBuffer(float growthFactor) : this(k_InitialCapacity, growthFactor) { }
/// <summary>
/// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data.
/// </summary>
/// <param name="capacity"></param>
public NetworkBuffer(int capacity) : this(capacity, k_InitialGrowthFactor) { }
/// <summary>
/// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data.
/// </summary>
public NetworkBuffer() : this(k_InitialCapacity, k_InitialGrowthFactor) { }
/// <summary>
/// A buffer that supports writing data smaller than a single byte. This buffer also has a built-in compression algorithm that can (optionally) be used to write compressed data.
/// NOTE: when using a pre-allocated buffer, the buffer will not grow!
/// </summary>
/// <param name="target">Pre-allocated buffer to write to</param>
public NetworkBuffer(byte[] target)
{
m_Target = target;
Resizable = false;
BitLength = (ulong)(target.Length << 3);
}
internal void SetTarget(byte[] target)
{
m_Target = target;
BitLength = (ulong)(target.Length << 3);
Position = 0;
}
/// <summary>
/// Whether or not the buffer will grow the buffer to accomodate more data.
/// </summary>
public bool Resizable { get; }
private float m_GrowthFactor;
/// <summary>
/// Factor by which buffer should grow when necessary.
/// </summary>
public float GrowthFactor { set { m_GrowthFactor = value <= 1 ? 1.5f : value; } get { return m_GrowthFactor; } }
/// <summary>
/// Whether or not buffeer supports reading. (Always true)
/// </summary>
public override bool CanRead => true;
/// <summary>
/// Whether or not or there is any data to be read from the buffer.
/// </summary>
public bool HasDataToRead => Position < Length;
/// <summary>
/// Whether or not seeking is supported by this buffer. (Always true)
/// </summary>
public override bool CanSeek => true;
/// <summary>
/// Whether or not this buffer can accept new data. NOTE: this will return true even if only fewer than 8 bits can be written!
/// </summary>
public override bool CanWrite => !BitAligned || Position < m_Target.LongLength || Resizable;
/// <summary>
/// Current buffer size. The buffer will not be resized (if possible) until Position is equal to Capacity and an attempt to write data is made.
/// </summary>
public long Capacity
{
get => m_Target.LongLength; // Optimized CeilingExact
set
{
if (value < Length) throw new ArgumentOutOfRangeException("New capcity too small!");
SetCapacity(value);
}
}
/// <summary>
/// The current length of data considered to be "written" to the buffer.
/// </summary>
public override long Length { get => Div8Ceil(BitLength); }
/// <summary>
/// The index that will be written to when any call to write data is made to this buffer.
/// </summary>
public override long Position { get => (long)(BitPosition >> 3); set => BitPosition = (ulong)value << 3; }
/// <summary>
/// Bit offset into the buffer that new data will be written to.
/// </summary>
public ulong BitPosition { get; set; }
/// <summary>
/// Length of data (in bits) that is considered to be written to the buffer.
/// </summary>
public ulong BitLength { get; private set; }
/// <summary>
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
/// </summary>
public bool BitAligned { get => (BitPosition & 7) == 0; }
/// <summary>
/// Flush buffer. This does nothing since data is written directly to a byte buffer.
/// </summary>
public override void Flush() { } // NOP
/// <summary>
/// Grow buffer if possible. According to Max(bufferLength, 1) * growthFactor^Ceil(newContent/Max(bufferLength, 1))
/// </summary>
/// <param name="newContent">How many new values need to be accomodated (at least).</param>
//private void Grow(long newContent) => SetCapacity(Math.Max(target.LongLength, 1) * (long)Math.Pow(GrowthFactor, CeilingExact(newContent, Math.Max(target.LongLength, 1))));
/*
private void Grow(long newContent)
{
float grow = newContent / 64;
if (((long)grow) != grow) grow += 1;
SetCapacity((Capacity + 64) * (long)grow);
}
*/
private void Grow(long newContent)
{
long value = newContent + Capacity;
long newCapacity = value;
if (newCapacity < 256) newCapacity = 256;
// We are ok with this overflowing since the next statement will deal
// with the cases where _capacity*2 overflows.
if (newCapacity < Capacity * 2) newCapacity = Capacity * 2;
// We want to expand the array up to Array.MaxArrayLengthOneDimensional
// And we want to give the user the value that they asked for
if ((uint)(Capacity * 2) > int.MaxValue) newCapacity = value > int.MaxValue ? value : int.MaxValue;
SetCapacity(newCapacity);
}
/// <summary>
/// Read a misaligned byte. WARNING: If the current BitPosition <strong>isn't</strong> byte misaligned,
/// avoid using this method as it <strong>may</strong> cause an IndexOutOfBoundsException in such a case.
/// </summary>
/// <returns>A byte extracted from up to two separate buffer indices.</returns>
private byte ReadByteMisaligned()
{
int mod = (int)(BitPosition & 7);
return (byte)((m_Target[(int)Position] >> mod) | (m_Target[(int)(BitPosition += 8) >> 3] << (8 - mod)));
}
/// <summary>
/// Read an aligned byte from the buffer. It's recommended to not use this when the BitPosition is byte-misaligned.
/// </summary>
/// <returns>The byte stored at the current Position index</returns>
private byte ReadByteAligned() => m_Target[Position++];
/// <summary>
/// Read a byte as a byte. This is just for internal use so as to minimize casts (cuz they ugly af).
/// </summary>
/// <returns></returns>
internal byte ReadByteInternal() => BitAligned ? ReadByteAligned() : ReadByteMisaligned();
/// <summary>
/// Read a byte from the buffer. This takes into account possible byte misalignment.
/// </summary>
/// <returns>A byte from the buffer or, if a byte can't be read, -1.</returns>
public override int ReadByte() => HasDataToRead ? BitAligned ? ReadByteAligned() : ReadByteMisaligned() : -1;
/// <summary>
/// Peeks a byte without advancing the position
/// </summary>
/// <returns>The peeked byte</returns>
public int PeekByte() =>
HasDataToRead
? BitAligned ? m_Target[Position] :
(byte)((m_Target[(int)Position] >> (int)(BitPosition & 7)) | (m_Target[(int)(BitPosition + 8) >> 3] << (8 - (int)(BitPosition & 7))))
: -1;
/// <summary>
/// Read a single bit from the buffer.
/// </summary>
/// <returns>A bit in bool format. (True represents 1, False represents 0)</returns>
public bool ReadBit() => (m_Target[Position] & (1 << (int)(BitPosition++ & 7))) != 0;
/// <summary>
/// Read a subset of the buffer buffer and write the contents to the supplied buffer.
/// </summary>
/// <param name="buffer">Buffer to copy data to.</param>
/// <param name="offset">Offset into the buffer to write data to.</param>
/// <param name="count">How many bytes to attempt to read.</param>
/// <returns>Amount of bytes read.</returns>
public override int Read(byte[] buffer, int offset, int count)
{
int tLen = Math.Min(count, (int)(m_Target.LongLength - Position) - ((BitPosition & 7) == 0 ? 0 : 1));
for (int i = 0; i < tLen; ++i) buffer[offset + i] = ReadByteInternal();
return tLen;
}
/// <summary>
/// Set position in buffer to read from/write to.
/// </summary>
/// <param name="offset">Offset from position origin.</param>
/// <param name="origin">How to calculate offset.</param>
/// <returns>The new position in the buffer that data will be written to.</returns>
public override long Seek(long offset, SeekOrigin origin)
{
return (long)((
BitPosition =
(
origin == SeekOrigin.Current ? offset > 0 ? Math.Min(BitPosition + ((ulong)offset << 3), (ulong)m_Target.Length << 3) :
(offset ^ SIGN_BIT_64) > Position ? 0UL :
BitPosition - (ulong)((offset ^ SIGN_BIT_64) << 3) :
origin == SeekOrigin.Begin ? (ulong)Math.Max(0, offset) << 3 :
(ulong)Math.Max(m_Target.Length - offset, 0) << 3
)) >> 3) + (long)((BitPosition & 1UL) | ((BitPosition >> 1) & 1UL) | ((BitPosition >> 2) & 1UL));
}
/// <summary>
/// Set the capacity of the internal buffer.
/// </summary>
/// <param name="value">New capacity of the buffer</param>
private void SetCapacity(long value)
{
if (!Resizable) throw new NotSupportedException("Can't resize non resizable buffer"); // Don't do shit because fuck you (comment by @GabrielTofvesson -TwoTen)
byte[] newTarg = new byte[value];
long len = Math.Min(value, m_Target.LongLength);
Buffer.BlockCopy(m_Target, 0, newTarg, 0, (int)len);
if (value < m_Target.LongLength) BitPosition = (ulong)value << 3;
m_Target = newTarg;
}
/// <summary>
/// Set length of data considered to be "written" to the buffer.
/// </summary>
/// <param name="value">New length of the written data.</param>
public override void SetLength(long value)
{
if (value < 0) throw new IndexOutOfRangeException("Cannot set a negative length!");
if (value > Capacity) Grow(value - Capacity);
BitLength = (ulong)value << 3;
BitPosition = Math.Min((ulong)value << 3, BitPosition);
}
/// <summary>
/// Write data from the given buffer to the internal buffer.
/// </summary>
/// <param name="buffer">Buffer to write from.</param>
/// <param name="offset">Offset in given buffer to start reading from.</param>
/// <param name="count">Amount of bytes to read copy from given buffer to buffer.</param>
public override void Write(byte[] buffer, int offset, int count)
{
// Check bit alignment. If misaligned, each byte written has to be misaligned
if (BitAligned)
{
if (Position + count >= m_Target.Length) Grow(count);
Buffer.BlockCopy(buffer, offset, m_Target, (int)Position, count);
Position += count;
}
else
{
if (Position + count + 1 >= m_Target.Length) Grow(count);
for (int i = 0; i < count; ++i) WriteMisaligned(buffer[offset + i]);
}
if (BitPosition > BitLength) BitLength = BitPosition;
}
/// <summary>
/// Write byte value to the internal buffer.
/// </summary>
/// <param name="value">The byte value to write.</param>
public override void WriteByte(byte value)
{
// Check bit alignment. If misaligned, each byte written has to be misaligned
if (BitAligned)
{
if (Position + 1 >= m_Target.Length) Grow(1);
m_Target[Position] = value;
Position += 1;
}
else
{
if (Position + 1 + 1 >= m_Target.Length) Grow(1);
WriteMisaligned(value);
}
if (BitPosition > BitLength) BitLength = BitPosition;
}
/// <summary>
/// Write a misaligned byte. NOTE: Using this when the bit position isn't byte-misaligned may cause an IndexOutOfBoundsException! This does not update the current Length of the buffer.
/// </summary>
/// <param name="value">Value to write</param>
private void WriteMisaligned(byte value)
{
int off = (int)(BitPosition & 7);
int shift1 = 8 - off;
m_Target[Position + 1] = (byte)((m_Target[Position + 1] & (0xFF << off)) | (value >> shift1));
m_Target[Position] = (byte)((m_Target[Position] & (0xFF >> shift1)) | (value << off));
BitPosition += 8;
}
/// <summary>
/// Write a byte (in an int format) to the buffer. This does not update the current Length of the buffer.
/// </summary>
/// <param name="value">Value to write</param>
private void WriteIntByte(int value) => WriteBytePrivate((byte)value);
/// <summary>
/// Write a byte (in a ulong format) to the buffer. This does not update the current Length of the buffer.
/// </summary>
/// <param name="byteValue">Value to write</param>
private void WriteULongByte(ulong byteValue) => WriteBytePrivate((byte)byteValue);
/// <summary>
/// Write a byte to the buffer. This does not update the current Length of the buffer.
/// </summary>
/// <param name="value">Value to write</param>
private void WriteBytePrivate(byte value)
{
if (Div8Ceil(BitPosition) == m_Target.LongLength) Grow(1);
if (BitAligned)
{
m_Target[Position] = value;
BitPosition += 8;
}
else WriteMisaligned(value);
UpdateLength();
}
/// <summary>
/// Write data from the given buffer to the internal buffer.
/// </summary>
/// <param name="buffer">Buffer to write from.</param>
public void Write(byte[] buffer) => Write(buffer, 0, buffer.Length);
/// <summary>
/// Write a single bit to the buffer
/// </summary>
/// <param name="bit">Value of the bit. True represents 1, False represents 0</param>
public void WriteBit(bool bit)
{
if (BitAligned && Position == m_Target.Length) Grow(1);
int offset = (int)(BitPosition & 7);
long pos = Position;
++BitPosition;
m_Target[pos] = (byte)(bit ? (m_Target[pos] & ~(1 << offset)) | (1 << offset) : (m_Target[pos] & ~(1 << offset)));
UpdateLength();
}
/// <summary>
/// Copy data from another stream
/// </summary>
/// <param name="s">Stream to copy from</param>
/// <param name="count">How many bytes to read. Set to value less than one to read until ReadByte returns -1</param>
public void CopyFrom(Stream s, int count = -1)
{
if (s is NetworkBuffer b) Write(b.m_Target, 0, count < 0 ? (int)b.Length : count);
else
{
long currentPosition = s.Position;
s.Position = 0;
int read;
bool readToEnd = count < 0;
while ((readToEnd || count-- > 0) && (read = s.ReadByte()) != -1)
WriteIntByte(read);
UpdateLength();
s.Position = currentPosition;
}
}
/// <summary>
/// Copies internal buffer to stream
/// </summary>
/// <param name="stream">The stream to copy to</param>
/// <param name="count">The maximum amount of bytes to copy. Set to value less than one to copy the full length</param>
#if !NET35
public new void CopyTo(Stream stream, int count = -1)
#else
public void CopyTo(Stream stream, int count = -1)
#endif
{
stream.Write(m_Target, 0, count < 0 ? (int)Length : count);
}
/// <summary>
/// Copies urnead bytes from the source stream
/// </summary>
/// <param name="s">The source stream to copy from</param>
/// <param name="count">The max amount of bytes to copy</param>
public void CopyUnreadFrom(Stream s, int count = -1)
{
long currentPosition = s.Position;
int read;
bool readToEnd = count < 0;
while ((readToEnd || count-- > 0) && (read = s.ReadByte()) != -1) WriteIntByte(read);
UpdateLength();
s.Position = currentPosition;
}
// TODO: Implement CopyFrom() for NetworkBuffer with bitCount parameter
/// <summary>
/// Copys the bits from the provided NetworkBuffer
/// </summary>
/// <param name="buffer">The buffer to copy from</param>
/// <param name="dataCount">The amount of data evel</param>
/// <param name="copyBits">Whether or not to copy at the bit level rather than the byte level</param>
public void CopyFrom(NetworkBuffer buffer, int dataCount, bool copyBits)
{
if (!copyBits)
{
CopyFrom(buffer, dataCount);
}
else
{
ulong count = dataCount < 0 ? buffer.BitLength : (ulong)dataCount;
if (buffer.BitLength < count) throw new IndexOutOfRangeException("Attempted to read more data than is available");
Write(buffer.GetBuffer(), 0, (int)(count >> 3));
for (int i = (int)(count & 7); i >= 0; --i) WriteBit(buffer.ReadBit());
}
}
/// <summary>
/// Update length of data considered to be "written" to the buffer.
/// </summary>
private void UpdateLength()
{
if (BitPosition > BitLength) BitLength = BitPosition;
}
/// <summary>
/// Get the internal buffer being written to by this buffer.
/// </summary>
/// <returns></returns>
public byte[] GetBuffer() => m_Target;
/// <summary>
/// Creates a copy of the internal buffer. This only contains the used bytes
/// </summary>
/// <returns>A copy of used bytes in the internal buffer</returns>
public byte[] ToArray()
{
byte[] copy = new byte[Length];
Buffer.BlockCopy(m_Target, 0, copy, 0, (int)Length);
return copy;
}
/// <summary>
/// Writes zeros to fill the last byte
/// </summary>
public void PadBuffer()
{
while (!BitAligned)
{
WriteBit(false);
}
}
/// <summary>
/// Reads zeros until the the buffer is byte aligned
/// </summary>
public void SkipPadBits()
{
while (!BitAligned)
{
ReadBit();
}
}
/// <summary>
/// Returns hex encoded version of the buffer
/// </summary>
/// <returns>Hex encoded version of the buffer</returns>
public override string ToString() => BitConverter.ToString(m_Target, 0, (int)Length);
}
}

View file

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

View file

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

View file

@ -0,0 +1,790 @@
using System;
using UnityEngine;
namespace MLAPI.Serialization
{
public sealed class NetworkSerializer
{
private readonly NetworkReader m_Reader;
private readonly NetworkWriter m_Writer;
public NetworkReader Reader => m_Reader;
public NetworkWriter Writer => m_Writer;
public bool IsReading { get; }
public NetworkSerializer(NetworkReader reader)
{
m_Reader = reader;
IsReading = true;
}
public NetworkSerializer(NetworkWriter writer)
{
m_Writer = writer;
IsReading = false;
}
public void Serialize(ref bool value)
{
if (IsReading) value = m_Reader.ReadBool();
else m_Writer.WriteBool(value);
}
public void Serialize(ref char value)
{
if (IsReading) value = m_Reader.ReadCharPacked();
else m_Writer.WriteCharPacked(value);
}
public void Serialize(ref sbyte value)
{
if (IsReading) value = m_Reader.ReadSByte();
else m_Writer.WriteSByte(value);
}
public void Serialize(ref byte value)
{
if (IsReading) value = m_Reader.ReadByteDirect();
else m_Writer.WriteByte(value);
}
public void Serialize(ref short value)
{
if (IsReading) value = m_Reader.ReadInt16Packed();
else m_Writer.WriteInt16Packed(value);
}
public void Serialize(ref ushort value)
{
if (IsReading) value = m_Reader.ReadUInt16Packed();
else m_Writer.WriteUInt16Packed(value);
}
public void Serialize(ref int value)
{
if (IsReading) value = m_Reader.ReadInt32Packed();
else m_Writer.WriteInt32Packed(value);
}
public void Serialize(ref uint value)
{
if (IsReading) value = m_Reader.ReadUInt32Packed();
else m_Writer.WriteUInt32Packed(value);
}
public void Serialize(ref long value)
{
if (IsReading) value = m_Reader.ReadInt64Packed();
else m_Writer.WriteInt64Packed(value);
}
public void Serialize(ref ulong value)
{
if (IsReading) value = m_Reader.ReadUInt64Packed();
else m_Writer.WriteUInt64Packed(value);
}
public void Serialize(ref float value)
{
if (IsReading) value = m_Reader.ReadSinglePacked();
else m_Writer.WriteSinglePacked(value);
}
public void Serialize(ref double value)
{
if (IsReading) value = m_Reader.ReadDoublePacked();
else m_Writer.WriteDoublePacked(value);
}
public void Serialize(ref string value)
{
if (IsReading)
{
var isSet = m_Reader.ReadBool();
value = isSet ? m_Reader.ReadStringPacked() : null;
}
else
{
var isSet = value != null;
m_Writer.WriteBool(isSet);
if (isSet)
{
m_Writer.WriteStringPacked(value);
}
}
}
public void Serialize(ref Color value)
{
if (IsReading) value = m_Reader.ReadColorPacked();
else m_Writer.WriteColorPacked(value);
}
public void Serialize(ref Color32 value)
{
if (IsReading) value = m_Reader.ReadColor32();
else m_Writer.WriteColor32(value);
}
public void Serialize(ref Vector2 value)
{
if (IsReading) value = m_Reader.ReadVector2Packed();
else m_Writer.WriteVector2Packed(value);
}
public void Serialize(ref Vector3 value)
{
if (IsReading) value = m_Reader.ReadVector3Packed();
else m_Writer.WriteVector3Packed(value);
}
public void Serialize(ref Vector4 value)
{
if (IsReading) value = m_Reader.ReadVector4Packed();
else m_Writer.WriteVector4Packed(value);
}
public void Serialize(ref Quaternion value)
{
if (IsReading) value = m_Reader.ReadRotationPacked();
else m_Writer.WriteRotationPacked(value);
}
public void Serialize(ref Ray value)
{
if (IsReading) value = m_Reader.ReadRayPacked();
else m_Writer.WriteRayPacked(value);
}
public void Serialize(ref Ray2D value)
{
if (IsReading) value = m_Reader.ReadRay2DPacked();
else m_Writer.WriteRay2DPacked(value);
}
public unsafe void Serialize<TEnum>(ref TEnum value) where TEnum : unmanaged, Enum
{
if (sizeof(TEnum) == sizeof(int))
{
if (IsReading)
{
int intValue = m_Reader.ReadInt32Packed();
value = *(TEnum*)&intValue;
}
else
{
TEnum enumValue = value;
m_Writer.WriteInt32Packed(*(int*)&enumValue);
}
}
else if (sizeof(TEnum) == sizeof(byte))
{
if (IsReading)
{
byte intValue = m_Reader.ReadByteDirect();
value = *(TEnum*)&intValue;
}
else
{
TEnum enumValue = value;
m_Writer.WriteByte(*(byte*)&enumValue);
}
}
else if (sizeof(TEnum) == sizeof(short))
{
if (IsReading)
{
short intValue = m_Reader.ReadInt16Packed();
value = *(TEnum*)&intValue;
}
else
{
TEnum enumValue = value;
m_Writer.WriteInt16Packed(*(short*)&enumValue);
}
}
else if (sizeof(TEnum) == sizeof(long))
{
if (IsReading)
{
long intValue = m_Reader.ReadInt64Packed();
value = *(TEnum*)&intValue;
}
else
{
TEnum enumValue = value;
m_Writer.WriteInt64Packed(*(long*)&enumValue);
}
}
else if (IsReading)
{
value = default;
}
}
public void Serialize(ref bool[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new bool[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadBool();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteBool(array[i]);
}
}
}
public void Serialize(ref char[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new char[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadCharPacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteCharPacked(array[i]);
}
}
}
public void Serialize(ref sbyte[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new sbyte[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadSByte();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteSByte(array[i]);
}
}
}
public void Serialize(ref byte[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new byte[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadByteDirect();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteByte(array[i]);
}
}
}
public void Serialize(ref short[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new short[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadInt16Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteInt16Packed(array[i]);
}
}
}
public void Serialize(ref ushort[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new ushort[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadUInt16Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteUInt16Packed(array[i]);
}
}
}
public void Serialize(ref int[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new int[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadInt32Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteInt32Packed(array[i]);
}
}
}
public void Serialize(ref uint[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new uint[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadUInt32Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteUInt32Packed(array[i]);
}
}
}
public void Serialize(ref long[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new long[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadInt64Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteInt64Packed(array[i]);
}
}
}
public void Serialize(ref ulong[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new ulong[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadUInt64Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteUInt64Packed(array[i]);
}
}
}
public void Serialize(ref float[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new float[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadSinglePacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteSinglePacked(array[i]);
}
}
}
public void Serialize(ref double[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new double[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadDoublePacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteDoublePacked(array[i]);
}
}
}
public void Serialize(ref string[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new string[length] : null;
for (var i = 0; i < length; ++i)
{
var isSet = m_Reader.ReadBool();
array[i] = isSet ? m_Reader.ReadStringPacked() : null;
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
var isSet = array[i] != null;
m_Writer.WriteBool(isSet);
if (isSet)
{
m_Writer.WriteStringPacked(array[i]);
}
}
}
}
public void Serialize(ref Color[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Color[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadColorPacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteColorPacked(array[i]);
}
}
}
public void Serialize(ref Color32[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Color32[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadColor32();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteColor32(array[i]);
}
}
}
public void Serialize(ref Vector2[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Vector2[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadVector2Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteVector2Packed(array[i]);
}
}
}
public void Serialize(ref Vector3[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Vector3[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadVector3Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteVector3Packed(array[i]);
}
}
}
public void Serialize(ref Vector4[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Vector4[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadVector4Packed();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteVector4Packed(array[i]);
}
}
}
public void Serialize(ref Quaternion[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Quaternion[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadRotationPacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteRotationPacked(array[i]);
}
}
}
public void Serialize(ref Ray[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Ray[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadRayPacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteRayPacked(array[i]);
}
}
}
public void Serialize(ref Ray2D[] array)
{
if (IsReading)
{
var length = m_Reader.ReadInt32Packed();
array = length > -1 ? new Ray2D[length] : null;
for (var i = 0; i < length; ++i)
{
array[i] = m_Reader.ReadRay2DPacked();
}
}
else
{
var length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
for (var i = 0; i < length; ++i)
{
m_Writer.WriteRay2DPacked(array[i]);
}
}
}
public unsafe void Serialize<TEnum>(ref TEnum[] array) where TEnum : unmanaged, Enum
{
int length;
if (IsReading)
{
length = m_Reader.ReadInt32Packed();
array = length > -1 ? new TEnum[length] : null;
}
else
{
length = array?.Length ?? -1;
m_Writer.WriteInt32Packed(length);
}
if (sizeof(TEnum) == sizeof(int))
{
if (IsReading)
{
for (var i = 0; i < length; ++i)
{
int intValue = m_Reader.ReadInt32Packed();
array[i] = *(TEnum*)&intValue;
}
}
else
{
for (var i = 0; i < length; ++i)
{
TEnum enumValue = array[i];
m_Writer.WriteInt32Packed(*(int*)&enumValue);
}
}
}
else if (sizeof(TEnum) == sizeof(byte))
{
if (IsReading)
{
for (var i = 0; i < length; ++i)
{
byte intValue = m_Reader.ReadByteDirect();
array[i] = *(TEnum*)&intValue;
}
}
else
{
for (var i = 0; i < length; ++i)
{
TEnum enumValue = array[i];
m_Writer.WriteByte(*(byte*)&enumValue);
}
}
}
else if (sizeof(TEnum) == sizeof(short))
{
if (IsReading)
{
for (var i = 0; i < length; ++i)
{
short intValue = m_Reader.ReadInt16Packed();
array[i] = *(TEnum*)&intValue;
}
}
else
{
for (var i = 0; i < length; ++i)
{
TEnum enumValue = array[i];
m_Writer.WriteInt16Packed(*(short*)&enumValue);
}
}
}
else if (sizeof(TEnum) == sizeof(long))
{
if (IsReading)
{
for (var i = 0; i < length; ++i)
{
long intValue = m_Reader.ReadInt64Packed();
array[i] = *(TEnum*)&intValue;
}
}
else
{
for (var i = 0; i < length; ++i)
{
TEnum enumValue = array[i];
m_Writer.WriteInt64Packed(*(long*)&enumValue);
}
}
}
else if (IsReading)
{
array = default;
}
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a40752eb876d6a64994fdd5eb5650fb7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using MLAPI.Logging;
namespace MLAPI.Serialization.Pooled
{
/// <summary>
/// Static class containing PooledNetworkBuffers
/// </summary>
public static class NetworkBufferPool
{
private static uint s_CreatedBuffers = 0;
private static Queue<WeakReference> s_OverflowBuffers = new Queue<WeakReference>();
private static Queue<PooledNetworkBuffer> s_Buffers = new Queue<PooledNetworkBuffer>();
private const uint k_MaxBitPoolBuffers = 1024;
private const uint k_MaxCreatedDelta = 768;
/// <summary>
/// Retrieves an expandable PooledNetworkBuffer from the pool
/// </summary>
/// <returns>An expandable PooledNetworkBuffer</returns>
public static PooledNetworkBuffer GetBuffer()
{
if (s_Buffers.Count == 0)
{
if (s_OverflowBuffers.Count > 0)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"Retrieving {nameof(PooledNetworkBuffer)} from overflow pool. Recent burst?");
}
object weakBuffer = null;
while (s_OverflowBuffers.Count > 0 && ((weakBuffer = s_OverflowBuffers.Dequeue().Target) == null)) ;
if (weakBuffer != null)
{
PooledNetworkBuffer strongBuffer = (PooledNetworkBuffer)weakBuffer;
strongBuffer.SetLength(0);
strongBuffer.Position = 0;
return strongBuffer;
}
}
if (s_CreatedBuffers == k_MaxBitPoolBuffers)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) NetworkLog.LogWarning($"{k_MaxBitPoolBuffers} buffers have been created. Did you forget to dispose?");
}
else if (s_CreatedBuffers < k_MaxBitPoolBuffers) s_CreatedBuffers++;
return new PooledNetworkBuffer();
}
PooledNetworkBuffer buffer = s_Buffers.Dequeue();
buffer.SetLength(0);
buffer.Position = 0;
return buffer;
}
/// <summary>
/// Puts a PooledNetworkBuffer back into the pool
/// </summary>
/// <param name="buffer">The buffer to put in the pool</param>
public static void PutBackInPool(PooledNetworkBuffer buffer)
{
if (s_Buffers.Count > k_MaxCreatedDelta)
{
// The user just created lots of buffers without returning them in between.
// Buffers are essentially byte array wrappers. This is valuable memory.
// Thus we put this buffer as a weak reference incase of another burst
// But still leave it to GC
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"Putting {nameof(PooledNetworkBuffer)} into overflow pool. Did you forget to dispose?");
}
s_OverflowBuffers.Enqueue(new WeakReference(buffer));
}
else
{
s_Buffers.Enqueue(buffer);
}
}
}
}

View file

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

View file

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.IO;
using MLAPI.Logging;
namespace MLAPI.Serialization.Pooled
{
/// <summary>
/// Static class containing PooledNetworkReaders
/// </summary>
public static class NetworkReaderPool
{
private static byte s_CreatedReaders = 0;
private static Queue<PooledNetworkReader> s_Readers = new Queue<PooledNetworkReader>();
/// <summary>
/// Retrieves a PooledNetworkReader
/// </summary>
/// <param name="stream">The stream the reader should read from</param>
/// <returns>A PooledNetworkReader</returns>
public static PooledNetworkReader GetReader(Stream stream)
{
if (s_Readers.Count == 0)
{
if (s_CreatedReaders == 254)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) NetworkLog.LogWarning("255 readers have been created. Did you forget to dispose?");
}
else if (s_CreatedReaders < 255) s_CreatedReaders++;
return new PooledNetworkReader(stream);
}
PooledNetworkReader reader = s_Readers.Dequeue();
reader.SetStream(stream);
return reader;
}
/// <summary>
/// Puts a PooledNetworkReader back into the pool
/// </summary>
/// <param name="reader">The reader to put in the pool</param>
public static void PutBackInPool(PooledNetworkReader reader)
{
if (s_Readers.Count < 64) s_Readers.Enqueue(reader);
else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"{nameof(NetworkReaderPool)} already has 64 queued. Throwing to GC. Did you forget to dispose?");
}
}
}
}

View file

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

View file

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.IO;
using MLAPI.Logging;
namespace MLAPI.Serialization.Pooled
{
/// <summary>
/// Static class containing PooledNetworkWriters
/// </summary>
public static class NetworkWriterPool
{
private static byte s_CreatedWriters = 0;
private static Queue<PooledNetworkWriter> s_Writers = new Queue<PooledNetworkWriter>();
/// <summary>
/// Retrieves a PooledNetworkWriter
/// </summary>
/// <param name="stream">The stream the writer should write to</param>
/// <returns>A PooledNetworkWriter</returns>
public static PooledNetworkWriter GetWriter(Stream stream)
{
if (s_Writers.Count == 0)
{
if (s_CreatedWriters == 254)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) NetworkLog.LogWarning("255 writers have been created. Did you forget to dispose?");
}
else if (s_CreatedWriters < 255) s_CreatedWriters++;
return new PooledNetworkWriter(stream);
}
PooledNetworkWriter writer = s_Writers.Dequeue();
writer.SetStream(stream);
return writer;
}
/// <summary>
/// Puts a PooledNetworkWriter back into the pool
/// </summary>
/// <param name="writer">The writer to put in the pool</param>
public static void PutBackInPool(PooledNetworkWriter writer)
{
if (s_Writers.Count < 64) s_Writers.Enqueue(writer);
else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"{nameof(NetworkWriterPool)} already has 64 queued. Throwing to GC. Did you forget to dispose?");
}
}
}
}

View file

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

View file

@ -0,0 +1,37 @@
using System;
namespace MLAPI.Serialization.Pooled
{
/// <summary>
/// Disposable NetworkBuffer that returns back to the NetworkBufferPool when disposed
/// </summary>
public sealed class PooledNetworkBuffer : NetworkBuffer, IDisposable
{
private bool isDisposed = false;
internal PooledNetworkBuffer() { }
/// <summary>
/// Gets a PooledNetworkBuffer from the static NetworkBufferPool
/// </summary>
/// <returns>PooledNetworkBuffer</returns>
public static PooledNetworkBuffer Get()
{
var buffer = NetworkBufferPool.GetBuffer();
buffer.isDisposed = false;
return buffer;
}
/// <summary>
/// Returns the PooledNetworkBuffer into the static NetworkBufferPool
/// </summary>
public new void Dispose()
{
if (!isDisposed)
{
isDisposed = true;
NetworkBufferPool.PutBackInPool(this);
}
}
}
}

View file

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

View file

@ -0,0 +1,46 @@
using System;
using System.IO;
using UnityEngine;
namespace MLAPI.Serialization.Pooled
{
/// <summary>
/// Disposable NetworkReader that returns the Reader to the NetworkReaderPool when disposed
/// </summary>
public sealed class PooledNetworkReader : NetworkReader, IDisposable
{
private NetworkSerializer m_Serializer;
public NetworkSerializer Serializer => m_Serializer ?? (m_Serializer = new NetworkSerializer(this));
private bool m_IsDisposed = false;
internal PooledNetworkReader(Stream stream) : base(stream) { }
/// <summary>
/// Gets a PooledNetworkReader from the static NetworkReaderPool
/// </summary>
/// <returns>PooledNetworkReader</returns>
public static PooledNetworkReader Get(Stream stream)
{
var reader = NetworkReaderPool.GetReader(stream);
reader.m_IsDisposed = false;
return reader;
}
/// <summary>
/// Returns the PooledNetworkReader into the static NetworkReaderPool
/// </summary>
public void Dispose()
{
if (!m_IsDisposed)
{
m_IsDisposed = true;
NetworkReaderPool.PutBackInPool(this);
}
else
{
Debug.LogWarning("Disposing reader that thinks it is already disposed!");
}
}
}
}

View file

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

View file

@ -0,0 +1,46 @@
using System;
using System.IO;
using UnityEngine;
namespace MLAPI.Serialization.Pooled
{
/// <summary>
/// Disposable NetworkWriter that returns the Writer to the NetworkWriterPool when disposed
/// </summary>
public sealed class PooledNetworkWriter : NetworkWriter, IDisposable
{
private NetworkSerializer m_Serializer;
public NetworkSerializer Serializer => m_Serializer ?? (m_Serializer = new NetworkSerializer(this));
private bool m_IsDisposed = false;
internal PooledNetworkWriter(Stream stream) : base(stream) { }
/// <summary>
/// Gets a PooledNetworkWriter from the static NetworkWriterPool
/// </summary>
/// <returns>PooledNetworkWriter</returns>
public static PooledNetworkWriter Get(Stream stream)
{
var writer = NetworkWriterPool.GetWriter(stream);
writer.m_IsDisposed = false;
return writer;
}
/// <summary>
/// Returns the PooledNetworkWriter into the static NetworkWriterPool
/// </summary>
public void Dispose()
{
if (!m_IsDisposed)
{
m_IsDisposed = true;
NetworkWriterPool.PutBackInPool(this);
}
else
{
Debug.LogError("Writer is being disposed but thinks it is already disposed");
}
}
}
}

View file

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

View file

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using MLAPI.Reflection;
using UnityEngine;
namespace MLAPI.Serialization
{
/// <summary>
/// Helper class to manage the MLAPI serialization.
/// </summary>
public static class SerializationManager
{
private static Dictionary<Type, FieldInfo[]> s_FieldCache = new Dictionary<Type, FieldInfo[]>();
private static Dictionary<Type, BoxedSerializationDelegate> s_CachedExternalSerializers = new Dictionary<Type, BoxedSerializationDelegate>();
private static Dictionary<Type, BoxedDeserializationDelegate> s_CachedExternalDeserializers = new Dictionary<Type, BoxedDeserializationDelegate>();
/// <summary>
/// The delegate used when registering custom deserialization for a type.
/// </summary>
/// <param name="stream">The stream to read the data required to construct the type.</param>
/// <typeparam name="T">The type to deserialize.</typeparam>
public delegate T CustomDeserializationDelegate<T>(Stream stream);
/// <summary>
/// The delegate used when registering custom serialization for a type.
/// </summary>
/// <param name="stream">The stream to write data to that is required to reconstruct the type in the deserialization delegate.</param>
/// <param name="instance">The instance to serialize to the stream.</param>
/// <typeparam name="T">The type to serialize.</typeparam>
public delegate void CustomSerializationDelegate<T>(Stream stream, T instance);
// These two are what we use internally. They box the value.
private delegate void BoxedSerializationDelegate(Stream stream, object instance);
private delegate object BoxedDeserializationDelegate(Stream stream);
/// <summary>
/// Registers a custom serialization and deserialization pair for a object.
/// This is useful for writing objects that are behind the third party wall. Such as .NET types.
/// </summary>
/// <param name="onSerialize">The delegate to invoke to serialize the type.</param>
/// <param name="onDeserialize">The delegate to invoke to deserialize the type.</param>
/// <typeparam name="T">The type to register.</typeparam>
public static void RegisterSerializationHandlers<T>(CustomSerializationDelegate<T> onSerialize, CustomDeserializationDelegate<T> onDeserialize)
{
s_CachedExternalSerializers[typeof(T)] = (stream, instance) => onSerialize(stream, (T)instance);
s_CachedExternalDeserializers[typeof(T)] = stream => onDeserialize(stream);
}
/// <summary>
/// Removes a serialization handler that was registered previously for a specific type.
/// This will remove both the serialization and deserialization handler.
/// </summary>
/// <typeparam name="T">The type for the serialization handlers to remove.</typeparam>
/// <returns>Whether or not either the serialization or deserialization handlers for the type was removed.</returns>
public static bool RemoveSerializationHandlers<T>()
{
bool serializationRemoval = s_CachedExternalSerializers.Remove(typeof(T));
bool deserializationRemoval = s_CachedExternalDeserializers.Remove(typeof(T));
return serializationRemoval || deserializationRemoval;
}
internal static bool TrySerialize(Stream stream, object obj)
{
if (s_CachedExternalSerializers.ContainsKey(obj.GetType()))
{
s_CachedExternalSerializers[obj.GetType()](stream, obj);
return true;
}
return false;
}
internal static bool TryDeserialize(Stream stream, Type type, out object obj)
{
if (s_CachedExternalDeserializers.ContainsKey(type))
{
obj = s_CachedExternalDeserializers[type](stream);
return true;
}
obj = null;
return false;
}
internal static FieldInfo[] GetFieldsForType(Type type)
{
if (s_FieldCache.ContainsKey(type)) return s_FieldCache[type];
FieldInfo[] fields = type
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(x => (x.IsPublic || x.GetCustomAttributes(typeof(SerializeField), true).Length > 0) && IsTypeSupported(x.FieldType))
.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
s_FieldCache.Add(type, fields);
return fields;
}
private static HashSet<Type> s_SupportedTypes = new HashSet<Type>()
{
typeof(byte),
typeof(byte),
typeof(sbyte),
typeof(ushort),
typeof(short),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(string),
typeof(bool),
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Color),
typeof(Color32),
typeof(Ray),
typeof(Quaternion),
typeof(char),
typeof(GameObject),
typeof(NetworkObject),
typeof(NetworkBehaviour)
};
/// <summary>
/// Returns if a type is supported for serialization
/// </summary>
/// <param name="type">The type to check</param>
/// <returns>Whether or not the type is supported</returns>
public static bool IsTypeSupported(Type type)
{
return type.IsEnum || s_SupportedTypes.Contains(type) || type.HasInterface(typeof(INetworkSerializable)) ||
(s_CachedExternalSerializers.ContainsKey(type) && s_CachedExternalDeserializers.ContainsKey(type)) ||
(type.IsArray && type.HasElementType && IsTypeSupported(type.GetElementType()));
}
}
}

View file

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