diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs
new file mode 100644
index 0000000000..4feb4477da
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs
@@ -0,0 +1,126 @@
+using System.Runtime.CompilerServices;
+
+namespace Unity.Netcode
+{
+ public static class BitCounter
+ {
+ // Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest)
+ // we use the De Bruijn sequence to do this calculation
+ // See https://en.wikipedia.org/wiki/De_Bruijn_sequence and https://www.chessprogramming.org/De_Bruijn_Sequence
+ private const ulong k_DeBruijnMagic64 = 0x37E84A99DAE458F;
+ private const uint k_DeBruijnMagic32 = 0x06EB14F9;
+
+ // We're counting bytes, not bits, so these have all had the operation x/8 + 1 applied
+ private static readonly int[] k_DeBruijnTableBytes64 =
+ {
+ 0/8+1, 1/8+1, 17/8+1, 2/8+1, 18/8+1, 50/8+1, 3/8+1, 57/8+1,
+ 47/8+1, 19/8+1, 22/8+1, 51/8+1, 29/8+1, 4/8+1, 33/8+1, 58/8+1,
+ 15/8+1, 48/8+1, 20/8+1, 27/8+1, 25/8+1, 23/8+1, 52/8+1, 41/8+1,
+ 54/8+1, 30/8+1, 38/8+1, 5/8+1, 43/8+1, 34/8+1, 59/8+1, 8/8+1,
+ 63/8+1, 16/8+1, 49/8+1, 56/8+1, 46/8+1, 21/8+1, 28/8+1, 32/8+1,
+ 14/8+1, 26/8+1, 24/8+1, 40/8+1, 53/8+1, 37/8+1, 42/8+1, 7/8+1,
+ 62/8+1, 55/8+1, 45/8+1, 31/8+1, 13/8+1, 39/8+1, 36/8+1, 6/8+1,
+ 61/8+1, 44/8+1, 12/8+1, 35/8+1, 60/8+1, 11/8+1, 10/8+1, 9/8+1,
+ };
+
+ private static readonly int[] k_DeBruijnTableBytes32 =
+ {
+ 0/8+1, 1/8+1, 16/8+1, 2/8+1, 29/8+1, 17/8+1, 3/8+1, 22/8+1,
+ 30/8+1, 20/8+1, 18/8+1, 11/8+1, 13/8+1, 4/8+1, 7/8+1, 23/8+1,
+ 31/8+1, 15/8+1, 28/8+1, 21/8+1, 19/8+1, 10/8+1, 12/8+1, 6/8+1,
+ 14/8+1, 27/8+1, 9/8+1, 5/8+1, 26/8+1, 8/8+1, 25/8+1, 24/8+1,
+ };
+
+ // And here we're counting the number of set bits, not the position of the highest set,
+ // so these still have +1 applied - unfortunately 0 and 1 both return the same value.
+ private static readonly int[] k_DeBruijnTableBits64 =
+ {
+ 0+1, 1+1, 17+1, 2+1, 18+1, 50+1, 3+1, 57+1,
+ 47+1, 19+1, 22+1, 51+1, 29+1, 4+1, 33+1, 58+1,
+ 15+1, 48+1, 20+1, 27+1, 25+1, 23+1, 52+1, 41+1,
+ 54+1, 30+1, 38+1, 5+1, 43+1, 34+1, 59+1, 8+1,
+ 63+1, 16+1, 49+1, 56+1, 46+1, 21+1, 28+1, 32+1,
+ 14+1, 26+1, 24+1, 40+1, 53+1, 37+1, 42+1, 7+1,
+ 62+1, 55+1, 45+1, 31+1, 13+1, 39+1, 36+1, 6+1,
+ 61+1, 44+1, 12+1, 35+1, 60+1, 11+1, 10+1, 9+1,
+ };
+
+ private static readonly int[] k_DeBruijnTableBits32 =
+ {
+ 0+1, 1+1, 16+1, 2+1, 29+1, 17+1, 3+1, 22+1,
+ 30+1, 20+1, 18+1, 11+1, 13+1, 4+1, 7+1, 23+1,
+ 31+1, 15+1, 28+1, 21+1, 19+1, 10+1, 12+1, 6+1,
+ 14+1, 27+1, 9+1, 5+1, 26+1, 8+1, 25+1, 24+1,
+ };
+
+ ///
+ /// Get the minimum number of bytes required to represent the given value
+ ///
+ /// The value
+ /// The number of bytes required
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetUsedByteCount(uint value)
+ {
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value = value & ~(value >> 1);
+ return k_DeBruijnTableBytes32[value * k_DeBruijnMagic32 >> 27];
+ }
+
+ ///
+ /// Get the minimum number of bytes required to represent the given value
+ ///
+ /// The value
+ /// The number of bytes required
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetUsedByteCount(ulong value)
+ {
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value |= value >> 32;
+ value = value & ~(value >> 1);
+ return k_DeBruijnTableBytes64[value * k_DeBruijnMagic64 >> 58];
+ }
+
+ ///
+ /// Get the minimum number of bits required to represent the given value
+ ///
+ /// The value
+ /// The number of bits required
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetUsedBitCount(uint value)
+ {
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value = value & ~(value >> 1);
+ return k_DeBruijnTableBits32[value * k_DeBruijnMagic32 >> 27];
+ }
+
+ ///
+ /// Get the minimum number of bits required to represent the given value
+ ///
+ /// The value
+ /// The number of bits required
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetUsedBitCount(ulong value)
+ {
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value |= value >> 32;
+ value = value & ~(value >> 1);
+ return k_DeBruijnTableBits64[value * k_DeBruijnMagic64 >> 58];
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs.meta
new file mode 100644
index 0000000000..f9d96d15b6
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitCounter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6983de23935090341bf45d5564401b9d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs
new file mode 100644
index 0000000000..bb97d0c95b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Runtime.CompilerServices;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace Unity.Netcode
+{
+ ///
+ /// Helper class for doing bitwise reads for a FastBufferReader.
+ /// Ensures all bitwise reads end on proper byte alignment so FastBufferReader doesn't have to be concerned
+ /// with misaligned reads.
+ ///
+ public ref struct BitReader
+ {
+ private Ref m_Reader;
+ private readonly unsafe byte* m_BufferPointer;
+ private readonly int m_Position;
+ private int m_BitPosition;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ private int m_AllowedBitwiseReadMark;
+#endif
+
+ private const int k_BitsPerByte = 8;
+
+ ///
+ /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
+ ///
+ public bool BitAligned
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (m_BitPosition & 7) == 0;
+ }
+
+ internal unsafe BitReader(ref FastBufferReader reader)
+ {
+ m_Reader = new Ref(ref reader);
+
+ m_BufferPointer = m_Reader.Value.BufferPointer + m_Reader.Value.Position;
+ m_Position = m_Reader.Value.Position;
+ m_BitPosition = 0;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_AllowedBitwiseReadMark = (m_Reader.Value.AllowedReadMark - m_Position) * k_BitsPerByte;
+#endif
+ }
+
+ ///
+ /// Pads the read bit count to byte alignment and commits the read back to the reader
+ ///
+ public void Dispose()
+ {
+ var bytesWritten = m_BitPosition >> 3;
+ if (!BitAligned)
+ {
+ // Accounting for the partial read
+ ++bytesWritten;
+ }
+
+ m_Reader.Value.CommitBitwiseReads(bytesWritten);
+ }
+
+ ///
+ /// Verifies the requested bit count can be read from the buffer.
+ /// This exists as a separate method to allow multiple bit reads to be bounds checked with a single call.
+ /// If it returns false, you may not read, and in editor and development builds, attempting to do so will
+ /// throw an exception. In release builds, attempting to do so will read junk memory.
+ ///
+ /// Number of bits you want to read, in total
+ /// True if you can read, false if that would exceed buffer bounds
+ public bool TryBeginReadBits(uint bitCount)
+ {
+ var newBitPosition = m_BitPosition + bitCount;
+ var totalBytesWrittenInBitwiseContext = newBitPosition >> 3;
+ if ((newBitPosition & 7) != 0)
+ {
+ // Accounting for the partial read
+ ++totalBytesWrittenInBitwiseContext;
+ }
+
+ if (m_Reader.Value.PositionInternal + totalBytesWrittenInBitwiseContext > m_Reader.Value.LengthInternal)
+ {
+ return false;
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_AllowedBitwiseReadMark = (int)newBitPosition;
+#endif
+ return true;
+ }
+
+ ///
+ /// Read a certain amount of bits from the stream.
+ ///
+ /// Value to store bits into.
+ /// Amount of bits to read
+ public unsafe void ReadBits(out ulong value, uint bitCount)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (bitCount > 64)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
+ }
+
+ if (bitCount < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
+ }
+
+ int checkPos = (int)(m_BitPosition + bitCount);
+ if (checkPos > m_AllowedBitwiseReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginReadBits)}()");
+ }
+#endif
+ ulong val = 0;
+
+ int wholeBytes = (int)bitCount / k_BitsPerByte;
+ byte* asBytes = (byte*)&val;
+ if (BitAligned)
+ {
+ if (wholeBytes != 0)
+ {
+ ReadPartialValue(out val, wholeBytes);
+ }
+ }
+ else
+ {
+ for (var i = 0; i < wholeBytes; ++i)
+ {
+ ReadMisaligned(out asBytes[i]);
+ }
+ }
+
+ val |= (ulong)ReadByteBits((int)bitCount & 7) << ((int)bitCount & ~7);
+ value = val;
+ }
+
+ ///
+ /// Read bits from stream.
+ ///
+ /// Value to store bits into.
+ /// Amount of bits to read.
+ public void ReadBits(out byte value, uint bitCount)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ int checkPos = (int)(m_BitPosition + bitCount);
+ if (checkPos > m_AllowedBitwiseReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginReadBits)}()");
+ }
+#endif
+ value = ReadByteBits((int)bitCount);
+ }
+
+ ///
+ /// Read a single bit from the buffer
+ ///
+ /// Out value of the bit. True represents 1, False represents 0
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadBit(out bool bit)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ int checkPos = (m_BitPosition + 1);
+ if (checkPos > m_AllowedBitwiseReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginReadBits)}()");
+ }
+#endif
+
+ int offset = m_BitPosition & 7;
+ int pos = m_BitPosition >> 3;
+ bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
+ ++m_BitPosition;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged
+ {
+ var val = new T();
+ byte* ptr = ((byte*)&val) + offsetBytes;
+ byte* bufferPointer = m_BufferPointer + m_Position;
+ UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
+
+ m_BitPosition += bytesToRead * k_BitsPerByte;
+ value = val;
+ }
+
+ private byte ReadByteBits(int bitCount)
+ {
+ if (bitCount > 8)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 8 bits into an 8-bit value!");
+ }
+
+ if (bitCount < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
+ }
+
+ int result = 0;
+ var convert = new ByteBool();
+ for (int i = 0; i < bitCount; ++i)
+ {
+ ReadBit(out bool bit);
+ result |= convert.Collapse(bit) << i;
+ }
+
+ return (byte)result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe void ReadMisaligned(out byte value)
+ {
+ int off = m_BitPosition & 7;
+ int pos = m_BitPosition >> 3;
+ int shift1 = 8 - off;
+
+ value = (byte)((m_BufferPointer[pos] >> shift1) | (m_BufferPointer[(m_BitPosition += 8) >> 3] << shift1));
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs.meta
new file mode 100644
index 0000000000..9b0d159683
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 72e2d94a96ca96a4fb2921df9adc2fdf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs
new file mode 100644
index 0000000000..3895c506fe
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs
@@ -0,0 +1,211 @@
+using System;
+using System.Runtime.CompilerServices;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace Unity.Netcode
+{
+ ///
+ /// Helper class for doing bitwise writes for a FastBufferWriter.
+ /// Ensures all bitwise writes end on proper byte alignment so FastBufferWriter doesn't have to be concerned
+ /// with misaligned writes.
+ ///
+ public ref struct BitWriter
+ {
+ private Ref m_Writer;
+ private unsafe byte* m_BufferPointer;
+ private readonly int m_Position;
+ private int m_BitPosition;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ private int m_AllowedBitwiseWriteMark;
+#endif
+ private const int k_BitsPerByte = 8;
+
+ ///
+ /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
+ ///
+ public bool BitAligned
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (m_BitPosition & 7) == 0;
+ }
+
+ internal unsafe BitWriter(ref FastBufferWriter writer)
+ {
+ m_Writer = new Ref(ref writer);
+ m_BufferPointer = writer.BufferPointer + writer.PositionInternal;
+ m_Position = writer.PositionInternal;
+ m_BitPosition = 0;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_AllowedBitwiseWriteMark = (m_Writer.Value.AllowedWriteMark - m_Writer.Value.Position) * k_BitsPerByte;
+#endif
+ }
+
+ ///
+ /// Pads the written bit count to byte alignment and commits the write back to the writer
+ ///
+ public void Dispose()
+ {
+ var bytesWritten = m_BitPosition >> 3;
+ if (!BitAligned)
+ {
+ // Accounting for the partial write
+ ++bytesWritten;
+ }
+
+ m_Writer.Value.CommitBitwiseWrites(bytesWritten);
+ }
+
+ ///
+ /// Allows faster serialization by batching bounds checking.
+ /// When you know you will be writing multiple fields back-to-back and you know the total size,
+ /// you can call TryBeginWriteBits() once on the total size, and then follow it with calls to
+ /// WriteBit() or WriteBits().
+ ///
+ /// Bitwise write operations will throw OverflowException in editor and development builds if you
+ /// go past the point you've marked using TryBeginWriteBits(). In release builds, OverflowException will not be thrown
+ /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
+ /// operations in release builds. Instead, attempting to write past the marked position in release builds
+ /// will write to random memory and cause undefined behavior, likely including instability and crashes.
+ ///
+ /// Number of bits you want to write, in total
+ /// True if you can write, false if that would exceed buffer bounds
+ public unsafe bool TryBeginWriteBits(int bitCount)
+ {
+ var newBitPosition = m_BitPosition + bitCount;
+ var totalBytesWrittenInBitwiseContext = newBitPosition >> 3;
+ if ((newBitPosition & 7) != 0)
+ {
+ // Accounting for the partial write
+ ++totalBytesWrittenInBitwiseContext;
+ }
+
+ if (m_Position + totalBytesWrittenInBitwiseContext > m_Writer.Value.CapacityInternal)
+ {
+ if (m_Position + totalBytesWrittenInBitwiseContext > m_Writer.Value.MaxCapacityInternal)
+ {
+ return false;
+ }
+ if (m_Writer.Value.CapacityInternal < m_Writer.Value.MaxCapacityInternal)
+ {
+ m_Writer.Value.Grow(totalBytesWrittenInBitwiseContext);
+ m_BufferPointer = m_Writer.Value.BufferPointer + m_Writer.Value.PositionInternal;
+ }
+ else
+ {
+ return false;
+ }
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_AllowedBitwiseWriteMark = newBitPosition;
+#endif
+ return true;
+ }
+
+ ///
+ /// Write s certain amount of bits to the stream.
+ ///
+ /// Value to get bits from.
+ /// Amount of bits to write
+ public unsafe void WriteBits(ulong value, uint bitCount)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (bitCount > 64)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot write more than 64 bits from a 64-bit value!");
+ }
+
+ int checkPos = (int)(m_BitPosition + bitCount);
+ if (checkPos > m_AllowedBitwiseWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWriteBits)}()");
+ }
+#endif
+
+ int wholeBytes = (int)bitCount / k_BitsPerByte;
+ byte* asBytes = (byte*)&value;
+ if (BitAligned)
+ {
+ if (wholeBytes != 0)
+ {
+ WritePartialValue(value, wholeBytes);
+ }
+ }
+ else
+ {
+ for (var i = 0; i < wholeBytes; ++i)
+ {
+ WriteMisaligned(asBytes[i]);
+ }
+ }
+
+ for (var count = wholeBytes * k_BitsPerByte; count < bitCount; ++count)
+ {
+ WriteBit((value & (1UL << count)) != 0);
+ }
+ }
+
+ ///
+ /// Write bits to stream.
+ ///
+ /// Value to get bits from.
+ /// Amount of bits to write.
+ public void WriteBits(byte value, uint bitCount)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ int checkPos = (int)(m_BitPosition + bitCount);
+ if (checkPos > m_AllowedBitwiseWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWriteBits)}()");
+ }
+#endif
+
+ for (int i = 0; i < bitCount; ++i)
+ {
+ WriteBit(((value >> i) & 1) != 0);
+ }
+ }
+
+ ///
+ /// Write a single bit to the buffer
+ ///
+ /// Value of the bit. True represents 1, False represents 0
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteBit(bool bit)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ int checkPos = (m_BitPosition + 1);
+ if (checkPos > m_AllowedBitwiseWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWriteBits)}()");
+ }
+#endif
+
+ int offset = m_BitPosition & 7;
+ int pos = m_BitPosition >> 3;
+ ++m_BitPosition;
+ m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
+ {
+ byte* ptr = ((byte*)&value) + offsetBytes;
+ byte* bufferPointer = m_BufferPointer + m_Position;
+ UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
+
+ m_BitPosition += bytesToWrite * k_BitsPerByte;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe void WriteMisaligned(byte value)
+ {
+ int off = m_BitPosition & 7;
+ int pos = m_BitPosition >> 3;
+ int shift1 = 8 - off;
+ m_BufferPointer[pos + 1] = (byte)((m_BufferPointer[pos + 1] & (0xFF << off)) | (value >> shift1));
+ m_BufferPointer[pos] = (byte)((m_BufferPointer[pos] & (0xFF >> shift1)) | (value << off));
+
+ m_BitPosition += 8;
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs.meta
new file mode 100644
index 0000000000..604982f3c0
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8d6360e096142c149a11a2e86560c350
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs
new file mode 100644
index 0000000000..acfecdd2d5
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs
@@ -0,0 +1,343 @@
+using System;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ ///
+ /// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
+ ///
+ /// Implemented as a ref struct for two reasons:
+ /// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
+ /// 2. The BufferSerializer must always be passed by reference and can't be copied
+ ///
+ /// Ref structs help enforce both of those rules: they can't out live the stack context in which they were
+ /// created, and they're always passed by reference no matter what.
+ ///
+ /// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't.
+ /// ref structs can't implement interfaces, and in order to be able to have two different implementations with
+ /// the same interface (which allows us to avoid an "if(IsReader)" on every call), the thing directly wrapping
+ /// the struct has to implement an interface. So IBufferSerializerImplementation exists as the interface,
+ /// which is implemented by a normal struct, while the ref struct wraps the normal one to enforce the two above
+ /// requirements. (Allowing direct access to the IBufferSerializerImplementation struct would allow dangerous
+ /// things to happen because the struct's lifetime could outlive the Reader/Writer's.)
+ ///
+ /// The implementation struct
+ public ref struct BufferSerializer where TImplementation : IBufferSerializerImplementation
+ {
+ private TImplementation m_Implementation;
+
+ ///
+ /// Check if the contained implementation is a reader
+ ///
+ public bool IsReader => m_Implementation.IsReader;
+
+ ///
+ /// Check if the contained implementation is a writer
+ ///
+ public bool IsWriter => m_Implementation.IsWriter;
+
+ public BufferSerializer(TImplementation implementation)
+ {
+ m_Implementation = implementation;
+ }
+
+ ///
+ /// Retrieves the FastBufferReader instance. Only valid if IsReader = true, throws
+ /// InvalidOperationException otherwise.
+ ///
+ /// Reader instance
+ public ref FastBufferReader GetFastBufferReader()
+ {
+ return ref m_Implementation.GetFastBufferReader();
+ }
+
+ ///
+ /// Retrieves the FastBufferWriter instance. Only valid if IsWriter = true, throws
+ /// InvalidOperationException otherwise.
+ ///
+ /// Writer instance
+ public ref FastBufferWriter GetFastBufferWriter()
+ {
+ return ref m_Implementation.GetFastBufferWriter();
+ }
+
+ ///
+ /// Serialize an object value.
+ /// Note: Will ALWAYS cause allocations when reading.
+ /// This function is also much slower than the others as it has to figure out how to serialize
+ /// the object using runtime reflection.
+ /// It's recommended not to use this unless you have no choice.
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ /// Type to deserialize to when reading
+ ///
+ /// If true, will force an isNull byte to be written.
+ /// Some types will write this byte regardless.
+ ///
+ public void SerializeValue(ref object value, Type type, bool isNullable = false)
+ {
+ m_Implementation.SerializeValue(ref value, type, isNullable);
+ }
+
+ ///
+ /// Serialize an INetworkSerializable
+ /// If your INetworkSerializable is implemented by a struct, as opposed to a class, use this
+ /// function instead of SerializeValue. SerializeValue will incur a boxing allocation,
+ /// SerializeNetworkSerializable will not.
+ ///
+ /// A definition of SerializeValue that doesn't allocate can't be created because C#
+ /// doesn't allow overriding generics based solely on the constraint, so this would conflict
+ /// with WriteValue<T>(ref T value) where T: unmanaged
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable
+ {
+ m_Implementation.SerializeNetworkSerializable(ref value);
+ }
+
+ ///
+ /// Serialize an INetworkSerializable
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref INetworkSerializable value)
+ {
+ m_Implementation.SerializeValue(ref value);
+ }
+
+ ///
+ /// Serialize a GameObject
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref GameObject value)
+ {
+ m_Implementation.SerializeValue(ref value);
+ }
+
+ ///
+ /// Serialize a NetworkObject
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref NetworkObject value)
+ {
+ m_Implementation.SerializeValue(ref value);
+ }
+
+ ///
+ /// Serialize a NetworkBehaviour
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref NetworkBehaviour value)
+ {
+ m_Implementation.SerializeValue(ref value);
+ }
+
+ ///
+ /// Serialize a string.
+ ///
+ /// Note: Will ALWAYS allocate a new string when reading.
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ ///
+ /// If true, will truncate each char to one byte.
+ /// This is slower than two-byte chars, but uses less bandwidth.
+ ///
+ public void SerializeValue(ref string s, bool oneByteChars = false)
+ {
+ m_Implementation.SerializeValue(ref s, oneByteChars);
+ }
+
+ ///
+ /// Serialize an array value.
+ ///
+ /// Note: Will ALWAYS allocate a new array when reading.
+ /// If you have a statically-sized array that you know is large enough, it's recommended to
+ /// serialize the size yourself and iterate serializing array members.
+ ///
+ /// (This is because C# doesn't allow setting an array's length value, so deserializing
+ /// into an existing array of larger size would result in an array that doesn't have as many values
+ /// as its Length indicates it should.)
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref T[] array) where T : unmanaged
+ {
+ m_Implementation.SerializeValue(ref array);
+ }
+
+ ///
+ /// Serialize a single byte
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref byte value)
+ {
+ m_Implementation.SerializeValue(ref value);
+ }
+
+ ///
+ /// Serialize an unmanaged type. Supports basic value types as well as structs.
+ /// The provided type will be copied to/from the buffer as it exists in memory.
+ ///
+ /// Throws OverflowException if the end of the buffer has been reached.
+ /// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
+ ///
+ /// Value to serialize
+ public void SerializeValue(ref T value) where T : unmanaged
+ {
+ m_Implementation.SerializeValue(ref value);
+ }
+
+ ///
+ /// Allows faster serialization by batching bounds checking.
+ /// When you know you will be writing multiple fields back-to-back and you know the total size,
+ /// you can call PreCheck() once on the total size, and then follow it with calls to
+ /// SerializeValuePreChecked() for faster serialization. Write buffers will grow during PreCheck()
+ /// if needed.
+ ///
+ /// PreChecked serialization operations will throw OverflowException in editor and development builds if you
+ /// go past the point you've marked using PreCheck(). In release builds, OverflowException will not be thrown
+ /// for performance reasons, since the point of using PreCheck is to avoid bounds checking in the following
+ /// operations in release builds.
+ ///
+ /// To get the correct size to check for, use FastBufferWriter.GetWriteSize(value) or
+ /// FastBufferWriter.GetWriteSize<type>()
+ ///
+ /// Number of bytes you plan to read or write
+ /// True if the read/write can proceed, false otherwise.
+ public bool PreCheck(int amount)
+ {
+ return m_Implementation.PreCheck(amount);
+ }
+
+ ///
+ /// Serialize a GameObject.
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ public void SerializeValuePreChecked(ref GameObject value)
+ {
+ m_Implementation.SerializeValuePreChecked(ref value);
+ }
+
+ ///
+ /// Serialize a NetworkObject
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ public void SerializeValuePreChecked(ref NetworkObject value)
+ {
+ m_Implementation.SerializeValuePreChecked(ref value);
+ }
+
+ ///
+ /// Serialize a NetworkBehaviour
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ public void SerializeValuePreChecked(ref NetworkBehaviour value)
+ {
+ m_Implementation.SerializeValuePreChecked(ref value);
+ }
+
+ ///
+ /// Serialize a string.
+ ///
+ /// Note: Will ALWAYS allocate a new string when reading.
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ ///
+ /// If true, will truncate each char to one byte.
+ /// This is slower than two-byte chars, but uses less bandwidth.
+ ///
+ public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
+ {
+ m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
+ }
+
+ ///
+ /// Serialize an array value.
+ ///
+ /// Note: Will ALWAYS allocate a new array when reading.
+ /// If you have a statically-sized array that you know is large enough, it's recommended to
+ /// serialize the size yourself and iterate serializing array members.
+ ///
+ /// (This is because C# doesn't allow setting an array's length value, so deserializing
+ /// into an existing array of larger size would result in an array that doesn't have as many values
+ /// as its Length indicates it should.)
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ public void SerializeValuePreChecked(ref T[] array) where T : unmanaged
+ {
+ m_Implementation.SerializeValuePreChecked(ref array);
+ }
+
+ ///
+ /// Serialize a single byte
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ public void SerializeValuePreChecked(ref byte value)
+ {
+ m_Implementation.SerializeValuePreChecked(ref value);
+ }
+
+ ///
+ /// Serialize an unmanaged type. Supports basic value types as well as structs.
+ /// The provided type will be copied to/from the buffer as it exists in memory.
+ ///
+ /// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
+ /// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
+ /// serialization operations in one function call instead of having to do bounds checking on every call.
+ ///
+ /// Value to serialize
+ public void SerializeValuePreChecked(ref T value) where T : unmanaged
+ {
+ m_Implementation.SerializeValuePreChecked(ref value);
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs.meta
new file mode 100644
index 0000000000..fe70dbe5be
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fca519b9bc3b32e4f9bd7cd138d690af
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs
new file mode 100644
index 0000000000..ae777d3735
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs
@@ -0,0 +1,118 @@
+using System;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ internal struct BufferSerializerReader : IBufferSerializerImplementation
+ {
+ private Ref m_Reader;
+
+ public BufferSerializerReader(ref FastBufferReader reader)
+ {
+ m_Reader = new Ref(ref reader);
+ }
+
+ public bool IsReader => true;
+ public bool IsWriter => false;
+
+ public ref FastBufferReader GetFastBufferReader()
+ {
+ return ref m_Reader.Value;
+ }
+
+ public ref FastBufferWriter GetFastBufferWriter()
+ {
+ throw new InvalidOperationException("Cannot retrieve a FastBufferWriter from a serializer where IsWriter = false");
+ }
+
+ public void SerializeValue(ref object value, Type type, bool isNullable = false)
+ {
+ m_Reader.Value.ReadObject(out value, type, isNullable);
+ }
+
+ public void SerializeValue(ref INetworkSerializable value)
+ {
+ m_Reader.Value.ReadNetworkSerializable(out value);
+ }
+
+ public void SerializeValue(ref GameObject value)
+ {
+ m_Reader.Value.ReadValueSafe(out value);
+ }
+
+ public void SerializeValue(ref NetworkObject value)
+ {
+ m_Reader.Value.ReadValueSafe(out value);
+ }
+
+ public void SerializeValue(ref NetworkBehaviour value)
+ {
+ m_Reader.Value.ReadValueSafe(out value);
+ }
+
+ public void SerializeValue(ref string s, bool oneByteChars = false)
+ {
+ m_Reader.Value.ReadValueSafe(out s, oneByteChars);
+ }
+
+ public void SerializeValue(ref T[] array) where T : unmanaged
+ {
+ m_Reader.Value.ReadValueSafe(out array);
+ }
+
+ public void SerializeValue(ref byte value)
+ {
+ m_Reader.Value.ReadByteSafe(out value);
+ }
+
+ public void SerializeValue(ref T value) where T : unmanaged
+ {
+ m_Reader.Value.ReadValueSafe(out value);
+ }
+
+ public void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable
+ {
+ m_Reader.Value.ReadNetworkSerializable(out value);
+ }
+
+ public bool PreCheck(int amount)
+ {
+ return m_Reader.Value.TryBeginRead(amount);
+ }
+
+ public void SerializeValuePreChecked(ref GameObject value)
+ {
+ m_Reader.Value.ReadValue(out value);
+ }
+
+ public void SerializeValuePreChecked(ref NetworkObject value)
+ {
+ m_Reader.Value.ReadValue(out value);
+ }
+
+ public void SerializeValuePreChecked(ref NetworkBehaviour value)
+ {
+ m_Reader.Value.ReadValue(out value);
+ }
+
+ public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
+ {
+ m_Reader.Value.ReadValue(out s, oneByteChars);
+ }
+
+ public void SerializeValuePreChecked(ref T[] array) where T : unmanaged
+ {
+ m_Reader.Value.ReadValue(out array);
+ }
+
+ public void SerializeValuePreChecked(ref byte value)
+ {
+ m_Reader.Value.ReadValue(out value);
+ }
+
+ public void SerializeValuePreChecked(ref T value) where T : unmanaged
+ {
+ m_Reader.Value.ReadValue(out value);
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs.meta
new file mode 100644
index 0000000000..ed94cf148a
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 70dd17b6c14f7cd43ba5380d01cf91ef
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs
new file mode 100644
index 0000000000..f3a345ea99
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs
@@ -0,0 +1,118 @@
+using System;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ internal struct BufferSerializerWriter : IBufferSerializerImplementation
+ {
+ private Ref m_Writer;
+
+ public BufferSerializerWriter(ref FastBufferWriter writer)
+ {
+ m_Writer = new Ref(ref writer);
+ }
+
+ public bool IsReader => false;
+ public bool IsWriter => true;
+
+ public ref FastBufferReader GetFastBufferReader()
+ {
+ throw new InvalidOperationException("Cannot retrieve a FastBufferReader from a serializer where IsReader = false");
+ }
+
+ public ref FastBufferWriter GetFastBufferWriter()
+ {
+ return ref m_Writer.Value;
+ }
+
+ public void SerializeValue(ref object value, Type type, bool isNullable = false)
+ {
+ m_Writer.Value.WriteObject(value, isNullable);
+ }
+
+ public void SerializeValue(ref INetworkSerializable value)
+ {
+ m_Writer.Value.WriteNetworkSerializable(value);
+ }
+
+ public void SerializeValue(ref GameObject value)
+ {
+ m_Writer.Value.WriteValueSafe(value);
+ }
+
+ public void SerializeValue(ref NetworkObject value)
+ {
+ m_Writer.Value.WriteValueSafe(value);
+ }
+
+ public void SerializeValue(ref NetworkBehaviour value)
+ {
+ m_Writer.Value.WriteValueSafe(value);
+ }
+
+ public void SerializeValue(ref string s, bool oneByteChars = false)
+ {
+ m_Writer.Value.WriteValueSafe(s, oneByteChars);
+ }
+
+ public void SerializeValue(ref T[] array) where T : unmanaged
+ {
+ m_Writer.Value.WriteValueSafe(array);
+ }
+
+ public void SerializeValue(ref byte value)
+ {
+ m_Writer.Value.WriteByteSafe(value);
+ }
+
+ public void SerializeValue(ref T value) where T : unmanaged
+ {
+ m_Writer.Value.WriteValueSafe(value);
+ }
+
+ public void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable
+ {
+ m_Writer.Value.WriteNetworkSerializable(value);
+ }
+
+ public bool PreCheck(int amount)
+ {
+ return m_Writer.Value.TryBeginWrite(amount);
+ }
+
+ public void SerializeValuePreChecked(ref GameObject value)
+ {
+ m_Writer.Value.WriteValue(value);
+ }
+
+ public void SerializeValuePreChecked(ref NetworkObject value)
+ {
+ m_Writer.Value.WriteValue(value);
+ }
+
+ public void SerializeValuePreChecked(ref NetworkBehaviour value)
+ {
+ m_Writer.Value.WriteValue(value);
+ }
+
+ public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
+ {
+ m_Writer.Value.WriteValue(s, oneByteChars);
+ }
+
+ public void SerializeValuePreChecked(ref T[] array) where T : unmanaged
+ {
+ m_Writer.Value.WriteValue(array);
+ }
+
+ public void SerializeValuePreChecked(ref byte value)
+ {
+ m_Writer.Value.WriteByte(value);
+ }
+
+ public void SerializeValuePreChecked(ref T value) where T : unmanaged
+ {
+ m_Writer.Value.WriteValue(value);
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs.meta
new file mode 100644
index 0000000000..183a7bafad
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerWriter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c05ed8e3061e62147a012cc01a64b5a5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs
new file mode 100644
index 0000000000..9eb5854e67
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs
@@ -0,0 +1,617 @@
+using System;
+using System.Runtime.CompilerServices;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ ///
+ /// Utility class for packing values in serialization.
+ ///
+ public static class BytePacker
+ {
+ #region Managed TypePacking
+
+ ///
+ /// Writes a boxed object in a packed format
+ /// Named differently from other WriteValuePacked methods to avoid accidental boxing.
+ /// Don't use this method unless you have no other choice.
+ ///
+ /// Writer to write to
+ /// The object to write
+ ///
+ /// If true, an extra byte will be written to indicate whether or not the value is null.
+ /// Some types will always write this.
+ ///
+ public static void WriteObjectPacked(ref FastBufferWriter writer, object value, bool isNullable = false)
+ {
+#if UNITY_NETCODE_DEBUG_NO_PACKING
+ writer.WriteObject(value, isNullable);
+ return;
+#endif
+ if (isNullable || value.GetType().IsNullable())
+ {
+ bool isNull = value == null || (value is UnityEngine.Object o && o == null);
+
+ WriteValuePacked(ref writer, isNull);
+
+ if (isNull)
+ {
+ return;
+ }
+ }
+
+ var type = value.GetType();
+ var hasSerializer = SerializationTypeTable.SerializersPacked.TryGetValue(type, out var serializer);
+ if (hasSerializer)
+ {
+ serializer(ref writer, value);
+ return;
+ }
+
+ if (value is Array array)
+ {
+ WriteValuePacked(ref writer, array.Length);
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ WriteObjectPacked(ref writer, array.GetValue(i));
+ }
+ }
+
+ if (value.GetType().IsEnum)
+ {
+ switch (Convert.GetTypeCode(value))
+ {
+ case TypeCode.Boolean:
+ WriteValuePacked(ref writer, (byte)value);
+ break;
+ case TypeCode.Char:
+ WriteValuePacked(ref writer, (char)value);
+ break;
+ case TypeCode.SByte:
+ WriteValuePacked(ref writer, (sbyte)value);
+ break;
+ case TypeCode.Byte:
+ WriteValuePacked(ref writer, (byte)value);
+ break;
+ case TypeCode.Int16:
+ WriteValuePacked(ref writer, (short)value);
+ break;
+ case TypeCode.UInt16:
+ WriteValuePacked(ref writer, (ushort)value);
+ break;
+ case TypeCode.Int32:
+ WriteValuePacked(ref writer, (int)value);
+ break;
+ case TypeCode.UInt32:
+ WriteValuePacked(ref writer, (uint)value);
+ break;
+ case TypeCode.Int64:
+ WriteValuePacked(ref writer, (long)value);
+ break;
+ case TypeCode.UInt64:
+ WriteValuePacked(ref writer, (ulong)value);
+ break;
+ }
+ return;
+ }
+ if (value is GameObject)
+ {
+ ((GameObject)value).TryGetComponent(out var networkObject);
+ if (networkObject == null)
+ {
+ throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {((GameObject)value).name}");
+ }
+
+ if (!networkObject.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {((GameObject)value).name}");
+ }
+
+ WriteValuePacked(ref writer, networkObject.NetworkObjectId);
+ return;
+ }
+ if (value is NetworkObject)
+ {
+ if (!((NetworkObject)value).IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {((NetworkObject)value).gameObject.name}");
+ }
+
+ WriteValuePacked(ref writer, ((NetworkObject)value).NetworkObjectId);
+ return;
+ }
+ if (value is NetworkBehaviour)
+ {
+ if (!((NetworkBehaviour)value).HasNetworkObject || !((NetworkBehaviour)value).NetworkObject.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(NetworkWriter)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {((NetworkBehaviour)value).gameObject.name}");
+ }
+
+ WriteValuePacked(ref writer, ((NetworkBehaviour)value).NetworkObjectId);
+ WriteValuePacked(ref writer, ((NetworkBehaviour)value).NetworkBehaviourId);
+ return;
+ }
+ if (value is INetworkSerializable)
+ {
+ //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this));
+ return;
+ }
+
+ throw new ArgumentException($"{nameof(NetworkWriter)} cannot write type {value.GetType().Name} - it does not implement {nameof(INetworkSerializable)}");
+ }
+ #endregion
+
+ #region Unmanaged Type Packing
+
+#if UNITY_NETCODE_DEBUG_NO_PACKING
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void WriteValuePacked(ref FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
+#else
+ ///
+ /// Write a packed enum value.
+ ///
+ /// The writer to write to
+ /// The value to write
+ /// An enum type
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void WriteValuePacked(ref FastBufferWriter writer, TEnum value) where TEnum : unmanaged, Enum
+ {
+ TEnum enumValue = value;
+ switch (sizeof(TEnum))
+ {
+ case sizeof(int):
+ WriteValuePacked(ref writer, *(int*)&enumValue);
+ break;
+ case sizeof(byte):
+ WriteValuePacked(ref writer, *(byte*)&enumValue);
+ break;
+ case sizeof(short):
+ WriteValuePacked(ref writer, *(short*)&enumValue);
+ break;
+ case sizeof(long):
+ WriteValuePacked(ref writer, *(long*)&enumValue);
+ break;
+ }
+ }
+
+ ///
+ /// Write single-precision floating point value to the buffer as a varint
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, float value)
+ {
+ WriteUInt32Packed(ref writer, ToUint(value));
+ }
+
+ ///
+ /// Write double-precision floating point value to the buffer as a varint
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, double value)
+ {
+ WriteUInt64Packed(ref writer, ToUlong(value));
+ }
+
+ ///
+ /// Write a byte to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, byte value) => writer.WriteByteSafe(value);
+
+ ///
+ /// Write a signed byte to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, sbyte value) => writer.WriteByteSafe((byte)value);
+
+ ///
+ /// Write a bool to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, bool value) => writer.WriteValueSafe(value);
+
+
+ ///
+ /// Write a signed short (Int16) as a ZigZag encoded varint to the buffer.
+ /// WARNING: If the value you're writing is > 2287, this will use MORE space
+ /// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all.
+ /// Only use this if you're certain your value will be small.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, short value) => WriteUInt32Packed(ref writer, (ushort)Arithmetic.ZigZagEncode(value));
+
+ ///
+ /// Write an unsigned short (UInt16) as a varint to the buffer.
+ /// WARNING: If the value you're writing is > 2287, this will use MORE space
+ /// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all.
+ /// Only use this if you're certain your value will be small.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, ushort value) => WriteUInt32Packed(ref writer, value);
+
+ ///
+ /// Write a two-byte character as a varint to the buffer.
+ /// WARNING: If the value you're writing is > 2287, this will use MORE space
+ /// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all.
+ /// Only use this if you're certain your value will be small.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, char c) => WriteUInt32Packed(ref writer, c);
+
+ ///
+ /// Write a signed int (Int32) as a ZigZag encoded varint to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, int value) => WriteUInt32Packed(ref writer, (uint)Arithmetic.ZigZagEncode(value));
+
+ ///
+ /// Write an unsigned int (UInt32) to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, uint value) => WriteUInt32Packed(ref writer, value);
+
+ ///
+ /// Write an unsigned long (UInt64) to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, ulong value) => WriteUInt64Packed(ref writer, value);
+
+ ///
+ /// Write a signed long (Int64) as a ZigZag encoded varint to the buffer.
+ ///
+ /// The writer to write to
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, long value) => WriteUInt64Packed(ref writer, Arithmetic.ZigZagEncode(value));
+
+ ///
+ /// Convenience method that writes two packed Vector3 from the ray to the buffer
+ ///
+ /// The writer to write to
+ /// Ray to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Ray ray)
+ {
+ WriteValuePacked(ref writer, ray.origin);
+ WriteValuePacked(ref writer, ray.direction);
+ }
+
+ ///
+ /// Convenience method that writes two packed Vector2 from the ray to the buffer
+ ///
+ /// The writer to write to
+ /// Ray2D to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Ray2D ray2d)
+ {
+ WriteValuePacked(ref writer, ray2d.origin);
+ WriteValuePacked(ref writer, ray2d.direction);
+ }
+
+ ///
+ /// Convenience method that writes four varint floats from the color to the buffer
+ ///
+ /// The writer to write to
+ /// Color to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Color color)
+ {
+ WriteValuePacked(ref writer, color.r);
+ WriteValuePacked(ref writer, color.g);
+ WriteValuePacked(ref writer, color.b);
+ WriteValuePacked(ref writer, color.a);
+ }
+
+ ///
+ /// Convenience method that writes four varint floats from the color to the buffer
+ ///
+ /// The writer to write to
+ /// Color to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Color32 color)
+ {
+ WriteValuePacked(ref writer, color.r);
+ WriteValuePacked(ref writer, color.g);
+ WriteValuePacked(ref writer, color.b);
+ WriteValuePacked(ref writer, color.a);
+ }
+
+ ///
+ /// Convenience method that writes two varint floats from the vector to the buffer
+ ///
+ /// The writer to write to
+ /// Vector to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Vector2 vector2)
+ {
+ WriteValuePacked(ref writer, vector2.x);
+ WriteValuePacked(ref writer, vector2.y);
+ }
+
+ ///
+ /// Convenience method that writes three varint floats from the vector to the buffer
+ ///
+ /// The writer to write to
+ /// Vector to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Vector3 vector3)
+ {
+ WriteValuePacked(ref writer, vector3.x);
+ WriteValuePacked(ref writer, vector3.y);
+ WriteValuePacked(ref writer, vector3.z);
+ }
+
+ ///
+ /// Convenience method that writes four varint floats from the vector to the buffer
+ ///
+ /// The writer to write to
+ /// Vector to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Vector4 vector4)
+ {
+ WriteValuePacked(ref writer, vector4.x);
+ WriteValuePacked(ref writer, vector4.y);
+ WriteValuePacked(ref writer, vector4.z);
+ WriteValuePacked(ref writer, vector4.w);
+ }
+
+ ///
+ /// Writes the rotation to the buffer.
+ ///
+ /// The writer to write to
+ /// Rotation to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, Quaternion rotation)
+ {
+ WriteValuePacked(ref writer, rotation.x);
+ WriteValuePacked(ref writer, rotation.y);
+ WriteValuePacked(ref writer, rotation.z);
+ WriteValuePacked(ref writer, rotation.w);
+ }
+
+ ///
+ /// Writes a string in a packed format
+ ///
+ /// The writer to write to
+ /// The value to pack
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteValuePacked(ref FastBufferWriter writer, string s)
+ {
+ WriteValuePacked(ref writer, (uint)s.Length);
+ int target = s.Length;
+ for (int i = 0; i < target; ++i)
+ {
+ WriteValuePacked(ref writer, s[i]);
+ }
+ }
+#endif
+ #endregion
+
+ #region Bit Packing
+
+#if UNITY_NETCODE_DEBUG_NO_PACKING
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void WriteValueBitPacked(ref FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
+#else
+ ///
+ /// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
+ /// The first bit indicates whether the value is 1 byte or 2.
+ /// The sign bit takes up another bit.
+ /// That leaves 14 bits for the value.
+ /// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds.
+ /// In release builds builds the exception is not thrown and the value is truncated by losing its two
+ /// most significant bits after zig-zag encoding.
+ ///
+ /// The writer to write to
+ /// The value to pack
+ public static void WriteValueBitPacked(ref FastBufferWriter writer, short value) => WriteValueBitPacked(ref writer, (ushort)Arithmetic.ZigZagEncode(value));
+
+ ///
+ /// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format.
+ /// The first bit indicates whether the value is 1 byte or 2.
+ /// That leaves 15 bits for the value.
+ /// A value greater than 2^15-1 will throw an exception in editor and development builds.
+ /// In release builds builds the exception is not thrown and the value is truncated by losing its
+ /// most significant bit.
+ ///
+ /// The writer to write to
+ /// The value to pack
+ public static void WriteValueBitPacked(ref FastBufferWriter writer, ushort value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (value >= 0b1000_0000_0000_0000)
+ {
+ throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
+ }
+#endif
+
+ if (value <= 0b0111_1111)
+ {
+ if (!writer.TryBeginWriteInternal(1))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WriteByte((byte)(value << 1));
+ return;
+ }
+
+ if (!writer.TryBeginWriteInternal(2))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WriteValue((ushort)((value << 1) | 0b1));
+ }
+
+ ///
+ /// Writes a 29-bit signed int to the buffer in a bit-encoded packed format.
+ /// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
+ /// The sign bit takes up another bit.
+ /// That leaves 29 bits for the value.
+ /// A value greater than 2^29-1 or less than -2^29 will throw an exception in editor and development builds.
+ /// In release builds builds the exception is not thrown and the value is truncated by losing its three
+ /// most significant bits after zig-zag encoding.
+ ///
+ /// The writer to write to
+ /// The value to pack
+ public static void WriteValueBitPacked(ref FastBufferWriter writer, int value) => WriteValueBitPacked(ref writer, (uint)Arithmetic.ZigZagEncode(value));
+
+ ///
+ /// Writes a 30-bit unsigned int to the buffer in a bit-encoded packed format.
+ /// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
+ /// That leaves 30 bits for the value.
+ /// A value greater than 2^30-1 will throw an exception in editor and development builds.
+ /// In release builds builds the exception is not thrown and the value is truncated by losing its two
+ /// most significant bits.
+ ///
+ /// The writer to write to
+ /// The value to pack
+ public static void WriteValueBitPacked(ref FastBufferWriter writer, uint value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (value >= 0b0100_0000_0000_0000_0000_0000_0000_0000)
+ {
+ throw new ArgumentException("BitPacked uints must be <= 30 bits");
+ }
+#endif
+ value <<= 2;
+ var numBytes = BitCounter.GetUsedByteCount(value);
+ if (!writer.TryBeginWriteInternal(numBytes))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
+ }
+
+ ///
+ /// Writes a 60-bit signed long to the buffer in a bit-encoded packed format.
+ /// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
+ /// The sign bit takes up another bit.
+ /// That leaves 60 bits for the value.
+ /// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds.
+ /// In release builds builds the exception is not thrown and the value is truncated by losing its four
+ /// most significant bits after zig-zag encoding.
+ ///
+ /// The writer to write to
+ /// The value to pack
+ public static void WriteValueBitPacked(ref FastBufferWriter writer, long value) => WriteValueBitPacked(ref writer, Arithmetic.ZigZagEncode(value));
+
+ ///
+ /// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format.
+ /// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
+ /// That leaves 31 bits for the value.
+ /// A value greater than 2^61-1 will throw an exception in editor and development builds.
+ /// In release builds builds the exception is not thrown and the value is truncated by losing its three
+ /// most significant bits.
+ ///
+ /// The writer to write to
+ /// The value to pack
+ public static void WriteValueBitPacked(ref FastBufferWriter writer, ulong value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (value >= 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000)
+ {
+ throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
+ }
+#endif
+ value <<= 3;
+ var numBytes = BitCounter.GetUsedByteCount(value);
+ if (!writer.TryBeginWriteInternal(numBytes))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
+ }
+#endif
+ #endregion
+
+ #region Private Methods
+ private static void WriteUInt64Packed(ref FastBufferWriter writer, ulong value)
+ {
+ if (value <= 240)
+ {
+ writer.WriteByteSafe((byte)value);
+ return;
+ }
+ if (value <= 2287)
+ {
+ writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
+ writer.WriteByteSafe((byte)(value - 240));
+ return;
+ }
+ var writeBytes = BitCounter.GetUsedByteCount(value);
+
+ if (!writer.TryBeginWriteInternal(writeBytes + 1))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WriteByte((byte)(247 + writeBytes));
+ writer.WritePartialValue(value, writeBytes);
+ }
+
+ // Looks like the same code as WriteUInt64Packed?
+ // It's actually different because it will call the more efficient 32-bit version
+ // of BytewiseUtility.GetUsedByteCount().
+ private static void WriteUInt32Packed(ref FastBufferWriter writer, uint value)
+ {
+ if (value <= 240)
+ {
+ writer.WriteByteSafe((byte)value);
+ return;
+ }
+ if (value <= 2287)
+ {
+ writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
+ writer.WriteByteSafe((byte)(value - 240));
+ return;
+ }
+ var writeBytes = BitCounter.GetUsedByteCount(value);
+
+ if (!writer.TryBeginWriteInternal(writeBytes + 1))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WriteByte((byte)(247 + writeBytes));
+ writer.WritePartialValue(value, writeBytes);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe uint ToUint(T value) where T : unmanaged
+ {
+ uint* asUint = (uint*)&value;
+ return *asUint;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe ulong ToUlong(T value) where T : unmanaged
+ {
+ ulong* asUlong = (ulong*)&value;
+ return *asUlong;
+ }
+ #endregion
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs.meta
new file mode 100644
index 0000000000..dfb5e75613
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a3ec13587ae68cb49b82af8612d47698
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs
new file mode 100644
index 0000000000..857829e126
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs
@@ -0,0 +1,697 @@
+using System;
+using System.Runtime.CompilerServices;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ public static class ByteUnpacker
+ {
+ #region Managed TypePacking
+
+ ///
+ /// Reads a boxed object in a packed format
+ /// Named differently from other ReadValuePacked methods to avoid accidental boxing
+ /// Don't use this method unless you have no other choice.
+ ///
+ /// The reader to read from
+ /// The object to read
+ /// The type of the object to read (i.e., typeof(int))
+ ///
+ /// If true, reads a byte indicating whether or not the object is null.
+ /// Should match the way the object was written.
+ ///
+ public static void ReadObjectPacked(ref FastBufferReader reader, out object value, Type type, bool isNullable = false)
+ {
+#if UNITY_NETCODE_DEBUG_NO_PACKING
+ reader.ReadObject(out value, type, isNullable);
+ return;
+#endif
+ if (isNullable || type.IsNullable())
+ {
+ reader.ReadValueSafe(out bool isNull);
+
+ if (isNull)
+ {
+ value = null;
+ return;
+ }
+ }
+
+ var hasDeserializer = SerializationTypeTable.DeserializersPacked.TryGetValue(type, out var deserializer);
+ if (hasDeserializer)
+ {
+ deserializer(ref reader, out value);
+ return;
+ }
+
+ if (type.IsArray && type.HasElementType)
+ {
+ ReadValuePacked(ref reader, out int length);
+
+ var arr = Array.CreateInstance(type.GetElementType(), length);
+
+ for (int i = 0; i < length; i++)
+ {
+ ReadObjectPacked(ref reader, out object item, type.GetElementType());
+ arr.SetValue(item, i);
+ }
+
+ value = arr;
+ return;
+ }
+
+ if (type.IsEnum)
+ {
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Boolean:
+ ReadValuePacked(ref reader, out byte boolVal);
+ value = Enum.ToObject(type, boolVal != 0);
+ return;
+ case TypeCode.Char:
+ ReadValuePacked(ref reader, out char charVal);
+ value = Enum.ToObject(type, charVal);
+ return;
+ case TypeCode.SByte:
+ ReadValuePacked(ref reader, out byte sbyteVal);
+ value = Enum.ToObject(type, sbyteVal);
+ return;
+ case TypeCode.Byte:
+ ReadValuePacked(ref reader, out byte byteVal);
+ value = Enum.ToObject(type, byteVal);
+ return;
+ case TypeCode.Int16:
+ ReadValuePacked(ref reader, out short shortVal);
+ value = Enum.ToObject(type, shortVal);
+ return;
+ case TypeCode.UInt16:
+ ReadValuePacked(ref reader, out ushort ushortVal);
+ value = Enum.ToObject(type, ushortVal);
+ return;
+ case TypeCode.Int32:
+ ReadValuePacked(ref reader, out int intVal);
+ value = Enum.ToObject(type, intVal);
+ return;
+ case TypeCode.UInt32:
+ ReadValuePacked(ref reader, out uint uintVal);
+ value = Enum.ToObject(type, uintVal);
+ return;
+ case TypeCode.Int64:
+ ReadValuePacked(ref reader, out long longVal);
+ value = Enum.ToObject(type, longVal);
+ return;
+ case TypeCode.UInt64:
+ ReadValuePacked(ref reader, out ulong ulongVal);
+ value = Enum.ToObject(type, ulongVal);
+ return;
+ }
+ }
+
+ if (type == typeof(GameObject))
+ {
+ reader.ReadValueSafe(out GameObject go);
+ value = go;
+ return;
+ }
+
+ if (type == typeof(NetworkObject))
+ {
+ reader.ReadValueSafe(out NetworkObject no);
+ value = no;
+ return;
+ }
+
+ if (typeof(NetworkBehaviour).IsAssignableFrom(type))
+ {
+ reader.ReadValueSafe(out NetworkBehaviour nb);
+ value = nb;
+ return;
+ }
+ /*if (value is INetworkSerializable)
+ {
+ //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this));
+ return;
+ }*/
+
+ throw new ArgumentException($"{nameof(FastBufferReader)} cannot read type {type.Name} - it does not implement {nameof(INetworkSerializable)}");
+ }
+ #endregion
+
+ #region Unmanaged Type Packing
+
+#if UNITY_NETCODE_DEBUG_NO_PACKING
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ReadValuePacked(ref FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value);
+#else
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void ReadValuePacked(ref FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum
+ {
+ switch (sizeof(TEnum))
+ {
+ case sizeof(int):
+ ReadValuePacked(ref reader, out int asInt);
+ value = *(TEnum*)&asInt;
+ break;
+ case sizeof(byte):
+ ReadValuePacked(ref reader, out byte asByte);
+ value = *(TEnum*)&asByte;
+ break;
+ case sizeof(short):
+ ReadValuePacked(ref reader, out short asShort);
+ value = *(TEnum*)&asShort;
+ break;
+ case sizeof(long):
+ ReadValuePacked(ref reader, out long asLong);
+ value = *(TEnum*)&asLong;
+ break;
+ default:
+ throw new InvalidOperationException("Enum is a size that cannot exist?!");
+ }
+ }
+
+ ///
+ /// Read single-precision floating point value from the stream as a varint
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out float value)
+ {
+ ReadUInt32Packed(ref reader, out uint asUInt);
+ value = ToSingle(asUInt);
+ }
+
+ ///
+ /// Read double-precision floating point value from the stream as a varint
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out double value)
+ {
+ ReadUInt64Packed(ref reader, out ulong asULong);
+ value = ToDouble(asULong);
+ }
+
+ ///
+ /// Read a byte from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out byte value) => reader.ReadByteSafe(out value);
+
+ ///
+ /// Read a signed byte from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out sbyte value)
+ {
+ reader.ReadByteSafe(out byte byteVal);
+ value = (sbyte)byteVal;
+ }
+
+ ///
+ /// Read a boolean from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out bool value) => reader.ReadValueSafe(out value);
+
+
+ ///
+ /// Read an usigned short (Int16) as a varint from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out short value)
+ {
+ ReadUInt32Packed(ref reader, out uint readValue);
+ value = (short)Arithmetic.ZigZagDecode(readValue);
+ }
+
+ ///
+ /// Read an unsigned short (UInt16) as a varint from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out ushort value)
+ {
+ ReadUInt32Packed(ref reader, out uint readValue);
+ value = (ushort)readValue;
+ }
+
+ ///
+ /// Read a two-byte character as a varint from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out char c)
+ {
+ ReadUInt32Packed(ref reader, out uint readValue);
+ c = (char)readValue;
+ }
+
+ ///
+ /// Read a signed int (Int32) as a ZigZag encoded varint from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out int value)
+ {
+ ReadUInt32Packed(ref reader, out uint readValue);
+ value = (int)Arithmetic.ZigZagDecode(readValue);
+ }
+
+ ///
+ /// Read an unsigned int (UInt32) from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out uint value) => ReadUInt32Packed(ref reader, out value);
+
+ ///
+ /// Read an unsigned long (UInt64) from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out ulong value) => ReadUInt64Packed(ref reader, out value);
+
+ ///
+ /// Read a signed long (Int64) as a ZigZag encoded varint from the stream.
+ ///
+ /// The reader to read from
+ /// Value to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out long value)
+ {
+ ReadUInt64Packed(ref reader, out ulong readValue);
+ value = Arithmetic.ZigZagDecode(readValue);
+ }
+
+ ///
+ /// Convenience method that reads two packed Vector3 from the ray from the stream
+ ///
+ /// The reader to read from
+ /// Ray to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Ray ray)
+ {
+ ReadValuePacked(ref reader, out Vector3 origin);
+ ReadValuePacked(ref reader, out Vector3 direction);
+ ray = new Ray(origin, direction);
+ }
+
+ ///
+ /// Convenience method that reads two packed Vector2 from the ray from the stream
+ ///
+ /// The reader to read from
+ /// Ray2D to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Ray2D ray2d)
+ {
+ ReadValuePacked(ref reader, out Vector2 origin);
+ ReadValuePacked(ref reader, out Vector2 direction);
+ ray2d = new Ray2D(origin, direction);
+ }
+
+ ///
+ /// Convenience method that reads four varint floats from the color from the stream
+ ///
+ /// The reader to read from
+ /// Color to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Color color)
+ {
+ color = new Color();
+ ReadValuePacked(ref reader, out color.r);
+ ReadValuePacked(ref reader, out color.g);
+ ReadValuePacked(ref reader, out color.b);
+ ReadValuePacked(ref reader, out color.a);
+ }
+
+ ///
+ /// Convenience method that reads four varint floats from the color from the stream
+ ///
+ /// The reader to read from
+ /// Color to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Color32 color)
+ {
+ color = new Color32();
+ ReadValuePacked(ref reader, out color.r);
+ ReadValuePacked(ref reader, out color.g);
+ ReadValuePacked(ref reader, out color.b);
+ ReadValuePacked(ref reader, out color.a);
+ }
+
+ ///
+ /// Convenience method that reads two varint floats from the vector from the stream
+ ///
+ /// The reader to read from
+ /// Vector to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Vector2 vector2)
+ {
+ vector2 = new Vector2();
+ ReadValuePacked(ref reader, out vector2.x);
+ ReadValuePacked(ref reader, out vector2.y);
+ }
+
+ ///
+ /// Convenience method that reads three varint floats from the vector from the stream
+ ///
+ /// The reader to read from
+ /// Vector to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Vector3 vector3)
+ {
+ vector3 = new Vector3();
+ ReadValuePacked(ref reader, out vector3.x);
+ ReadValuePacked(ref reader, out vector3.y);
+ ReadValuePacked(ref reader, out vector3.z);
+ }
+
+ ///
+ /// Convenience method that reads four varint floats from the vector from the stream
+ ///
+ /// The reader to read from
+ /// Vector to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Vector4 vector4)
+ {
+ vector4 = new Vector4();
+ ReadValuePacked(ref reader, out vector4.x);
+ ReadValuePacked(ref reader, out vector4.y);
+ ReadValuePacked(ref reader, out vector4.z);
+ ReadValuePacked(ref reader, out vector4.w);
+ }
+
+ ///
+ /// Reads the rotation from the stream.
+ ///
+ /// The reader to read from
+ /// Rotation to read
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ReadValuePacked(ref FastBufferReader reader, out Quaternion rotation)
+ {
+ rotation = new Quaternion();
+ ReadValuePacked(ref reader, out rotation.x);
+ ReadValuePacked(ref reader, out rotation.y);
+ ReadValuePacked(ref reader, out rotation.z);
+ ReadValuePacked(ref reader, out rotation.w);
+ }
+
+ ///
+ /// Reads a string in a packed format
+ ///
+ /// The reader to read from
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void ReadValuePacked(ref FastBufferReader reader, out string s)
+ {
+ ReadValuePacked(ref reader, out uint length);
+ s = "".PadRight((int)length);
+ int target = s.Length;
+ fixed (char* c = s)
+ {
+ for (int i = 0; i < target; ++i)
+ {
+ ReadValuePacked(ref reader, out c[i]);
+ }
+ }
+ }
+#endif
+ #endregion
+
+ #region Bit Packing
+
+#if UNITY_NETCODE_DEBUG_NO_PACKING
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ReadValueBitPacked(ref FastBufferReader reader, T value) where T: unmanaged => reader.ReadValueSafe(out value);
+#else
+ ///
+ /// Read a bit-packed 14-bit signed short from the stream.
+ /// See BytePacker.cs for a description of the format.
+ ///
+ /// The reader to read from
+ /// The value to read
+ public static void ReadValueBitPacked(ref FastBufferReader reader, out short value)
+ {
+ ReadValueBitPacked(ref reader, out ushort readValue);
+ value = (short)Arithmetic.ZigZagDecode(readValue);
+ }
+
+ ///
+ /// Read a bit-packed 15-bit unsigned short from the stream.
+ /// See BytePacker.cs for a description of the format.
+ ///
+ /// The reader to read from
+ /// The value to read
+ public static unsafe void ReadValueBitPacked(ref FastBufferReader reader, out ushort value)
+ {
+ ushort returnValue = 0;
+ byte* ptr = ((byte*)&returnValue);
+ byte* data = reader.GetUnsafePtrAtCurrentPosition();
+ int numBytes = (data[0] & 0b1) + 1;
+ if (!reader.TryBeginReadInternal(numBytes))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ reader.MarkBytesRead(numBytes);
+ switch (numBytes)
+ {
+ case 1:
+ *ptr = *data;
+ break;
+ case 2:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ break;
+ default:
+ throw new InvalidOperationException("Could not read bit-packed value: impossible byte count");
+ }
+
+ value = (ushort)(returnValue >> 1);
+ }
+
+ ///
+ /// Read a bit-packed 29-bit signed int from the stream.
+ /// See BytePacker.cs for a description of the format.
+ ///
+ /// The reader to read from
+ /// The value to read
+ public static void ReadValueBitPacked(ref FastBufferReader reader, out int value)
+ {
+ ReadValueBitPacked(ref reader, out uint readValue);
+ value = (int)Arithmetic.ZigZagDecode(readValue);
+ }
+
+ ///
+ /// Read a bit-packed 30-bit unsigned int from the stream.
+ /// See BytePacker.cs for a description of the format.
+ ///
+ /// The reader to read from
+ /// The value to read
+ public static unsafe void ReadValueBitPacked(ref FastBufferReader reader, out uint value)
+ {
+ uint returnValue = 0;
+ byte* ptr = ((byte*)&returnValue);
+ byte* data = reader.GetUnsafePtrAtCurrentPosition();
+ int numBytes = (data[0] & 0b11) + 1;
+ if (!reader.TryBeginReadInternal(numBytes))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ reader.MarkBytesRead(numBytes);
+ switch (numBytes)
+ {
+ case 1:
+ *ptr = *data;
+ break;
+ case 2:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ break;
+ case 3:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ break;
+ case 4:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ *(ptr + 3) = *(data + 3);
+ break;
+ }
+
+ value = returnValue >> 2;
+ }
+
+ ///
+ /// Read a bit-packed 60-bit signed long from the stream.
+ /// See BytePacker.cs for a description of the format.
+ ///
+ /// The reader to read from
+ /// The value to read
+ public static void ReadValueBitPacked(ref FastBufferReader reader, out long value)
+ {
+ ReadValueBitPacked(ref reader, out ulong readValue);
+ value = Arithmetic.ZigZagDecode(readValue);
+ }
+
+ ///
+ /// Read a bit-packed 61-bit signed long from the stream.
+ /// See BytePacker.cs for a description of the format.
+ ///
+ /// The reader to read from
+ /// The value to read
+ public static unsafe void ReadValueBitPacked(ref FastBufferReader reader, out ulong value)
+ {
+ ulong returnValue = 0;
+ byte* ptr = ((byte*)&returnValue);
+ byte* data = reader.GetUnsafePtrAtCurrentPosition();
+ int numBytes = (data[0] & 0b111) + 1;
+ if (!reader.TryBeginReadInternal(numBytes))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ reader.MarkBytesRead(numBytes);
+ switch (numBytes)
+ {
+ case 1:
+ *ptr = *data;
+ break;
+ case 2:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ break;
+ case 3:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ break;
+ case 4:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ *(ptr + 3) = *(data + 3);
+ break;
+ case 5:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ *(ptr + 3) = *(data + 3);
+ *(ptr + 4) = *(data + 4);
+ break;
+ case 6:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ *(ptr + 3) = *(data + 3);
+ *(ptr + 4) = *(data + 4);
+ *(ptr + 5) = *(data + 5);
+ break;
+ case 7:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ *(ptr + 3) = *(data + 3);
+ *(ptr + 4) = *(data + 4);
+ *(ptr + 5) = *(data + 5);
+ *(ptr + 6) = *(data + 6);
+ break;
+ case 8:
+ *ptr = *data;
+ *(ptr + 1) = *(data + 1);
+ *(ptr + 2) = *(data + 2);
+ *(ptr + 3) = *(data + 3);
+ *(ptr + 4) = *(data + 4);
+ *(ptr + 5) = *(data + 5);
+ *(ptr + 6) = *(data + 6);
+ *(ptr + 7) = *(data + 7);
+ break;
+ }
+
+ value = returnValue >> 3;
+ }
+#endif
+ #endregion
+
+ #region Private Methods
+ private static void ReadUInt64Packed(ref FastBufferReader reader, out ulong value)
+ {
+ reader.ReadByteSafe(out byte firstByte);
+ if (firstByte <= 240)
+ {
+ value = firstByte;
+ return;
+ }
+
+ if (firstByte <= 248)
+ {
+ reader.ReadByteSafe(out byte secondByte);
+ value = 240UL + ((firstByte - 241UL) << 8) + secondByte;
+ return;
+ }
+
+ var numBytes = firstByte - 247;
+ if (!reader.TryBeginReadInternal(numBytes))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ reader.ReadPartialValue(out value, numBytes);
+ }
+
+ private static void ReadUInt32Packed(ref FastBufferReader reader, out uint value)
+ {
+ reader.ReadByteSafe(out byte firstByte);
+ if (firstByte <= 240)
+ {
+ value = firstByte;
+ return;
+ }
+
+ if (firstByte <= 248)
+ {
+ reader.ReadByteSafe(out byte secondByte);
+ value = 240U + ((firstByte - 241U) << 8) + secondByte;
+ return;
+ }
+
+ var numBytes = firstByte - 247;
+ if (!reader.TryBeginReadInternal(numBytes))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ reader.ReadPartialValue(out value, numBytes);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe float ToSingle(T value) where T : unmanaged
+ {
+ float* asFloat = (float*)&value;
+ return *asFloat;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe double ToDouble(T value) where T : unmanaged
+ {
+ double* asDouble = (double*)&value;
+ return *asDouble;
+ }
+ #endregion
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs.meta
new file mode 100644
index 0000000000..f3784bcdb4
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 73484532f9cd8a7418b6a7ac770df851
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs
new file mode 100644
index 0000000000..add6a0d487
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs
@@ -0,0 +1,742 @@
+using System;
+using System.Runtime.CompilerServices;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace Unity.Netcode
+{
+ public struct FastBufferReader : IDisposable
+ {
+ internal readonly unsafe byte* BufferPointer;
+ internal int PositionInternal;
+ internal readonly int LengthInternal;
+ private readonly Allocator m_Allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ internal int AllowedReadMark;
+ private bool m_InBitwiseContext;
+#endif
+
+ ///
+ /// Get the current read position
+ ///
+ public int Position
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => PositionInternal;
+ }
+
+ ///
+ /// Get the total length of the buffer
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => LengthInternal;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void CommitBitwiseReads(int amount)
+ {
+ PositionInternal += amount;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ public unsafe FastBufferReader(NativeArray buffer, Allocator allocator, int length = -1, int offset = 0)
+ {
+ LengthInternal = Math.Max(1, length == -1 ? buffer.Length : length);
+ if (allocator == Allocator.None)
+ {
+ BufferPointer = (byte*)buffer.GetUnsafePtr() + offset;
+ }
+ else
+ {
+ void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator);
+ UnsafeUtility.MemCpy(bufferPtr, (byte*)buffer.GetUnsafePtr() + offset, LengthInternal);
+ BufferPointer = (byte*)bufferPtr;
+ }
+ PositionInternal = offset;
+ m_Allocator = allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = 0;
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Create a FastBufferReader from an ArraySegment.
+ /// A new buffer will be created using the given allocator and the value will be copied in.
+ /// FastBufferReader will then own the data.
+ ///
+ /// The buffer to copy from
+ /// The allocator to use
+ /// The number of bytes to copy (all if this is -1)
+ /// The offset of the buffer to start copying from
+ public unsafe FastBufferReader(ArraySegment buffer, Allocator allocator, int length = -1, int offset = 0)
+ {
+ LengthInternal = Math.Max(1, length == -1 ? (buffer.Count - offset) : length);
+ if (allocator == Allocator.None)
+ {
+ throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
+ }
+ else
+ {
+ void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator);
+ fixed (byte* data = buffer.Array)
+ {
+ UnsafeUtility.MemCpy(bufferPtr, data + offset, LengthInternal);
+ }
+
+ BufferPointer = (byte*)bufferPtr;
+ }
+
+ PositionInternal = 0;
+ m_Allocator = allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = 0;
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Create a FastBufferReader from an existing byte array.
+ /// A new buffer will be created using the given allocator and the value will be copied in.
+ /// FastBufferReader will then own the data.
+ ///
+ /// The buffer to copy from
+ /// The allocator to use
+ /// The number of bytes to copy (all if this is -1)
+ /// The offset of the buffer to start copying from
+ public unsafe FastBufferReader(byte[] buffer, Allocator allocator, int length = -1, int offset = 0)
+ {
+ LengthInternal = Math.Max(1, length == -1 ? (buffer.Length - offset) : length);
+ if (allocator == Allocator.None)
+ {
+ throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
+ }
+ else
+ {
+ void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator);
+ fixed (byte* data = buffer)
+ {
+ UnsafeUtility.MemCpy(bufferPtr, data + offset, LengthInternal);
+ }
+
+ BufferPointer = (byte*)bufferPtr;
+ }
+
+ PositionInternal = 0;
+ m_Allocator = allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = 0;
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Create a FastBufferReader from an existing byte buffer.
+ /// A new buffer will be created using the given allocator and the value will be copied in.
+ /// FastBufferReader will then own the data.
+ ///
+ /// The buffer to copy from
+ /// The allocator to use
+ /// The number of bytes to copy
+ /// The offset of the buffer to start copying from
+ public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0)
+ {
+ LengthInternal = Math.Max(1, length);
+ if (allocator == Allocator.None)
+ {
+ BufferPointer = buffer + offset;
+ }
+ else
+ {
+ void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator);
+ UnsafeUtility.MemCpy(bufferPtr, buffer + offset, LengthInternal);
+ BufferPointer = (byte*)bufferPtr;
+ }
+
+ PositionInternal = 0;
+ m_Allocator = allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = 0;
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Create a FastBufferReader from a FastBufferWriter.
+ /// A new buffer will be created using the given allocator and the value will be copied in.
+ /// FastBufferReader will then own the data.
+ ///
+ /// The writer to copy from
+ /// The allocator to use
+ /// The number of bytes to copy (all if this is -1)
+ /// The offset of the buffer to start copying from
+ public unsafe FastBufferReader(ref FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0)
+ {
+ LengthInternal = Math.Max(1, length == -1 ? writer.Length : length);
+ void* bufferPtr = UnsafeUtility.Malloc(LengthInternal, UnsafeUtility.AlignOf(), allocator);
+ UnsafeUtility.MemCpy(bufferPtr, writer.GetUnsafePtr() + offset, LengthInternal);
+ BufferPointer = (byte*)bufferPtr;
+ PositionInternal = 0;
+ m_Allocator = allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = 0;
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Frees the allocated buffer
+ ///
+ public unsafe void Dispose()
+ {
+ UnsafeUtility.Free(BufferPointer, m_Allocator);
+ }
+
+ ///
+ /// Move the read position in the stream
+ ///
+ /// Absolute value to move the position to, truncated to Length
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Seek(int where)
+ {
+ PositionInternal = Math.Min(Length, where);
+ }
+
+ ///
+ /// Mark that some bytes are going to be read via GetUnsafePtr().
+ ///
+ /// Amount that will be read
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void MarkBytesRead(int amount)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + amount > AllowedReadMark)
+ {
+ throw new OverflowException("Attempted to read without first calling TryBeginRead()");
+ }
+#endif
+ PositionInternal += amount;
+ }
+
+ ///
+ /// Retrieve a BitReader to be able to perform bitwise operations on the buffer.
+ /// No bytewise operations can be performed on the buffer until bitReader.Dispose() has been called.
+ /// At the end of the operation, FastBufferReader will remain byte-aligned.
+ ///
+ /// A BitReader
+ public BitReader EnterBitwiseContext()
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_InBitwiseContext = true;
+#endif
+ return new BitReader(ref this);
+ }
+
+ ///
+ /// Allows faster serialization by batching bounds checking.
+ /// When you know you will be reading multiple fields back-to-back and you know the total size,
+ /// you can call TryBeginRead() once on the total size, and then follow it with calls to
+ /// ReadValue() instead of ReadValueSafe() for faster serialization.
+ ///
+ /// Unsafe read operations will throw OverflowException in editor and development builds if you
+ /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown
+ /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
+ /// operations in release builds.
+ ///
+ /// Amount of bytes to read
+ /// True if the read is allowed, false otherwise
+ /// If called while in a bitwise context
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryBeginRead(int bytes)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+ if (PositionInternal + bytes > LengthInternal)
+ {
+ return false;
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = PositionInternal + bytes;
+#endif
+ return true;
+ }
+
+ ///
+ /// Allows faster serialization by batching bounds checking.
+ /// When you know you will be reading multiple fields back-to-back and you know the total size,
+ /// you can call TryBeginRead() once on the total size, and then follow it with calls to
+ /// ReadValue() instead of ReadValueSafe() for faster serialization.
+ ///
+ /// Unsafe read operations will throw OverflowException in editor and development builds if you
+ /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown
+ /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
+ /// operations in release builds.
+ ///
+ /// The value you want to read
+ /// True if the read is allowed, false otherwise
+ /// If called while in a bitwise context
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe bool TryBeginReadValue(in T value) where T : unmanaged
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+ int len = sizeof(T);
+ if (PositionInternal + len > LengthInternal)
+ {
+ return false;
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedReadMark = PositionInternal + len;
+#endif
+ return true;
+ }
+
+ ///
+ /// Internal version of TryBeginRead.
+ /// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward.
+ ///
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool TryBeginReadInternal(int bytes)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+ if (PositionInternal + bytes > LengthInternal)
+ {
+ return false;
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (PositionInternal + bytes > AllowedReadMark)
+ {
+ AllowedReadMark = PositionInternal + bytes;
+ }
+#endif
+ return true;
+ }
+
+ ///
+ /// Returns an array representation of the underlying byte buffer.
+ /// !!Allocates a new array!!
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe byte[] ToArray()
+ {
+ byte[] ret = new byte[Length];
+ fixed (byte* b = ret)
+ {
+ UnsafeUtility.MemCpy(b, BufferPointer, Length);
+ }
+ return ret;
+ }
+
+ ///
+ /// Gets a direct pointer to the underlying buffer
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe byte* GetUnsafePtr()
+ {
+ return BufferPointer;
+ }
+
+ ///
+ /// Gets a direct pointer to the underlying buffer at the current read position
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe byte* GetUnsafePtrAtCurrentPosition()
+ {
+ return BufferPointer + PositionInternal;
+ }
+
+ ///
+ /// Reads a string
+ /// NOTE: ALLOCATES
+ ///
+ /// Stores the read string
+ /// Whether or not to use one byte per character. This will only allow ASCII
+ public unsafe void ReadValue(out string s, bool oneByteChars = false)
+ {
+ ReadValue(out uint length);
+ s = "".PadRight((int)length);
+ int target = s.Length;
+ fixed (char* native = s)
+ {
+ if (oneByteChars)
+ {
+ for (int i = 0; i < target; ++i)
+ {
+ ReadByte(out byte b);
+ native[i] = (char)b;
+ }
+ }
+ else
+ {
+ ReadBytes((byte*)native, target * sizeof(char));
+ }
+ }
+ }
+
+ ///
+ /// Reads a string.
+ /// NOTE: ALLOCATES
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// Stores the read string
+ /// Whether or not to use one byte per character. This will only allow ASCII
+ public unsafe void ReadValueSafe(out string s, bool oneByteChars = false)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginReadInternal(sizeof(uint)))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+
+ ReadValue(out uint length);
+
+ if (!TryBeginReadInternal((int)length * (oneByteChars ? 1 : sizeof(char))))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ s = "".PadRight((int)length);
+ int target = s.Length;
+ fixed (char* native = s)
+ {
+ if (oneByteChars)
+ {
+ for (int i = 0; i < target; ++i)
+ {
+ ReadByte(out byte b);
+ native[i] = (char)b;
+ }
+ }
+ else
+ {
+ ReadBytes((byte*)native, target * sizeof(char));
+ }
+ }
+ }
+
+ ///
+ /// Writes an unmanaged array
+ /// NOTE: ALLOCATES
+ ///
+ /// Stores the read array
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadValue(out T[] array) where T : unmanaged
+ {
+ ReadValue(out int sizeInTs);
+ int sizeInBytes = sizeInTs * sizeof(T);
+ array = new T[sizeInTs];
+ fixed (T* native = array)
+ {
+ byte* bytes = (byte*)(native);
+ ReadBytes(bytes, sizeInBytes);
+ }
+ }
+
+ ///
+ /// Reads an unmanaged array
+ /// NOTE: ALLOCATES
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// Stores the read array
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadValueSafe(out T[] array) where T : unmanaged
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginReadInternal(sizeof(int)))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ ReadValue(out int sizeInTs);
+ int sizeInBytes = sizeInTs * sizeof(T);
+ if (!TryBeginReadInternal(sizeInBytes))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ array = new T[sizeInTs];
+ fixed (T* native = array)
+ {
+ byte* bytes = (byte*)(native);
+ ReadBytes(bytes, sizeInBytes);
+ }
+ }
+
+ ///
+ /// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it.
+ ///
+ /// Value to read
+ /// Number of bytes
+ /// Offset into the value to write the bytes
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + bytesToRead > AllowedReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
+ }
+#endif
+
+ var val = new T();
+ byte* ptr = ((byte*)&val) + offsetBytes;
+ byte* bufferPointer = BufferPointer + PositionInternal;
+ UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
+
+ PositionInternal += bytesToRead;
+ value = val;
+ }
+
+ ///
+ /// Read a byte to the stream.
+ ///
+ /// Stores the read value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadByte(out byte value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + 1 > AllowedReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
+ }
+#endif
+ value = BufferPointer[PositionInternal++];
+ }
+
+ ///
+ /// Read a byte to the stream.
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// Stores the read value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadByteSafe(out byte value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginReadInternal(1))
+ {
+ throw new OverflowException("Reading past the end of the buffer");
+ }
+ value = BufferPointer[PositionInternal++];
+ }
+
+ ///
+ /// Read multiple bytes to the stream
+ ///
+ /// Pointer to the destination buffer
+ /// Number of bytes to read - MUST BE <= BUFFER SIZE
+ /// Offset of the byte buffer to store into
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadBytes(byte* value, int size, int offset = 0)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + size > AllowedReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
+ }
+#endif
+ UnsafeUtility.MemCpy(value + offset, (BufferPointer + PositionInternal), size);
+ PositionInternal += size;
+ }
+
+ ///
+ /// Read multiple bytes to the stream
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// Pointer to the destination buffer
+ /// Number of bytes to read - MUST BE <= BUFFER SIZE
+ /// Offset of the byte buffer to store into
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadBytesSafe(byte* value, int size, int offset = 0)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginReadInternal(size))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ UnsafeUtility.MemCpy(value + offset, (BufferPointer + PositionInternal), size);
+ PositionInternal += size;
+ }
+
+ ///
+ /// Read multiple bytes from the stream
+ ///
+ /// Pointer to the destination buffer
+ /// Number of bytes to read - MUST BE <= BUFFER SIZE
+ /// Offset of the byte buffer to store into
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadBytes(ref byte[] value, int size, int offset = 0)
+ {
+ fixed (byte* ptr = value)
+ {
+ ReadBytes(ptr, size, offset);
+ }
+ }
+
+ ///
+ /// Read multiple bytes from the stream
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// Pointer to the destination buffer
+ /// Number of bytes to read - MUST BE <= BUFFER SIZE
+ /// Offset of the byte buffer to store into
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadBytesSafe(ref byte[] value, int size, int offset = 0)
+ {
+ fixed (byte* ptr = value)
+ {
+ ReadBytesSafe(ptr, size, offset);
+ }
+ }
+
+ ///
+ /// Read a value of any unmanaged type to the buffer.
+ /// It will be copied from the buffer exactly as it existed in memory on the writing end.
+ ///
+ /// The read value
+ /// Any unmanaged type
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadValue(out T value) where T : unmanaged
+ {
+ int len = sizeof(T);
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + len > AllowedReadMark)
+ {
+ throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
+ }
+#endif
+
+ fixed (T* ptr = &value)
+ {
+ UnsafeUtility.MemCpy((byte*)ptr, BufferPointer + PositionInternal, len);
+ }
+ PositionInternal += len;
+ }
+
+ ///
+ /// Read a value of any unmanaged type to the buffer.
+ /// It will be copied from the buffer exactly as it existed in memory on the writing end.
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// The read value
+ /// Any unmanaged type
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ReadValueSafe(out T value) where T : unmanaged
+ {
+ int len = sizeof(T);
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferReader in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginReadInternal(len))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+
+
+ fixed (T* ptr = &value)
+ {
+ UnsafeUtility.MemCpy((byte*)ptr, BufferPointer + PositionInternal, len);
+ }
+ PositionInternal += len;
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs.meta
new file mode 100644
index 0000000000..3667f738aa
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5479786a4b1f57648a0fe56bd37a823b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs
new file mode 100644
index 0000000000..02f567a9df
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs
@@ -0,0 +1,286 @@
+
+using System;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ public static class FastBufferReaderExtensions
+ {
+
+ ///
+ /// Reads a boxed object in a standard format
+ /// Named differently from other ReadValue methods to avoid accidental boxing
+ ///
+ /// The object to read
+ /// The type to be read
+ ///
+ /// If true, reads a byte indicating whether or not the object is null.
+ /// Should match the way the object was written.
+ ///
+ public static void ReadObject(this ref FastBufferReader reader, out object value, Type type, bool isNullable = false)
+ {
+ if (isNullable || type.IsNullable())
+ {
+ reader.ReadValueSafe(out bool isNull);
+
+ if (isNull)
+ {
+ value = null;
+ return;
+ }
+ }
+
+ var hasDeserializer = SerializationTypeTable.Deserializers.TryGetValue(type, out var deserializer);
+ if (hasDeserializer)
+ {
+ deserializer(ref reader, out value);
+ return;
+ }
+
+ if (type.IsArray && type.HasElementType)
+ {
+ reader.ReadValueSafe(out int length);
+
+ var arr = Array.CreateInstance(type.GetElementType(), length);
+
+ for (int i = 0; i < length; i++)
+ {
+ reader.ReadObject(out object item, type.GetElementType());
+ arr.SetValue(item, i);
+ }
+
+ value = arr;
+ return;
+ }
+
+ if (type.IsEnum)
+ {
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Boolean:
+ reader.ReadValueSafe(out byte boolVal);
+ value = Enum.ToObject(type, boolVal != 0);
+ return;
+ case TypeCode.Char:
+ reader.ReadValueSafe(out char charVal);
+ value = Enum.ToObject(type, charVal);
+ return;
+ case TypeCode.SByte:
+ reader.ReadValueSafe(out sbyte sbyteVal);
+ value = Enum.ToObject(type, sbyteVal);
+ return;
+ case TypeCode.Byte:
+ reader.ReadValueSafe(out byte byteVal);
+ value = Enum.ToObject(type, byteVal);
+ return;
+ case TypeCode.Int16:
+ reader.ReadValueSafe(out short shortVal);
+ value = Enum.ToObject(type, shortVal);
+ return;
+ case TypeCode.UInt16:
+ reader.ReadValueSafe(out ushort ushortVal);
+ value = Enum.ToObject(type, ushortVal);
+ return;
+ case TypeCode.Int32:
+ reader.ReadValueSafe(out int intVal);
+ value = Enum.ToObject(type, intVal);
+ return;
+ case TypeCode.UInt32:
+ reader.ReadValueSafe(out uint uintVal);
+ value = Enum.ToObject(type, uintVal);
+ return;
+ case TypeCode.Int64:
+ reader.ReadValueSafe(out long longVal);
+ value = Enum.ToObject(type, longVal);
+ return;
+ case TypeCode.UInt64:
+ reader.ReadValueSafe(out ulong ulongVal);
+ value = Enum.ToObject(type, ulongVal);
+ return;
+ }
+ }
+
+ if (type == typeof(GameObject))
+ {
+ reader.ReadValueSafe(out GameObject go);
+ value = go;
+ return;
+ }
+
+ if (type == typeof(NetworkObject))
+ {
+ reader.ReadValueSafe(out NetworkObject no);
+ value = no;
+ return;
+ }
+
+ if (typeof(NetworkBehaviour).IsAssignableFrom(type))
+ {
+ reader.ReadValueSafe(out NetworkBehaviour nb);
+ value = nb;
+ return;
+ }
+ /*if (value is INetworkSerializable)
+ {
+ //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this));
+ return;
+ }*/
+
+ throw new ArgumentException($"{nameof(FastBufferReader)} cannot read type {type.Name} - it does not implement {nameof(INetworkSerializable)}");
+ }
+
+ ///
+ /// Read an INetworkSerializable
+ ///
+ /// INetworkSerializable instance
+ ///
+ ///
+ public static void ReadNetworkSerializable(this ref FastBufferReader reader, out T value) where T : INetworkSerializable
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Read a GameObject
+ ///
+ /// value to read
+ public static void ReadValue(this ref FastBufferReader reader, out GameObject value)
+ {
+ reader.ReadValue(out ulong networkObjectId);
+
+ if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject))
+ {
+ value = networkObject.gameObject;
+ return;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}");
+ }
+
+ value = null;
+ }
+
+ ///
+ /// Read a GameObject
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// value to read
+ public static void ReadValueSafe(this ref FastBufferReader reader, out GameObject value)
+ {
+ reader.ReadValueSafe(out ulong networkObjectId);
+
+ if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject))
+ {
+ value = networkObject.gameObject;
+ return;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}");
+ }
+
+ value = null;
+ }
+
+ ///
+ /// Read a NetworkObject
+ ///
+ /// value to read
+ public static void ReadValue(this ref FastBufferReader reader, out NetworkObject value)
+ {
+ reader.ReadValue(out ulong networkObjectId);
+
+ if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject))
+ {
+ value = networkObject;
+ return;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}");
+ }
+
+ value = null;
+ }
+
+ ///
+ /// Read a NetworkObject
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// value to read
+ public static void ReadValueSafe(this ref FastBufferReader reader, out NetworkObject value)
+ {
+ reader.ReadValueSafe(out ulong networkObjectId);
+
+ if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject))
+ {
+ value = networkObject;
+ return;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(GameObject)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}");
+ }
+
+ value = null;
+ }
+
+ ///
+ /// Read a NetworkBehaviour
+ ///
+ /// value to read
+ public static void ReadValue(this ref FastBufferReader reader, out NetworkBehaviour value)
+ {
+ reader.ReadValue(out ulong networkObjectId);
+ reader.ReadValue(out ushort networkBehaviourId);
+
+ if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject))
+ {
+ value = networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
+ return;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(NetworkBehaviour)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}");
+ }
+
+ value = null;
+ }
+
+ ///
+ /// Read a NetworkBehaviour
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple reads at once by calling TryBeginRead.
+ ///
+ /// value to read
+ public static void ReadValueSafe(this ref FastBufferReader reader, out NetworkBehaviour value)
+ {
+ reader.ReadValueSafe(out ulong networkObjectId);
+ reader.ReadValueSafe(out ushort networkBehaviourId);
+
+ if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject networkObject))
+ {
+ value = networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
+ return;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning($"{nameof(FastBufferReader)} cannot find the {nameof(NetworkBehaviour)} sent in the {nameof(NetworkSpawnManager.SpawnedObjects)} list, it may have been destroyed. {nameof(networkObjectId)}: {networkObjectId}");
+ }
+
+ value = null;
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs.meta
new file mode 100644
index 0000000000..d00798303c
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReaderExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4dc2c9158967ec847895c0d9653283fa
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs
new file mode 100644
index 0000000000..8e7ec57933
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs
@@ -0,0 +1,772 @@
+using System;
+using System.Runtime.CompilerServices;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace Unity.Netcode
+{
+ public struct FastBufferWriter : IDisposable
+ {
+ internal unsafe byte* BufferPointer;
+ internal int PositionInternal;
+ private int m_Length;
+ internal int CapacityInternal;
+ internal readonly int MaxCapacityInternal;
+ private readonly Allocator m_Allocator;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ internal int AllowedWriteMark;
+ private bool m_InBitwiseContext;
+#endif
+
+ ///
+ /// The current write position
+ ///
+ public int Position
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => PositionInternal;
+ }
+
+ ///
+ /// The current total buffer size
+ ///
+ public int Capacity
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => CapacityInternal;
+ }
+
+ ///
+ /// The maximum possible total buffer size
+ ///
+ public int MaxCapacity
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => MaxCapacityInternal;
+ }
+
+ ///
+ /// The total amount of bytes that have been written to the stream
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => PositionInternal > m_Length ? PositionInternal : m_Length;
+ }
+
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void CommitBitwiseWrites(int amount)
+ {
+ PositionInternal += amount;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Create a FastBufferWriter.
+ ///
+ /// Size of the buffer to create
+ /// Allocator to use in creating it
+ /// Maximum size the buffer can grow to. If less than size, buffer cannot grow.
+ public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1)
+ {
+ void* buffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator);
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ UnsafeUtility.MemSet(buffer, 0, size);
+#endif
+ BufferPointer = (byte*)buffer;
+ PositionInternal = 0;
+ m_Length = 0;
+ CapacityInternal = size;
+ m_Allocator = allocator;
+ MaxCapacityInternal = maxSize < size ? size : maxSize;
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedWriteMark = 0;
+ m_InBitwiseContext = false;
+#endif
+ }
+
+ ///
+ /// Frees the allocated buffer
+ ///
+ public unsafe void Dispose()
+ {
+ UnsafeUtility.Free(BufferPointer, m_Allocator);
+ }
+
+ ///
+ /// Move the write position in the stream.
+ /// Note that moving forward past the current length will extend the buffer's Length value even if you don't write.
+ ///
+ /// Absolute value to move the position to, truncated to Capacity
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Seek(int where)
+ {
+ // This avoids us having to synchronize length all the time.
+ // Writing things is a much more common operation than seeking
+ // or querying length. The length here is a high watermark of
+ // what's been written. So before we seek, if the current position
+ // is greater than the length, we update that watermark.
+ // When querying length later, we'll return whichever of the two
+ // values is greater, thus if we write past length, length increases
+ // because position increases, and if we seek backward, length remembers
+ // the position it was in.
+ // Seeking forward will not update the length.
+ where = Math.Min(where, CapacityInternal);
+ if (PositionInternal > m_Length && where < PositionInternal)
+ {
+ m_Length = PositionInternal;
+ }
+ PositionInternal = where;
+ }
+
+ ///
+ /// Truncate the stream by setting Length to the specified value.
+ /// If Position is greater than the specified value, it will be moved as well.
+ ///
+ /// The value to truncate to. If -1, the current position will be used.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Truncate(int where = -1)
+ {
+ if (where == -1)
+ {
+ where = Position;
+ }
+
+ if (PositionInternal > where)
+ {
+ PositionInternal = where;
+ }
+ if (m_Length > where)
+ {
+ m_Length = where;
+ }
+ }
+
+ ///
+ /// Retrieve a BitWriter to be able to perform bitwise operations on the buffer.
+ /// No bytewise operations can be performed on the buffer until bitWriter.Dispose() has been called.
+ /// At the end of the operation, FastBufferWriter will remain byte-aligned.
+ ///
+ /// A BitWriter
+ public BitWriter EnterBitwiseContext()
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ m_InBitwiseContext = true;
+#endif
+ return new BitWriter(ref this);
+ }
+
+ internal unsafe void Grow(int additionalSizeRequired)
+ {
+ var desiredSize = CapacityInternal * 2;
+ while (desiredSize < Position + additionalSizeRequired)
+ {
+ desiredSize *= 2;
+ }
+ var newSize = Math.Min(desiredSize, MaxCapacityInternal);
+ void* buffer = UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf(), m_Allocator);
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ UnsafeUtility.MemSet(buffer, 0, newSize);
+#endif
+ UnsafeUtility.MemCpy(buffer, BufferPointer, Length);
+ UnsafeUtility.Free(BufferPointer, m_Allocator);
+ BufferPointer = (byte*)buffer;
+ CapacityInternal = newSize;
+ }
+
+ ///
+ /// Allows faster serialization by batching bounds checking.
+ /// When you know you will be writing multiple fields back-to-back and you know the total size,
+ /// you can call TryBeginWrite() once on the total size, and then follow it with calls to
+ /// WriteValue() instead of WriteValueSafe() for faster serialization.
+ ///
+ /// Unsafe write operations will throw OverflowException in editor and development builds if you
+ /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
+ /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
+ /// operations in release builds.
+ ///
+ /// Amount of bytes to write
+ /// True if the write is allowed, false otherwise
+ /// If called while in a bitwise context
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryBeginWrite(int bytes)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+ if (PositionInternal + bytes > CapacityInternal)
+ {
+ if (PositionInternal + bytes > MaxCapacityInternal)
+ {
+ return false;
+ }
+ if (CapacityInternal < MaxCapacityInternal)
+ {
+ Grow(bytes);
+ }
+ else
+ {
+ return false;
+ }
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedWriteMark = PositionInternal + bytes;
+#endif
+ return true;
+ }
+
+ ///
+ /// Allows faster serialization by batching bounds checking.
+ /// When you know you will be writing multiple fields back-to-back and you know the total size,
+ /// you can call TryBeginWrite() once on the total size, and then follow it with calls to
+ /// WriteValue() instead of WriteValueSafe() for faster serialization.
+ ///
+ /// Unsafe write operations will throw OverflowException in editor and development builds if you
+ /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
+ /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
+ /// operations in release builds. Instead, attempting to write past the marked position in release builds
+ /// will write to random memory and cause undefined behavior, likely including instability and crashes.
+ ///
+ /// The value you want to write
+ /// True if the write is allowed, false otherwise
+ /// If called while in a bitwise context
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+ int len = sizeof(T);
+ if (PositionInternal + len > CapacityInternal)
+ {
+ if (PositionInternal + len > MaxCapacityInternal)
+ {
+ return false;
+ }
+ if (CapacityInternal < MaxCapacityInternal)
+ {
+ Grow(len);
+ }
+ else
+ {
+ return false;
+ }
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ AllowedWriteMark = PositionInternal + len;
+#endif
+ return true;
+ }
+
+ ///
+ /// Internal version of TryBeginWrite.
+ /// Differs from TryBeginWrite only in that it won't ever move the AllowedWriteMark backward.
+ ///
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryBeginWriteInternal(int bytes)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+ if (PositionInternal + bytes > CapacityInternal)
+ {
+ if (PositionInternal + bytes > MaxCapacityInternal)
+ {
+ return false;
+ }
+ if (CapacityInternal < MaxCapacityInternal)
+ {
+ Grow(bytes);
+ }
+ else
+ {
+ return false;
+ }
+ }
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (PositionInternal + bytes > AllowedWriteMark)
+ {
+ AllowedWriteMark = PositionInternal + bytes;
+ }
+#endif
+ return true;
+ }
+
+ ///
+ /// Returns an array representation of the underlying byte buffer.
+ /// !!Allocates a new array!!
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe byte[] ToArray()
+ {
+ byte[] ret = new byte[Length];
+ fixed (byte* b = ret)
+ {
+ UnsafeUtility.MemCpy(b, BufferPointer, Length);
+ }
+ return ret;
+ }
+
+ ///
+ /// Gets a direct pointer to the underlying buffer
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe byte* GetUnsafePtr()
+ {
+ return BufferPointer;
+ }
+
+ ///
+ /// Gets a direct pointer to the underlying buffer at the current read position
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe byte* GetUnsafePtrAtCurrentPosition()
+ {
+ return BufferPointer + PositionInternal;
+ }
+
+ ///
+ /// Get the required size to write a string
+ ///
+ /// The string to write
+ /// Whether or not to use one byte per character. This will only allow ASCII
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetWriteSize(string s, bool oneByteChars = false)
+ {
+ return sizeof(int) + s.Length * (oneByteChars ? sizeof(byte) : sizeof(char));
+ }
+
+ ///
+ /// Writes a string
+ ///
+ /// The string to write
+ /// Whether or not to use one byte per character. This will only allow ASCII
+ public unsafe void WriteValue(string s, bool oneByteChars = false)
+ {
+ WriteValue((uint)s.Length);
+ int target = s.Length;
+ if (oneByteChars)
+ {
+ for (int i = 0; i < target; ++i)
+ {
+ WriteByte((byte)s[i]);
+ }
+ }
+ else
+ {
+ fixed (char* native = s)
+ {
+ WriteBytes((byte*)native, target * sizeof(char));
+ }
+ }
+ }
+
+ ///
+ /// Writes a string
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// The string to write
+ /// Whether or not to use one byte per character. This will only allow ASCII
+ public unsafe void WriteValueSafe(string s, bool oneByteChars = false)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ int sizeInBytes = GetWriteSize(s, oneByteChars);
+
+ if (!TryBeginWriteInternal(sizeInBytes))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+
+ WriteValue((uint)s.Length);
+ int target = s.Length;
+ if (oneByteChars)
+ {
+ for (int i = 0; i < target; ++i)
+ {
+ WriteByte((byte)s[i]);
+ }
+ }
+ else
+ {
+ fixed (char* native = s)
+ {
+ WriteBytes((byte*)native, target * sizeof(char));
+ }
+ }
+ }
+
+ ///
+ /// Get the required size to write an unmanaged array
+ ///
+ /// The array to write
+ /// The amount of elements to write
+ /// Where in the array to start
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe int GetWriteSize(T[] array, int count = -1, int offset = 0) where T : unmanaged
+ {
+ int sizeInTs = count != -1 ? count : array.Length - offset;
+ int sizeInBytes = sizeInTs * sizeof(T);
+ return sizeof(int) + sizeInBytes;
+ }
+
+ ///
+ /// Writes an unmanaged array
+ ///
+ /// The array to write
+ /// The amount of elements to write
+ /// Where in the array to start
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteValue(T[] array, int count = -1, int offset = 0) where T : unmanaged
+ {
+ int sizeInTs = count != -1 ? count : array.Length - offset;
+ int sizeInBytes = sizeInTs * sizeof(T);
+ WriteValue(sizeInTs);
+ fixed (T* native = array)
+ {
+ byte* bytes = (byte*)(native + offset);
+ WriteBytes(bytes, sizeInBytes);
+ }
+ }
+
+ ///
+ /// Writes an unmanaged array
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// The array to write
+ /// The amount of elements to write
+ /// Where in the array to start
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteValueSafe(T[] array, int count = -1, int offset = 0) where T : unmanaged
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ int sizeInTs = count != -1 ? count : array.Length - offset;
+ int sizeInBytes = sizeInTs * sizeof(T);
+
+ if (!TryBeginWriteInternal(sizeInBytes + sizeof(int)))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ WriteValue(sizeInTs);
+ fixed (T* native = array)
+ {
+ byte* bytes = (byte*)(native + offset);
+ WriteBytes(bytes, sizeInBytes);
+ }
+ }
+
+ ///
+ /// Write a partial value. The specified number of bytes is written from the value and the rest is ignored.
+ ///
+ /// Value to write
+ /// Number of bytes
+ /// Offset into the value to begin reading the bytes
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + bytesToWrite > AllowedWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
+ }
+#endif
+
+ byte* ptr = ((byte*)&value) + offsetBytes;
+ byte* bufferPointer = BufferPointer + PositionInternal;
+ UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
+
+ PositionInternal += bytesToWrite;
+ }
+
+ ///
+ /// Write a byte to the stream.
+ ///
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteByte(byte value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + 1 > AllowedWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
+ }
+#endif
+ BufferPointer[PositionInternal++] = value;
+ }
+
+ ///
+ /// Write a byte to the stream.
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// Value to write
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteByteSafe(byte value)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginWriteInternal(1))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ BufferPointer[PositionInternal++] = value;
+ }
+
+ ///
+ /// Write multiple bytes to the stream
+ ///
+ /// Value to write
+ /// Number of bytes to write
+ /// Offset into the buffer to begin writing
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteBytes(byte* value, int size, int offset = 0)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + size > AllowedWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
+ }
+#endif
+ UnsafeUtility.MemCpy((BufferPointer + PositionInternal), value + offset, size);
+ PositionInternal += size;
+ }
+
+ ///
+ /// Write multiple bytes to the stream
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// Value to write
+ /// Number of bytes to write
+ /// Offset into the buffer to begin writing
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0)
+ {
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginWriteInternal(size))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ UnsafeUtility.MemCpy((BufferPointer + PositionInternal), value + offset, size);
+ PositionInternal += size;
+ }
+
+ ///
+ /// Write multiple bytes to the stream
+ ///
+ /// Value to write
+ /// Number of bytes to write
+ /// Offset into the buffer to begin writing
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteBytes(byte[] value, int size, int offset = 0)
+ {
+ fixed (byte* ptr = value)
+ {
+ WriteBytes(ptr, size, offset);
+ }
+ }
+
+ ///
+ /// Write multiple bytes to the stream
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// Value to write
+ /// Number of bytes to write
+ /// Offset into the buffer to begin writing
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteBytesSafe(byte[] value, int size, int offset = 0)
+ {
+ fixed (byte* ptr = value)
+ {
+ WriteBytesSafe(ptr, size, offset);
+ }
+ }
+
+ ///
+ /// Copy the contents of this writer into another writer.
+ /// The contents will be copied from the beginning of this writer to its current position.
+ /// They will be copied to the other writer starting at the other writer's current position.
+ ///
+ /// Writer to copy to
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void CopyTo(FastBufferWriter other)
+ {
+ other.WriteBytes(BufferPointer, PositionInternal);
+ }
+
+ ///
+ /// Copy the contents of another writer into this writer.
+ /// The contents will be copied from the beginning of the other writer to its current position.
+ /// They will be copied to this writer starting at this writer's current position.
+ ///
+ /// Writer to copy to
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void CopyFrom(FastBufferWriter other)
+ {
+ WriteBytes(other.BufferPointer, other.PositionInternal);
+ }
+
+ ///
+ /// Get the size required to write an unmanaged value
+ ///
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe int GetWriteSize(in T value) where T : unmanaged
+ {
+ return sizeof(T);
+ }
+
+ ///
+ /// Get the size required to write an unmanaged value of type T
+ ///
+ ///
+ ///
+ ///
+ public static unsafe int GetWriteSize() where T : unmanaged
+ {
+ return sizeof(T);
+ }
+
+ ///
+ /// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
+ /// It will be copied into the buffer exactly as it exists in memory.
+ ///
+ /// The value to copy
+ /// Any unmanaged type
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteValue(in T value) where T : unmanaged
+ {
+ int len = sizeof(T);
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+ if (PositionInternal + len > AllowedWriteMark)
+ {
+ throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
+ }
+#endif
+
+ fixed (T* ptr = &value)
+ {
+ UnsafeUtility.MemCpy(BufferPointer + PositionInternal, (byte*)ptr, len);
+ }
+ PositionInternal += len;
+ }
+
+ ///
+ /// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
+ /// It will be copied into the buffer exactly as it exists in memory.
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// The value to copy
+ /// Any unmanaged type
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void WriteValueSafe(in T value) where T : unmanaged
+ {
+ int len = sizeof(T);
+
+#if DEVELOPMENT_BUILD || UNITY_EDITOR
+ if (m_InBitwiseContext)
+ {
+ throw new InvalidOperationException(
+ "Cannot use BufferWriter in bytewise mode while in a bitwise context.");
+ }
+#endif
+
+ if (!TryBeginWriteInternal(len))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+
+ fixed (T* ptr = &value)
+ {
+ UnsafeUtility.MemCpy(BufferPointer + PositionInternal, (byte*)ptr, len);
+ }
+ PositionInternal += len;
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs.meta
new file mode 100644
index 0000000000..0c31b46524
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 819a511316a46104db673c8a0eab9e72
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs
new file mode 100644
index 0000000000..af269f811e
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs
@@ -0,0 +1,297 @@
+
+using System;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ public static class FastBufferWriterExtensions
+ {
+
+ ///
+ /// Writes a boxed object in a standard format
+ /// Named differently from other WriteValue methods to avoid accidental boxing
+ ///
+ /// The object to write
+ ///
+ /// If true, an extra byte will be written to indicate whether or not the value is null.
+ /// Some types will always write this.
+ ///
+ public static void WriteObject(this ref FastBufferWriter writer, object value, bool isNullable = false)
+ {
+ if (isNullable || value.GetType().IsNullable())
+ {
+ bool isNull = value == null || (value is UnityEngine.Object o && o == null);
+
+ writer.WriteValueSafe(isNull);
+
+ if (isNull)
+ {
+ return;
+ }
+ }
+
+ var type = value.GetType();
+ var hasSerializer = SerializationTypeTable.Serializers.TryGetValue(type, out var serializer);
+ if (hasSerializer)
+ {
+ serializer(ref writer, value);
+ return;
+ }
+
+ if (value is Array array)
+ {
+ writer.WriteValueSafe(array.Length);
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ writer.WriteObject(array.GetValue(i));
+ }
+
+ return;
+ }
+
+ if (value.GetType().IsEnum)
+ {
+ switch (Convert.GetTypeCode(value))
+ {
+ case TypeCode.Boolean:
+ writer.WriteValueSafe((byte)value);
+ break;
+ case TypeCode.Char:
+ writer.WriteValueSafe((char)value);
+ break;
+ case TypeCode.SByte:
+ writer.WriteValueSafe((sbyte)value);
+ break;
+ case TypeCode.Byte:
+ writer.WriteValueSafe((byte)value);
+ break;
+ case TypeCode.Int16:
+ writer.WriteValueSafe((short)value);
+ break;
+ case TypeCode.UInt16:
+ writer.WriteValueSafe((ushort)value);
+ break;
+ case TypeCode.Int32:
+ writer.WriteValueSafe((int)value);
+ break;
+ case TypeCode.UInt32:
+ writer.WriteValueSafe((uint)value);
+ break;
+ case TypeCode.Int64:
+ writer.WriteValueSafe((long)value);
+ break;
+ case TypeCode.UInt64:
+ writer.WriteValueSafe((ulong)value);
+ break;
+ }
+ return;
+ }
+ if (value is GameObject)
+ {
+ writer.WriteValueSafe((GameObject)value);
+ return;
+ }
+ if (value is NetworkObject)
+ {
+ writer.WriteValueSafe((NetworkObject)value);
+ return;
+ }
+ if (value is NetworkBehaviour)
+ {
+ writer.WriteValueSafe((NetworkBehaviour)value);
+ return;
+ }
+ if (value is INetworkSerializable)
+ {
+ //TODO ((INetworkSerializable)value).NetworkSerialize(new NetworkSerializer(this));
+ return;
+ }
+
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write type {value.GetType().Name} - it does not implement {nameof(INetworkSerializable)}");
+ }
+
+ ///
+ /// Write an INetworkSerializable
+ ///
+ /// The value to write
+ ///
+ public static void WriteNetworkSerializable(this ref FastBufferWriter writer, in T value) where T : INetworkSerializable
+ {
+ // TODO
+ }
+
+ ///
+ /// Get the required amount of space to write a GameObject
+ ///
+ ///
+ ///
+ public static int GetWriteSize(GameObject value)
+ {
+ return sizeof(ulong);
+ }
+
+ ///
+ /// Get the required amount of space to write a GameObject
+ ///
+ ///
+ public static int GetGameObjectWriteSize()
+ {
+ return sizeof(ulong);
+ }
+
+ ///
+ /// Write a GameObject
+ ///
+ /// The value to write
+ public static void WriteValue(this ref FastBufferWriter writer, GameObject value)
+ {
+ value.TryGetComponent(out var networkObject);
+ if (networkObject == null)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {(value).name}");
+ }
+
+ if (!networkObject.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {(value).name}");
+ }
+
+ writer.WriteValue(networkObject.NetworkObjectId);
+ }
+
+ ///
+ /// Write a GameObject
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// The value to write
+ public static void WriteValueSafe(this ref FastBufferWriter writer, GameObject value)
+ {
+ value.TryGetComponent(out var networkObject);
+ if (networkObject == null)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(GameObject)} types that does not has a {nameof(NetworkObject)} component attached. {nameof(GameObject)}: {(value).name}");
+ }
+
+ if (!networkObject.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {(value).name}");
+ }
+
+ writer.WriteValueSafe(networkObject.NetworkObjectId);
+ }
+
+ ///
+ /// Get the required size to write a NetworkObject
+ ///
+ ///
+ ///
+ public static int GetWriteSize(NetworkObject value)
+ {
+ return sizeof(ulong);
+ }
+
+ ///
+ /// Get the required size to write a NetworkObject
+ ///
+ ///
+ public static int GetNetworkObjectWriteSize()
+ {
+ return sizeof(ulong);
+ }
+
+
+ ///
+ /// Write a NetworkObject
+ ///
+ /// The value to write
+ public static void WriteValue(this ref FastBufferWriter writer, in NetworkObject value)
+ {
+ if (!value.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {value.name}");
+ }
+
+ writer.WriteValue(value.NetworkObjectId);
+ }
+
+ ///
+ /// Write a NetworkObject
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// The value to write
+ public static void WriteValueSafe(this ref FastBufferWriter writer, NetworkObject value)
+ {
+ if (!value.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkObject)} types that are not spawned. {nameof(GameObject)}: {value.name}");
+ }
+ writer.WriteValueSafe(value.NetworkObjectId);
+ }
+
+ ///
+ /// Get the required size to write a NetworkBehaviour
+ ///
+ ///
+ ///
+ public static int GetWriteSize(NetworkBehaviour value)
+ {
+ return sizeof(ulong) + sizeof(ushort);
+ }
+
+
+ ///
+ /// Get the required size to write a NetworkBehaviour
+ ///
+ ///
+ public static int GetNetworkBehaviourWriteSize()
+ {
+ return sizeof(ulong) + sizeof(ushort);
+ }
+
+
+ ///
+ /// Write a NetworkBehaviour
+ ///
+ /// The value to write
+ public static void WriteValue(this ref FastBufferWriter writer, NetworkBehaviour value)
+ {
+ if (!value.HasNetworkObject || !value.NetworkObject.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {(value).gameObject.name}");
+ }
+
+ writer.WriteValue(value.NetworkObjectId);
+ writer.WriteValue(value.NetworkBehaviourId);
+ }
+
+ ///
+ /// Write a NetworkBehaviour
+ ///
+ /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
+ /// for multiple writes at once by calling TryBeginWrite.
+ ///
+ /// The value to write
+ ///
+ ///
+ public static void WriteValueSafe(this ref FastBufferWriter writer, NetworkBehaviour value)
+ {
+ if (!value.HasNetworkObject || !value.NetworkObject.IsSpawned)
+ {
+ throw new ArgumentException($"{nameof(FastBufferWriter)} cannot write {nameof(NetworkBehaviour)} types that are not spawned. {nameof(GameObject)}: {(value).gameObject.name}");
+ }
+
+ if (!writer.TryBeginWriteInternal(sizeof(ulong) + sizeof(ushort)))
+ {
+ throw new OverflowException("Writing past the end of the buffer");
+ }
+ writer.WriteValue(value.NetworkObjectId);
+ writer.WriteValue(value.NetworkBehaviourId);
+ }
+
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs.meta
new file mode 100644
index 0000000000..b96c1f3b21
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriterExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1a9dd964797964b4a99e03706516a8a9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs
new file mode 100644
index 0000000000..d97954c243
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs
@@ -0,0 +1,38 @@
+using System;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ public interface IBufferSerializerImplementation
+ {
+ bool IsReader { get; }
+ bool IsWriter { get; }
+
+ ref FastBufferReader GetFastBufferReader();
+ ref FastBufferWriter GetFastBufferWriter();
+
+ void SerializeValue(ref object value, Type type, bool isNullable = false);
+ void SerializeValue(ref INetworkSerializable value);
+ void SerializeValue(ref GameObject value);
+ void SerializeValue(ref NetworkObject value);
+ void SerializeValue(ref NetworkBehaviour value);
+ void SerializeValue(ref string s, bool oneByteChars = false);
+ void SerializeValue(ref T[] array) where T : unmanaged;
+ void SerializeValue(ref byte value);
+ void SerializeValue(ref T value) where T : unmanaged;
+
+ // Has to have a different name to avoid conflicting with "where T: unmananged"
+ // Using SerializeValue(INetworkSerializable) will result in boxing on struct INetworkSerializables
+ // So this is provided as an alternative to avoid boxing allocations.
+ void SerializeNetworkSerializable(ref T value) where T : INetworkSerializable;
+
+ bool PreCheck(int amount);
+ void SerializeValuePreChecked(ref GameObject value);
+ void SerializeValuePreChecked(ref NetworkObject value);
+ void SerializeValuePreChecked(ref NetworkBehaviour value);
+ void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
+ void SerializeValuePreChecked(ref T[] array) where T : unmanaged;
+ void SerializeValuePreChecked(ref byte value);
+ void SerializeValuePreChecked(ref T value) where T : unmanaged;
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs.meta
new file mode 100644
index 0000000000..f5c741d324
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/IBufferSerializerImplementation.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9220f80eab4051e4d9b9504ef840eba4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs
new file mode 100644
index 0000000000..fb861c91d7
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs
@@ -0,0 +1,449 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ ///
+ /// Registry for telling FastBufferWriter and FastBufferReader how to read types when passed to
+ /// WriteObject and ReadObject, as well as telling BytePacker and ByteUnpacker how to do it when passed to
+ /// WriteObjectPacked and ReadObjectPacked.
+ ///
+ /// These object-based serialization functions shouldn't be used if at all possible, but if they're required,
+ /// and you need to serialize a type that's not natively supported, you can register it with the dictionaries here:
+ ///
+ /// Serializers and Deserializers for FastBufferWriter and FasteBufferReader
+ /// SerializersPacked and DeserializersPacked for BytePacker and ByteUnpacker
+ ///
+ public static class SerializationTypeTable
+ {
+ public delegate void Serialize(ref FastBufferWriter writer, object value);
+ public delegate void Deserialize(ref FastBufferReader reader, out object value);
+
+ public static Dictionary Serializers = new Dictionary
+ {
+ [typeof(byte)] = (ref FastBufferWriter writer, object value) => writer.WriteByteSafe((byte)value),
+ [typeof(sbyte)] = (ref FastBufferWriter writer, object value) => writer.WriteByteSafe((byte)(sbyte)value),
+
+ [typeof(ushort)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ushort)value),
+ [typeof(short)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((short)value),
+ [typeof(uint)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((uint)value),
+ [typeof(int)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((int)value),
+ [typeof(ulong)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ulong)value),
+ [typeof(long)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((long)value),
+
+ [typeof(float)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((float)value),
+ [typeof(double)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((double)value),
+
+ [typeof(string)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((string)value),
+
+ [typeof(Vector2)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector2)value),
+ [typeof(Vector3)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector3)value),
+ [typeof(Vector4)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector4)value),
+ [typeof(Color)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color)value),
+ [typeof(Color32)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color32)value),
+ [typeof(Ray)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray)value),
+ [typeof(Ray2D)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray2D)value),
+ [typeof(Quaternion)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Quaternion)value),
+
+ [typeof(char)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((char)value),
+
+ [typeof(bool)] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((bool)value),
+
+
+ [typeof(byte[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((byte[])value),
+ [typeof(sbyte[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((sbyte[])value),
+
+ [typeof(ushort[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ushort[])value),
+ [typeof(short[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((short[])value),
+ [typeof(uint[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((uint[])value),
+ [typeof(int[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((int[])value),
+ [typeof(ulong[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((ulong[])value),
+ [typeof(long[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((long[])value),
+
+ [typeof(float[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((float[])value),
+ [typeof(double[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((double[])value),
+
+ [typeof(Vector2[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector2[])value),
+ [typeof(Vector3[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector3[])value),
+ [typeof(Vector4[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Vector4[])value),
+ [typeof(Color[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color[])value),
+ [typeof(Color32[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Color32[])value),
+ [typeof(Ray[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray[])value),
+ [typeof(Ray2D[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Ray2D[])value),
+ [typeof(Quaternion[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((Quaternion[])value),
+
+ [typeof(char[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((char[])value),
+
+ [typeof(bool[])] = (ref FastBufferWriter writer, object value) => writer.WriteValueSafe((bool[])value),
+ };
+
+ public static Dictionary Deserializers = new Dictionary
+ {
+ [typeof(byte)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadByteSafe(out byte tmp);
+ value = tmp;
+ },
+ [typeof(sbyte)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadByteSafe(out byte tmp);
+ value = (sbyte)tmp;
+ },
+
+ [typeof(ushort)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out ushort tmp);
+ value = tmp;
+ },
+ [typeof(short)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out short tmp);
+ value = tmp;
+ },
+ [typeof(uint)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out uint tmp);
+ value = tmp;
+ },
+ [typeof(int)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out int tmp);
+ value = tmp;
+ },
+ [typeof(ulong)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out ulong tmp);
+ value = tmp;
+ },
+ [typeof(long)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out long tmp);
+ value = tmp;
+ },
+
+ [typeof(float)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out float tmp);
+ value = tmp;
+ },
+ [typeof(double)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out double tmp);
+ value = tmp;
+ },
+
+ [typeof(string)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out string tmp);
+ value = tmp;
+ },
+
+ [typeof(Vector2)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Vector2 tmp);
+ value = tmp;
+ },
+ [typeof(Vector3)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Vector3 tmp);
+ value = tmp;
+ },
+ [typeof(Vector4)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Vector4 tmp);
+ value = tmp;
+ },
+ [typeof(Color)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Color tmp);
+ value = tmp;
+ },
+ [typeof(Color32)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Color32 tmp);
+ value = tmp;
+ },
+ [typeof(Ray)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Ray tmp);
+ value = tmp;
+ },
+ [typeof(Ray2D)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Ray2D tmp);
+ value = tmp;
+ },
+ [typeof(Quaternion)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Quaternion tmp);
+ value = tmp;
+ },
+
+ [typeof(char)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out char tmp);
+ value = tmp;
+ },
+
+ [typeof(bool)] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out bool tmp);
+ value = tmp;
+ },
+
+
+ [typeof(byte[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out byte[] tmp);
+ value = tmp;
+ },
+ [typeof(sbyte[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out sbyte[] tmp);
+ value = tmp;
+ },
+
+ [typeof(ushort[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out ushort[] tmp);
+ value = tmp;
+ },
+ [typeof(short[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out short[] tmp);
+ value = tmp;
+ },
+ [typeof(uint[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out uint[] tmp);
+ value = tmp;
+ },
+ [typeof(int[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out int[] tmp);
+ value = tmp;
+ },
+ [typeof(ulong[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out ulong[] tmp);
+ value = tmp;
+ },
+ [typeof(long[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out long[] tmp);
+ value = tmp;
+ },
+
+ [typeof(float[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out float[] tmp);
+ value = tmp;
+ },
+ [typeof(double[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out double[] tmp);
+ value = tmp;
+ },
+
+ [typeof(Vector2[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Vector2[] tmp);
+ value = tmp;
+ },
+ [typeof(Vector3[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Vector3[] tmp);
+ value = tmp;
+ },
+ [typeof(Vector4[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Vector4[] tmp);
+ value = tmp;
+ },
+ [typeof(Color[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Color[] tmp);
+ value = tmp;
+ },
+ [typeof(Color32[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Color32[] tmp);
+ value = tmp;
+ },
+ [typeof(Ray[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Ray[] tmp);
+ value = tmp;
+ },
+ [typeof(Ray2D[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Ray2D[] tmp);
+ value = tmp;
+ },
+ [typeof(Quaternion[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out Quaternion[] tmp);
+ value = tmp;
+ },
+
+ [typeof(char[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out char[] tmp);
+ value = tmp;
+ },
+
+ [typeof(bool[])] = (ref FastBufferReader reader, out object value) =>
+ {
+ reader.ReadValueSafe(out bool[] tmp);
+ value = tmp;
+ },
+ };
+
+ public static Dictionary SerializersPacked = new Dictionary
+ {
+ [typeof(byte)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (byte)value),
+ [typeof(sbyte)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (byte)(sbyte)value),
+
+ [typeof(ushort)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (ushort)value),
+ [typeof(short)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (short)value),
+ [typeof(uint)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (uint)value),
+ [typeof(int)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (int)value),
+ [typeof(ulong)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (ulong)value),
+ [typeof(long)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (long)value),
+
+ [typeof(float)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (float)value),
+ [typeof(double)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (double)value),
+
+ [typeof(string)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (string)value),
+
+ [typeof(Vector2)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Vector2)value),
+ [typeof(Vector3)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Vector3)value),
+ [typeof(Vector4)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Vector4)value),
+ [typeof(Color)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Color)value),
+ [typeof(Color32)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Color32)value),
+ [typeof(Ray)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Ray)value),
+ [typeof(Ray2D)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Ray2D)value),
+ [typeof(Quaternion)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (Quaternion)value),
+
+ [typeof(char)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (char)value),
+
+ [typeof(bool)] = (ref FastBufferWriter writer, object value) => BytePacker.WriteValuePacked(ref writer, (bool)value),
+ };
+
+ public static Dictionary DeserializersPacked = new Dictionary
+ {
+ [typeof(byte)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out byte tmp);
+ value = tmp;
+ },
+ [typeof(sbyte)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out byte tmp);
+ value = (sbyte)tmp;
+ },
+
+ [typeof(ushort)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out ushort tmp);
+ value = tmp;
+ },
+ [typeof(short)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out short tmp);
+ value = tmp;
+ },
+ [typeof(uint)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out uint tmp);
+ value = tmp;
+ },
+ [typeof(int)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out int tmp);
+ value = tmp;
+ },
+ [typeof(ulong)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out ulong tmp);
+ value = tmp;
+ },
+ [typeof(long)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out long tmp);
+ value = tmp;
+ },
+
+ [typeof(float)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out float tmp);
+ value = tmp;
+ },
+ [typeof(double)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out double tmp);
+ value = tmp;
+ },
+
+ [typeof(string)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out string tmp);
+ value = tmp;
+ },
+
+ [typeof(Vector2)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Vector2 tmp);
+ value = tmp;
+ },
+ [typeof(Vector3)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Vector3 tmp);
+ value = tmp;
+ },
+ [typeof(Vector4)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Vector4 tmp);
+ value = tmp;
+ },
+ [typeof(Color)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Color tmp);
+ value = tmp;
+ },
+ [typeof(Color32)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Color32 tmp);
+ value = tmp;
+ },
+ [typeof(Ray)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Ray tmp);
+ value = tmp;
+ },
+ [typeof(Ray2D)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Ray2D tmp);
+ value = tmp;
+ },
+ [typeof(Quaternion)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Quaternion tmp);
+ value = tmp;
+ },
+
+ [typeof(char)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out char tmp);
+ value = tmp;
+ },
+
+ [typeof(bool)] = (ref FastBufferReader reader, out object value) =>
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out bool tmp);
+ value = tmp;
+ },
+ };
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs.meta
new file mode 100644
index 0000000000..58c25d1646
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Serialization/SerializationTypeTable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 49ee6a0e2ea6e9441a74b173c31cf389
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Utility.meta b/com.unity.netcode.gameobjects/Runtime/Utility.meta
new file mode 100644
index 0000000000..a71098491b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Utility.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 07815748c1ee48a69d6981ec990575ed
+timeCreated: 1629412677
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs
new file mode 100644
index 0000000000..02572cbc46
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs
@@ -0,0 +1,25 @@
+using System.Runtime.CompilerServices;
+
+namespace Unity.Netcode
+{
+ public struct Ref where T : unmanaged
+ {
+ private unsafe T* m_Value;
+
+ public unsafe Ref(ref T value)
+ {
+ fixed (T* ptr = &value)
+ {
+ m_Value = ptr;
+ }
+ }
+
+ public unsafe bool IsSet => m_Value != null;
+
+ public unsafe ref T Value
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref *m_Value;
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs.meta b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs.meta
new file mode 100644
index 0000000000..56afe0844f
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Utility/Ref.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 00adc41a9c8699349a50dba23dbd46a2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization.meta
new file mode 100644
index 0000000000..b3c7beee0b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e12c4be6e89f459aa2826abba8c8d301
+timeCreated: 1628799671
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs
new file mode 100644
index 0000000000..fa067d73a6
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs
@@ -0,0 +1,664 @@
+using System;
+using NUnit.Framework;
+using Unity.Netcode.EditorTests;
+using UnityEngine;
+using Random = System.Random;
+
+namespace Unity.Netcode
+{
+ public abstract class BaseFastBufferReaderWriterTest
+ {
+
+ #region Test Types
+ protected enum ByteEnum : byte
+ {
+ A,
+ B,
+ C
+ };
+ protected enum SByteEnum : sbyte
+ {
+ A,
+ B,
+ C
+ };
+ protected enum ShortEnum : short
+ {
+ A,
+ B,
+ C
+ };
+ protected enum UShortEnum : ushort
+ {
+ A,
+ B,
+ C
+ };
+ protected enum IntEnum : int
+ {
+ A,
+ B,
+ C
+ };
+ protected enum UIntEnum : uint
+ {
+ A,
+ B,
+ C
+ };
+ protected enum LongEnum : long
+ {
+ A,
+ B,
+ C
+ };
+ protected enum ULongEnum : ulong
+ {
+ A,
+ B,
+ C
+ };
+
+ protected struct TestStruct
+ {
+ public byte A;
+ public short B;
+ public ushort C;
+ public int D;
+ public uint E;
+ public long F;
+ public ulong G;
+ public bool H;
+ public char I;
+ public float J;
+ public double K;
+ }
+
+ public enum WriteType
+ {
+ WriteDirect,
+ WriteSafe,
+ WriteAsObject
+ }
+ #endregion
+
+
+ protected abstract void RunTypeTest(T valueToTest) where T : unmanaged;
+
+ protected abstract void RunTypeTestSafe(T valueToTest) where T : unmanaged;
+
+ protected abstract void RunObjectTypeTest(T valueToTest) where T : unmanaged;
+
+ protected abstract void RunTypeArrayTest(T[] valueToTest) where T : unmanaged;
+
+ protected abstract void RunTypeArrayTestSafe(T[] valueToTest) where T : unmanaged;
+
+ protected abstract void RunObjectTypeArrayTest(T[] valueToTest) where T : unmanaged;
+
+ #region Helpers
+ protected TestStruct GetTestStruct()
+ {
+ var random = new Random();
+
+ var testStruct = new TestStruct
+ {
+ A = (byte)random.Next(),
+ B = (short)random.Next(),
+ C = (ushort)random.Next(),
+ D = (int)random.Next(),
+ E = (uint)random.Next(),
+ F = ((long)random.Next() << 32) + random.Next(),
+ G = ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ H = true,
+ I = '\u263a',
+ J = (float)random.NextDouble(),
+ K = random.NextDouble(),
+ };
+
+ return testStruct;
+ }
+
+ protected delegate void GameObjectTestDelegate(GameObject obj, NetworkBehaviour networkBehaviour,
+ NetworkObject networkObject);
+ protected void RunGameObjectTest(GameObjectTestDelegate testCode)
+ {
+ var obj = new GameObject("Object");
+ var networkBehaviour = obj.AddComponent();
+ var networkObject = obj.AddComponent();
+ // Create networkManager component
+ var networkManager = obj.AddComponent();
+ networkManager.SetSingleton();
+ networkObject.NetworkManagerOwner = networkManager;
+
+ // Set the NetworkConfig
+ networkManager.NetworkConfig = new NetworkConfig()
+ {
+ // Set transport
+ NetworkTransport = obj.AddComponent()
+ };
+
+ networkManager.StartHost();
+
+ try
+ {
+ testCode(obj, networkBehaviour, networkObject);
+ }
+ finally
+ {
+ UnityEngine.Object.DestroyImmediate(obj);
+ networkManager.Shutdown();
+ }
+ }
+ #endregion
+
+ public void BaseTypeTest(Type testType, WriteType writeType)
+ {
+ var random = new Random();
+
+ void RunTypeTestLocal(T val, WriteType wt) where T : unmanaged
+ {
+ switch (wt)
+ {
+ case WriteType.WriteDirect:
+ RunTypeTest(val);
+ break;
+ case WriteType.WriteSafe:
+ RunTypeTestSafe(val);
+ break;
+ default:
+ RunObjectTypeTest(val);
+ break;
+ }
+ }
+
+ if (testType == typeof(byte))
+ {
+ RunTypeTestLocal((byte)random.Next(), writeType);
+ }
+ else if (testType == typeof(sbyte))
+ {
+ RunTypeTestLocal((sbyte)random.Next(), writeType);
+ }
+ else if (testType == typeof(short))
+ {
+ RunTypeTestLocal((short)random.Next(), writeType);
+ }
+ else if (testType == typeof(ushort))
+ {
+ RunTypeTestLocal((ushort)random.Next(), writeType);
+ }
+ else if (testType == typeof(int))
+ {
+ RunTypeTestLocal((int)random.Next(), writeType);
+ }
+ else if (testType == typeof(uint))
+ {
+ RunTypeTestLocal((uint)random.Next(), writeType);
+ }
+ else if (testType == typeof(long))
+ {
+ RunTypeTestLocal(((long)random.Next() << 32) + random.Next(), writeType);
+ }
+ else if (testType == typeof(ulong))
+ {
+ RunTypeTestLocal(((ulong)random.Next() << 32) + (ulong)random.Next(), writeType);
+ }
+ else if (testType == typeof(bool))
+ {
+ RunTypeTestLocal(true, writeType);
+ }
+ else if (testType == typeof(char))
+ {
+ RunTypeTestLocal('a', writeType);
+ RunTypeTestLocal('\u263a', writeType);
+ }
+ else if (testType == typeof(float))
+ {
+ RunTypeTestLocal((float)random.NextDouble(), writeType);
+ }
+ else if (testType == typeof(double))
+ {
+ RunTypeTestLocal(random.NextDouble(), writeType);
+ }
+ else if (testType == typeof(ByteEnum))
+ {
+ RunTypeTestLocal(ByteEnum.C, writeType);
+ }
+ else if (testType == typeof(SByteEnum))
+ {
+ RunTypeTestLocal(SByteEnum.C, writeType);
+ }
+ else if (testType == typeof(ShortEnum))
+ {
+ RunTypeTestLocal(ShortEnum.C, writeType);
+ }
+ else if (testType == typeof(UShortEnum))
+ {
+ RunTypeTestLocal(UShortEnum.C, writeType);
+ }
+ else if (testType == typeof(IntEnum))
+ {
+ RunTypeTestLocal(IntEnum.C, writeType);
+ }
+ else if (testType == typeof(UIntEnum))
+ {
+ RunTypeTestLocal(UIntEnum.C, writeType);
+ }
+ else if (testType == typeof(LongEnum))
+ {
+ RunTypeTestLocal(LongEnum.C, writeType);
+ }
+ else if (testType == typeof(ULongEnum))
+ {
+ RunTypeTestLocal(ULongEnum.C, writeType);
+ }
+ else if (testType == typeof(Vector2))
+ {
+ RunTypeTestLocal(new Vector2((float)random.NextDouble(), (float)random.NextDouble()), writeType);
+ }
+ else if (testType == typeof(Vector3))
+ {
+ RunTypeTestLocal(new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
+ }
+ else if (testType == typeof(Vector4))
+ {
+ RunTypeTestLocal(new Vector4((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
+ }
+ else if (testType == typeof(Quaternion))
+ {
+ RunTypeTestLocal(new Quaternion((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
+ }
+ else if (testType == typeof(Color))
+ {
+ RunTypeTestLocal(new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()), writeType);
+ }
+ else if (testType == typeof(Color32))
+ {
+ RunTypeTestLocal(new Color32((byte)random.Next(), (byte)random.Next(), (byte)random.Next(), (byte)random.Next()), writeType);
+ }
+ else if (testType == typeof(Ray))
+ {
+ RunTypeTestLocal(new Ray(
+ new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()),
+ new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())), writeType);
+ }
+ else if (testType == typeof(Ray2D))
+ {
+ RunTypeTestLocal(new Ray2D(
+ new Vector2((float)random.NextDouble(), (float)random.NextDouble()),
+ new Vector2((float)random.NextDouble(), (float)random.NextDouble())), writeType);
+ }
+ else if (testType == typeof(TestStruct))
+ {
+ SerializationTypeTable.Serializers[typeof(TestStruct)] = (ref FastBufferWriter writer, object obj) =>
+ {
+ writer.WriteValueSafe((TestStruct)obj);
+ };
+ SerializationTypeTable.Deserializers[typeof(TestStruct)] = (ref FastBufferReader reader, out object obj) =>
+ {
+ reader.ReadValueSafe(out TestStruct value);
+ obj = value;
+ };
+ try
+ {
+ RunTypeTestLocal(GetTestStruct(), writeType);
+ }
+ finally
+ {
+ SerializationTypeTable.Serializers.Remove(typeof(TestStruct));
+ SerializationTypeTable.Deserializers.Remove(typeof(TestStruct));
+ }
+ }
+ else
+ {
+ Assert.Fail("No type handler was provided for this type in the test!");
+ }
+ }
+
+ public void BaseArrayTypeTest(Type testType, WriteType writeType)
+ {
+ var random = new Random();
+ void RunTypeTestLocal(T[] val, WriteType wt) where T : unmanaged
+ {
+ switch (wt)
+ {
+ case WriteType.WriteDirect:
+ RunTypeArrayTest(val);
+ break;
+ case WriteType.WriteSafe:
+ RunTypeArrayTestSafe(val);
+ break;
+ default:
+ RunObjectTypeArrayTest(val);
+ break;
+ }
+ }
+
+ if (testType == typeof(byte))
+ {
+ RunTypeTestLocal(new[]{
+ (byte) random.Next(),
+ (byte) random.Next(),
+ (byte) random.Next(),
+ (byte) random.Next(),
+ (byte) random.Next(),
+ (byte) random.Next(),
+ (byte) random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(sbyte))
+ {
+ RunTypeTestLocal(new[]{
+ (sbyte) random.Next(),
+ (sbyte) random.Next(),
+ (sbyte) random.Next(),
+ (sbyte) random.Next(),
+ (sbyte) random.Next(),
+ (sbyte) random.Next(),
+ (sbyte) random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(short))
+ {
+ RunTypeTestLocal(new[]{
+ (short) random.Next(),
+ (short) random.Next(),
+ (short) random.Next(),
+ (short) random.Next(),
+ (short) random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(ushort))
+ {
+ RunTypeTestLocal(new[]{
+ (ushort) random.Next(),
+ (ushort) random.Next(),
+ (ushort) random.Next(),
+ (ushort) random.Next(),
+ (ushort) random.Next(),
+ (ushort) random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(int))
+ {
+ RunTypeTestLocal(new[]{
+ random.Next(),
+ random.Next(),
+ random.Next(),
+ random.Next(),
+ random.Next(),
+ random.Next(),
+ random.Next(),
+ random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(uint))
+ {
+ RunTypeTestLocal(new[]{
+ (uint) random.Next(),
+ (uint) random.Next(),
+ (uint) random.Next(),
+ (uint) random.Next(),
+ (uint) random.Next(),
+ (uint) random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(long))
+ {
+ RunTypeTestLocal(new[]{
+ ((long)random.Next() << 32) + (long)random.Next(),
+ ((long)random.Next() << 32) + (long)random.Next(),
+ ((long)random.Next() << 32) + (long)random.Next(),
+ ((long)random.Next() << 32) + (long)random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(ulong))
+ {
+ RunTypeTestLocal(new[]{
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next(),
+ ((ulong)random.Next() << 32) + (ulong)random.Next()
+ }, writeType);
+ }
+ else if (testType == typeof(bool))
+ {
+ RunTypeTestLocal(new[]{
+ true,
+ false,
+ true,
+ true,
+ false,
+ false,
+ true,
+ false,
+ true
+ }, writeType);
+ }
+ else if (testType == typeof(char))
+ {
+ RunTypeTestLocal(new[]{
+ 'a',
+ '\u263a'
+ }, writeType);
+ }
+ else if (testType == typeof(float))
+ {
+ RunTypeTestLocal(new[]{
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble(),
+ (float)random.NextDouble()
+ }, writeType);
+ }
+ else if (testType == typeof(double))
+ {
+ RunTypeTestLocal(new[]{
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble(),
+ random.NextDouble()
+ }, writeType);
+ }
+ else if (testType == typeof(ByteEnum))
+ {
+ RunTypeTestLocal(new[]{
+ ByteEnum.C,
+ ByteEnum.A,
+ ByteEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(SByteEnum))
+ {
+ RunTypeTestLocal(new[]{
+ SByteEnum.C,
+ SByteEnum.A,
+ SByteEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(ShortEnum))
+ {
+ RunTypeTestLocal(new[]{
+ ShortEnum.C,
+ ShortEnum.A,
+ ShortEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(UShortEnum))
+ {
+ RunTypeTestLocal(new[]{
+ UShortEnum.C,
+ UShortEnum.A,
+ UShortEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(IntEnum))
+ {
+ RunTypeTestLocal(new[]{
+ IntEnum.C,
+ IntEnum.A,
+ IntEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(UIntEnum))
+ {
+ RunTypeTestLocal(new[]{
+ UIntEnum.C,
+ UIntEnum.A,
+ UIntEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(LongEnum))
+ {
+ RunTypeTestLocal(new[]{
+ LongEnum.C,
+ LongEnum.A,
+ LongEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(ULongEnum))
+ {
+ RunTypeTestLocal(new[]{
+ ULongEnum.C,
+ ULongEnum.A,
+ ULongEnum.B
+ }, writeType);
+ }
+ else if (testType == typeof(Vector2))
+ {
+ RunTypeTestLocal(new[]{
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble()),
+ }, writeType);
+ }
+ else if (testType == typeof(Vector3))
+ {
+ RunTypeTestLocal(new[]{
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble()),
+ }, writeType);
+ }
+ else if (testType == typeof(Vector4))
+ {
+ RunTypeTestLocal(new[]{
+ new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Vector4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ }, writeType);
+ }
+ else if (testType == typeof(Quaternion))
+ {
+ RunTypeTestLocal(new[]{
+ new Quaternion((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble(), (float) random.NextDouble()),
+ new Quaternion((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble(), (float) random.NextDouble()),
+ new Quaternion((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble(), (float) random.NextDouble()),
+ }, writeType);
+ }
+ else if (testType == typeof(Color))
+ {
+ RunTypeTestLocal(new[]{
+ new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Color((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ }, writeType);
+ }
+ else if (testType == typeof(Color32))
+ {
+ RunTypeTestLocal(new[]{
+ new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()),
+ new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()),
+ new Color32((byte) random.Next(), (byte) random.Next(), (byte) random.Next(), (byte) random.Next()),
+ }, writeType);
+ }
+ else if (testType == typeof(Ray))
+ {
+ RunTypeTestLocal(new[]{
+ new Ray(
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble())),
+ new Ray(
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble())),
+ new Ray(
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble()),
+ new Vector3((float) random.NextDouble(), (float) random.NextDouble(),
+ (float) random.NextDouble())),
+ }, writeType);
+ }
+ else if (testType == typeof(Ray2D))
+ {
+ RunTypeTestLocal(new[]{
+ new Ray2D(
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble())),
+ new Ray2D(
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble())),
+ new Ray2D(
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble()),
+ new Vector2((float) random.NextDouble(), (float) random.NextDouble())),
+ }, writeType);
+ }
+ else if (testType == typeof(TestStruct))
+ {
+ SerializationTypeTable.Serializers[typeof(TestStruct)] = (ref FastBufferWriter writer, object obj) =>
+ {
+ writer.WriteValueSafe((TestStruct)obj);
+ };
+ SerializationTypeTable.Deserializers[typeof(TestStruct)] = (ref FastBufferReader reader, out object obj) =>
+ {
+ reader.ReadValueSafe(out TestStruct value);
+ obj = value;
+ };
+ try
+ {
+ RunTypeTestLocal(new[] {
+ GetTestStruct(),
+ GetTestStruct(),
+ GetTestStruct(),
+ }, writeType);
+ }
+ finally
+ {
+ SerializationTypeTable.Serializers.Remove(typeof(TestStruct));
+ SerializationTypeTable.Deserializers.Remove(typeof(TestStruct));
+ }
+ }
+ else
+ {
+ Assert.Fail("No type handler was provided for this type in the test!");
+ }
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs.meta
new file mode 100644
index 0000000000..f0b683d274
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 573b1f36caed496a9c6e0eaa788d0c29
+timeCreated: 1629917174
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs
new file mode 100644
index 0000000000..5e0f4c8a12
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs
@@ -0,0 +1,71 @@
+using NUnit.Framework;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class BitCounterTests
+ {
+ [Test]
+ public void WhenCountingUsedBitsIn64BitValue_ResultMatchesHighBitSetPlusOne([Range(0, 63)] int highBit)
+ {
+ if (highBit == 0)
+ {
+ ulong value = 0;
+ // 0 is a special case. All values are considered at least 1 bit.
+ Assert.AreEqual(1, BitCounter.GetUsedBitCount(value));
+ }
+ else
+ {
+ ulong value = 1UL << highBit;
+ Assert.AreEqual(highBit + 1, BitCounter.GetUsedBitCount(value));
+ }
+ }
+
+ [Test]
+ public void WhenCountingUsedBitsIn32BitValue_ResultMatchesHighBitSetPlusOne([Range(0, 31)] int highBit)
+ {
+ if (highBit == 0)
+ {
+ uint value = 0;
+ // 0 is a special case. All values are considered at least 1 bit.
+ Assert.AreEqual(1, BitCounter.GetUsedBitCount(value));
+ }
+ else
+ {
+ uint value = 1U << highBit;
+ Assert.AreEqual(highBit + 1, BitCounter.GetUsedBitCount(value));
+ }
+ }
+
+ [Test]
+ public void WhenCountingUsedBytesIn64BitValue_ResultMatchesHighBitSetOver8PlusOne([Range(0, 63)] int highBit)
+ {
+ if (highBit == 0)
+ {
+ ulong value = 0;
+ // 0 is a special case. All values are considered at least 1 byte.
+ Assert.AreEqual(1, BitCounter.GetUsedByteCount(value));
+ }
+ else
+ {
+ ulong value = 1UL << highBit;
+ Assert.AreEqual(highBit / 8 + 1, BitCounter.GetUsedByteCount(value));
+ }
+ }
+
+ [Test]
+ public void WhenCountingUsedBytesIn32BitValue_ResultMatchesHighBitSetOver8PlusOne([Range(0, 31)] int highBit)
+ {
+ if (highBit == 0)
+ {
+ uint value = 0;
+ // 0 is a special case. All values are considered at least 1 byte.
+ Assert.AreEqual(1, BitCounter.GetUsedByteCount(value));
+ }
+ else
+ {
+ uint value = 1U << highBit;
+ Assert.AreEqual(highBit / 8 + 1, BitCounter.GetUsedByteCount(value));
+ }
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs.meta
new file mode 100644
index 0000000000..64a137560b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 76e459b9c2aeea94ebf448c237061485
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs
new file mode 100644
index 0000000000..e8c4cf3f29
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs
@@ -0,0 +1,359 @@
+using System;
+using NUnit.Framework;
+using Unity.Collections;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class BitReaderTests
+ {
+ [Test]
+ public void TestReadingOneBit()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(3));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBit(true);
+
+ bitWriter.WriteBit(true);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ }
+
+ writer.WriteByte(0b11111111);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(3));
+ using (var bitReader = reader.EnterBitwiseContext())
+ {
+ bool b;
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+ }
+
+ reader.ReadByte(out byte lastByte);
+ Assert.AreEqual(0b11111111, lastByte);
+ }
+ }
+ }
+ [Test]
+ public unsafe void TestTryBeginReadBits()
+ {
+ var nativeArray = new NativeArray(4, Allocator.Temp);
+ var reader = new FastBufferReader(nativeArray, Allocator.Temp);
+ nativeArray.Dispose();
+ using (reader)
+ {
+ int* asInt = (int*)reader.GetUnsafePtr();
+ *asInt = 0b11111111_00001010_10101011;
+
+ using (var bitReader = reader.EnterBitwiseContext())
+ {
+ Assert.Throws(() => reader.TryBeginRead(1));
+ Assert.Throws(() => reader.TryBeginReadValue(1));
+ Assert.IsTrue(bitReader.TryBeginReadBits(1));
+ bitReader.ReadBit(out bool b);
+ Assert.IsTrue(b);
+
+ // Can't use Assert.Throws() because ref struct BitWriter can't be captured in a lambda
+ try
+ {
+ bitReader.ReadBit(out b);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ Assert.IsTrue(bitReader.TryBeginReadBits(3));
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+ bitReader.ReadBit(out b);
+ Assert.IsFalse(b);
+ bitReader.ReadBit(out b);
+ Assert.IsTrue(b);
+
+ byte byteVal;
+ try
+ {
+ bitReader.ReadBits(out byteVal, 4);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+
+ try
+ {
+ bitReader.ReadBits(out byteVal, 1);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ Assert.IsTrue(bitReader.TryBeginReadBits(3));
+
+ try
+ {
+ bitReader.ReadBits(out byteVal, 4);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ Assert.IsTrue(bitReader.TryBeginReadBits(4));
+ bitReader.ReadBits(out byteVal, 3);
+ Assert.AreEqual(0b010, byteVal);
+
+ Assert.IsTrue(bitReader.TryBeginReadBits(5));
+
+ bitReader.ReadBits(out byteVal, 5);
+ Assert.AreEqual(0b10101, byteVal);
+ }
+
+ Assert.AreEqual(2, reader.Position);
+
+ Assert.IsTrue(reader.TryBeginRead(1));
+ reader.ReadByte(out byte nextByte);
+ Assert.AreEqual(0b11111111, nextByte);
+
+ Assert.IsTrue(reader.TryBeginRead(1));
+ reader.ReadByte(out nextByte);
+ Assert.AreEqual(0b00000000, nextByte);
+
+ Assert.IsFalse(reader.TryBeginRead(1));
+ using (var bitReader = reader.EnterBitwiseContext())
+ {
+ Assert.IsFalse(bitReader.TryBeginReadBits(1));
+ }
+ }
+ }
+
+ [Test]
+ public void TestReadingMultipleBits()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(3));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBits(0b11111111, 1);
+ bitWriter.WriteBits(0b11111111, 1);
+ bitWriter.WriteBits(0b11111110, 2);
+ bitWriter.WriteBits(0b11111000, 4);
+ bitWriter.WriteBits(0b11111010, 4);
+ }
+ writer.WriteByte(0b11111111);
+
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(3));
+ using (var bitReader = reader.EnterBitwiseContext())
+ {
+ byte b;
+ bitReader.ReadBits(out b, 1);
+ Assert.AreEqual(0b1, b);
+
+ bitReader.ReadBits(out b, 1);
+ Assert.AreEqual(0b1, b);
+
+ bitReader.ReadBits(out b, 2);
+ Assert.AreEqual(0b10, b);
+
+ bitReader.ReadBits(out b, 4);
+ Assert.AreEqual(0b1000, b);
+
+ bitReader.ReadBits(out b, 4);
+ Assert.AreEqual(0b1010, b);
+ }
+
+ reader.ReadByte(out byte lastByte);
+ Assert.AreEqual(0b11111111, lastByte);
+ }
+ }
+ }
+
+ [Test]
+ public void TestReadingMultipleBitsToLongs()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(3));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBits(0b11111111UL, 1);
+ bitWriter.WriteBits(0b11111111UL, 1);
+ bitWriter.WriteBits(0b11111110UL, 2);
+ bitWriter.WriteBits(0b11111000UL, 4);
+ bitWriter.WriteBits(0b11111010UL, 4);
+ }
+
+ writer.WriteByte(0b11111111);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(3));
+ using (var bitReader = reader.EnterBitwiseContext())
+ {
+ ulong ul;
+ bitReader.ReadBits(out ul, 1);
+ Assert.AreEqual(0b1, ul);
+
+ bitReader.ReadBits(out ul, 1);
+ Assert.AreEqual(0b1, ul);
+
+ bitReader.ReadBits(out ul, 2);
+ Assert.AreEqual(0b10, ul);
+
+ bitReader.ReadBits(out ul, 4);
+ Assert.AreEqual(0b1000, ul);
+
+ bitReader.ReadBits(out ul, 4);
+ Assert.AreEqual(0b1010, ul);
+ }
+
+ reader.ReadByte(out byte lastByte);
+ Assert.AreEqual(0b11111111, lastByte);
+ }
+ }
+ }
+
+ [Test]
+ public unsafe void TestReadingMultipleBytesToLongs([Range(1U, 64U)] uint numBits)
+ {
+ ulong value = 0xFFFFFFFFFFFFFFFF;
+ var reader = new FastBufferReader((byte*)&value, Allocator.Temp, sizeof(ulong));
+ using (reader)
+ {
+ ulong* asUlong = (ulong*)reader.GetUnsafePtr();
+
+ Assert.AreEqual(value, *asUlong);
+ var mask = 0UL;
+ for (var i = 0; i < numBits; ++i)
+ {
+ mask |= (1UL << i);
+ }
+
+ ulong readValue;
+
+ Assert.IsTrue(reader.TryBeginRead(sizeof(ulong)));
+ using (var bitReader = reader.EnterBitwiseContext())
+ {
+ bitReader.ReadBits(out readValue, numBits);
+ }
+ Assert.AreEqual(value & mask, readValue);
+ }
+ }
+
+ [Test]
+ public unsafe void TestReadingBitsThrowsIfTryBeginReadNotCalled()
+ {
+ var nativeArray = new NativeArray(4, Allocator.Temp);
+ var reader = new FastBufferReader(nativeArray, Allocator.Temp);
+ nativeArray.Dispose();
+ using (reader)
+ {
+ int* asInt = (int*)reader.GetUnsafePtr();
+ *asInt = 0b11111111_00001010_10101011;
+
+ Assert.Throws(() =>
+ {
+ using var bitReader = reader.EnterBitwiseContext();
+ bitReader.ReadBit(out bool b);
+ });
+
+ Assert.Throws(() =>
+ {
+ using var bitReader = reader.EnterBitwiseContext();
+ bitReader.ReadBits(out byte b, 1);
+ });
+
+ Assert.Throws(() =>
+ {
+ using var bitReader = reader.EnterBitwiseContext();
+ bitReader.ReadBits(out ulong ul, 1);
+ });
+
+ Assert.AreEqual(0, reader.Position);
+
+ Assert.Throws(() =>
+ {
+ Assert.IsTrue(reader.TryBeginRead(1));
+ using var bitReader = reader.EnterBitwiseContext();
+ ulong ul;
+ try
+ {
+ bitReader.ReadBits(out ul, 4);
+ bitReader.ReadBits(out ul, 4);
+ }
+ catch (OverflowException e)
+ {
+ Assert.Fail("Overflow exception was thrown too early.");
+ throw;
+ }
+ bitReader.ReadBits(out ul, 4);
+ });
+
+ }
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs.meta
new file mode 100644
index 0000000000..0dc0f36136
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 67df11865abcd5843a4e142cf6bbd901
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs
new file mode 100644
index 0000000000..ab98d06da9
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs
@@ -0,0 +1,322 @@
+using System;
+using NUnit.Framework;
+using Unity.Collections;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class BitWriterTests
+ {
+ [Test]
+ public unsafe void TestWritingOneBit()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ int* asInt = (int*)writer.GetUnsafePtr();
+
+ Assert.AreEqual(0, *asInt);
+
+ Assert.IsTrue(writer.TryBeginWrite(3));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b1, *asInt);
+
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b11, *asInt);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b1011, *asInt);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b10001011, *asInt);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b1010_10001011, *asInt);
+ }
+
+ Assert.AreEqual(2, writer.Position);
+ Assert.AreEqual(0b1010_10001011, *asInt);
+
+ writer.WriteByte(0b11111111);
+ Assert.AreEqual(0b11111111_00001010_10001011, *asInt);
+ }
+ }
+ [Test]
+ public unsafe void TestTryBeginWriteBits()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ int* asInt = (int*)writer.GetUnsafePtr();
+
+ Assert.AreEqual(0, *asInt);
+
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ Assert.Throws(() => writer.TryBeginWrite(1));
+ Assert.Throws(() => writer.TryBeginWriteValue(1));
+ Assert.IsTrue(bitWriter.TryBeginWriteBits(1));
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b1, *asInt);
+
+ // Can't use Assert.Throws() because ref struct BitWriter can't be captured in a lambda
+ try
+ {
+ bitWriter.WriteBit(true);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ Assert.IsTrue(bitWriter.TryBeginWriteBits(3));
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b11, *asInt);
+
+ bitWriter.WriteBit(false);
+ bitWriter.WriteBit(true);
+ Assert.AreEqual(0b1011, *asInt);
+
+ try
+ {
+ bitWriter.WriteBits(0b11111111, 4);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+
+ try
+ {
+ bitWriter.WriteBits(0b11111111, 1);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ Assert.IsTrue(bitWriter.TryBeginWriteBits(3));
+
+ try
+ {
+ bitWriter.WriteBits(0b11111111, 4);
+ }
+ catch (OverflowException e)
+ {
+ // Should get called here.
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ Assert.IsTrue(bitWriter.TryBeginWriteBits(4));
+
+ bitWriter.WriteBits(0b11111010, 3);
+
+ Assert.AreEqual(0b00101011, *asInt);
+
+ Assert.IsTrue(bitWriter.TryBeginWriteBits(5));
+
+ bitWriter.WriteBits(0b11110101, 5);
+ Assert.AreEqual(0b1010_10101011, *asInt);
+ }
+
+ Assert.AreEqual(2, writer.Position);
+ Assert.AreEqual(0b1010_10101011, *asInt);
+
+ Assert.IsTrue(writer.TryBeginWrite(1));
+ writer.WriteByte(0b11111111);
+ Assert.AreEqual(0b11111111_00001010_10101011, *asInt);
+
+ Assert.IsTrue(writer.TryBeginWrite(1));
+ writer.WriteByte(0b00000000);
+ Assert.AreEqual(0b11111111_00001010_10101011, *asInt);
+
+ Assert.IsFalse(writer.TryBeginWrite(1));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ Assert.IsFalse(bitWriter.TryBeginWriteBits(1));
+ }
+ }
+ }
+
+ [Test]
+ public unsafe void TestWritingMultipleBits()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ int* asInt = (int*)writer.GetUnsafePtr();
+
+ Assert.AreEqual(0, *asInt);
+
+ Assert.IsTrue(writer.TryBeginWrite(3));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBits(0b11111111, 1);
+ Assert.AreEqual(0b1, *asInt);
+
+ bitWriter.WriteBits(0b11111111, 1);
+ Assert.AreEqual(0b11, *asInt);
+
+ bitWriter.WriteBits(0b11111110, 2);
+ Assert.AreEqual(0b1011, *asInt);
+
+ bitWriter.WriteBits(0b11111000, 4);
+ Assert.AreEqual(0b10001011, *asInt);
+
+ bitWriter.WriteBits(0b11111010, 4);
+ Assert.AreEqual(0b1010_10001011, *asInt);
+ }
+
+ Assert.AreEqual(2, writer.Position);
+ Assert.AreEqual(0b1010_10001011, *asInt);
+
+ writer.WriteByte(0b11111111);
+ Assert.AreEqual(0b11111111_00001010_10001011, *asInt);
+ }
+ }
+
+ [Test]
+ public unsafe void TestWritingMultipleBitsFromLongs()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ int* asInt = (int*)writer.GetUnsafePtr();
+
+ Assert.AreEqual(0, *asInt);
+
+ Assert.IsTrue(writer.TryBeginWrite(3));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBits(0b11111111UL, 1);
+ Assert.AreEqual(0b1, *asInt);
+
+ bitWriter.WriteBits(0b11111111UL, 1);
+ Assert.AreEqual(0b11, *asInt);
+
+ bitWriter.WriteBits(0b11111110UL, 2);
+ Assert.AreEqual(0b1011, *asInt);
+
+ bitWriter.WriteBits(0b11111000UL, 4);
+ Assert.AreEqual(0b10001011, *asInt);
+
+ bitWriter.WriteBits(0b11111010UL, 4);
+ Assert.AreEqual(0b1010_10001011, *asInt);
+ }
+
+ Assert.AreEqual(2, writer.Position);
+ Assert.AreEqual(0b1010_10001011, *asInt);
+
+ writer.WriteByte(0b11111111);
+ Assert.AreEqual(0b11111111_00001010_10001011, *asInt);
+ }
+ }
+
+ [Test]
+ public unsafe void TestWritingMultipleBytesFromLongs([Range(1U, 64U)] uint numBits)
+ {
+ var writer = new FastBufferWriter(sizeof(ulong), Allocator.Temp);
+ using (writer)
+ {
+ ulong* asUlong = (ulong*)writer.GetUnsafePtr();
+
+ Assert.AreEqual(0, *asUlong);
+ var mask = 0UL;
+ for (var i = 0; i < numBits; ++i)
+ {
+ mask |= (1UL << i);
+ }
+
+ ulong value = 0xFFFFFFFFFFFFFFFF;
+
+ Assert.IsTrue(writer.TryBeginWrite(sizeof(ulong)));
+ using (var bitWriter = writer.EnterBitwiseContext())
+ {
+ bitWriter.WriteBits(value, numBits);
+ }
+ Assert.AreEqual(value & mask, *asUlong);
+ }
+ }
+
+ [Test]
+ public unsafe void TestWritingBitsThrowsIfTryBeginWriteNotCalled()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ int* asInt = (int*)writer.GetUnsafePtr();
+
+ Assert.AreEqual(0, *asInt);
+
+ Assert.Throws(() =>
+ {
+ using var bitWriter = writer.EnterBitwiseContext();
+ bitWriter.WriteBit(true);
+ });
+
+ Assert.Throws(() =>
+ {
+ using var bitWriter = writer.EnterBitwiseContext();
+ bitWriter.WriteBit(false);
+ });
+
+ Assert.Throws(() =>
+ {
+ using var bitWriter = writer.EnterBitwiseContext();
+ bitWriter.WriteBits(0b11111111, 1);
+ });
+
+ Assert.Throws(() =>
+ {
+ using var bitWriter = writer.EnterBitwiseContext();
+ bitWriter.WriteBits(0b11111111UL, 1);
+ });
+
+ Assert.AreEqual(0, writer.Position);
+ Assert.AreEqual(0, *asInt);
+
+ writer.WriteByteSafe(0b11111111);
+ Assert.AreEqual(0b11111111, *asInt);
+
+
+ Assert.Throws(() =>
+ {
+ Assert.IsTrue(writer.TryBeginWrite(1));
+ using var bitWriter = writer.EnterBitwiseContext();
+ try
+ {
+ bitWriter.WriteBits(0b11111111UL, 4);
+ bitWriter.WriteBits(0b11111111UL, 4);
+ }
+ catch (OverflowException e)
+ {
+ Assert.Fail("Overflow exception was thrown too early.");
+ throw;
+ }
+ bitWriter.WriteBits(0b11111111UL, 1);
+ });
+
+ }
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs.meta
new file mode 100644
index 0000000000..3a8da0e2cc
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fed657e0516a72f469fbf886e3e5149a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs
new file mode 100644
index 0000000000..1847dd0954
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs
@@ -0,0 +1,644 @@
+using System;
+using NUnit.Framework;
+using Unity.Collections;
+using UnityEngine;
+using Random = System.Random;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class BufferSerializerTests
+ {
+ [Test]
+ public void TestIsReaderIsWriter()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ Assert.IsFalse(serializer.IsReader);
+ Assert.IsTrue(serializer.IsWriter);
+ }
+ byte[] readBuffer = new byte[4];
+ var reader = new FastBufferReader(readBuffer, Allocator.Temp);
+ using (reader)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ Assert.IsTrue(serializer.IsReader);
+ Assert.IsFalse(serializer.IsWriter);
+ }
+ }
+ [Test]
+ public unsafe void TestGetUnderlyingStructs()
+ {
+ var writer = new FastBufferWriter(4, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ ref FastBufferWriter underlyingWriter = ref serializer.GetFastBufferWriter();
+ fixed (FastBufferWriter* ptr = &underlyingWriter)
+ {
+ Assert.IsTrue(ptr == &writer);
+ }
+ // Can't use Assert.Throws() because ref structs can't be passed into lambdas.
+ try
+ {
+ serializer.GetFastBufferReader();
+ }
+ catch (InvalidOperationException)
+ {
+ // pass
+ }
+
+ }
+ byte[] readBuffer = new byte[4];
+ var reader = new FastBufferReader(readBuffer, Allocator.Temp);
+ using (reader)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ ref FastBufferReader underlyingReader = ref serializer.GetFastBufferReader();
+ fixed (FastBufferReader* ptr = &underlyingReader)
+ {
+ Assert.IsTrue(ptr == &reader);
+ }
+ // Can't use Assert.Throws() because ref structs can't be passed into lambdas.
+ try
+ {
+ serializer.GetFastBufferWriter();
+ }
+ catch (InvalidOperationException)
+ {
+ // pass
+ }
+ }
+ }
+
+ // Not reimplementing the entire suite of all value tests for BufferSerializer since they're already tested
+ // for the underlying structures. These are just basic tests to make sure the correct underlying functions
+ // are being called.
+ [Test]
+ public void TestSerializingObjects()
+ {
+ var random = new Random();
+ int value = random.Next();
+ object asObj = value;
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref asObj, typeof(int));
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ object readValue = 0;
+ deserializer.SerializeValue(ref readValue, typeof(int));
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+
+ [Test]
+ public void TestSerializingValues()
+ {
+ var random = new Random();
+ int value = random.Next();
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref value);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ int readValue = 0;
+ deserializer.SerializeValue(ref readValue);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+ [Test]
+ public void TestSerializingBytes()
+ {
+ var random = new Random();
+ byte value = (byte)random.Next();
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref value);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ byte readValue = 0;
+ deserializer.SerializeValue(ref readValue);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+ [Test]
+ public void TestSerializingArrays()
+ {
+ var random = new Random();
+ int[] value = { random.Next(), random.Next(), random.Next() };
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref value);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ int[] readValue = null;
+ deserializer.SerializeValue(ref readValue);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+ [Test]
+ public void TestSerializingStrings([Values] bool oneBytChars)
+ {
+ string value = "I am a test string";
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref value, oneBytChars);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ string readValue = null;
+ deserializer.SerializeValue(ref readValue, oneBytChars);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+
+
+ [Test]
+ public void TestSerializingValuesPreChecked()
+ {
+ var random = new Random();
+ int value = random.Next();
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref value);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value)));
+ serializer.SerializeValuePreChecked(ref value);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ int readValue = 0;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value)));
+ deserializer.SerializeValuePreChecked(ref readValue);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+ [Test]
+ public void TestSerializingBytesPreChecked()
+ {
+ var random = new Random();
+ byte value = (byte)random.Next();
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref value);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value)));
+ serializer.SerializeValuePreChecked(ref value);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ byte readValue = 0;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value)));
+ deserializer.SerializeValuePreChecked(ref readValue);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+ [Test]
+ public void TestSerializingArraysPreChecked()
+ {
+ var random = new Random();
+ int[] value = { random.Next(), random.Next(), random.Next() };
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref value);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value)));
+ serializer.SerializeValuePreChecked(ref value);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ int[] readValue = null;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value)));
+ deserializer.SerializeValuePreChecked(ref readValue);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+ [Test]
+ public void TestSerializingStringsPreChecked([Values] bool oneBytChars)
+ {
+ string value = "I am a test string";
+
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref value, oneBytChars);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriter.GetWriteSize(value, oneBytChars)));
+ serializer.SerializeValuePreChecked(ref value, oneBytChars);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ string readValue = null;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue, oneBytChars);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriter.GetWriteSize(value, oneBytChars)));
+ deserializer.SerializeValuePreChecked(ref readValue, oneBytChars);
+
+ Assert.AreEqual(value, readValue);
+ }
+ }
+ }
+
+ private delegate void GameObjectTestDelegate(GameObject obj, NetworkBehaviour networkBehaviour,
+ NetworkObject networkObject);
+ private void RunGameObjectTest(GameObjectTestDelegate testCode)
+ {
+ var obj = new GameObject("Object");
+ var networkBehaviour = obj.AddComponent();
+ var networkObject = obj.AddComponent();
+ // Create networkManager component
+ var networkManager = obj.AddComponent();
+ networkManager.SetSingleton();
+ networkObject.NetworkManagerOwner = networkManager;
+
+ // Set the NetworkConfig
+ networkManager.NetworkConfig = new NetworkConfig()
+ {
+ // Set transport
+ NetworkTransport = obj.AddComponent()
+ };
+
+ networkManager.StartServer();
+
+ try
+ {
+ testCode(obj, networkBehaviour, networkObject);
+ }
+ finally
+ {
+ UnityEngine.Object.DestroyImmediate(obj);
+ networkManager.Shutdown();
+ }
+ }
+
+ [Test]
+ public void TestSerializingGameObjects()
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref obj);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ GameObject readValue = null;
+ deserializer.SerializeValue(ref readValue);
+
+ Assert.AreEqual(obj, readValue);
+ }
+ }
+ }
+ );
+ }
+
+ [Test]
+ public void TestSerializingNetworkObjects()
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref networkObject);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ NetworkObject readValue = null;
+ deserializer.SerializeValue(ref readValue);
+
+ Assert.AreEqual(networkObject, readValue);
+ }
+ }
+ }
+ );
+ }
+
+ [Test]
+ public void TestSerializingNetworkBehaviours()
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ serializer.SerializeValue(ref networkBehaviour);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ NetworkBehaviour readValue = null;
+ deserializer.SerializeValue(ref readValue);
+
+ Assert.AreEqual(networkBehaviour, readValue);
+ }
+ }
+ }
+ );
+ }
+
+ [Test]
+ public void TestSerializingGameObjectsPreChecked()
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref obj);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(obj)));
+ serializer.SerializeValuePreChecked(ref obj);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ GameObject readValue = null;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(readValue)));
+ deserializer.SerializeValuePreChecked(ref readValue);
+
+ Assert.AreEqual(obj, readValue);
+ }
+ }
+ }
+ );
+ }
+
+ [Test]
+ public void TestSerializingNetworkObjectsPreChecked()
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref networkObject);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(networkObject)));
+ serializer.SerializeValuePreChecked(ref networkObject);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ NetworkObject readValue = null;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(readValue)));
+ deserializer.SerializeValuePreChecked(ref readValue);
+
+ Assert.AreEqual(networkObject, readValue);
+ }
+ }
+ }
+ );
+ }
+
+ [Test]
+ public void TestSerializingNetworkBehavioursPreChecked()
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ var serializer =
+ new BufferSerializer(new BufferSerializerWriter(ref writer));
+ try
+ {
+ serializer.SerializeValuePreChecked(ref networkBehaviour);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(serializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(networkBehaviour)));
+ serializer.SerializeValuePreChecked(ref networkBehaviour);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var deserializer =
+ new BufferSerializer(new BufferSerializerReader(ref reader));
+ NetworkBehaviour readValue = null;
+ try
+ {
+ deserializer.SerializeValuePreChecked(ref readValue);
+ }
+ catch (OverflowException e)
+ {
+ // Pass
+ }
+
+ Assert.IsTrue(deserializer.PreCheck(FastBufferWriterExtensions.GetWriteSize(readValue)));
+ deserializer.SerializeValuePreChecked(ref readValue);
+
+ Assert.AreEqual(networkBehaviour, readValue);
+ }
+ }
+ }
+ );
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs.meta
new file mode 100644
index 0000000000..cb62c7e2d2
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0cf899c97866c76498b71585a61a8142
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs
new file mode 100644
index 0000000000..1c2ff159e9
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs
@@ -0,0 +1,1268 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using NUnit.Framework;
+using Unity.Collections;
+using UnityEngine;
+using Random = System.Random;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class BytePackerTests
+ {
+ #region Test Types
+
+ private enum ByteEnum : byte
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum SByteEnum : sbyte
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum ShortEnum : short
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum UShortEnum : ushort
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum IntEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum UIntEnum : uint
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum LongEnum : long
+ {
+ A,
+ B,
+ C
+ }
+
+ private enum ULongEnum : ulong
+ {
+ A,
+ B,
+ C
+ }
+
+ public enum WriteType
+ {
+ WriteDirect,
+ WriteAsObject
+ }
+
+ #endregion
+
+ private void CheckUnsignedPackedSize64(ref FastBufferWriter writer, ulong value)
+ {
+
+ if (value <= 240)
+ {
+ Assert.AreEqual(1, writer.Position);
+ }
+ else if (value <= 2287)
+ {
+ Assert.AreEqual(2, writer.Position);
+ }
+ else
+ {
+ Assert.AreEqual(BitCounter.GetUsedByteCount(value) + 1, writer.Position);
+ }
+ }
+
+ private void CheckUnsignedPackedValue64(ref FastBufferWriter writer, ulong value)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out ulong readValue);
+ Assert.AreEqual(readValue, value);
+ }
+ }
+
+ private void CheckUnsignedPackedSize32(ref FastBufferWriter writer, uint value)
+ {
+
+ if (value <= 240)
+ {
+ Assert.AreEqual(1, writer.Position);
+ }
+ else if (value <= 2287)
+ {
+ Assert.AreEqual(2, writer.Position);
+ }
+ else
+ {
+ Assert.AreEqual(BitCounter.GetUsedByteCount(value) + 1, writer.Position);
+ }
+ }
+
+ private void CheckUnsignedPackedValue32(ref FastBufferWriter writer, uint value)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out uint readValue);
+ Assert.AreEqual(readValue, value);
+ }
+ }
+
+ private void CheckSignedPackedSize64(ref FastBufferWriter writer, long value)
+ {
+ ulong asUlong = Arithmetic.ZigZagEncode(value);
+
+ if (asUlong <= 240)
+ {
+ Assert.AreEqual(1, writer.Position);
+ }
+ else if (asUlong <= 2287)
+ {
+ Assert.AreEqual(2, writer.Position);
+ }
+ else
+ {
+ Assert.AreEqual(BitCounter.GetUsedByteCount(asUlong) + 1, writer.Position);
+ }
+ }
+
+ private void CheckSignedPackedValue64(ref FastBufferWriter writer, long value)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out long readValue);
+ Assert.AreEqual(readValue, value);
+ }
+ }
+
+ private void CheckSignedPackedSize32(ref FastBufferWriter writer, int value)
+ {
+ ulong asUlong = Arithmetic.ZigZagEncode(value);
+
+ if (asUlong <= 240)
+ {
+ Assert.AreEqual(1, writer.Position);
+ }
+ else if (asUlong <= 2287)
+ {
+ Assert.AreEqual(2, writer.Position);
+ }
+ else
+ {
+ Assert.AreEqual(BitCounter.GetUsedByteCount(asUlong) + 1, writer.Position);
+ }
+ }
+
+ private void CheckSignedPackedValue32(ref FastBufferWriter writer, int value)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out int readValue);
+ Assert.AreEqual(readValue, value);
+ }
+ }
+
+ private unsafe void VerifyBytewiseEquality(T value, T otherValue) where T : unmanaged
+ {
+ byte* asBytePointer = (byte*)&value;
+ byte* otherBytePointer = (byte*)&otherValue;
+ for (var i = 0; i < sizeof(T); ++i)
+ {
+ Assert.AreEqual(asBytePointer[i], otherBytePointer[i]);
+ }
+ }
+
+ private unsafe void RunTypeTest(T value) where T : unmanaged
+ {
+ var writer = new FastBufferWriter(sizeof(T) * 2, Allocator.Temp);
+ using (writer)
+ {
+ BytePacker.WriteValuePacked(ref writer, (dynamic)value);
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+
+ var outVal = new T();
+ MethodInfo method;
+ if (value is Enum)
+ {
+ method = typeof(ByteUnpacker).GetMethods().Single(x =>
+ x.Name == "ReadValuePacked" && x.IsGenericMethodDefinition)
+ .MakeGenericMethod(typeof(T));
+ }
+ else
+ {
+ method = typeof(ByteUnpacker).GetMethod("ReadValuePacked",
+ new[] { typeof(FastBufferReader).MakeByRefType(), typeof(T).MakeByRefType() });
+ }
+
+ object[] args = { reader, outVal };
+ method.Invoke(null, args);
+ outVal = (T)args[1];
+ Assert.AreEqual(value, outVal);
+ VerifyBytewiseEquality(value, outVal);
+ }
+ }
+ }
+
+ private unsafe void RunObjectTypeTest(T value) where T : unmanaged
+ {
+ var writer = new FastBufferWriter(sizeof(T) * 2, Allocator.Temp);
+ using (writer)
+ {
+ BytePacker.WriteObjectPacked(ref writer, value);
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+
+ ByteUnpacker.ReadObjectPacked(ref reader, out object outVal, typeof(T));
+ Assert.AreEqual(value, outVal);
+ VerifyBytewiseEquality(value, (T)outVal);
+ }
+ }
+ }
+
+
+
+ [Test]
+ public void TestPacking64BitsUnsigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(9);
+ ulong value = 0;
+ BytePacker.WriteValuePacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+
+ for (var i = 0; i < 64; ++i)
+ {
+ value = 1UL << i;
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckUnsignedPackedSize64(ref writer, value);
+ CheckUnsignedPackedValue64(ref writer, value);
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1UL << i) | (1UL << j);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckUnsignedPackedSize64(ref writer, value);
+ CheckUnsignedPackedValue64(ref writer, value);
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void TestPacking32BitsUnsigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(9);
+ uint value = 0;
+ BytePacker.WriteValuePacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+
+ for (var i = 0; i < 64; ++i)
+ {
+ value = 1U << i;
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckUnsignedPackedSize32(ref writer, value);
+ CheckUnsignedPackedValue32(ref writer, value);
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1U << i) | (1U << j);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckUnsignedPackedSize32(ref writer, value);
+ CheckUnsignedPackedValue32(ref writer, value);
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void TestPacking64BitsSigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(9);
+ long value = 0;
+ BytePacker.WriteValuePacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+
+ for (var i = 0; i < 64; ++i)
+ {
+ value = 1L << i;
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckSignedPackedSize64(ref writer, value);
+ CheckSignedPackedValue64(ref writer, value);
+
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, -value);
+ CheckSignedPackedSize64(ref writer, -value);
+ CheckSignedPackedValue64(ref writer, -value);
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1L << i) | (1L << j);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckSignedPackedSize64(ref writer, value);
+ CheckSignedPackedValue64(ref writer, value);
+
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, -value);
+ CheckSignedPackedSize64(ref writer, -value);
+ CheckSignedPackedValue64(ref writer, -value);
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void TestPacking32BitsSigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(5);
+ int value = 0;
+ BytePacker.WriteValuePacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+
+ for (var i = 0; i < 64; ++i)
+ {
+ value = 1 << i;
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckSignedPackedSize32(ref writer, value);
+ CheckSignedPackedValue32(ref writer, value);
+
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, -value);
+ CheckSignedPackedSize32(ref writer, -value);
+ CheckSignedPackedValue32(ref writer, -value);
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1 << i) | (1 << j);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, value);
+ CheckSignedPackedSize32(ref writer, value);
+ CheckSignedPackedValue32(ref writer, value);
+
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValuePacked(ref writer, -value);
+ CheckSignedPackedSize32(ref writer, -value);
+ CheckSignedPackedValue32(ref writer, -value);
+ }
+ }
+ }
+ }
+
+ private int GetByteCount61Bits(ulong value)
+ {
+
+ if (value <= 0b0001_1111)
+ {
+ return 1;
+ }
+
+ if (value <= 0b0001_1111_1111_1111)
+ {
+ return 2;
+ }
+
+ if (value <= 0b0001_1111_1111_1111_1111_1111)
+ {
+ return 3;
+ }
+
+ if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111)
+ {
+ return 4;
+ }
+
+ if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111)
+ {
+ return 5;
+ }
+
+ if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111)
+ {
+ return 6;
+ }
+
+ if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111)
+ {
+ return 7;
+ }
+
+ return 8;
+ }
+
+ private int GetByteCount30Bits(uint value)
+ {
+
+ if (value <= 0b0011_1111)
+ {
+ return 1;
+ }
+
+ if (value <= 0b0011_1111_1111_1111)
+ {
+ return 2;
+ }
+
+ if (value <= 0b0011_1111_1111_1111_1111_1111)
+ {
+ return 3;
+ }
+
+ return 4;
+ }
+
+ private int GetByteCount15Bits(ushort value)
+ {
+
+ if (value <= 0b0111_1111)
+ {
+ return 1;
+ }
+
+ return 2;
+ }
+
+ private ulong Get61BitEncodedValue(ref FastBufferWriter writer)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(ref reader, out ulong value);
+ return value;
+ }
+ }
+
+ private long Get60BitSignedEncodedValue(ref FastBufferWriter writer)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(ref reader, out long value);
+ return value;
+ }
+ }
+
+ private uint Get30BitEncodedValue(ref FastBufferWriter writer)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(ref reader, out uint value);
+ return value;
+ }
+ }
+
+ private int Get29BitSignedEncodedValue(ref FastBufferWriter writer)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(ref reader, out int value);
+ return value;
+ }
+ }
+
+ private ushort Get15BitEncodedValue(ref FastBufferWriter writer)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(ref reader, out ushort value);
+ return value;
+ }
+ }
+
+ private short Get14BitSignedEncodedValue(ref FastBufferWriter writer)
+ {
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValueBitPacked(ref reader, out short value);
+ return value;
+ }
+ }
+
+ [Test]
+ public void TestBitPacking61BitsUnsigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(8);
+ ulong value = 0;
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+ Assert.AreEqual(0, writer.ToArray()[0] & 0b111);
+ Assert.AreEqual(value, Get61BitEncodedValue(ref writer));
+
+ for (var i = 0; i < 61; ++i)
+ {
+ value = 1UL << i;
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount61Bits(value), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount61Bits(value) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get61BitEncodedValue(ref writer));
+
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1UL << i) | (1UL << j);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount61Bits(value), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount61Bits(value) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get61BitEncodedValue(ref writer));
+ }
+ }
+
+ Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, 1UL << 61); });
+ }
+ }
+
+ [Test]
+ public void TestBitPacking60BitsSigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(8);
+ long value = 0;
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+ Assert.AreEqual(0, writer.ToArray()[0] & 0b111);
+ Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer));
+
+ for (var i = 0; i < 61; ++i)
+ {
+ value = 1U << i;
+ ulong zzvalue = Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer));
+
+ value = -value;
+ zzvalue = Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer));
+
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1U << i) | (1U << j);
+ zzvalue = Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer));
+
+ value = -value;
+ zzvalue = Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get60BitSignedEncodedValue(ref writer));
+ }
+ }
+
+ Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, 1UL << 61); });
+ }
+ }
+
+ [Test]
+ public void TestBitPacking30BitsUnsigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(4);
+ uint value = 0;
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+ Assert.AreEqual(0, writer.ToArray()[0] & 0b11);
+ Assert.AreEqual(value, Get30BitEncodedValue(ref writer));
+
+ for (var i = 0; i < 30; ++i)
+ {
+ value = 1U << i;
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount30Bits(value), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount30Bits(value) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get30BitEncodedValue(ref writer));
+
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1U << i) | (1U << j);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount30Bits(value), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount30Bits(value) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get30BitEncodedValue(ref writer));
+ }
+ }
+
+ Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, 1U << 30); });
+ }
+ }
+
+ [Test]
+ public void TestBitPacking29BitsSigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(4);
+ int value = 0;
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+ Assert.AreEqual(0, writer.ToArray()[0] & 0b11);
+ Assert.AreEqual(value, Get30BitEncodedValue(ref writer));
+
+ for (var i = 0; i < 29; ++i)
+ {
+ value = 1 << i;
+ uint zzvalue = (uint)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer));
+
+ value = -value;
+ zzvalue = (uint)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer));
+
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (1 << i) | (1 << j);
+ zzvalue = (uint)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer));
+
+ value = -value;
+ zzvalue = (uint)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get29BitSignedEncodedValue(ref writer));
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void TestBitPacking15BitsUnsigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(2);
+ ushort value = 0;
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+ Assert.AreEqual(0, writer.ToArray()[0] & 0b1);
+ Assert.AreEqual(value, Get15BitEncodedValue(ref writer));
+
+ for (var i = 0; i < 15; ++i)
+ {
+ value = (ushort)(1U << i);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount15Bits(value), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount15Bits(value) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get15BitEncodedValue(ref writer));
+
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (ushort)((1U << i) | (1U << j));
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount15Bits(value), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount15Bits(value) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get15BitEncodedValue(ref writer));
+ }
+ }
+
+ Assert.Throws(() => { BytePacker.WriteValueBitPacked(ref writer, (ushort)(1U << 15)); });
+ }
+ }
+ [Test]
+ public void TestBitPacking14BitsSigned()
+ {
+ var writer = new FastBufferWriter(9, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(2);
+ short value = 0;
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(1, writer.Position);
+ Assert.AreEqual(0, writer.ToArray()[0] & 0b1);
+ Assert.AreEqual(value, Get15BitEncodedValue(ref writer));
+
+ for (var i = 0; i < 14; ++i)
+ {
+ value = (short)(1 << i);
+ ushort zzvalue = (ushort)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer));
+
+ value = (short)-value;
+ zzvalue = (ushort)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i})");
+ Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})");
+ Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer));
+
+ for (var j = 0; j < 8; ++j)
+ {
+ value = (short)((1 << i) | (1 << j));
+ zzvalue = (ushort)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer));
+
+ value = (short)-value;
+ zzvalue = (ushort)Arithmetic.ZigZagEncode(value);
+ writer.Seek(0);
+ writer.Truncate();
+ BytePacker.WriteValueBitPacked(ref writer, value);
+ Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})");
+ Assert.AreEqual(value, Get14BitSignedEncodedValue(ref writer));
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void TestPackingBasicTypes(
+ [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
+ typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
+ typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
+ typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D))]
+ Type testType,
+ [Values] WriteType writeType)
+ {
+ var random = new Random();
+
+ if (testType == typeof(byte))
+ {
+ byte b = (byte)random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(b);
+ }
+ else
+ {
+ RunObjectTypeTest(b);
+ }
+ }
+ else if (testType == typeof(sbyte))
+ {
+ sbyte sb = (sbyte)random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(sb);
+ }
+ else
+ {
+ RunObjectTypeTest(sb);
+ }
+ }
+ else if (testType == typeof(short))
+ {
+ short s = (short)random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(s);
+ }
+ else
+ {
+ RunObjectTypeTest(s);
+ }
+ }
+ else if (testType == typeof(ushort))
+ {
+ ushort us = (ushort)random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(us);
+ }
+ else
+ {
+ RunObjectTypeTest(us);
+ }
+ }
+ else if (testType == typeof(int))
+ {
+ int i = random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(i);
+ }
+ else
+ {
+ RunObjectTypeTest(i);
+ }
+ }
+ else if (testType == typeof(uint))
+ {
+ uint ui = (uint)random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(ui);
+ }
+ else
+ {
+ RunObjectTypeTest(ui);
+ }
+ }
+ else if (testType == typeof(long))
+ {
+ long l = ((long)random.Next() << 32) + random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(l);
+ }
+ else
+ {
+ RunObjectTypeTest(l);
+ }
+ }
+ else if (testType == typeof(ulong))
+ {
+ ulong ul = ((ulong)random.Next() << 32) + (ulong)random.Next();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(ul);
+ }
+ else
+ {
+ RunObjectTypeTest(ul);
+ }
+ }
+ else if (testType == typeof(bool))
+ {
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(true);
+ }
+ else
+ {
+ RunObjectTypeTest(true);
+ }
+ }
+ else if (testType == typeof(char))
+ {
+ char c = 'a';
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(c);
+ }
+ else
+ {
+ RunObjectTypeTest(c);
+ }
+
+ c = '\u263a';
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(c);
+ }
+ else
+ {
+ RunObjectTypeTest(c);
+ }
+ }
+ else if (testType == typeof(float))
+ {
+ float f = (float)random.NextDouble();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(f);
+ }
+ else
+ {
+ RunObjectTypeTest(f);
+ }
+ }
+ else if (testType == typeof(double))
+ {
+ double d = random.NextDouble();
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(d);
+ }
+ else
+ {
+ RunObjectTypeTest(d);
+ }
+ }
+ else if (testType == typeof(ByteEnum))
+ {
+ ByteEnum e = ByteEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(SByteEnum))
+ {
+ SByteEnum e = SByteEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(ShortEnum))
+ {
+ ShortEnum e = ShortEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(UShortEnum))
+ {
+ UShortEnum e = UShortEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(IntEnum))
+ {
+ IntEnum e = IntEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(UIntEnum))
+ {
+ UIntEnum e = UIntEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(LongEnum))
+ {
+ LongEnum e = LongEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(ULongEnum))
+ {
+ ULongEnum e = ULongEnum.C;
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(e);
+ }
+ else
+ {
+ RunObjectTypeTest(e);
+ }
+ }
+ else if (testType == typeof(Vector2))
+ {
+ var v = new Vector2((float)random.NextDouble(), (float)random.NextDouble());
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(v);
+ }
+ else
+ {
+ RunObjectTypeTest(v);
+ }
+ }
+ else if (testType == typeof(Vector3))
+ {
+ var v = new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(v);
+ }
+ else
+ {
+ RunObjectTypeTest(v);
+ }
+ }
+ else if (testType == typeof(Vector4))
+ {
+ var v = new Vector4((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(v);
+ }
+ else
+ {
+ RunObjectTypeTest(v);
+ }
+ }
+ else if (testType == typeof(Quaternion))
+ {
+ var v = new Quaternion((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(v);
+ }
+ else
+ {
+ RunObjectTypeTest(v);
+ }
+ }
+ else if (testType == typeof(Color))
+ {
+ var v = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(v);
+ }
+ else
+ {
+ RunObjectTypeTest(v);
+ }
+ }
+ else if (testType == typeof(Color32))
+ {
+ var v = new Color32((byte)random.Next(), (byte)random.Next(), (byte)random.Next(), (byte)random.Next());
+ if (writeType == WriteType.WriteDirect)
+ {
+ RunTypeTest(v);
+ }
+ else
+ {
+ RunObjectTypeTest(v);
+ }
+ }
+ else if (testType == typeof(Ray))
+ {
+ // Rays need special handling on the equality checks because the constructor normalizes direction
+ // Which can cause slight variations in the result
+ var v = new Ray(
+ new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()),
+ new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()));
+ if (writeType == WriteType.WriteDirect)
+ {
+ unsafe
+ {
+ var writer = new FastBufferWriter(sizeof(Ray) * 2, Allocator.Temp);
+ using (writer)
+ {
+ BytePacker.WriteValuePacked(ref writer, v);
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Ray outVal);
+ Assert.AreEqual(v.origin, outVal.origin);
+ Assert.AreEqual(v.direction.x, outVal.direction.x, 0.00001);
+ Assert.AreEqual(v.direction.y, outVal.direction.y, 0.00001);
+ Assert.AreEqual(v.direction.z, outVal.direction.z, 0.00001);
+ }
+ }
+ }
+ }
+ else
+ {
+ unsafe
+ {
+ var writer = new FastBufferWriter(sizeof(Ray) * 2, Allocator.Temp);
+ using (writer)
+ {
+ BytePacker.WriteObjectPacked(ref writer, v);
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadObjectPacked(ref reader, out object outVal, typeof(Ray));
+ Assert.AreEqual(v.origin, ((Ray)outVal).origin);
+ Assert.AreEqual(v.direction.x, ((Ray)outVal).direction.x, 0.00001);
+ Assert.AreEqual(v.direction.y, ((Ray)outVal).direction.y, 0.00001);
+ Assert.AreEqual(v.direction.z, ((Ray)outVal).direction.z, 0.00001);
+ }
+ }
+ }
+ }
+ }
+ else if (testType == typeof(Ray2D))
+ {
+ // Rays need special handling on the equality checks because the constructor normalizes direction
+ // Which can cause slight variations in the result
+ var v = new Ray2D(
+ new Vector2((float)random.NextDouble(), (float)random.NextDouble()),
+ new Vector2((float)random.NextDouble(), (float)random.NextDouble()));
+ if (writeType == WriteType.WriteDirect)
+ {
+ unsafe
+ {
+ var writer = new FastBufferWriter(sizeof(Ray2D) * 2, Allocator.Temp);
+ using (writer)
+ {
+ BytePacker.WriteValuePacked(ref writer, v);
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadValuePacked(ref reader, out Ray2D outVal);
+ Assert.AreEqual(v.origin, outVal.origin);
+ Assert.AreEqual(v.direction.x, outVal.direction.x, 0.00001);
+ Assert.AreEqual(v.direction.y, outVal.direction.y, 0.00001);
+ }
+ }
+ }
+ }
+ else
+ {
+ unsafe
+ {
+ var writer = new FastBufferWriter(sizeof(Ray2D) * 2, Allocator.Temp);
+ using (writer)
+ {
+ BytePacker.WriteObjectPacked(ref writer, v);
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ ByteUnpacker.ReadObjectPacked(ref reader, out object outVal, typeof(Ray2D));
+ Assert.AreEqual(v.origin, ((Ray2D)outVal).origin);
+ Assert.AreEqual(v.direction.x, ((Ray2D)outVal).direction.x, 0.00001);
+ Assert.AreEqual(v.direction.y, ((Ray2D)outVal).direction.y, 0.00001);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ Assert.Fail("No type handler was provided for this type in the test!");
+ }
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs.meta
new file mode 100644
index 0000000000..4c07179b82
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b50db056cd7443b4eb2e00b603d4c15c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs
new file mode 100644
index 0000000000..0f3e36e2e8
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs
@@ -0,0 +1,1027 @@
+using System;
+using NUnit.Framework;
+using Unity.Collections;
+using UnityEngine;
+using Random = System.Random;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class FastBufferReaderTests : BaseFastBufferReaderWriterTest
+ {
+ #region Common Checks
+ private void WriteCheckBytes(ref FastBufferWriter writer, int writeSize, string failMessage = "")
+ {
+ Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
+ writer.WriteValue((byte)0x80);
+ Assert.AreEqual(writeSize + 1, writer.Position, failMessage);
+ Assert.AreEqual(writeSize + 1, writer.Length, failMessage);
+ writer.WriteValue((byte)0xFF);
+ Assert.AreEqual(writeSize + 2, writer.Position, failMessage);
+ Assert.AreEqual(writeSize + 2, writer.Length, failMessage);
+ }
+
+ private void VerifyCheckBytes(ref FastBufferReader reader, int checkPosition, string failMessage = "")
+ {
+ reader.Seek(checkPosition);
+ reader.TryBeginRead(2);
+
+ reader.ReadByte(out byte value);
+ Assert.AreEqual(0x80, value, failMessage);
+ reader.ReadByte(out value);
+ Assert.AreEqual(0xFF, value, failMessage);
+ }
+
+ private void VerifyPositionAndLength(ref FastBufferReader reader, int length, string failMessage = "")
+ {
+ Assert.AreEqual(0, reader.Position, failMessage);
+ Assert.AreEqual(length, reader.Length, failMessage);
+ }
+
+ private FastBufferReader CommonChecks(ref FastBufferWriter writer, T valueToTest, int writeSize, string failMessage = "") where T : unmanaged
+ {
+ WriteCheckBytes(ref writer, writeSize, failMessage);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+
+ VerifyPositionAndLength(ref reader, writer.Length, failMessage);
+
+ VerifyCheckBytes(ref reader, writeSize, failMessage);
+
+ reader.Seek(0);
+
+ return reader;
+ }
+ #endregion
+
+ #region Generic Checks
+ protected override unsafe void RunTypeTest(T valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ Assert.AreEqual(sizeof(T), writeSize);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
+
+ var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
+
+ writer.WriteValue(valueToTest);
+
+ var reader = CommonChecks(ref writer, valueToTest, writeSize, failMessage);
+
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriter.GetWriteSize()));
+ reader.ReadValue(out T result);
+ Assert.AreEqual(valueToTest, result);
+ }
+ }
+ }
+ protected override unsafe void RunTypeTestSafe(T valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(T), writeSize);
+
+ var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
+
+ writer.WriteValueSafe(valueToTest);
+
+
+ var reader = CommonChecks(ref writer, valueToTest, writeSize, failMessage);
+
+ using (reader)
+ {
+ reader.ReadValueSafe(out T result);
+ Assert.AreEqual(valueToTest, result);
+ }
+ }
+ }
+
+ protected override unsafe void RunObjectTypeTest(T valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(T), writeSize);
+
+ var failMessage = $"RunObjectTypeTest failed with type {typeof(T)} and value {valueToTest}";
+ writer.WriteObject(valueToTest);
+
+ var reader = CommonChecks(ref writer, valueToTest, writeSize, failMessage);
+
+ using (reader)
+ {
+ reader.ReadObject(out object result, typeof(T));
+ Assert.AreEqual(valueToTest, result);
+ }
+ }
+ }
+
+ private void VerifyArrayEquality(T[] value, T[] compareValue, int offset) where T : unmanaged
+ {
+ Assert.AreEqual(value.Length, compareValue.Length);
+
+ for (var i = 0; i < value.Length; ++i)
+ {
+ Assert.AreEqual(value[i], compareValue[i]);
+ }
+ }
+
+ protected override unsafe void RunTypeArrayTest(T[] valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
+ Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
+
+ writer.WriteValue(valueToTest);
+
+ WriteCheckBytes(ref writer, writeSize);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ VerifyPositionAndLength(ref reader, writer.Length);
+
+ Assert.IsTrue(reader.TryBeginRead(writeSize));
+ reader.ReadValue(out T[] result);
+ VerifyArrayEquality(valueToTest, result, 0);
+
+ VerifyCheckBytes(ref reader, writeSize);
+ }
+ }
+ }
+
+ protected override unsafe void RunTypeArrayTestSafe(T[] valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
+
+ writer.WriteValueSafe(valueToTest);
+
+ WriteCheckBytes(ref writer, writeSize);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ VerifyPositionAndLength(ref reader, writer.Length);
+
+ reader.ReadValueSafe(out T[] result);
+ VerifyArrayEquality(valueToTest, result, 0);
+
+ VerifyCheckBytes(ref reader, writeSize);
+ }
+ }
+ }
+
+ protected override unsafe void RunObjectTypeArrayTest(T[] valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ // Extra byte for WriteObject adding isNull flag
+ var writer = new FastBufferWriter(writeSize + 3, Allocator.Temp);
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
+
+ writer.WriteObject(valueToTest);
+
+ WriteCheckBytes(ref writer, writeSize + 1);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ VerifyPositionAndLength(ref reader, writer.Length);
+
+ reader.ReadObject(out object result, typeof(T[]));
+ VerifyArrayEquality(valueToTest, (T[])result, 0);
+
+ VerifyCheckBytes(ref reader, writeSize + 1);
+ }
+ }
+ }
+ #endregion
+
+ #region Tests
+ [Test]
+ public void GivenFastBufferWriterContainingValue_WhenReadingUnmanagedType_ValueMatchesWhatWasWritten(
+ [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
+ typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
+ typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
+ typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
+ Type testType,
+ [Values] WriteType writeType)
+ {
+ BaseTypeTest(testType, writeType);
+ }
+
+ [Test]
+ public void GivenFastBufferWriterContainingValue_WhenReadingArrayOfUnmanagedElementType_ValueMatchesWhatWasWritten(
+ [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
+ typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
+ typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
+ typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
+ Type testType,
+ [Values] WriteType writeType)
+ {
+ BaseArrayTypeTest(testType, writeType);
+ }
+
+ [TestCase(false, WriteType.WriteDirect)]
+ [TestCase(false, WriteType.WriteSafe)]
+ [TestCase(false, WriteType.WriteAsObject)]
+ [TestCase(true, WriteType.WriteDirect)]
+ [TestCase(true, WriteType.WriteSafe)]
+ public void GivenFastBufferWriterContainingValue_WhenReadingString_ValueMatchesWhatWasWritten(bool oneByteChars, WriteType writeType)
+ {
+ string valueToTest = "Hello, I am a test string!";
+
+ var serializedValueSize = FastBufferWriter.GetWriteSize(valueToTest, oneByteChars);
+
+ var writer = new FastBufferWriter(serializedValueSize + 3, Allocator.Temp);
+ using (writer)
+ {
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(writer.TryBeginWrite(serializedValueSize + 2), "Writer denied write permission");
+ writer.WriteValue(valueToTest, oneByteChars);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(valueToTest, oneByteChars);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(valueToTest);
+ serializedValueSize += 1;
+ break;
+ }
+
+ WriteCheckBytes(ref writer, serializedValueSize);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ VerifyPositionAndLength(ref reader, writer.Length);
+
+ string result = null;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(reader.TryBeginRead(serializedValueSize + 2), "Reader denied read permission");
+ reader.ReadValue(out result, oneByteChars);
+ break;
+ case WriteType.WriteSafe:
+ reader.ReadValueSafe(out result, oneByteChars);
+ break;
+ case WriteType.WriteAsObject:
+ reader.ReadObject(out object resultObj, typeof(string), oneByteChars);
+ result = (string)resultObj;
+ break;
+ }
+ Assert.AreEqual(valueToTest, result);
+
+ VerifyCheckBytes(ref reader, serializedValueSize);
+ }
+ }
+ }
+
+
+ [TestCase(1, 0)]
+ [TestCase(2, 0)]
+ [TestCase(3, 0)]
+ [TestCase(4, 0)]
+ [TestCase(5, 0)]
+ [TestCase(6, 0)]
+ [TestCase(7, 0)]
+ [TestCase(8, 0)]
+
+ [TestCase(1, 1)]
+ [TestCase(2, 1)]
+ [TestCase(3, 1)]
+ [TestCase(4, 1)]
+ [TestCase(5, 1)]
+ [TestCase(6, 1)]
+ [TestCase(7, 1)]
+
+ [TestCase(1, 2)]
+ [TestCase(2, 2)]
+ [TestCase(3, 2)]
+ [TestCase(4, 2)]
+ [TestCase(5, 2)]
+ [TestCase(6, 2)]
+
+ [TestCase(1, 3)]
+ [TestCase(2, 3)]
+ [TestCase(3, 3)]
+ [TestCase(4, 3)]
+ [TestCase(5, 3)]
+
+ [TestCase(1, 4)]
+ [TestCase(2, 4)]
+ [TestCase(3, 4)]
+ [TestCase(4, 4)]
+
+ [TestCase(1, 5)]
+ [TestCase(2, 5)]
+ [TestCase(3, 5)]
+
+ [TestCase(1, 6)]
+ [TestCase(2, 6)]
+
+ [TestCase(1, 7)]
+ public void GivenFastBufferWriterContainingValue_WhenReadingPartialValue_ValueMatchesWhatWasWritten(int count, int offset)
+ {
+ var random = new Random();
+ var valueToTest = ((ulong)random.Next() << 32) + (ulong)random.Next();
+ var writer = new FastBufferWriter(sizeof(ulong) + 2, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(count + 2), "Writer denied write permission");
+ writer.WritePartialValue(valueToTest, count, offset);
+
+ var failMessage = $"TestReadingPartialValues failed with value {valueToTest}";
+ WriteCheckBytes(ref writer, count, failMessage);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ VerifyPositionAndLength(ref reader, writer.Length, failMessage);
+ Assert.IsTrue(reader.TryBeginRead(count + 2), "Reader denied read permission");
+
+ ulong mask = 0;
+ for (var i = 0; i < count; ++i)
+ {
+ mask = (mask << 8) | 0b11111111;
+ }
+
+ mask <<= (offset * 8);
+
+ reader.ReadPartialValue(out ulong result, count, offset);
+ Assert.AreEqual(valueToTest & mask, result & mask, failMessage);
+ VerifyCheckBytes(ref reader, count, failMessage);
+ }
+ }
+ }
+
+
+ [Test]
+ public unsafe void GivenFastBufferReaderInitializedFromFastBufferWriterContainingValue_WhenCallingToArray_ReturnedArrayMatchesContentOfWriter()
+ {
+ var testStruct = GetTestStruct();
+ var requiredSize = FastBufferWriter.GetWriteSize(testStruct);
+ var writer = new FastBufferWriter(requiredSize, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(requiredSize);
+ writer.WriteValue(testStruct);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ var array = reader.ToArray();
+ var underlyingArray = writer.GetUnsafePtr();
+ for (var i = 0; i < array.Length; ++i)
+ {
+ Assert.AreEqual(array[i], underlyingArray[i]);
+ }
+ }
+ }
+ }
+
+
+ [Test]
+ public void WhenCallingReadByteWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ Assert.Throws(() => { emptyReader.ReadByte(out byte b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadBytesWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ byte[] b = { 0, 1, 2 };
+ using (emptyReader)
+ {
+ Assert.Throws(() => { emptyReader.ReadBytes(ref b, 3); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithUnmanagedTypeWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ Assert.Throws(() => { emptyReader.ReadValue(out int i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithByteArrayWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ Assert.Throws(() => { emptyReader.ReadValue(out byte[] b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithStringWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ Assert.Throws(() => { emptyReader.ReadValue(out string s); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueAfterCallingTryBeginWriteWithTooFewBytes_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(sizeof(int) - 1);
+ Assert.Throws(() => { emptyReader.ReadValue(out int i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadBytePastBoundaryMarkedByTryBeginWrite_OverflowExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(sizeof(int) - 1);
+ emptyReader.ReadByte(out byte b);
+ emptyReader.ReadByte(out b);
+ emptyReader.ReadByte(out b);
+ Assert.Throws(() => { emptyReader.ReadByte(out b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadByteDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadByte(out byte b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadBytesDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ byte[] b = { 0, 1, 2 };
+ Assert.Throws(() => { emptyReader.ReadBytes(ref b, 3); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadValue(out int i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadValue(out byte[] b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadValue(out string s); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadByteSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadByteSafe(out byte b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadBytesSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ byte[] b = { 0, 1, 2 };
+ Assert.Throws(() => { emptyReader.ReadBytesSafe(ref b, 3); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueSafeWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadValueSafe(out int i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueSafeWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadValueSafe(out byte[] b); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueSafeWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ using var context = emptyReader.EnterBitwiseContext();
+ Assert.Throws(() => { emptyReader.ReadValueSafe(out string s); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadByteAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadByte(out byte theByte);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadBytesAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+
+ byte[] theBytes = { 0, 1, 2 };
+ emptyReader.ReadBytes(ref theBytes, 3);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadValue(out int i);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadValue(out byte[] theBytes);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadValue(out string s);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadByteSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadByteSafe(out byte theByte);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadBytesSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+
+ byte[] theBytes = { 0, 1, 2 };
+ emptyReader.ReadBytesSafe(ref theBytes, 3);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueSafeWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadValueSafe(out int i);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueSafeWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadValueSafe(out byte[] theBytes);
+ }
+ }
+
+ [Test]
+ public void WhenCallingReadValueSafeWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ using (var context = emptyReader.EnterBitwiseContext())
+ {
+ context.ReadBit(out bool theBit);
+ }
+ emptyReader.ReadValueSafe(out string s);
+ }
+ }
+
+ [Test]
+ public void WhenCallingTryBeginRead_TheAllowedReadPositionIsMarkedRelativeToCurrentPosition()
+ {
+ var nativeArray = new NativeArray(100, Allocator.Temp);
+ var emptyReader = new FastBufferReader(nativeArray, Allocator.Temp);
+
+ using (emptyReader)
+ {
+ emptyReader.TryBeginRead(100);
+ emptyReader.ReadByte(out byte b);
+ emptyReader.TryBeginRead(1);
+ emptyReader.ReadByte(out b);
+ Assert.Throws(() => { emptyReader.ReadByte(out byte b); });
+ }
+ }
+
+ [Test]
+ public void WhenReadingAfterSeeking_TheNewReadComesFromTheCorrectPosition()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.WriteByteSafe(1);
+ writer.WriteByteSafe(3);
+ writer.WriteByteSafe(2);
+ writer.WriteByteSafe(5);
+ writer.WriteByteSafe(4);
+ writer.WriteByteSafe(0);
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ reader.Seek(5);
+ reader.ReadByteSafe(out byte b);
+ Assert.AreEqual(reader.Position, 6);
+ Assert.AreEqual(reader.Length, writer.Length);
+ Assert.AreEqual(0, b);
+
+ reader.Seek(0);
+ reader.ReadByteSafe(out b);
+ Assert.AreEqual(reader.Position, 1);
+ Assert.AreEqual(reader.Length, writer.Length);
+ Assert.AreEqual(1, b);
+
+ reader.Seek(10);
+ Assert.AreEqual(reader.Position, writer.Length);
+ Assert.AreEqual(reader.Length, writer.Length);
+
+ reader.Seek(2);
+ reader.ReadByteSafe(out b);
+ Assert.AreEqual(2, b);
+
+ reader.Seek(1);
+ reader.ReadByteSafe(out b);
+ Assert.AreEqual(3, b);
+
+ reader.Seek(4);
+ reader.ReadByteSafe(out b);
+ Assert.AreEqual(4, b);
+
+ reader.Seek(3);
+ reader.ReadByteSafe(out b);
+ Assert.AreEqual(5, b);
+
+ Assert.AreEqual(reader.Position, 4);
+ Assert.AreEqual(reader.Length, writer.Length);
+ }
+ }
+ }
+
+ [Test]
+ public void GivenFastBufferWriterWithNetworkObjectWritten_WhenReadingNetworkObject_TheSameObjectIsRetrieved([Values] WriteType writeType)
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkObject) + 1, Allocator.Temp);
+ using (writer)
+ {
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkObject)));
+ writer.WriteValue(networkObject);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(networkObject);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(networkObject);
+ break;
+ }
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetNetworkObjectWriteSize()));
+ NetworkObject result = null;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetWriteSize(networkObject)));
+ reader.ReadValue(out result);
+ break;
+ case WriteType.WriteSafe:
+ reader.ReadValueSafe(out result);
+ break;
+ case WriteType.WriteAsObject:
+ reader.ReadObject(out object resultObj, typeof(NetworkObject));
+ result = (NetworkObject)resultObj;
+ break;
+ }
+ Assert.AreSame(result, networkObject);
+ }
+ }
+ });
+ }
+
+ [Test]
+ public void GivenFastBufferWriterWithGameObjectWritten_WhenReadingGameObject_TheSameObjectIsRetrieved([Values] WriteType writeType)
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(obj) + 1, Allocator.Temp);
+ using (writer)
+ {
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(obj)));
+ writer.WriteValue(obj);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(obj);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(obj);
+ break;
+ }
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetGameObjectWriteSize()));
+ GameObject result = null;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetWriteSize(obj)));
+ reader.ReadValue(out result);
+ break;
+ case WriteType.WriteSafe:
+ reader.ReadValueSafe(out result);
+ break;
+ case WriteType.WriteAsObject:
+ reader.ReadObject(out object resultObj, typeof(GameObject));
+ result = (GameObject)resultObj;
+ break;
+ }
+ Assert.AreSame(result, obj);
+ }
+ }
+ });
+ }
+
+ [Test]
+ public void GivenFastBufferWriterWithNetworkBehaviourWritten_WhenReadingNetworkBehaviour_TheSameObjectIsRetrieved([Values] WriteType writeType)
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkBehaviour) + 1, Allocator.Temp);
+ using (writer)
+ {
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkBehaviour)));
+ writer.WriteValue(networkBehaviour);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(networkBehaviour);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(networkBehaviour);
+ break;
+ }
+
+ var reader = new FastBufferReader(ref writer, Allocator.Temp);
+ using (reader)
+ {
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetNetworkBehaviourWriteSize()));
+ NetworkBehaviour result = null;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(reader.TryBeginRead(FastBufferWriterExtensions.GetWriteSize(networkBehaviour)));
+ reader.ReadValue(out result);
+ break;
+ case WriteType.WriteSafe:
+ reader.ReadValueSafe(out result);
+ break;
+ case WriteType.WriteAsObject:
+ reader.ReadObject(out object resultObj, typeof(NetworkBehaviour));
+ result = (NetworkBehaviour)resultObj;
+ break;
+ }
+ Assert.AreSame(result, networkBehaviour);
+ }
+ }
+ });
+ }
+
+ [Test]
+ public void WhenCallingTryBeginReadInternal_AllowedReadPositionDoesNotMoveBackward()
+ {
+ var reader = new FastBufferReader(new NativeArray(100, Allocator.Temp), Allocator.Temp);
+ using (reader)
+ {
+ reader.TryBeginRead(25);
+ reader.TryBeginReadInternal(5);
+ Assert.AreEqual(reader.AllowedReadMark, 25);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs.meta
new file mode 100644
index 0000000000..52af796039
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2881f8138b479c34389b76687e5307ab
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs
new file mode 100644
index 0000000000..f1d3897737
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs
@@ -0,0 +1,1215 @@
+using System;
+using NUnit.Framework;
+using Unity.Collections;
+using UnityEngine;
+using Random = System.Random;
+
+namespace Unity.Netcode.EditorTests
+{
+ public class FastBufferWriterTests : BaseFastBufferReaderWriterTest
+ {
+
+ #region Common Checks
+ private void WriteCheckBytes(ref FastBufferWriter writer, int writeSize, string failMessage = "")
+ {
+ Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
+ writer.WriteValue((byte)0x80);
+ Assert.AreEqual(writeSize + 1, writer.Position, failMessage);
+ Assert.AreEqual(writeSize + 1, writer.Length, failMessage);
+ writer.WriteValue((byte)0xFF);
+ Assert.AreEqual(writeSize + 2, writer.Position, failMessage);
+ Assert.AreEqual(writeSize + 2, writer.Length, failMessage);
+ }
+
+ private void VerifyCheckBytes(byte[] underlyingArray, int writeSize, string failMessage = "")
+ {
+ Assert.AreEqual(0x80, underlyingArray[writeSize], failMessage);
+ Assert.AreEqual(0xFF, underlyingArray[writeSize + 1], failMessage);
+ }
+
+ private unsafe void VerifyBytewiseEquality(T value, byte[] underlyingArray, int valueOffset, int bufferOffset, int size, string failMessage = "") where T : unmanaged
+ {
+ byte* asBytePointer = (byte*)&value;
+ for (var i = 0; i < size; ++i)
+ {
+ Assert.AreEqual(asBytePointer[i + valueOffset], underlyingArray[i + bufferOffset], failMessage);
+ }
+ }
+
+ private unsafe void VerifyTypedEquality(T value, byte* unsafePtr) where T : unmanaged
+ {
+ var checkValue = (T*)unsafePtr;
+ Assert.AreEqual(value, *checkValue);
+ }
+
+ private void VerifyPositionAndLength(ref FastBufferWriter writer, int position, string failMessage = "")
+ {
+ Assert.AreEqual(position, writer.Position, failMessage);
+ Assert.AreEqual(position, writer.Length, failMessage);
+ }
+
+ private unsafe void CommonChecks(ref FastBufferWriter writer, T valueToTest, int writeSize, string failMessage = "") where T : unmanaged
+ {
+
+ VerifyPositionAndLength(ref writer, writeSize, failMessage);
+
+ WriteCheckBytes(ref writer, writeSize, failMessage);
+
+ var underlyingArray = writer.ToArray();
+
+ VerifyBytewiseEquality(valueToTest, underlyingArray, 0, 0, writeSize, failMessage);
+
+ VerifyCheckBytes(underlyingArray, writeSize, failMessage);
+
+ VerifyTypedEquality(valueToTest, writer.GetUnsafePtr());
+ }
+ #endregion
+
+ #region Generic Checks
+ protected override unsafe void RunTypeTest(T valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var alternateWriteSize = FastBufferWriter.GetWriteSize();
+ Assert.AreEqual(sizeof(T), writeSize);
+ Assert.AreEqual(sizeof(T), alternateWriteSize);
+
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+
+ using (writer)
+ {
+
+ Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
+
+ var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
+
+ writer.WriteValue(valueToTest);
+
+ CommonChecks(ref writer, valueToTest, writeSize, failMessage);
+ }
+ }
+ protected override unsafe void RunTypeTestSafe(T valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(T), writeSize);
+
+ var failMessage = $"RunTypeTest failed with type {typeof(T)} and value {valueToTest}";
+
+ writer.WriteValueSafe(valueToTest);
+
+ CommonChecks(ref writer, valueToTest, writeSize, failMessage);
+ }
+ }
+
+ protected override unsafe void RunObjectTypeTest(T valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.AreEqual(sizeof(T), writeSize);
+
+ var failMessage = $"RunObjectTypeTest failed with type {typeof(T)} and value {valueToTest}";
+ writer.WriteObject(valueToTest);
+
+ CommonChecks(ref writer, valueToTest, writeSize, failMessage);
+ }
+ }
+
+ private unsafe void VerifyArrayEquality(T[] value, byte* unsafePtr, int offset) where T : unmanaged
+ {
+ int* sizeValue = (int*)(unsafePtr + offset);
+ Assert.AreEqual(value.Length, *sizeValue);
+
+ fixed (T* asTPointer = value)
+ {
+ var underlyingTArray = (T*)(unsafePtr + sizeof(int) + offset);
+ for (var i = 0; i < value.Length; ++i)
+ {
+ Assert.AreEqual(asTPointer[i], underlyingTArray[i]);
+ }
+ }
+ }
+
+ protected override unsafe void RunTypeArrayTest(T[] valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+ using (writer)
+ {
+
+ Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
+ Assert.IsTrue(writer.TryBeginWrite(writeSize + 2), "Writer denied write permission");
+
+ writer.WriteValue(valueToTest);
+ VerifyPositionAndLength(ref writer, writeSize);
+
+ WriteCheckBytes(ref writer, writeSize);
+
+ VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0);
+
+ var underlyingArray = writer.ToArray();
+ VerifyCheckBytes(underlyingArray, writeSize);
+ }
+ }
+
+ protected override unsafe void RunTypeArrayTestSafe(T[] valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ var writer = new FastBufferWriter(writeSize + 2, Allocator.Temp);
+ using (writer)
+ {
+
+ Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
+
+ writer.WriteValueSafe(valueToTest);
+ VerifyPositionAndLength(ref writer, writeSize);
+
+ WriteCheckBytes(ref writer, writeSize);
+
+ VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), 0);
+
+ var underlyingArray = writer.ToArray();
+ VerifyCheckBytes(underlyingArray, writeSize);
+ }
+ }
+
+ protected override unsafe void RunObjectTypeArrayTest(T[] valueToTest)
+ {
+ var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
+ // Extra byte for WriteObject adding isNull flag
+ var writer = new FastBufferWriter(writeSize + 3, Allocator.Temp);
+ using (writer)
+ {
+
+ Assert.AreEqual(sizeof(int) + sizeof(T) * valueToTest.Length, writeSize);
+
+ writer.WriteObject(valueToTest);
+ Assert.AreEqual(0, writer.ToArray()[0]);
+ VerifyPositionAndLength(ref writer, writeSize + sizeof(byte));
+
+ WriteCheckBytes(ref writer, writeSize + sizeof(byte));
+
+ VerifyArrayEquality(valueToTest, writer.GetUnsafePtr(), sizeof(byte));
+
+ var underlyingArray = writer.ToArray();
+ VerifyCheckBytes(underlyingArray, writeSize + sizeof(byte));
+ }
+ }
+ #endregion
+
+
+ #region Tests
+ [Test, Description("Tests ")]
+ public void WhenWritingUnmanagedType_ValueIsWrittenCorrectly(
+ [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
+ typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
+ typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
+ typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
+ Type testType,
+ [Values] WriteType writeType)
+ {
+ BaseTypeTest(testType, writeType);
+ }
+
+ [Test]
+ public void WhenWritingArrayOfUnmanagedElementType_ArrayIsWrittenCorrectly(
+ [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
+ typeof(ByteEnum), typeof(SByteEnum), typeof(ShortEnum), typeof(UShortEnum), typeof(IntEnum),
+ typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(Vector2), typeof(Vector3), typeof(Vector4),
+ typeof(Quaternion), typeof(Color), typeof(Color32), typeof(Ray), typeof(Ray2D), typeof(TestStruct))]
+ Type testType,
+ [Values] WriteType writeType)
+ {
+ BaseArrayTypeTest(testType, writeType);
+ }
+
+ [TestCase(false, WriteType.WriteDirect)]
+ [TestCase(false, WriteType.WriteSafe)]
+ [TestCase(false, WriteType.WriteAsObject)]
+ [TestCase(true, WriteType.WriteDirect)]
+ [TestCase(true, WriteType.WriteSafe)]
+ public unsafe void WhenWritingString_ValueIsWrittenCorrectly(bool oneByteChars, WriteType writeType)
+ {
+ string valueToTest = "Hello, I am a test string!";
+
+ var serializedValueSize = FastBufferWriter.GetWriteSize(valueToTest, oneByteChars);
+
+ var writer = new FastBufferWriter(serializedValueSize + 3, Allocator.Temp);
+ using (writer)
+ {
+ var offset = 0;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ Assert.IsTrue(writer.TryBeginWrite(serializedValueSize + 2), "Writer denied write permission");
+ writer.WriteValue(valueToTest, oneByteChars);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(valueToTest, oneByteChars);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(valueToTest);
+ // account for isNull byte
+ offset = sizeof(byte);
+ break;
+
+ }
+
+ VerifyPositionAndLength(ref writer, serializedValueSize + offset);
+ WriteCheckBytes(ref writer, serializedValueSize + offset);
+
+ int* sizeValue = (int*)(writer.GetUnsafePtr() + offset);
+ Assert.AreEqual(valueToTest.Length, *sizeValue);
+
+ fixed (char* asCharPointer = valueToTest)
+ {
+ if (oneByteChars)
+ {
+ byte* underlyingByteArray = writer.GetUnsafePtr() + sizeof(int) + offset;
+ for (var i = 0; i < valueToTest.Length; ++i)
+ {
+ Assert.AreEqual((byte)asCharPointer[i], underlyingByteArray[i]);
+ }
+
+ }
+ else
+ {
+ char* underlyingCharArray = (char*)(writer.GetUnsafePtr() + sizeof(int) + offset);
+ for (var i = 0; i < valueToTest.Length; ++i)
+ {
+ Assert.AreEqual(asCharPointer[i], underlyingCharArray[i]);
+ }
+ }
+ }
+
+ var underlyingArray = writer.ToArray();
+ VerifyCheckBytes(underlyingArray, serializedValueSize + offset);
+ }
+ }
+
+ [TestCase(1, 0)]
+ [TestCase(2, 0)]
+ [TestCase(3, 0)]
+ [TestCase(4, 0)]
+ [TestCase(5, 0)]
+ [TestCase(6, 0)]
+ [TestCase(7, 0)]
+ [TestCase(8, 0)]
+
+ [TestCase(1, 1)]
+ [TestCase(2, 1)]
+ [TestCase(3, 1)]
+ [TestCase(4, 1)]
+ [TestCase(5, 1)]
+ [TestCase(6, 1)]
+ [TestCase(7, 1)]
+
+ [TestCase(1, 2)]
+ [TestCase(2, 2)]
+ [TestCase(3, 2)]
+ [TestCase(4, 2)]
+ [TestCase(5, 2)]
+ [TestCase(6, 2)]
+
+ [TestCase(1, 3)]
+ [TestCase(2, 3)]
+ [TestCase(3, 3)]
+ [TestCase(4, 3)]
+ [TestCase(5, 3)]
+
+ [TestCase(1, 4)]
+ [TestCase(2, 4)]
+ [TestCase(3, 4)]
+ [TestCase(4, 4)]
+
+ [TestCase(1, 5)]
+ [TestCase(2, 5)]
+ [TestCase(3, 5)]
+
+ [TestCase(1, 6)]
+ [TestCase(2, 6)]
+
+ [TestCase(1, 7)]
+ public unsafe void WhenWritingPartialValueWithCountAndOffset_ValueIsWrittenCorrectly(int count, int offset)
+ {
+ var random = new Random();
+ var valueToTest = ((ulong)random.Next() << 32) + (ulong)random.Next();
+ var writer = new FastBufferWriter(sizeof(ulong) + 2, Allocator.Temp);
+ using (writer)
+ {
+
+ Assert.IsTrue(writer.TryBeginWrite(count + 2), "Writer denied write permission");
+ writer.WritePartialValue(valueToTest, count, offset);
+
+ var failMessage = $"TestWritingPartialValues failed with value {valueToTest}";
+ VerifyPositionAndLength(ref writer, count, failMessage);
+ WriteCheckBytes(ref writer, count, failMessage);
+ var underlyingArray = writer.ToArray();
+ VerifyBytewiseEquality(valueToTest, underlyingArray, offset, 0, count, failMessage);
+ VerifyCheckBytes(underlyingArray, count, failMessage);
+
+ ulong mask = 0;
+ for (var i = 0; i < count; ++i)
+ {
+ mask = (mask << 8) | 0b11111111;
+ }
+
+ ulong* checkValue = (ulong*)writer.GetUnsafePtr();
+ Assert.AreEqual((valueToTest >> (offset * 8)) & mask, *checkValue & mask);
+ }
+ }
+
+ [Test]
+ public void WhenCallingToArray_ReturnedArrayContainsCorrectData()
+ {
+ var testStruct = GetTestStruct();
+ var requiredSize = FastBufferWriter.GetWriteSize(testStruct);
+ var writer = new FastBufferWriter(requiredSize, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(requiredSize);
+ writer.WriteValue(testStruct);
+ var array = writer.ToArray();
+ var underlyingArray = writer.ToArray();
+ for (var i = 0; i < array.Length; ++i)
+ {
+ Assert.AreEqual(array[i], underlyingArray[i]);
+ }
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteByteWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.Throws(() => { writer.WriteByte(1); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteBytesWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ var bytes = new byte[] { 0, 1, 2 };
+ Assert.Throws(() => { writer.WriteBytes(bytes, bytes.Length); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithUnmanagedTypeWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ int i = 1;
+ Assert.Throws(() => { writer.WriteValue(i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithByteArrayWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ var bytes = new byte[] { 0, 1, 2 };
+ Assert.Throws(() => { writer.WriteValue(bytes); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithStringWithoutCallingTryBeingWriteFirst_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ Assert.Throws(() => { writer.WriteValue(""); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueAfterCallingTryBeginWriteWithTooFewBytes_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ int i = 0;
+ writer.TryBeginWrite(sizeof(int) - 1);
+ Assert.Throws(() => { writer.WriteValue(i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteBytePastBoundaryMarkedByTryBeginWrite_OverflowExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(sizeof(int) - 1);
+ writer.WriteByte(1);
+ writer.WriteByte(2);
+ writer.WriteByte(3);
+ Assert.Throws(() => { writer.WriteByte(4); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteByteDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteByte(1); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteBytesDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteBytes(bytes, bytes.Length); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ int i = 1;
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteValue(i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteBytes(bytes, bytes.Length); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ int i = 1;
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteValue(""); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteByteSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteByteSafe(1); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteBytesSafeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteBytesSafe(bytes, bytes.Length); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueSafeWithUnmanagedTypeDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ int i = 1;
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteValueSafe(i); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueSafeWithByteArrayDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteBytesSafe(bytes, bytes.Length); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueSafeWithStringDuringBitwiseContext_InvalidOperationExceptionIsThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using var context = writer.EnterBitwiseContext();
+ Assert.Throws(() => { writer.WriteValueSafe(""); });
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteByteAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteByte(1);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteBytesAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteBytes(bytes, bytes.Length);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ int i = 1;
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteValue(i);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ int i = 1;
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteValue(bytes);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteValue("");
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteByteSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteByteSafe(1);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteBytesSafeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteBytesSafe(bytes, bytes.Length);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueSafeWithUnmanagedTypeAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ int i = 1;
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteValueSafe(i);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueSafeWithByteArrayAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ var bytes = new byte[] { 0, 1, 2 };
+ int i = 1;
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteValueSafe(bytes);
+ }
+ }
+
+ [Test]
+ public void WhenCallingWriteValueSafeWithStringAfterExitingBitwiseContext_InvalidOperationExceptionIsNotThrown()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ using (var context = writer.EnterBitwiseContext())
+ {
+ context.WriteBit(true);
+ }
+ writer.WriteValueSafe("");
+ }
+ }
+
+ [Test]
+ public void WhenCallingTryBeginWrite_TheAllowedWritePositionIsMarkedRelativeToCurrentPosition()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.TryBeginWrite(100);
+ writer.WriteByte(1);
+ writer.TryBeginWrite(1);
+ writer.WriteByte(1);
+ Assert.Throws(() => { writer.WriteByte(1); });
+ }
+ }
+
+ [Test]
+ public void WhenWritingAfterSeeking_TheNewWriteGoesToTheCorrectPosition()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.Seek(5);
+ writer.WriteByteSafe(0);
+ Assert.AreEqual(writer.Position, 6);
+
+ writer.Seek(0);
+ writer.WriteByteSafe(1);
+ Assert.AreEqual(writer.Position, 1);
+
+ writer.Seek(10);
+ Assert.AreEqual(writer.Position, 10);
+
+ writer.Seek(2);
+ writer.WriteByteSafe(2);
+
+ writer.Seek(1);
+ writer.WriteByteSafe(3);
+
+ writer.Seek(4);
+ writer.WriteByteSafe(4);
+
+ writer.Seek(3);
+ writer.WriteByteSafe(5);
+
+ Assert.AreEqual(writer.Position, 4);
+
+ var expected = new byte[] { 1, 3, 2, 5, 4, 0 };
+ var underlyingArray = writer.ToArray();
+ for (var i = 0; i < expected.Length; ++i)
+ {
+ Assert.AreEqual(expected[i], underlyingArray[i]);
+ }
+ }
+ }
+
+ [Test]
+ public void WhenSeekingForward_LengthUpdatesToNewPosition()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ Assert.AreEqual(writer.Length, 0);
+ writer.Seek(5);
+ Assert.AreEqual(writer.Length, 5);
+ writer.Seek(10);
+ Assert.AreEqual(writer.Length, 10);
+ }
+ }
+
+ [Test]
+ public void WhenSeekingBackward_LengthDoesNotChange()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ Assert.AreEqual(writer.Length, 0);
+ writer.Seek(5);
+ Assert.AreEqual(writer.Length, 5);
+ writer.Seek(0);
+ Assert.AreEqual(writer.Length, 5);
+ }
+ }
+
+ [Test]
+ public void WhenTruncatingToSpecificPositionAheadOfWritePosition_LengthIsUpdatedAndPositionIsNot()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.Seek(10);
+ Assert.AreEqual(writer.Position, 10);
+ Assert.AreEqual(writer.Length, 10);
+
+ writer.Seek(5);
+ Assert.AreEqual(writer.Position, 5);
+ Assert.AreEqual(writer.Length, 10);
+
+ writer.Truncate(8);
+ Assert.AreEqual(writer.Position, 5);
+ Assert.AreEqual(writer.Length, 8);
+ }
+ }
+
+ [Test]
+ public void WhenTruncatingToSpecificPositionBehindWritePosition_BothLengthAndPositionAreUpdated()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.Seek(10);
+ Assert.AreEqual(writer.Position, 10);
+ Assert.AreEqual(writer.Length, 10);
+
+ writer.Truncate(8);
+ Assert.AreEqual(writer.Position, 8);
+ Assert.AreEqual(writer.Length, 8);
+ }
+ }
+
+ [Test]
+ public void WhenTruncatingToCurrentPosition_LengthIsUpdated()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.Seek(10);
+ Assert.AreEqual(writer.Position, 10);
+ Assert.AreEqual(writer.Length, 10);
+
+ writer.Seek(5);
+ writer.Truncate();
+ Assert.AreEqual(writer.Position, 5);
+ Assert.AreEqual(writer.Length, 5);
+ }
+ }
+
+ [Test]
+ public void WhenCreatingNewFastBufferWriter_CapacityIsCorrect()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ Assert.AreEqual(100, writer.Capacity);
+ writer.Dispose();
+
+ writer = new FastBufferWriter(200, Allocator.Temp);
+ Assert.AreEqual(200, writer.Capacity);
+ writer.Dispose();
+ }
+
+ [Test]
+ public void WhenCreatingNewFastBufferWriter_MaxCapacityIsCorrect()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ Assert.AreEqual(100, writer.MaxCapacity);
+ writer.Dispose();
+
+ writer = new FastBufferWriter(100, Allocator.Temp, 200);
+ Assert.AreEqual(200, writer.MaxCapacity);
+ writer.Dispose();
+ }
+
+ [Test]
+ public void WhenRequestingWritePastBoundsForNonGrowingWriter_TryBeginWriteReturnsFalse()
+ {
+ var writer = new FastBufferWriter(150, Allocator.Temp);
+ using (writer)
+ {
+ var testStruct = GetTestStruct();
+ writer.TryBeginWriteValue(testStruct);
+ writer.WriteValue(testStruct);
+
+ // Seek to exactly where the write would cross the buffer boundary
+ writer.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1);
+
+ // Writer isn't allowed to grow because it didn't specify a maxSize
+ Assert.IsFalse(writer.TryBeginWriteValue(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenTryBeginWriteReturnsFalse_WritingThrowsOverflowException()
+ {
+ var writer = new FastBufferWriter(150, Allocator.Temp);
+ using (writer)
+ {
+ var testStruct = GetTestStruct();
+ writer.TryBeginWriteValue(testStruct);
+ writer.WriteValue(testStruct);
+
+ // Seek to exactly where the write would cross the buffer boundary
+ writer.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1);
+
+ // Writer isn't allowed to grow because it didn't specify a maxSize
+ Assert.IsFalse(writer.TryBeginWriteValue(testStruct));
+ Assert.Throws(() => writer.WriteValue(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenTryBeginWriteReturnsFalseAndOverflowExceptionIsThrown_DataIsNotAffected()
+ {
+ var writer = new FastBufferWriter(150, Allocator.Temp);
+ using (writer)
+ {
+ var testStruct = GetTestStruct();
+ writer.TryBeginWriteValue(testStruct);
+ writer.WriteValue(testStruct);
+
+ // Seek to exactly where the write would cross the buffer boundary
+ writer.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1);
+
+ // Writer isn't allowed to grow because it didn't specify a maxSize
+ Assert.IsFalse(writer.TryBeginWriteValue(testStruct));
+ Assert.Throws(() => writer.WriteValue(testStruct));
+ VerifyBytewiseEquality(testStruct, writer.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenRequestingWritePastBoundsForGrowingWriter_BufferGrowsWithoutLosingData()
+ {
+ var growingWriter = new FastBufferWriter(150, Allocator.Temp, 500);
+ using (growingWriter)
+ {
+ var testStruct = GetTestStruct();
+ growingWriter.TryBeginWriteValue(testStruct);
+ growingWriter.WriteValue(testStruct);
+
+ // Seek to exactly where the write would cross the buffer boundary
+ growingWriter.Seek(150 - FastBufferWriter.GetWriteSize(testStruct) + 1);
+
+ Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct));
+
+ // Growth doubles the size
+ Assert.AreEqual(300, growingWriter.Capacity);
+ Assert.AreEqual(growingWriter.Position, 150 - FastBufferWriter.GetWriteSize(testStruct) + 1);
+ growingWriter.WriteValue(testStruct);
+
+ // Verify the growth properly copied the existing data
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct));
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 150 - FastBufferWriter.GetWriteSize(testStruct) + 1, FastBufferWriter.GetWriteSize(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenRequestingWriteExactlyAtBoundsForGrowingWriter_BufferDoesntGrow()
+ {
+ var growingWriter = new FastBufferWriter(300, Allocator.Temp, 500);
+ using (growingWriter)
+ {
+ var testStruct = GetTestStruct();
+ growingWriter.TryBeginWriteValue(testStruct);
+ growingWriter.WriteValue(testStruct);
+
+ growingWriter.Seek(300 - FastBufferWriter.GetWriteSize(testStruct));
+ Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct));
+ Assert.AreEqual(300, growingWriter.Capacity);
+ Assert.AreEqual(growingWriter.Position, growingWriter.ToArray().Length);
+ growingWriter.WriteValue(testStruct);
+ Assert.AreEqual(300, growingWriter.Position);
+
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct));
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 300 - FastBufferWriter.GetWriteSize(testStruct), FastBufferWriter.GetWriteSize(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenBufferGrows_MaxCapacityIsNotExceeded()
+ {
+ var growingWriter = new FastBufferWriter(300, Allocator.Temp, 500);
+ using (growingWriter)
+ {
+ var testStruct = GetTestStruct();
+ growingWriter.TryBeginWriteValue(testStruct);
+ growingWriter.WriteValue(testStruct);
+
+ growingWriter.Seek(300);
+ Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct));
+
+ Assert.AreEqual(500, growingWriter.Capacity);
+ Assert.AreEqual(growingWriter.Position, 300);
+
+ growingWriter.WriteValue(testStruct);
+
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct));
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 300, FastBufferWriter.GetWriteSize(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenBufferGrowthRequiredIsMoreThanDouble_BufferGrowsEnoughToContainRequestedValue()
+ {
+ var growingWriter = new FastBufferWriter(1, Allocator.Temp, 500);
+ using (growingWriter)
+ {
+ var testStruct = GetTestStruct();
+ Assert.IsTrue(growingWriter.TryBeginWriteValue(testStruct));
+
+ // Buffer size doubles with each growth, so since we're starting with a size of 1, that means
+ // the resulting size should be the next power of 2 above the size of testStruct.
+ Assert.AreEqual(Math.Pow(2, Math.Ceiling(Mathf.Log(FastBufferWriter.GetWriteSize(testStruct), 2))),
+ growingWriter.Capacity);
+ Assert.AreEqual(growingWriter.Position, 0);
+
+ growingWriter.WriteValue(testStruct);
+
+ VerifyBytewiseEquality(testStruct, growingWriter.ToArray(), 0, 0, FastBufferWriter.GetWriteSize(testStruct));
+ }
+ }
+
+ [Test]
+ public void WhenTryingToWritePastMaxCapacity_GrowthDoesNotOccurAndTryBeginWriteReturnsFalse()
+ {
+ var growingWriter = new FastBufferWriter(300, Allocator.Temp, 500);
+ using (growingWriter)
+ {
+ Assert.IsFalse(growingWriter.TryBeginWrite(501));
+
+ Assert.AreEqual(300, growingWriter.Capacity);
+ Assert.AreEqual(growingWriter.Position, 0);
+ }
+ }
+
+ [Test]
+ public void WhenWritingNetworkBehaviour_ObjectIdAndBehaviourIdAreWritten([Values] WriteType writeType)
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkBehaviour) + 1, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkBehaviour)));
+
+ var offset = 0;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ writer.WriteValue(networkBehaviour);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(networkBehaviour);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(networkBehaviour);
+ // account for isNull byte
+ offset = 1;
+ break;
+ }
+
+ Assert.AreEqual(FastBufferWriterExtensions.GetWriteSize(networkBehaviour) + offset, writer.Position);
+ VerifyBytewiseEquality(networkObject.NetworkObjectId, writer.ToArray(), 0, offset, sizeof(ulong));
+ VerifyBytewiseEquality(networkBehaviour.NetworkBehaviourId, writer.ToArray(), 0,
+ sizeof(ulong) + offset, sizeof(ushort));
+ }
+ });
+ }
+
+ [Test]
+ public void WhenWritingNetworkObject_NetworkObjectIdIsWritten([Values] WriteType writeType)
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(networkObject) + 1, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(networkObject)));
+
+ var offset = 0;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ writer.WriteValue(networkObject);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(networkObject);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(networkObject);
+ // account for isNull byte
+ offset = 1;
+ break;
+ }
+
+ Assert.AreEqual(FastBufferWriterExtensions.GetWriteSize(networkObject) + offset, writer.Position);
+ VerifyBytewiseEquality(networkObject.NetworkObjectId, writer.ToArray(), 0, offset, sizeof(ulong));
+ }
+ });
+ }
+
+ [Test]
+ public void WhenWritingGameObject_NetworkObjectIdIsWritten([Values] WriteType writeType)
+ {
+ RunGameObjectTest((obj, networkBehaviour, networkObject) =>
+ {
+ var writer = new FastBufferWriter(FastBufferWriterExtensions.GetWriteSize(obj) + 1, Allocator.Temp);
+ using (writer)
+ {
+ Assert.IsTrue(writer.TryBeginWrite(FastBufferWriterExtensions.GetWriteSize(obj)));
+
+ var offset = 0;
+ switch (writeType)
+ {
+ case WriteType.WriteDirect:
+ writer.WriteValue(obj);
+ break;
+ case WriteType.WriteSafe:
+ writer.WriteValueSafe(obj);
+ break;
+ case WriteType.WriteAsObject:
+ writer.WriteObject(obj);
+ // account for isNull byte
+ offset = 1;
+ break;
+ }
+
+ Assert.AreEqual(FastBufferWriterExtensions.GetWriteSize(obj) + offset, writer.Position);
+ VerifyBytewiseEquality(networkObject.NetworkObjectId, writer.ToArray(), 0, offset, sizeof(ulong));
+ }
+ });
+ }
+
+ [Test]
+ public void WhenCallingTryBeginWriteInternal_AllowedWritePositionDoesNotMoveBackward()
+ {
+ var writer = new FastBufferWriter(100, Allocator.Temp);
+ using (writer)
+ {
+ writer.TryBeginWrite(25);
+ writer.TryBeginWriteInternal(5);
+ Assert.AreEqual(writer.AllowedWriteMark, 25);
+ }
+ }
+ #endregion
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs.meta
new file mode 100644
index 0000000000..b549311462
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1cef42b60935e29469ed1404fb30ba2d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
index 1c8e0294d9..a08074009c 100644
--- a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
+++ b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
@@ -13,6 +13,18 @@
"includePlatforms": [
"Editor"
],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": true,
+ "precompiledReferences": [
+ "nunit.framework.dll"
+ ],
+ "autoReferenced": false,
+ "defineConstraints": [
+ "UNITY_INCLUDE_TESTS"
+ ],
+ "versionDefines": [],
+ "noEngineReferences": false,
"versionDefines": [
{
"name": "com.unity.multiplayer.tools",
diff --git a/testproject/.gitignore b/testproject/.gitignore
index acbbe841e6..fbe0ca9b9e 100644
--- a/testproject/.gitignore
+++ b/testproject/.gitignore
@@ -69,5 +69,7 @@ crashlytics-build.properties
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
+/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json
+/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json.meta
InitTestScene*