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."); | ||
|  |         } | ||
|  |     } | ||
|  | } |