using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode
{
///
/// Utility class for packing values in serialization.
///
public static class BytePacker
{
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValuePacked(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(FastBufferWriter writer, TEnum value) where TEnum : unmanaged, Enum
{
TEnum enumValue = value;
switch (sizeof(TEnum))
{
case sizeof(int):
WriteValuePacked(writer, *(int*)&enumValue);
break;
case sizeof(byte):
WriteValuePacked(writer, *(byte*)&enumValue);
break;
case sizeof(short):
WriteValuePacked(writer, *(short*)&enumValue);
break;
case sizeof(long):
WriteValuePacked(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(FastBufferWriter writer, float value)
{
WriteUInt32Packed(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(FastBufferWriter writer, double value)
{
WriteUInt64Packed(writer, ToUlong(value));
}
///
/// Write a byte to the buffer.
///
/// The writer to write to
/// Value to write
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(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(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(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(FastBufferWriter writer, short value) => WriteUInt32Packed(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(FastBufferWriter writer, ushort value) => WriteUInt32Packed(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(FastBufferWriter writer, char c) => WriteUInt32Packed(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(FastBufferWriter writer, int value) => WriteUInt32Packed(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(FastBufferWriter writer, uint value) => WriteUInt32Packed(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(FastBufferWriter writer, ulong value) => WriteUInt64Packed(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(FastBufferWriter writer, long value) => WriteUInt64Packed(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(FastBufferWriter writer, Ray ray)
{
WriteValuePacked(writer, ray.origin);
WriteValuePacked(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(FastBufferWriter writer, Ray2D ray2d)
{
WriteValuePacked(writer, ray2d.origin);
WriteValuePacked(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(FastBufferWriter writer, Color color)
{
WriteValuePacked(writer, color.r);
WriteValuePacked(writer, color.g);
WriteValuePacked(writer, color.b);
WriteValuePacked(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(FastBufferWriter writer, Color32 color)
{
WriteValuePacked(writer, color.r);
WriteValuePacked(writer, color.g);
WriteValuePacked(writer, color.b);
WriteValuePacked(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(FastBufferWriter writer, Vector2 vector2)
{
WriteValuePacked(writer, vector2.x);
WriteValuePacked(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(FastBufferWriter writer, Vector3 vector3)
{
WriteValuePacked(writer, vector3.x);
WriteValuePacked(writer, vector3.y);
WriteValuePacked(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(FastBufferWriter writer, Vector4 vector4)
{
WriteValuePacked(writer, vector4.x);
WriteValuePacked(writer, vector4.y);
WriteValuePacked(writer, vector4.z);
WriteValuePacked(writer, vector4.w);
}
///
/// Writes the rotation to the buffer.
///
/// The writer to write to
/// Rotation to write
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Quaternion rotation)
{
WriteValuePacked(writer, rotation.x);
WriteValuePacked(writer, rotation.y);
WriteValuePacked(writer, rotation.z);
WriteValuePacked(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(FastBufferWriter writer, string s)
{
WriteValuePacked(writer, (uint)s.Length);
int target = s.Length;
for (int i = 0; i < target; ++i)
{
WriteValuePacked(writer, s[i]);
}
}
#endif
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueBitPacked(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(FastBufferWriter writer, short value) => WriteValueBitPacked(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(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(FastBufferWriter writer, int value) => WriteValueBitPacked(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(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(FastBufferWriter writer, long value) => WriteValueBitPacked(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(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
private static void WriteUInt64Packed(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(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;
}
}
}