819 lines
38 KiB
C#
819 lines
38 KiB
C#
|
using System;
|
||
|
using System.Diagnostics;
|
||
|
using Unity.Collections.LowLevel.Unsafe;
|
||
|
using UnityEngine.Scripting.APIUpdating;
|
||
|
|
||
|
namespace Unity.Collections
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Writes data in an endian format to deserialize data.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// The DataStreamReader class is the counterpart of the
|
||
|
/// <see cref="DataStreamWriter"/> class and can be be used to deserialize
|
||
|
/// data which was prepared with it.
|
||
|
///
|
||
|
/// DataStreamWriter writes this data in the endian format native
|
||
|
/// to the current machine architecture.
|
||
|
/// <br/>
|
||
|
/// For network byte order use the so named methods.
|
||
|
/// <br/>
|
||
|
/// Simple usage example:
|
||
|
/// <code>
|
||
|
/// 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);
|
||
|
/// }
|
||
|
/// </code>
|
||
|
///
|
||
|
/// 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.
|
||
|
///
|
||
|
/// <seealso cref="DataStreamWriter"/>
|
||
|
/// <seealso cref="IsLittleEndian"/>
|
||
|
/// </remarks>
|
||
|
[MovedFrom(true, "Unity.Networking.Transport")]
|
||
|
[GenerateTestsForBurstCompatibility]
|
||
|
public unsafe struct DataStreamReader
|
||
|
{
|
||
|
struct Context
|
||
|
{
|
||
|
public int m_ReadByteIndex;
|
||
|
public int m_BitIndex;
|
||
|
public ulong m_BitBuffer;
|
||
|
public int m_FailedReads;
|
||
|
}
|
||
|
|
||
|
[NativeDisableUnsafePtrRestriction] internal byte* m_BufferPtr;
|
||
|
Context m_Context;
|
||
|
int m_Length;
|
||
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
|
AtomicSafetyHandle m_Safety;
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes a new instance of the DataStreamReader struct with a NativeArray<byte>
|
||
|
/// </summary>
|
||
|
/// <param name="array">The buffer to attach to the DataStreamReader.</param>
|
||
|
public DataStreamReader(NativeArray<byte> array)
|
||
|
{
|
||
|
Initialize(out this, array);
|
||
|
}
|
||
|
|
||
|
static void Initialize(out DataStreamReader self, NativeArray<byte> 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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Show the byte order in which the current computer architecture stores data.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Different computer architectures store data using different byte orders.
|
||
|
/// <list type="bullet">
|
||
|
/// <item>Big-endian: the most significant byte is at the left end of a word.</item>
|
||
|
/// <item>Little-endian: means the most significant byte is at the right end of a word.</item>
|
||
|
/// </list>
|
||
|
/// </remarks>
|
||
|
public static bool IsLittleEndian { get { return DataStreamWriter.IsLittleEndian; } }
|
||
|
|
||
|
static short ByteSwap(short val)
|
||
|
{
|
||
|
return (short)(((val & 0xff) << 8) | ((val >> 8) & 0xff));
|
||
|
}
|
||
|
|
||
|
static int ByteSwap(int val)
|
||
|
{
|
||
|
return (int)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// If there is a read failure this returns true. A read failure might happen if this attempts to read more than there is capacity for.
|
||
|
/// </summary>
|
||
|
public readonly bool HasFailedReads => m_Context.m_FailedReads > 0;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The total size of the buffer space this reader is working with.
|
||
|
/// </summary>
|
||
|
public readonly int Length
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
CheckRead();
|
||
|
return m_Length;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// True if the reader has been pointed to a valid buffer space. This
|
||
|
/// would be false if the reader was created with no arguments.
|
||
|
/// </summary>
|
||
|
public readonly bool IsCreated
|
||
|
{
|
||
|
get { return m_BufferPtr != null; }
|
||
|
}
|
||
|
|
||
|
void ReadBytesInternal(byte* data, int length)
|
||
|
{
|
||
|
CheckRead();
|
||
|
if (GetBytesRead() + length > m_Length)
|
||
|
{
|
||
|
++m_Context.m_FailedReads;
|
||
|
#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
|
||
|
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
|
||
|
Flush();
|
||
|
UnsafeUtility.MemCpy(data, m_BufferPtr + m_Context.m_ReadByteIndex, length);
|
||
|
m_Context.m_ReadByteIndex += length;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Read and copy data into the given NativeArray of bytes. An error will
|
||
|
/// be logged if not enough bytes are available to fill the array, and
|
||
|
/// <see cref="HasFailedReads"/> will then be true.
|
||
|
/// </summary>
|
||
|
/// <param name="array">Array to copy data into.</param>
|
||
|
public void ReadBytes(NativeArray<byte> array)
|
||
|
{
|
||
|
ReadBytesInternal((byte*)array.GetUnsafePtr(), array.Length);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Read and copy data into the given <c>Span</c> of bytes. An error will
|
||
|
/// be logged if not enough bytes are available to fill the array, and
|
||
|
/// <see cref="HasFailedReads"/> will then be true.
|
||
|
/// </summary>
|
||
|
/// <param name="span">Span to copy data into.</param>
|
||
|
public void ReadBytes(Span<byte> span)
|
||
|
{
|
||
|
fixed (byte* ptr = span)
|
||
|
{
|
||
|
ReadBytesInternal(ptr, span.Length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the number of bytes read from the data stream.
|
||
|
/// </summary>
|
||
|
/// <returns>Number of bytes read.</returns>
|
||
|
public int GetBytesRead()
|
||
|
{
|
||
|
return m_Context.m_ReadByteIndex - (m_Context.m_BitIndex >> 3);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the number of bits read from the data stream.
|
||
|
/// </summary>
|
||
|
/// <returns>Number of bits read.</returns>
|
||
|
public int GetBitsRead()
|
||
|
{
|
||
|
return (m_Context.m_ReadByteIndex << 3) - m_Context.m_BitIndex;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets the current position of this stream to the given value.
|
||
|
/// An error will be logged if <paramref name="pos"/> is outside the length of the stream.
|
||
|
/// <br/>
|
||
|
/// In addition this will reset the bit index and the bit buffer.
|
||
|
/// </summary>
|
||
|
/// <param name="pos">Seek position.</param>
|
||
|
public void SeekSet(int pos)
|
||
|
{
|
||
|
if (pos > m_Length)
|
||
|
{
|
||
|
++m_Context.m_FailedReads;
|
||
|
#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an unsigned byte from the current stream and advances the current position of the stream by one byte.
|
||
|
/// </summary>
|
||
|
/// <returns>The next byte read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public byte ReadByte()
|
||
|
{
|
||
|
byte data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(byte));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 2-byte signed short from the current stream and advances the current position of the stream by two bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>A 2-byte signed short read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public short ReadShort()
|
||
|
{
|
||
|
short data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(short));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 2-byte unsigned short from the current stream and advances the current position of the stream by two bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>A 2-byte unsigned short read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public ushort ReadUShort()
|
||
|
{
|
||
|
ushort data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(ushort));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public int ReadInt()
|
||
|
{
|
||
|
int data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(int));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public uint ReadUInt()
|
||
|
{
|
||
|
uint data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(uint));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an 8-byte signed long from the stream and advances the current position of the stream by eight bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public long ReadLong()
|
||
|
{
|
||
|
long data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(long));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an 8-byte unsigned long from the stream and advances the current position of the stream by eight bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public ulong ReadULong()
|
||
|
{
|
||
|
ulong data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(ulong));
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Aligns the read pointer to the next byte-aligned position. Does nothing if already aligned.
|
||
|
/// </summary>
|
||
|
/// <remarks>If you call <see cref="DataStreamWriter.Flush"/>, call this to bit-align the reader.</remarks>
|
||
|
public void Flush()
|
||
|
{
|
||
|
m_Context.m_ReadByteIndex -= (m_Context.m_BitIndex >> 3);
|
||
|
m_Context.m_BitIndex = 0;
|
||
|
m_Context.m_BitBuffer = 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 2-byte signed short from the current stream in Big-endian byte order and advances the current position of the stream by two bytes.
|
||
|
/// If the current endianness is in little-endian order, the byte order will be swapped.
|
||
|
/// </summary>
|
||
|
/// <returns>A 2-byte signed short read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public short ReadShortNetworkByteOrder()
|
||
|
{
|
||
|
short data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(short));
|
||
|
return IsLittleEndian ? ByteSwap(data) : data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 2-byte unsigned short from the current stream in Big-endian byte order and advances the current position of the stream by two bytes.
|
||
|
/// If the current endianness is in little-endian order, the byte order will be swapped.
|
||
|
/// </summary>
|
||
|
/// <returns>A 2-byte unsigned short read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public ushort ReadUShortNetworkByteOrder()
|
||
|
{
|
||
|
return (ushort)ReadShortNetworkByteOrder();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte signed integer from the current stream in Big-endian byte order and advances the current position of the stream by four bytes.
|
||
|
/// If the current endianness is in little-endian order, the byte order will be swapped.
|
||
|
/// </summary>
|
||
|
/// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public int ReadIntNetworkByteOrder()
|
||
|
{
|
||
|
int data;
|
||
|
ReadBytesInternal((byte*)&data, sizeof(int));
|
||
|
return IsLittleEndian ? ByteSwap(data) : data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte unsigned integer from the current stream in Big-endian byte order and advances the current position of the stream by four bytes.
|
||
|
/// If the current endianness is in little-endian order, the byte order will be swapped.
|
||
|
/// </summary>
|
||
|
/// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public uint ReadUIntNetworkByteOrder()
|
||
|
{
|
||
|
return (uint)ReadIntNetworkByteOrder();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>A 4-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public float ReadFloat()
|
||
|
{
|
||
|
UIntFloat uf = new UIntFloat();
|
||
|
uf.intValue = (uint)ReadInt();
|
||
|
return uf.floatValue;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 8-byte floating point value from the current stream and advances the current position of the stream by four bytes.
|
||
|
/// </summary>
|
||
|
/// <returns>A 8-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public double ReadDouble()
|
||
|
{
|
||
|
UIntFloat uf = new UIntFloat();
|
||
|
uf.longValue = (ulong)ReadLong();
|
||
|
return uf.doubleValue;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte unsigned integer from the current stream using a <see cref="StreamCompressionModel"/> and advances the current position the number of bits depending on the model.
|
||
|
/// </summary>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public uint ReadPackedUInt(in StreamCompressionModel model)
|
||
|
{
|
||
|
return ReadPackedUIntInternal(StreamCompressionModel.k_MaxHuffmanSymbolLength, model);
|
||
|
}
|
||
|
|
||
|
uint ReadPackedUIntInternal(int maxSymbolLength, in StreamCompressionModel model)
|
||
|
{
|
||
|
CheckRead();
|
||
|
FillBitBuffer();
|
||
|
uint peekMask = (1u << maxSymbolLength) - 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_DOTS_DEBUG)
|
||
|
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];
|
||
|
byte 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_DOTS_DEBUG)
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a specified number of bits from the data stream.
|
||
|
/// </summary>
|
||
|
/// <param name="numbits">A positive number of bytes to write.</param>
|
||
|
/// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public uint ReadRawBits(int numbits)
|
||
|
{
|
||
|
CheckRead();
|
||
|
FillBitBuffer();
|
||
|
return ReadRawBitsInternal(numbits);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an 8-byte unsigned long value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public ulong ReadPackedULong(in StreamCompressionModel model)
|
||
|
{
|
||
|
ulong value;
|
||
|
((uint*)&value)[0] = ReadPackedUInt(model);
|
||
|
((uint*)&value)[1] = ReadPackedUInt(model);
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte signed integer value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// <br/>
|
||
|
/// Negative values de-interleaves from positive values before returning, for example (0, -1, 1, -2, 2) -> (-2, -1, 0, 1, 2)
|
||
|
/// </summary>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public int ReadPackedInt(in StreamCompressionModel 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, ...]
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an 8-byte signed long value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// <br/>
|
||
|
/// Negative values de-interleaves from positive values before returning, for example (0, -1, 1, -2, 2) -> (-2, -1, 0, 1, 2)
|
||
|
/// </summary>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public long ReadPackedLong(in StreamCompressionModel 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, ...]
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte floating point value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>A 4-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public float ReadPackedFloat(in StreamCompressionModel model)
|
||
|
{
|
||
|
return ReadPackedFloatDelta(0, model);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 8-byte floating point value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>A 8-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public double ReadPackedDouble(in StreamCompressionModel model)
|
||
|
{
|
||
|
return ReadPackedDoubleDelta(0, model);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte signed integer delta value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous 4-byte signed integer value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.
|
||
|
/// If the data did not change, this also returns 0.
|
||
|
/// <br/>
|
||
|
/// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
|
||
|
public int ReadPackedIntDelta(int baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
int delta = ReadPackedInt(model);
|
||
|
return baseline - delta;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte unsigned integer delta value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous 4-byte unsigned integer value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.
|
||
|
/// If the data did not change, this also returns 0.
|
||
|
/// <br/>
|
||
|
/// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
|
||
|
public uint ReadPackedUIntDelta(uint baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
uint delta = (uint)ReadPackedInt(model);
|
||
|
return baseline - delta;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an 8-byte signed long delta value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous 8-byte signed long value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached.
|
||
|
/// If the data did not change, this also returns 0.
|
||
|
/// <br/>
|
||
|
/// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
|
||
|
public long ReadPackedLongDelta(long baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
long delta = ReadPackedLong(model);
|
||
|
return baseline - delta;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads an 8-byte unsigned long delta value from the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous 8-byte unsigned long value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
|
||
|
/// <returns>An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached.
|
||
|
/// If the data did not change, this also returns 0.
|
||
|
/// <br/>
|
||
|
/// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
|
||
|
public ulong ReadPackedULongDelta(ulong baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
ulong delta = (ulong)ReadPackedLong(model);
|
||
|
return baseline - delta;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 4-byte floating point value from the data stream.
|
||
|
///
|
||
|
/// If the first bit is 0, the data did not change and <paramref name="baseline"/> will be returned.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous 4-byte floating point value.</param>
|
||
|
/// <param name="model">Not currently used.</param>
|
||
|
/// <returns>A 4-byte floating point value read from the current stream, or <paramref name="baseline"/> if there are no changes to the value.
|
||
|
/// <br/>
|
||
|
/// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
|
||
|
public float ReadPackedFloatDelta(float baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
CheckRead();
|
||
|
FillBitBuffer();
|
||
|
if (ReadRawBitsInternal(1) == 0)
|
||
|
return baseline;
|
||
|
|
||
|
var bits = 32;
|
||
|
UIntFloat uf = new UIntFloat();
|
||
|
uf.intValue = ReadRawBitsInternal(bits);
|
||
|
return uf.floatValue;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a 8-byte floating point value from the data stream.
|
||
|
///
|
||
|
/// If the first bit is 0, the data did not change and <paramref name="baseline"/> will be returned.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous 8-byte floating point value.</param>
|
||
|
/// <param name="model">Not currently used.</param>
|
||
|
/// <returns>A 8-byte floating point value read from the current stream, or <paramref name="baseline"/> if there are no changes to the value.
|
||
|
/// <br/>
|
||
|
/// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
|
||
|
public double ReadPackedDoubleDelta(double baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
CheckRead();
|
||
|
FillBitBuffer();
|
||
|
if (ReadRawBitsInternal(1) == 0)
|
||
|
return baseline;
|
||
|
|
||
|
var bits = 32;
|
||
|
UIntFloat uf = new UIntFloat();
|
||
|
var data = (uint*)&uf.longValue;
|
||
|
data[0] = ReadRawBitsInternal(bits);
|
||
|
FillBitBuffer();
|
||
|
data[1] |= ReadRawBitsInternal(bits);
|
||
|
return uf.doubleValue;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString32Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
|
||
|
/// </summary>
|
||
|
/// <returns>A <c>FixedString32Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString32Bytes ReadFixedString32()
|
||
|
{
|
||
|
FixedString32Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString64Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
|
||
|
/// </summary>
|
||
|
/// <returns>A <c>FixedString64Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString64Bytes ReadFixedString64()
|
||
|
{
|
||
|
FixedString64Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString128Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
|
||
|
/// </summary>
|
||
|
/// <returns>A <c>FixedString128Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString128Bytes ReadFixedString128()
|
||
|
{
|
||
|
FixedString128Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString512Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
|
||
|
/// </summary>
|
||
|
/// <returns>A <c>FixedString512Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString512Bytes ReadFixedString512()
|
||
|
{
|
||
|
FixedString512Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString4096Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
|
||
|
/// </summary>
|
||
|
/// <returns>A <c>FixedString4096Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString4096Bytes ReadFixedString4096()
|
||
|
{
|
||
|
FixedString4096Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Read and copy data into the given NativeArray of bytes, an error will
|
||
|
/// be logged if not enough bytes are available in the array.
|
||
|
/// </summary>
|
||
|
/// <param name="array">Buffer to write the string bytes to.</param>
|
||
|
/// <returns>Length of data read into byte array, or zero if error occurred.</returns>
|
||
|
public ushort ReadFixedString(NativeArray<byte> array)
|
||
|
{
|
||
|
return ReadFixedStringInternal((byte*)array.GetUnsafePtr(), array.Length);
|
||
|
}
|
||
|
|
||
|
unsafe ushort ReadFixedStringInternal(byte* data, int maxLength)
|
||
|
{
|
||
|
ushort length = ReadUShort();
|
||
|
if (length > maxLength)
|
||
|
{
|
||
|
#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
|
||
|
UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}");
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
ReadBytesInternal(data, length);
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString32Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous <c>FixedString32Bytes</c> value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
|
||
|
/// <returns>A <c>FixedString32Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString32Bytes ReadPackedFixedString32Delta(FixedString32Bytes baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
FixedString32Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString64Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous <c>FixedString64Bytes</c> value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
|
||
|
/// <returns>A <c>FixedString64Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString64Bytes ReadPackedFixedString64Delta(FixedString64Bytes baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
FixedString64Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString128Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous <c>FixedString128Bytes</c> value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
|
||
|
/// <returns>A <c>FixedString128Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString128Bytes ReadPackedFixedString128Delta(FixedString128Bytes baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
FixedString128Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString512Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous <c>FixedString512Bytes</c> value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
|
||
|
/// <returns>A <c>FixedString512Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString512Bytes ReadPackedFixedString512Delta(FixedString512Bytes baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
FixedString512Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads a <c>FixedString4096Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="baseline">The previous <c>FixedString4096Bytes</c> value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
|
||
|
/// <returns>A <c>FixedString4096Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
|
||
|
public unsafe FixedString4096Bytes ReadPackedFixedString4096Delta(FixedString4096Bytes baseline, in StreamCompressionModel model)
|
||
|
{
|
||
|
FixedString4096Bytes str;
|
||
|
byte* data = ((byte*)&str) + 2;
|
||
|
*(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Read and copy data into the given NativeArray of bytes, an error will
|
||
|
/// be logged if not enough bytes are available in the array.
|
||
|
/// </summary>
|
||
|
/// <param name="data">Array for the current fixed string.</param>
|
||
|
/// <param name="baseData">Array containing the previous value, used to compute the diff.</param>
|
||
|
/// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
|
||
|
/// <returns>Length of data read into byte array, or zero if error occurred.</returns>
|
||
|
public ushort ReadPackedFixedStringDelta(NativeArray<byte> data, NativeArray<byte> baseData, in StreamCompressionModel model)
|
||
|
{
|
||
|
return ReadPackedFixedStringDeltaInternal((byte*)data.GetUnsafePtr(), data.Length, (byte*)baseData.GetUnsafePtr(), (ushort)baseData.Length, model);
|
||
|
}
|
||
|
|
||
|
unsafe ushort ReadPackedFixedStringDeltaInternal(byte* data, int maxLength, byte* baseData, ushort baseLength, in StreamCompressionModel model)
|
||
|
{
|
||
|
uint length = ReadPackedUIntDelta(baseLength, model);
|
||
|
if (length > (uint)maxLength)
|
||
|
{
|
||
|
#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
|
||
|
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")]
|
||
|
internal readonly void CheckRead()
|
||
|
{
|
||
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
|
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
|
||
|
static void CheckBits(int numBits)
|
||
|
{
|
||
|
if (numBits < 0 || numBits > 32)
|
||
|
throw new ArgumentOutOfRangeException($"Invalid number of bits specified: {numBits}! Valid range is (0, 32) inclusive.");
|
||
|
}
|
||
|
}
|
||
|
}
|