using System; using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode { /// /// Byte Unpacking Helper Class /// Use this class to unpack values during deserialization for values that were packed. /// to pack unpacked values /// public static class ByteUnpacker { #if UNITY_NETCODE_DEBUG_NO_PACKING [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValuePacked(FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value); #else /// /// Read a packed enum value /// /// The reader to read from /// The value that's read /// Type of enum to read /// Throws InvalidOperationException if an enum somehow ends up not being the size of a byte, short, int, or long (which should be impossible) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void ReadValuePacked(FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum { switch (sizeof(TEnum)) { case sizeof(int): ReadValuePacked(reader, out int asInt); value = *(TEnum*)&asInt; break; case sizeof(byte): ReadValuePacked(reader, out byte asByte); value = *(TEnum*)&asByte; break; case sizeof(short): ReadValuePacked(reader, out short asShort); value = *(TEnum*)&asShort; break; case sizeof(long): ReadValuePacked(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(FastBufferReader reader, out float value) { ReadValueBitPacked(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(FastBufferReader reader, out double value) { ReadValueBitPacked(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(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(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(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(FastBufferReader reader, out short value) => ReadValueBitPacked(reader, out value); /// /// 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(FastBufferReader reader, out ushort value) => ReadValueBitPacked(reader, out value); /// /// 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(FastBufferReader reader, out char c) { ReadValueBitPacked(reader, out ushort 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(FastBufferReader reader, out int value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned int (UInt32) from the stream. /// /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadValueBitPacked(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(FastBufferReader reader, out ulong value) => ReadValueBitPacked(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(FastBufferReader reader, out long value) { ReadValueBitPacked(reader, out value); } /// /// 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(FastBufferReader reader, out Ray ray) { ReadValuePacked(reader, out Vector3 origin); ReadValuePacked(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(FastBufferReader reader, out Ray2D ray2d) { ReadValuePacked(reader, out Vector2 origin); ReadValuePacked(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(FastBufferReader reader, out Color color) { color = new Color(); ReadValuePacked(reader, out color.r); ReadValuePacked(reader, out color.g); ReadValuePacked(reader, out color.b); ReadValuePacked(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(FastBufferReader reader, out Color32 color) { color = new Color32(); ReadValuePacked(reader, out color.r); ReadValuePacked(reader, out color.g); ReadValuePacked(reader, out color.b); ReadValuePacked(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(FastBufferReader reader, out Vector2 vector2) { vector2 = new Vector2(); ReadValuePacked(reader, out vector2.x); ReadValuePacked(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(FastBufferReader reader, out Vector3 vector3) { vector3 = new Vector3(); ReadValuePacked(reader, out vector3.x); ReadValuePacked(reader, out vector3.y); ReadValuePacked(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(FastBufferReader reader, out Vector4 vector4) { vector4 = new Vector4(); ReadValuePacked(reader, out vector4.x); ReadValuePacked(reader, out vector4.y); ReadValuePacked(reader, out vector4.z); ReadValuePacked(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(FastBufferReader reader, out Quaternion rotation) { rotation = new Quaternion(); ReadValuePacked(reader, out rotation.x); ReadValuePacked(reader, out rotation.y); ReadValuePacked(reader, out rotation.z); ReadValuePacked(reader, out rotation.w); } /// /// Reads a string in a packed format /// /// The reader to read from /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void ReadValuePacked(FastBufferReader reader, out string s) { ReadValuePacked(reader, out uint length); s = "".PadRight((int)length); int target = s.Length; fixed (char* c = s) { for (int i = 0; i < target; ++i) { ReadValuePacked(reader, out c[i]); } } } #endif #if UNITY_NETCODE_DEBUG_NO_PACKING [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueBitPacked(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(FastBufferReader reader, out short value) { ReadValueBitPacked(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(FastBufferReader reader, out ushort value) { ushort returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); // Mask out the first two bits - they contain the total byte count // (1, 2, or 3) int numBytes = (data[0] & 0b11); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); } reader.MarkBytesRead(numBytes); switch (numBytes) { case 1: ptr[0] = data[0]; break; case 2: ptr[0] = data[0]; ptr[1] = data[1]; break; case 3: // First byte contains no data, it's just a marker. The data is in the remaining two bytes. ptr[0] = data[1]; ptr[1] = data[2]; value = returnValue; return; default: throw new InvalidOperationException("Could not read bit-packed value: impossible byte count"); } value = (ushort)(returnValue >> 2); } /// /// 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(FastBufferReader reader, out int value) { ReadValueBitPacked(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(FastBufferReader reader, out uint value) { uint returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); // Mask out the first three bits - they contain the total byte count (1-5) int numBytes = (data[0] & 0b111); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); } reader.MarkBytesRead(numBytes); switch (numBytes) { case 1: ptr[0] = data[0]; break; case 2: ptr[0] = data[0]; ptr[1] = data[1]; break; case 3: ptr[0] = data[0]; ptr[1] = data[1]; ptr[2] = data[2]; break; case 4: ptr[0] = data[0]; ptr[1] = data[1]; ptr[2] = data[2]; ptr[3] = data[3]; break; case 5: // First byte contains no data, it's just a marker. The data is in the remaining two bytes. ptr[0] = data[1]; ptr[1] = data[2]; ptr[2] = data[3]; ptr[3] = data[4]; value = returnValue; return; } value = returnValue >> 3; } /// /// 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(FastBufferReader reader, out long value) { ReadValueBitPacked(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(FastBufferReader reader, out ulong value) { ulong returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); // Mask out the first four bits - they contain the total byte count (1-9) int numBytes = (data[0] & 0b1111); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); } reader.MarkBytesRead(numBytes); switch (numBytes) { case 1: ptr[0] = data[0]; break; case 2: ptr[0] = data[0]; ptr[1] = data[1]; break; case 3: ptr[0] = data[0]; ptr[1] = data[1]; ptr[2] = data[2]; break; case 4: ptr[0] = data[0]; ptr[1] = data[1]; ptr[2] = data[2]; ptr[3] = data[3]; break; case 5: ptr[0] = data[0]; ptr[1] = data[1]; ptr[2] = data[2]; ptr[3] = data[3]; ptr[4] = data[4]; break; case 6: ptr[0] = data[0]; ptr[1] = data[1]; ptr[2] = data[2]; ptr[3] = data[3]; ptr[4] = data[4]; ptr[5] = data[5]; break; case 7: ptr[0] = data[0]; 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[0] = data[0]; 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; case 9: // First byte contains no data, it's just a marker. The data is in the remaining two bytes. ptr[0] = data[1]; ptr[1] = data[2]; ptr[2] = data[3]; ptr[3] = data[4]; ptr[4] = data[5]; ptr[5] = data[6]; ptr[6] = data[7]; ptr[7] = data[8]; value = returnValue; return; } value = returnValue >> 4; } #endif [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; } } }