using System; using System.Diagnostics; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.Scripting.APIUpdating; namespace Unity.Collections { /// /// Writes data in an endian format to deserialize data. /// /// /// The DataStreamReader class is the counterpart of the /// 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. ///
/// For network byte order use the so named methods. ///
/// 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); /// } /// /// /// 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. /// /// /// ///
[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 /// /// Initializes a new instance of the DataStreamReader struct with a NativeArray<byte> /// /// The buffer to attach to the DataStreamReader. public DataStreamReader(NativeArray array) { Initialize(out this, array); } 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; } /// /// Show the byte order in which the current computer architecture stores data. /// /// /// Different computer architectures store data using different byte orders. /// /// Big-endian: the most significant byte is at the left end of a word. /// Little-endian: means the most significant byte is at the right end of a word. /// /// 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)); } /// /// 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. /// public readonly bool HasFailedReads => m_Context.m_FailedReads > 0; /// /// The total size of the buffer space this reader is working with. /// public readonly 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 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; } /// /// 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 /// will then be true. /// /// Array to copy data into. public void ReadBytes(NativeArray array) { ReadBytesInternal((byte*)array.GetUnsafePtr(), array.Length); } /// /// Read and copy data into the given Span of bytes. An error will /// be logged if not enough bytes are available to fill the array, and /// will then be true. /// /// Span to copy data into. public void ReadBytes(Span span) { fixed (byte* ptr = span) { ReadBytesInternal(ptr, span.Length); } } /// /// Gets the number of bytes read from the data stream. /// /// Number of bytes read. public int GetBytesRead() { return m_Context.m_ReadByteIndex - (m_Context.m_BitIndex >> 3); } /// /// Gets the number of bits read from the data stream. /// /// Number of bits read. public int GetBitsRead() { return (m_Context.m_ReadByteIndex << 3) - m_Context.m_BitIndex; } /// /// Sets the current position of this stream to the given value. /// An error will be logged if is outside the length of the stream. ///
/// In addition this will reset the bit index and the bit buffer. ///
/// Seek position. 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; } /// /// Reads an unsigned byte from the current stream and advances the current position of the stream by one byte. /// /// The next byte read from the current stream, or 0 if the end of the stream has been reached. public byte ReadByte() { byte data; ReadBytesInternal((byte*)&data, sizeof(byte)); return data; } /// /// Reads a 2-byte signed short from the current stream and advances the current position of the stream by two bytes. /// /// A 2-byte signed short read from the current stream, or 0 if the end of the stream has been reached. public short ReadShort() { short data; ReadBytesInternal((byte*)&data, sizeof(short)); return data; } /// /// Reads a 2-byte unsigned short from the current stream and advances the current position of the stream by two bytes. /// /// A 2-byte unsigned short read from the current stream, or 0 if the end of the stream has been reached. public ushort ReadUShort() { ushort data; ReadBytesInternal((byte*)&data, sizeof(ushort)); return data; } /// /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes. /// /// A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached. public int ReadInt() { int data; ReadBytesInternal((byte*)&data, sizeof(int)); return data; } /// /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four bytes. /// /// A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached. public uint ReadUInt() { uint data; ReadBytesInternal((byte*)&data, sizeof(uint)); return data; } /// /// Reads an 8-byte signed long from the stream and advances the current position of the stream by eight bytes. /// /// An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached. public long ReadLong() { long data; ReadBytesInternal((byte*)&data, sizeof(long)); return data; } /// /// Reads an 8-byte unsigned long from the stream and advances the current position of the stream by eight bytes. /// /// An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached. public ulong ReadULong() { ulong data; ReadBytesInternal((byte*)&data, sizeof(ulong)); return data; } /// /// Aligns the read pointer to the next byte-aligned position. Does nothing if already aligned. /// /// If you call , call this to bit-align the reader. public void Flush() { m_Context.m_ReadByteIndex -= (m_Context.m_BitIndex >> 3); m_Context.m_BitIndex = 0; m_Context.m_BitBuffer = 0; } /// /// 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. /// /// A 2-byte signed short read from the current stream, or 0 if the end of the stream has been reached. public short ReadShortNetworkByteOrder() { short data; ReadBytesInternal((byte*)&data, sizeof(short)); return IsLittleEndian ? ByteSwap(data) : data; } /// /// 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. /// /// A 2-byte unsigned short read from the current stream, or 0 if the end of the stream has been reached. public ushort ReadUShortNetworkByteOrder() { return (ushort)ReadShortNetworkByteOrder(); } /// /// 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. /// /// A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached. public int ReadIntNetworkByteOrder() { int data; ReadBytesInternal((byte*)&data, sizeof(int)); return IsLittleEndian ? ByteSwap(data) : data; } /// /// 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. /// /// A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached. public uint ReadUIntNetworkByteOrder() { return (uint)ReadIntNetworkByteOrder(); } /// /// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes. /// /// A 4-byte floating point value read from the current stream, or 0 if the end of the stream has been reached. public float ReadFloat() { UIntFloat uf = new UIntFloat(); uf.intValue = (uint)ReadInt(); return uf.floatValue; } /// /// Reads a 8-byte floating point value from the current stream and advances the current position of the stream by four bytes. /// /// A 8-byte floating point value read from the current stream, or 0 if the end of the stream has been reached. public double ReadDouble() { UIntFloat uf = new UIntFloat(); uf.longValue = (ulong)ReadLong(); return uf.doubleValue; } /// /// Reads a 4-byte unsigned integer from the current stream using a and advances the current position the number of bits depending on the model. /// /// model for reading value in a packed manner. /// A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached. 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; } /// /// Reads a specified number of bits from the data stream. /// /// A positive number of bytes to write. /// A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached. public uint ReadRawBits(int numbits) { CheckRead(); FillBitBuffer(); return ReadRawBitsInternal(numbits); } /// /// Reads an 8-byte unsigned long value from the data stream using a . /// /// model for reading value in a packed manner. /// An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached. public ulong ReadPackedULong(in StreamCompressionModel model) { ulong value; ((uint*)&value)[0] = ReadPackedUInt(model); ((uint*)&value)[1] = ReadPackedUInt(model); return value; } /// /// Reads a 4-byte signed integer value from the data stream using a . ///
/// Negative values de-interleaves from positive values before returning, for example (0, -1, 1, -2, 2) -> (-2, -1, 0, 1, 2) ///
/// model for reading value in a packed manner. /// A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached. 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, ...] } /// /// Reads an 8-byte signed long value from the data stream using a . ///
/// Negative values de-interleaves from positive values before returning, for example (0, -1, 1, -2, 2) -> (-2, -1, 0, 1, 2) ///
/// model for reading value in a packed manner. /// An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached. 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, ...] } /// /// Reads a 4-byte floating point value from the data stream using a . /// /// model for reading value in a packed manner. /// A 4-byte floating point value read from the current stream, or 0 if the end of the stream has been reached. public float ReadPackedFloat(in StreamCompressionModel model) { return ReadPackedFloatDelta(0, model); } /// /// Reads a 8-byte floating point value from the data stream using a . /// /// model for reading value in a packed manner. /// A 8-byte floating point value read from the current stream, or 0 if the end of the stream has been reached. public double ReadPackedDouble(in StreamCompressionModel model) { return ReadPackedDoubleDelta(0, model); } /// /// Reads a 4-byte signed integer delta value from the data stream using a . /// /// The previous 4-byte signed integer value, used to compute the diff. /// model for reading value in a packed manner. /// 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. ///
/// See: to verify if the read failed.
public int ReadPackedIntDelta(int baseline, in StreamCompressionModel model) { int delta = ReadPackedInt(model); return baseline - delta; } /// /// Reads a 4-byte unsigned integer delta value from the data stream using a . /// /// The previous 4-byte unsigned integer value, used to compute the diff. /// model for reading value in a packed manner. /// 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. ///
/// See: to verify if the read failed.
public uint ReadPackedUIntDelta(uint baseline, in StreamCompressionModel model) { uint delta = (uint)ReadPackedInt(model); return baseline - delta; } /// /// Reads an 8-byte signed long delta value from the data stream using a . /// /// The previous 8-byte signed long value, used to compute the diff. /// model for reading value in a packed manner. /// 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. ///
/// See: to verify if the read failed.
public long ReadPackedLongDelta(long baseline, in StreamCompressionModel model) { long delta = ReadPackedLong(model); return baseline - delta; } /// /// Reads an 8-byte unsigned long delta value from the data stream using a . /// /// The previous 8-byte unsigned long value, used to compute the diff. /// model for reading value in a packed manner. /// 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. ///
/// See: to verify if the read failed.
public ulong ReadPackedULongDelta(ulong baseline, in StreamCompressionModel model) { ulong delta = (ulong)ReadPackedLong(model); return baseline - delta; } /// /// Reads a 4-byte floating point value from the data stream. /// /// If the first bit is 0, the data did not change and will be returned. /// /// The previous 4-byte floating point value. /// Not currently used. /// A 4-byte floating point value read from the current stream, or if there are no changes to the value. ///
/// See: to verify if the read failed.
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; } /// /// Reads a 8-byte floating point value from the data stream. /// /// If the first bit is 0, the data did not change and will be returned. /// /// The previous 8-byte floating point value. /// Not currently used. /// A 8-byte floating point value read from the current stream, or if there are no changes to the value. ///
/// See: to verify if the read failed.
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; } /// /// Reads a FixedString32Bytes value from the current stream and advances the current position of the stream by the length of the string. /// /// A FixedString32Bytes value read from the current stream, or 0 if the end of the stream has been reached. public unsafe FixedString32Bytes ReadFixedString32() { FixedString32Bytes str; byte* data = ((byte*)&str) + 2; *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity); return str; } /// /// Reads a FixedString64Bytes value from the current stream and advances the current position of the stream by the length of the string. /// /// A FixedString64Bytes value read from the current stream, or 0 if the end of the stream has been reached. public unsafe FixedString64Bytes ReadFixedString64() { FixedString64Bytes str; byte* data = ((byte*)&str) + 2; *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity); return str; } /// /// Reads a FixedString128Bytes value from the current stream and advances the current position of the stream by the length of the string. /// /// A FixedString128Bytes value read from the current stream, or 0 if the end of the stream has been reached. public unsafe FixedString128Bytes ReadFixedString128() { FixedString128Bytes str; byte* data = ((byte*)&str) + 2; *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity); return str; } /// /// Reads a FixedString512Bytes value from the current stream and advances the current position of the stream by the length of the string. /// /// A FixedString512Bytes value read from the current stream, or 0 if the end of the stream has been reached. public unsafe FixedString512Bytes ReadFixedString512() { FixedString512Bytes str; byte* data = ((byte*)&str) + 2; *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity); return str; } /// /// Reads a FixedString4096Bytes value from the current stream and advances the current position of the stream by the length of the string. /// /// A FixedString4096Bytes value read from the current stream, or 0 if the end of the stream has been reached. public unsafe FixedString4096Bytes ReadFixedString4096() { FixedString4096Bytes str; byte* data = ((byte*)&str) + 2; *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity); return str; } /// /// Read and copy data into the given NativeArray of bytes, an error will /// be logged if not enough bytes are available in the array. /// /// Buffer to write the string bytes to. /// Length of data read into byte array, or zero if error occurred. public ushort ReadFixedString(NativeArray 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; } /// /// Reads a FixedString32Bytes delta value to the data stream using a . /// /// The previous FixedString32Bytes value, used to compute the diff. /// model for writing value in a packed manner. /// A FixedString32Bytes value read from the current stream, or 0 if the end of the stream has been reached. 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; } /// /// Reads a FixedString64Bytes delta value to the data stream using a . /// /// The previous FixedString64Bytes value, used to compute the diff. /// model for writing value in a packed manner. /// A FixedString64Bytes value read from the current stream, or 0 if the end of the stream has been reached. 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; } /// /// Reads a FixedString128Bytes delta value to the data stream using a . /// /// The previous FixedString128Bytes value, used to compute the diff. /// model for writing value in a packed manner. /// A FixedString128Bytes value read from the current stream, or 0 if the end of the stream has been reached. 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; } /// /// Reads a FixedString512Bytes delta value to the data stream using a . /// /// The previous FixedString512Bytes value, used to compute the diff. /// model for writing value in a packed manner. /// A FixedString512Bytes value read from the current stream, or 0 if the end of the stream has been reached. 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; } /// /// Reads a FixedString4096Bytes delta value to the data stream using a . /// /// The previous FixedString4096Bytes value, used to compute the diff. /// model for writing value in a packed manner. /// A FixedString4096Bytes value read from the current stream, or 0 if the end of the stream has been reached. 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; } /// /// Read and copy data into the given NativeArray of bytes, an error will /// be logged if not enough bytes are available in the array. /// /// Array for the current fixed string. /// Array containing the previous value, used to compute the diff. /// model for writing value in a packed manner. /// Length of data read into byte array, or zero if error occurred. public ushort ReadPackedFixedStringDelta(NativeArray data, NativeArray 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."); } } }