using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System;
using System.Diagnostics;
using Unity.Burst;
namespace Unity.Networking.Transport
{
[StructLayout(LayoutKind.Explicit)]
internal struct UIntFloat
{
[FieldOffset(0)] public float floatValue;
[FieldOffset(0)] public uint intValue;
[FieldOffset(0)] public double doubleValue;
[FieldOffset(0)] public ulong longValue;
}
///
/// Data streams can be used to serialize data over the network. The
/// DataStreamWriter and DataStreamReader classes work together
/// to serialize data for sending and then to deserialize when receiving.
///
///
/// The reader can be used to deserialize the data from a NativeArray, writing data
/// to a NativeArray and reading it back can be done like this:
///
/// using (var data = new NativeArray(16, Allocator.Persistent))
/// {
/// var dataWriter = new DataStreamWriter(data);
/// dataWriter.WriteInt(42);
/// dataWriter.WriteInt(1234);
/// // Length is the actual amount of data inside the writer,
/// // Capacity is the total amount.
/// var dataReader = new DataStreamReader(nativeArrayOfBytes.GetSubArray(0, dataWriter.Length));
/// var myFirstInt = dataReader.ReadInt();
/// var mySecondInt = dataReader.ReadInt();
/// }
///
///
/// There are a number of functions for various data types. If a copy of the writer
/// is stored it can be used to overwrite the data later on, this is particularly useful when
/// the size of the data is written at the start and you want to write it at
/// the end when you know the value.
///
///
/// using (var data = new NativeArray(16, Allocator.Persistent))
/// {
/// var dataWriter = new DataStreamWriter(data);
/// // My header data
/// var headerSizeMark = dataWriter;
/// dataWriter.WriteUShort((ushort)0);
/// var payloadSizeMark = dataWriter;
/// dataWriter.WriteUShort((ushort)0);
/// dataWriter.WriteInt(42);
/// dataWriter.WriteInt(1234);
/// var headerSize = data.Length;
/// // Update header size to correct value
/// headerSizeMark.WriteUShort((ushort)headerSize);
/// // My payload data
/// byte[] someBytes = Encoding.ASCII.GetBytes("some string");
/// dataWriter.Write(someBytes, someBytes.Length);
/// // Update payload size to correct value
/// payloadSizeMark.WriteUShort((ushort)(dataWriter.Length - headerSize));
/// }
///
///
[StructLayout(LayoutKind.Sequential)]
public unsafe struct DataStreamWriter
{
struct IsLittleEndianStructKey { }
private static readonly SharedStatic m_IsLittleEndian = SharedStatic.GetOrCreate();
public static bool IsLittleEndian
{
get
{
if (m_IsLittleEndian.Data == 0)
{
uint test = 1;
byte* testPtr = (byte*) &test;
m_IsLittleEndian.Data = testPtr[0] == 1 ? 1 : 2;
}
return m_IsLittleEndian.Data == 1;
}
}
struct StreamData
{
public byte* buffer;
public int length;
public int capacity;
public ulong bitBuffer;
public int bitIndex;
public int failedWrites;
}
[NativeDisableUnsafePtrRestriction] StreamData m_Data;
internal IntPtr m_SendHandleData;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle m_Safety;
#endif
///
/// Initializes a new instance of the DataStreamWriter struct.
///
/// The length of the buffer.
/// The used to allocate the memory.
public DataStreamWriter(int length, Allocator allocator)
{
CheckAllocator(allocator);
Initialize(out this, new NativeArray(length, allocator));
}
///
/// Initializes a new instance of the DataStreamWriter struct with a NativeArray{byte}
///
/// The buffer we want to attach to our DataStreamWriter.
public DataStreamWriter(NativeArray data)
{
Initialize(out this, data);
}
///
/// Initializes a new instance of the DataStreamWriter struct with a memory we don't own
///
/// Pointer to the data
/// Length of the data
public DataStreamWriter(byte* data, int length)
{
var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data, length, Allocator.Invalid);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, AtomicSafetyHandle.GetTempMemoryHandle());
#endif
Initialize(out this, na);
}
public NativeArray AsNativeArray()
{
var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_Data.buffer, Length, Allocator.Invalid);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, m_Safety);
#endif
return na;
}
private static void Initialize(out DataStreamWriter self, NativeArray data)
{
self.m_SendHandleData = IntPtr.Zero;
self.m_Data.capacity = data.Length;
self.m_Data.length = 0;
self.m_Data.buffer = (byte*)data.GetUnsafePtr();
self.m_Data.bitBuffer = 0;
self.m_Data.bitIndex = 0;
self.m_Data.failedWrites = 0;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(data);
#endif
}
private static short ByteSwap(short val)
{
return (short)(((val & 0xff) << 8) | ((val >> 8)&0xff));
}
private static int ByteSwap(int val)
{
return (int)(((val & 0xff) << 24) |((val&0xff00)<<8) | ((val>>8)&0xff00) | ((val >> 24)&0xff));
}
///
/// True if there is a valid data buffer present. This would be false
/// if the writer was created with no arguments.
///
public bool IsCreated
{
get { return m_Data.buffer != null; }
}
public bool HasFailedWrites => m_Data.failedWrites > 0;
///
/// The total size of the data buffer, see for
/// the size of space used in the buffer.
///
public int Capacity
{
get
{
CheckRead();
return m_Data.capacity;
}
}
///
/// The size of the buffer used. See for the total size.
///
public int Length
{
get
{
CheckRead();
SyncBitData();
return m_Data.length + ((m_Data.bitIndex + 7) >> 3);
}
}
///
/// The size of the buffer used in bits. See for the length in bytes.
///
public int LengthInBits
{
get
{
CheckRead();
SyncBitData();
return m_Data.length*8 + m_Data.bitIndex;
}
}
private void SyncBitData()
{
var bitIndex = m_Data.bitIndex;
if (bitIndex <= 0)
return;
CheckWrite();
var bitBuffer = m_Data.bitBuffer;
int offset = 0;
while (bitIndex > 0)
{
m_Data.buffer[m_Data.length + offset] = (byte)bitBuffer;
bitIndex -= 8;
bitBuffer >>= 8;
++offset;
}
}
public void Flush()
{
while (m_Data.bitIndex > 0)
{
m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer;
m_Data.bitIndex -= 8;
m_Data.bitBuffer >>= 8;
}
m_Data.bitIndex = 0;
}
public bool WriteBytes(byte* data, int bytes)
{
CheckWrite();
if (m_Data.length + ((m_Data.bitIndex + 7) >> 3) + bytes > m_Data.capacity)
{
++m_Data.failedWrites;
return false;
}
Flush();
UnsafeUtility.MemCpy(m_Data.buffer + m_Data.length, data, bytes);
m_Data.length += bytes;
return true;
}
public bool WriteByte(byte value)
{
return WriteBytes((byte*) &value, sizeof(byte));
}
///
/// Copy NativeArray of bytes into the writers data buffer.
///
/// Source byte array
public bool WriteBytes(NativeArray value)
{
return WriteBytes((byte*)value.GetUnsafeReadOnlyPtr(), value.Length);
}
public bool WriteShort(short value)
{
return WriteBytes((byte*) &value, sizeof(short));
}
public bool WriteUShort(ushort value)
{
return WriteBytes((byte*) &value, sizeof(ushort));
}
public bool WriteInt(int value)
{
return WriteBytes((byte*) &value, sizeof(int));
}
public bool WriteUInt(uint value)
{
return WriteBytes((byte*) &value, sizeof(uint));
}
public bool WriteLong(long value)
{
return WriteBytes((byte*) &value, sizeof(long));
}
public bool WriteULong(ulong value)
{
return WriteBytes((byte*) &value, sizeof(ulong));
}
public bool WriteShortNetworkByteOrder(short value)
{
short netValue = IsLittleEndian ? ByteSwap(value) : value;
return WriteBytes((byte*) &netValue, sizeof(short));
}
public bool WriteUShortNetworkByteOrder(ushort value)
{
return WriteShortNetworkByteOrder((short) value);
}
public bool WriteIntNetworkByteOrder(int value)
{
int netValue = IsLittleEndian ? ByteSwap(value) : value;
return WriteBytes((byte*) &netValue, sizeof(int));
}
public bool WriteUIntNetworkByteOrder(uint value)
{
return WriteIntNetworkByteOrder((int)value);
}
public bool WriteFloat(float value)
{
UIntFloat uf = new UIntFloat();
uf.floatValue = value;
return WriteInt((int) uf.intValue);
}
private void FlushBits()
{
while (m_Data.bitIndex >= 8)
{
m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer;
m_Data.bitIndex -= 8;
m_Data.bitBuffer >>= 8;
}
}
void WriteRawBitsInternal(uint value, int numbits)
{
CheckBits(value, numbits);
m_Data.bitBuffer |= ((ulong)value << m_Data.bitIndex);
m_Data.bitIndex += numbits;
}
public bool WriteRawBits(uint value, int numbits)
{
CheckWrite();
if (m_Data.length + ((m_Data.bitIndex + numbits + 7) >> 3) > m_Data.capacity)
{
++m_Data.failedWrites;
return false;
}
WriteRawBitsInternal(value, numbits);
FlushBits();
return true;
}
public bool WritePackedUInt(uint value, NetworkCompressionModel model)
{
CheckWrite();
int bucket = model.CalculateBucket(value);
uint offset = model.bucketOffsets[bucket];
int bits = model.bucketSizes[bucket];
ushort encodeEntry = model.encodeTable[bucket];
if (m_Data.length + ((m_Data.bitIndex + (encodeEntry&0xff) + bits + 7) >> 3) > m_Data.capacity)
{
++m_Data.failedWrites;
return false;
}
WriteRawBitsInternal((uint)(encodeEntry >> 8), encodeEntry & 0xFF);
WriteRawBitsInternal(value - offset, bits);
FlushBits();
return true;
}
public bool WritePackedULong(ulong value, NetworkCompressionModel model)
{
return WritePackedUInt((uint) (value >> 32), model) &
WritePackedUInt((uint) (value & 0xFFFFFFFF), model);
}
public bool WritePackedInt(int value, NetworkCompressionModel model)
{
uint interleaved = (uint)((value >> 31) ^ (value << 1)); // interleave negative values between positive values: 0, -1, 1, -2, 2
return WritePackedUInt(interleaved, model);
}
public bool WritePackedLong(long value, NetworkCompressionModel model)
{
ulong interleaved = (ulong)((value >> 63) ^ (value << 1)); // interleave negative values between positive values: 0, -1, 1, -2, 2
return WritePackedULong(interleaved, model);
}
public bool WritePackedFloat(float value, NetworkCompressionModel model)
{
return WritePackedFloatDelta(value, 0, model);
}
public bool WritePackedUIntDelta(uint value, uint baseline, NetworkCompressionModel model)
{
int diff = (int)(baseline - value);
return WritePackedInt(diff, model);
}
public bool WritePackedIntDelta(int value, int baseline, NetworkCompressionModel model)
{
int diff = (int)(baseline - value);
return WritePackedInt(diff, model);
}
public bool WritePackedLongDelta(long value, long baseline, NetworkCompressionModel model)
{
long diff = (long)(baseline - value);
return WritePackedLong(diff, model);
}
public bool WritePackedULongDelta(ulong value, ulong baseline, NetworkCompressionModel model)
{
long diff = (long)(baseline - value);
return WritePackedLong(diff, model);
}
public bool WritePackedFloatDelta(float value, float baseline, NetworkCompressionModel model)
{
CheckWrite();
var bits = 0;
if (value != baseline)
bits = 32;
if (m_Data.length + ((m_Data.bitIndex + 1 + bits + 7) >> 3) > m_Data.capacity)
{
++m_Data.failedWrites;
return false;
}
if (bits == 0)
WriteRawBitsInternal(0, 1);
else
{
WriteRawBitsInternal(1, 1);
UIntFloat uf = new UIntFloat();
uf.floatValue = value;
WriteRawBitsInternal(uf.intValue, bits);
}
FlushBits();
return true;
}
public unsafe bool WriteFixedString32(FixedString32 str)
{
int length = (int)*((ushort*)&str) + 2;
byte* data = ((byte*)&str);
return WriteBytes(data, length);
}
public unsafe bool WriteFixedString64(FixedString64 str)
{
int length = (int)*((ushort*)&str) + 2;
byte* data = ((byte*)&str);
return WriteBytes(data, length);
}
public unsafe bool WriteFixedString128(FixedString128 str)
{
int length = (int)*((ushort*)&str) + 2;
byte* data = ((byte*)&str);
return WriteBytes(data, length);
}
public unsafe bool WriteFixedString512(FixedString512 str)
{
int length = (int)*((ushort*)&str) + 2;
byte* data = ((byte*)&str);
return WriteBytes(data, length);
}
public unsafe bool WriteFixedString4096(FixedString4096 str)
{
int length = (int)*((ushort*)&str) + 2;
byte* data = ((byte*)&str);
return WriteBytes(data, length);
}
public unsafe bool WritePackedFixedString32Delta(FixedString32 str, FixedString32 baseline, NetworkCompressionModel model)
{
ushort length = *((ushort*)&str);
byte* data = ((byte*)&str) + 2;
return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
}
public unsafe bool WritePackedFixedString64Delta(FixedString64 str, FixedString64 baseline, NetworkCompressionModel model)
{
ushort length = *((ushort*)&str);
byte* data = ((byte*)&str) + 2;
return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
}
public unsafe bool WritePackedFixedString128Delta(FixedString128 str, FixedString128 baseline, NetworkCompressionModel model)
{
ushort length = *((ushort*)&str);
byte* data = ((byte*)&str) + 2;
return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
}
public unsafe bool WritePackedFixedString512Delta(FixedString512 str, FixedString512 baseline, NetworkCompressionModel model)
{
ushort length = *((ushort*)&str);
byte* data = ((byte*)&str) + 2;
return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
}
public unsafe bool WritePackedFixedString4096Delta(FixedString4096 str, FixedString4096 baseline, NetworkCompressionModel model)
{
ushort length = *((ushort*)&str);
byte* data = ((byte*)&str) + 2;
return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
}
private unsafe bool WritePackedFixedStringDelta(byte* data, uint length, byte* baseData, uint baseLength, NetworkCompressionModel model)
{
var oldData = m_Data;
if (!WritePackedUIntDelta(length, baseLength, model))
return false;
bool didFailWrite = false;
if (length <= baseLength)
{
for (uint i = 0; i < length; ++i)
didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model);
}
else
{
for (uint i = 0; i < baseLength; ++i)
didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model);
for (uint i = baseLength; i < length; ++i)
didFailWrite |= !WritePackedUInt(data[i], model);
}
// If anything was not written, rewind to the previous position
if (didFailWrite)
{
m_Data = oldData;
++m_Data.failedWrites;
}
return !didFailWrite;
}
///
/// Moves the write position to the start of the data buffer used.
///
public void Clear()
{
m_Data.length = 0;
m_Data.bitIndex = 0;
m_Data.bitBuffer = 0;
m_Data.failedWrites = 0;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckWrite()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckAllocator(Allocator allocator)
{
if (allocator != Allocator.Temp)
throw new InvalidOperationException("DataStreamWriters can only be created with temp memory");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckBits(uint value, int numbits)
{
if (numbits < 0 || numbits > 32)
throw new ArgumentOutOfRangeException("Invalid number of bits");
if (value >= (1UL << numbits))
throw new ArgumentOutOfRangeException("Value does not fit in the specified number of bits");
}
}
///
/// The DataStreamReader class is the counterpart of the
/// DataStreamWriter class and can be be used to deserialize
/// data which was prepared with it.
///
///
/// Simple usage example:
///
/// using (var dataWriter = new DataStreamWriter(16, Allocator.Persistent))
/// {
/// dataWriter.Write(42);
/// dataWriter.Write(1234);
/// // Length is the actual amount of data inside the writer,
/// // Capacity is the total amount.
/// var dataReader = new DataStreamReader(dataWriter, 0, dataWriter.Length);
/// var context = default(DataStreamReader.Context);
/// var myFirstInt = dataReader.ReadInt(ref context);
/// var mySecondInt = dataReader.ReadInt(ref context);
/// }
///
///
/// The DataStreamReader carries the position of the read pointer inside the struct,
/// taking a copy of the reader will also copy the read position. This includes passing the
/// reader to a method by value instead of by ref.
///
/// See the class for more information
/// and examples.
///
public unsafe struct DataStreamReader
{
struct Context
{
public int m_ReadByteIndex;
public int m_BitIndex;
public ulong m_BitBuffer;
public int m_FailedReads;
}
byte* m_bufferPtr;
Context m_Context;
int m_Length;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle m_Safety;
#endif
public DataStreamReader(NativeArray array)
{
Initialize(out this, array);
}
public DataStreamReader(byte* data, int length)
{
var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data, length, Allocator.Invalid);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, AtomicSafetyHandle.GetTempMemoryHandle());
#endif
Initialize(out this, na);
}
private static void Initialize(out DataStreamReader self, NativeArray array)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array);
#endif
self.m_bufferPtr = (byte*)array.GetUnsafeReadOnlyPtr();
self.m_Length = array.Length;
self.m_Context = default;
}
public bool IsLittleEndian => DataStreamWriter.IsLittleEndian;
private static short ByteSwap(short val)
{
return (short)(((val & 0xff) << 8) | ((val >> 8)&0xff));
}
private static int ByteSwap(int val)
{
return (int)(((val & 0xff) << 24) |((val&0xff00)<<8) | ((val>>8)&0xff00) | ((val >> 24)&0xff));
}
public bool HasFailedReads => m_Context.m_FailedReads > 0;
///
/// The total size of the buffer space this reader is working with.
///
public int Length
{
get
{
CheckRead();
return m_Length;
}
}
///
/// True if the reader has been pointed to a valid buffer space. This
/// would be false if the reader was created with no arguments.
///
public bool IsCreated
{
get { return m_bufferPtr != null; }
}
///
/// Read and copy data to the memory location pointed to, an exception will
/// be thrown if it does not fit.
///
///
///
/// Thrown if the length
/// will put the reader out of bounds based on the current read pointer
/// position.
public void ReadBytes(byte* data, int length)
{
CheckRead();
if (GetBytesRead() + length > m_Length)
{
++m_Context.m_FailedReads;
#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME
UnityEngine.Debug.LogError($"Trying to read {length} bytes from a stream where only {m_Length - GetBytesRead()} are available");
#endif
UnsafeUtility.MemClear(data, length);
return;
}
// Restore the full bytes moved to the bit buffer but no consumed
m_Context.m_ReadByteIndex -= (m_Context.m_BitIndex >> 3);
m_Context.m_BitIndex = 0;
m_Context.m_BitBuffer = 0;
UnsafeUtility.MemCpy(data, m_bufferPtr + m_Context.m_ReadByteIndex, length);
m_Context.m_ReadByteIndex += length;
}
///
/// Read and copy data into the given NativeArray of bytes, an exception will
/// be thrown if not enough bytes are available.
///
///
public void ReadBytes(NativeArray array)
{
ReadBytes((byte*)array.GetUnsafePtr(), array.Length);
}
public int GetBytesRead()
{
return m_Context.m_ReadByteIndex - (m_Context.m_BitIndex >> 3);
}
public int GetBitsRead()
{
return (m_Context.m_ReadByteIndex<<3) - m_Context.m_BitIndex;
}
public void SeekSet(int pos)
{
if (pos > m_Length)
{
++m_Context.m_FailedReads;
#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME
UnityEngine.Debug.LogError($"Trying to seek to {pos} in a stream of length {m_Length}");
#endif
return;
}
m_Context.m_ReadByteIndex = pos;
m_Context.m_BitIndex = 0;
m_Context.m_BitBuffer = 0UL;
}
public byte ReadByte()
{
byte data;
ReadBytes((byte*) &data, sizeof(byte));
return data;
}
public short ReadShort()
{
short data;
ReadBytes((byte*) &data, sizeof(short));
return data;
}
public ushort ReadUShort()
{
ushort data;
ReadBytes((byte*) &data, sizeof(ushort));
return data;
}
public int ReadInt()
{
int data;
ReadBytes((byte*) &data, sizeof(int));
return data;
}
public uint ReadUInt()
{
uint data;
ReadBytes((byte*) &data, sizeof(uint));
return data;
}
public ulong ReadULong()
{
ulong data;
ReadBytes((byte*) &data, sizeof(ulong));
return data;
}
public short ReadShortNetworkByteOrder()
{
short data;
ReadBytes((byte*) &data, sizeof(short));
return IsLittleEndian ? ByteSwap(data) : data;
}
public ushort ReadUShortNetworkByteOrder()
{
return (ushort) ReadShortNetworkByteOrder();
}
public int ReadIntNetworkByteOrder()
{
int data;
ReadBytes((byte*) &data, sizeof(int));
return IsLittleEndian ? ByteSwap(data) : data;
}
public uint ReadUIntNetworkByteOrder()
{
return (uint) ReadIntNetworkByteOrder();
}
public float ReadFloat()
{
UIntFloat uf = new UIntFloat();
uf.intValue = (uint) ReadInt();
return uf.floatValue;
}
public uint ReadPackedUInt(NetworkCompressionModel model)
{
CheckRead();
FillBitBuffer();
uint peekMask = (1u << NetworkCompressionModel.k_MaxHuffmanSymbolLength) - 1u;
uint peekBits = (uint)m_Context.m_BitBuffer & peekMask;
ushort huffmanEntry = model.decodeTable[(int)peekBits];
int symbol = huffmanEntry >> 8;
int length = huffmanEntry & 0xFF;
if (m_Context.m_BitIndex < length)
{
++m_Context.m_FailedReads;
#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME
UnityEngine.Debug.LogError($"Trying to read {length} bits from a stream where only {m_Context.m_BitIndex} are available");
#endif
return 0;
}
// Skip Huffman bits
m_Context.m_BitBuffer >>= length;
m_Context.m_BitIndex -= length;
uint offset = model.bucketOffsets[symbol];
int bits = model.bucketSizes[symbol];
return ReadRawBitsInternal(bits) + offset;
}
void FillBitBuffer()
{
while (m_Context.m_BitIndex <= 56 && m_Context.m_ReadByteIndex < m_Length)
{
m_Context.m_BitBuffer |= (ulong)m_bufferPtr[m_Context.m_ReadByteIndex++] << m_Context.m_BitIndex;
m_Context.m_BitIndex += 8;
}
}
uint ReadRawBitsInternal(int numbits)
{
CheckBits(numbits);
if (m_Context.m_BitIndex < numbits)
{
++m_Context.m_FailedReads;
#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME
UnityEngine.Debug.LogError($"Trying to read {numbits} bits from a stream where only {m_Context.m_BitIndex} are available");
#endif
return 0;
}
uint res = (uint)(m_Context.m_BitBuffer & ((1UL << numbits) - 1UL));
m_Context.m_BitBuffer >>= numbits;
m_Context.m_BitIndex -= numbits;
return res;
}
public uint ReadRawBits(int numbits)
{
CheckRead();
FillBitBuffer();
return ReadRawBitsInternal(numbits);
}
public ulong ReadPackedULong(NetworkCompressionModel model)
{
//hi
ulong hi = ReadPackedUInt(model);
hi <<= 32;
hi |= ReadPackedUInt(model);
return hi;
}
public int ReadPackedInt(NetworkCompressionModel model)
{
uint folded = ReadPackedUInt(model);
return (int)(folded >> 1) ^ -(int)(folded & 1); // Deinterleave values from [0, -1, 1, -2, 2...] to [..., -2, -1, -0, 1, 2, ...]
}
public long ReadPackedLong(NetworkCompressionModel model)
{
ulong folded = ReadPackedULong(model);
return (long)(folded >> 1) ^ -(long)(folded & 1); // Deinterleave values from [0, -1, 1, -2, 2...] to [..., -2, -1, -0, 1, 2, ...]
}
public float ReadPackedFloat(NetworkCompressionModel model)
{
return ReadPackedFloatDelta(0, model);
}
public int ReadPackedIntDelta(int baseline, NetworkCompressionModel model)
{
int delta = ReadPackedInt(model);
return baseline - delta;
}
public uint ReadPackedUIntDelta(uint baseline, NetworkCompressionModel model)
{
uint delta = (uint)ReadPackedInt(model);
return baseline - delta;
}
public long ReadPackedLongDelta(long baseline, NetworkCompressionModel model)
{
long delta = ReadPackedLong(model);
return baseline - delta;
}
public ulong ReadPackedULongDelta(ulong baseline, NetworkCompressionModel model)
{
ulong delta = (ulong)ReadPackedLong(model);
return baseline - delta;
}
public float ReadPackedFloatDelta(float baseline, NetworkCompressionModel model)
{
CheckRead();
FillBitBuffer();
if (ReadRawBitsInternal(1) == 0)
return baseline;
var bits = 32;
UIntFloat uf = new UIntFloat();
uf.intValue = ReadRawBitsInternal(bits);
return uf.floatValue;
}
public unsafe FixedString32 ReadFixedString32()
{
FixedString32 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadFixedString(data, str.Capacity);
return str;
}
public unsafe FixedString64 ReadFixedString64()
{
FixedString64 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadFixedString(data, str.Capacity);
return str;
}
public unsafe FixedString128 ReadFixedString128()
{
FixedString128 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadFixedString(data, str.Capacity);
return str;
}
public unsafe FixedString512 ReadFixedString512()
{
FixedString512 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadFixedString(data, str.Capacity);
return str;
}
public unsafe FixedString4096 ReadFixedString4096()
{
FixedString4096 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadFixedString(data, str.Capacity);
return str;
}
public unsafe ushort ReadFixedString(byte* data, int maxLength)
{
ushort length = ReadUShort();
if (length > maxLength)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME
UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}");
#endif
return 0;
}
ReadBytes(data, length);
return length;
}
public unsafe FixedString32 ReadPackedFixedString32Delta(FixedString32 baseline, NetworkCompressionModel model)
{
FixedString32 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
return str;
}
public unsafe FixedString64 ReadPackedFixedString64Delta(FixedString64 baseline, NetworkCompressionModel model)
{
FixedString64 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
return str;
}
public unsafe FixedString128 ReadPackedFixedString128Delta(FixedString128 baseline, NetworkCompressionModel model)
{
FixedString128 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
return str;
}
public unsafe FixedString512 ReadPackedFixedString512Delta(FixedString512 baseline, NetworkCompressionModel model)
{
FixedString512 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
return str;
}
public unsafe FixedString4096 ReadPackedFixedString4096Delta(FixedString4096 baseline, NetworkCompressionModel model)
{
FixedString4096 str;
byte* data = ((byte*)&str) + 2;
*(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
return str;
}
public unsafe ushort ReadPackedFixedStringDelta(byte* data, int maxLength, byte* baseData, ushort baseLength, NetworkCompressionModel model)
{
uint length = ReadPackedUIntDelta(baseLength, model);
if (length > (uint)maxLength)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME
UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}");
#endif
return 0;
}
if (length <= baseLength)
{
for (int i = 0; i < length; ++i)
data[i] = (byte)ReadPackedUIntDelta(baseData[i], model);
}
else
{
for (int i = 0; i < baseLength; ++i)
data[i] = (byte)ReadPackedUIntDelta(baseData[i], model);
for (int i = baseLength; i < length; ++i)
data[i] = (byte)ReadPackedUInt(model);
}
return (ushort)length;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckBits(int numbits)
{
if (numbits < 0 || numbits > 32)
throw new ArgumentOutOfRangeException("Invalid number of bits");
}
}
}