A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using Unity.Collections.LowLevel.Unsafe;
4using UnityEngine.Scripting.APIUpdating;
5
6namespace Unity.Collections
7{
8 /// <summary>
9 /// Writes data in an endian format to deserialize data.
10 /// </summary>
11 /// <remarks>
12 /// The DataStreamReader class is the counterpart of the
13 /// <see cref="DataStreamWriter"/> class and can be be used to deserialize
14 /// data which was prepared with it.
15 ///
16 /// DataStreamWriter writes this data in the endian format native
17 /// to the current machine architecture.
18 /// <br/>
19 /// For network byte order use the so named methods.
20 /// <br/>
21 /// Simple usage example:
22 /// <code>
23 /// using (var dataWriter = new DataStreamWriter(16, Allocator.Persistent))
24 /// {
25 /// dataWriter.Write(42);
26 /// dataWriter.Write(1234);
27 /// // Length is the actual amount of data inside the writer,
28 /// // Capacity is the total amount.
29 /// var dataReader = new DataStreamReader(dataWriter, 0, dataWriter.Length);
30 /// var context = default(DataStreamReader.Context);
31 /// var myFirstInt = dataReader.ReadInt(ref context);
32 /// var mySecondInt = dataReader.ReadInt(ref context);
33 /// }
34 /// </code>
35 ///
36 /// DataStreamReader carries the position of the read pointer inside the struct,
37 /// taking a copy of the reader will also copy the read position. This includes passing the
38 /// reader to a method by value instead of by ref.
39 ///
40 /// <seealso cref="DataStreamWriter"/>
41 /// <seealso cref="IsLittleEndian"/>
42 /// </remarks>
43 [MovedFrom(true, "Unity.Networking.Transport")]
44 [GenerateTestsForBurstCompatibility]
45 public unsafe struct DataStreamReader
46 {
47 struct Context
48 {
49 public int m_ReadByteIndex;
50 public int m_BitIndex;
51 public ulong m_BitBuffer;
52 public int m_FailedReads;
53 }
54
55 [NativeDisableUnsafePtrRestriction] internal byte* m_BufferPtr;
56 Context m_Context;
57 int m_Length;
58#if ENABLE_UNITY_COLLECTIONS_CHECKS
59 AtomicSafetyHandle m_Safety;
60#endif
61
62 /// <summary>
63 /// Initializes a new instance of the DataStreamReader struct with a NativeArray<byte>
64 /// </summary>
65 /// <param name="array">The buffer to attach to the DataStreamReader.</param>
66 public DataStreamReader(NativeArray<byte> array)
67 {
68 Initialize(out this, array);
69 }
70
71 static void Initialize(out DataStreamReader self, NativeArray<byte> array)
72 {
73#if ENABLE_UNITY_COLLECTIONS_CHECKS
74 self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array);
75#endif
76 self.m_BufferPtr = (byte*)array.GetUnsafeReadOnlyPtr();
77 self.m_Length = array.Length;
78 self.m_Context = default;
79 }
80
81 /// <summary>
82 /// Show the byte order in which the current computer architecture stores data.
83 /// </summary>
84 /// <remarks>
85 /// Different computer architectures store data using different byte orders.
86 /// <list type="bullet">
87 /// <item>Big-endian: the most significant byte is at the left end of a word.</item>
88 /// <item>Little-endian: means the most significant byte is at the right end of a word.</item>
89 /// </list>
90 /// </remarks>
91 public static bool IsLittleEndian { get { return DataStreamWriter.IsLittleEndian; } }
92
93 static short ByteSwap(short val)
94 {
95 return (short)(((val & 0xff) << 8) | ((val >> 8) & 0xff));
96 }
97
98 static int ByteSwap(int val)
99 {
100 return (int)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff));
101 }
102
103 /// <summary>
104 /// 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.
105 /// </summary>
106 public readonly bool HasFailedReads => m_Context.m_FailedReads > 0;
107
108 /// <summary>
109 /// The total size of the buffer space this reader is working with.
110 /// </summary>
111 public readonly int Length
112 {
113 get
114 {
115 CheckRead();
116 return m_Length;
117 }
118 }
119
120 /// <summary>
121 /// True if the reader has been pointed to a valid buffer space. This
122 /// would be false if the reader was created with no arguments.
123 /// </summary>
124 public readonly bool IsCreated
125 {
126 get { return m_BufferPtr != null; }
127 }
128
129 void ReadBytesInternal(byte* data, int length)
130 {
131 CheckRead();
132 if (GetBytesRead() + length > m_Length)
133 {
134 ++m_Context.m_FailedReads;
135#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
136 UnityEngine.Debug.LogError($"Trying to read {length} bytes from a stream where only {m_Length - GetBytesRead()} are available");
137#endif
138 UnsafeUtility.MemClear(data, length);
139 return;
140 }
141 // Restore the full bytes moved to the bit buffer but no consumed
142 Flush();
143 UnsafeUtility.MemCpy(data, m_BufferPtr + m_Context.m_ReadByteIndex, length);
144 m_Context.m_ReadByteIndex += length;
145 }
146
147 /// <summary>
148 /// Read and copy data into the given NativeArray of bytes. An error will
149 /// be logged if not enough bytes are available to fill the array, and
150 /// <see cref="HasFailedReads"/> will then be true.
151 /// </summary>
152 /// <param name="array">Array to copy data into.</param>
153 public void ReadBytes(NativeArray<byte> array)
154 {
155 ReadBytesInternal((byte*)array.GetUnsafePtr(), array.Length);
156 }
157
158 /// <summary>
159 /// Read and copy data into the given <c>Span</c> of bytes. An error will
160 /// be logged if not enough bytes are available to fill the array, and
161 /// <see cref="HasFailedReads"/> will then be true.
162 /// </summary>
163 /// <param name="span">Span to copy data into.</param>
164 public void ReadBytes(Span<byte> span)
165 {
166 fixed (byte* ptr = span)
167 {
168 ReadBytesInternal(ptr, span.Length);
169 }
170 }
171
172 /// <summary>
173 /// Gets the number of bytes read from the data stream.
174 /// </summary>
175 /// <returns>Number of bytes read.</returns>
176 public int GetBytesRead()
177 {
178 return m_Context.m_ReadByteIndex - (m_Context.m_BitIndex >> 3);
179 }
180
181 /// <summary>
182 /// Gets the number of bits read from the data stream.
183 /// </summary>
184 /// <returns>Number of bits read.</returns>
185 public int GetBitsRead()
186 {
187 return (m_Context.m_ReadByteIndex << 3) - m_Context.m_BitIndex;
188 }
189
190 /// <summary>
191 /// Sets the current position of this stream to the given value.
192 /// An error will be logged if <paramref name="pos"/> is outside the length of the stream.
193 /// <br/>
194 /// In addition this will reset the bit index and the bit buffer.
195 /// </summary>
196 /// <param name="pos">Seek position.</param>
197 public void SeekSet(int pos)
198 {
199 if (pos > m_Length)
200 {
201 ++m_Context.m_FailedReads;
202#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
203 UnityEngine.Debug.LogError($"Trying to seek to {pos} in a stream of length {m_Length}");
204#endif
205 return;
206 }
207 m_Context.m_ReadByteIndex = pos;
208 m_Context.m_BitIndex = 0;
209 m_Context.m_BitBuffer = 0UL;
210 }
211
212 /// <summary>
213 /// Reads an unsigned byte from the current stream and advances the current position of the stream by one byte.
214 /// </summary>
215 /// <returns>The next byte read from the current stream, or 0 if the end of the stream has been reached.</returns>
216 public byte ReadByte()
217 {
218 byte data;
219 ReadBytesInternal((byte*)&data, sizeof(byte));
220 return data;
221 }
222
223 /// <summary>
224 /// Reads a 2-byte signed short from the current stream and advances the current position of the stream by two bytes.
225 /// </summary>
226 /// <returns>A 2-byte signed short read from the current stream, or 0 if the end of the stream has been reached.</returns>
227 public short ReadShort()
228 {
229 short data;
230 ReadBytesInternal((byte*)&data, sizeof(short));
231 return data;
232 }
233
234 /// <summary>
235 /// Reads a 2-byte unsigned short from the current stream and advances the current position of the stream by two bytes.
236 /// </summary>
237 /// <returns>A 2-byte unsigned short read from the current stream, or 0 if the end of the stream has been reached.</returns>
238 public ushort ReadUShort()
239 {
240 ushort data;
241 ReadBytesInternal((byte*)&data, sizeof(ushort));
242 return data;
243 }
244
245 /// <summary>
246 /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
247 /// </summary>
248 /// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
249 public int ReadInt()
250 {
251 int data;
252 ReadBytesInternal((byte*)&data, sizeof(int));
253 return data;
254 }
255
256 /// <summary>
257 /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four bytes.
258 /// </summary>
259 /// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
260 public uint ReadUInt()
261 {
262 uint data;
263 ReadBytesInternal((byte*)&data, sizeof(uint));
264 return data;
265 }
266
267 /// <summary>
268 /// Reads an 8-byte signed long from the stream and advances the current position of the stream by eight bytes.
269 /// </summary>
270 /// <returns>An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached.</returns>
271 public long ReadLong()
272 {
273 long data;
274 ReadBytesInternal((byte*)&data, sizeof(long));
275 return data;
276 }
277
278 /// <summary>
279 /// Reads an 8-byte unsigned long from the stream and advances the current position of the stream by eight bytes.
280 /// </summary>
281 /// <returns>An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached.</returns>
282 public ulong ReadULong()
283 {
284 ulong data;
285 ReadBytesInternal((byte*)&data, sizeof(ulong));
286 return data;
287 }
288
289 /// <summary>
290 /// Aligns the read pointer to the next byte-aligned position. Does nothing if already aligned.
291 /// </summary>
292 /// <remarks>If you call <see cref="DataStreamWriter.Flush"/>, call this to bit-align the reader.</remarks>
293 public void Flush()
294 {
295 m_Context.m_ReadByteIndex -= (m_Context.m_BitIndex >> 3);
296 m_Context.m_BitIndex = 0;
297 m_Context.m_BitBuffer = 0;
298 }
299
300 /// <summary>
301 /// 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.
302 /// If the current endianness is in little-endian order, the byte order will be swapped.
303 /// </summary>
304 /// <returns>A 2-byte signed short read from the current stream, or 0 if the end of the stream has been reached.</returns>
305 public short ReadShortNetworkByteOrder()
306 {
307 short data;
308 ReadBytesInternal((byte*)&data, sizeof(short));
309 return IsLittleEndian ? ByteSwap(data) : data;
310 }
311
312 /// <summary>
313 /// 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.
314 /// If the current endianness is in little-endian order, the byte order will be swapped.
315 /// </summary>
316 /// <returns>A 2-byte unsigned short read from the current stream, or 0 if the end of the stream has been reached.</returns>
317 public ushort ReadUShortNetworkByteOrder()
318 {
319 return (ushort)ReadShortNetworkByteOrder();
320 }
321
322 /// <summary>
323 /// 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.
324 /// If the current endianness is in little-endian order, the byte order will be swapped.
325 /// </summary>
326 /// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
327 public int ReadIntNetworkByteOrder()
328 {
329 int data;
330 ReadBytesInternal((byte*)&data, sizeof(int));
331 return IsLittleEndian ? ByteSwap(data) : data;
332 }
333
334 /// <summary>
335 /// 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.
336 /// If the current endianness is in little-endian order, the byte order will be swapped.
337 /// </summary>
338 /// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
339 public uint ReadUIntNetworkByteOrder()
340 {
341 return (uint)ReadIntNetworkByteOrder();
342 }
343
344 /// <summary>
345 /// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
346 /// </summary>
347 /// <returns>A 4-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
348 public float ReadFloat()
349 {
350 UIntFloat uf = new UIntFloat();
351 uf.intValue = (uint)ReadInt();
352 return uf.floatValue;
353 }
354
355 /// <summary>
356 /// Reads a 8-byte floating point value from the current stream and advances the current position of the stream by four bytes.
357 /// </summary>
358 /// <returns>A 8-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
359 public double ReadDouble()
360 {
361 UIntFloat uf = new UIntFloat();
362 uf.longValue = (ulong)ReadLong();
363 return uf.doubleValue;
364 }
365
366 /// <summary>
367 /// 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.
368 /// </summary>
369 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
370 /// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
371 public uint ReadPackedUInt(in StreamCompressionModel model)
372 {
373 return ReadPackedUIntInternal(StreamCompressionModel.k_MaxHuffmanSymbolLength, model);
374 }
375
376 uint ReadPackedUIntInternal(int maxSymbolLength, in StreamCompressionModel model)
377 {
378 CheckRead();
379 FillBitBuffer();
380 uint peekMask = (1u << maxSymbolLength) - 1u;
381 uint peekBits = (uint)m_Context.m_BitBuffer & peekMask;
382 ushort huffmanEntry = model.decodeTable[(int)peekBits];
383 int symbol = huffmanEntry >> 8;
384 int length = huffmanEntry & 0xFF;
385
386 if (m_Context.m_BitIndex < length)
387 {
388 ++m_Context.m_FailedReads;
389#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
390 UnityEngine.Debug.LogError($"Trying to read {length} bits from a stream where only {m_Context.m_BitIndex} are available");
391#endif
392 return 0;
393 }
394
395 // Skip Huffman bits
396 m_Context.m_BitBuffer >>= length;
397 m_Context.m_BitIndex -= length;
398
399 uint offset = model.bucketOffsets[symbol];
400 byte bits = model.bucketSizes[symbol];
401 return ReadRawBitsInternal(bits) + offset;
402 }
403
404 void FillBitBuffer()
405 {
406 while (m_Context.m_BitIndex <= 56 && m_Context.m_ReadByteIndex < m_Length)
407 {
408 m_Context.m_BitBuffer |= (ulong)m_BufferPtr[m_Context.m_ReadByteIndex++] << m_Context.m_BitIndex;
409 m_Context.m_BitIndex += 8;
410 }
411 }
412
413 uint ReadRawBitsInternal(int numbits)
414 {
415 CheckBits(numbits);
416 if (m_Context.m_BitIndex < numbits)
417 {
418 ++m_Context.m_FailedReads;
419#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
420 UnityEngine.Debug.LogError($"Trying to read {numbits} bits from a stream where only {m_Context.m_BitIndex} are available");
421#endif
422 return 0;
423 }
424 uint res = (uint)(m_Context.m_BitBuffer & ((1UL << numbits) - 1UL));
425 m_Context.m_BitBuffer >>= numbits;
426 m_Context.m_BitIndex -= numbits;
427 return res;
428 }
429
430 /// <summary>
431 /// Reads a specified number of bits from the data stream.
432 /// </summary>
433 /// <param name="numbits">A positive number of bytes to write.</param>
434 /// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
435 public uint ReadRawBits(int numbits)
436 {
437 CheckRead();
438 FillBitBuffer();
439 return ReadRawBitsInternal(numbits);
440 }
441
442 /// <summary>
443 /// Reads an 8-byte unsigned long value from the data stream using a <see cref="StreamCompressionModel"/>.
444 /// </summary>
445 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
446 /// <returns>An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached.</returns>
447 public ulong ReadPackedULong(in StreamCompressionModel model)
448 {
449 ulong value;
450 ((uint*)&value)[0] = ReadPackedUInt(model);
451 ((uint*)&value)[1] = ReadPackedUInt(model);
452 return value;
453 }
454
455 /// <summary>
456 /// Reads a 4-byte signed integer value from the data stream using a <see cref="StreamCompressionModel"/>.
457 /// <br/>
458 /// Negative values de-interleaves from positive values before returning, for example (0, -1, 1, -2, 2) -> (-2, -1, 0, 1, 2)
459 /// </summary>
460 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
461 /// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.</returns>
462 public int ReadPackedInt(in StreamCompressionModel model)
463 {
464 uint folded = ReadPackedUInt(model);
465 return (int)(folded >> 1) ^ -(int)(folded & 1); // Deinterleave values from [0, -1, 1, -2, 2...] to [..., -2, -1, -0, 1, 2, ...]
466 }
467
468 /// <summary>
469 /// Reads an 8-byte signed long value from the data stream using a <see cref="StreamCompressionModel"/>.
470 /// <br/>
471 /// Negative values de-interleaves from positive values before returning, for example (0, -1, 1, -2, 2) -> (-2, -1, 0, 1, 2)
472 /// </summary>
473 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
474 /// <returns>An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached.</returns>
475 public long ReadPackedLong(in StreamCompressionModel model)
476 {
477 ulong folded = ReadPackedULong(model);
478 return (long)(folded >> 1) ^ -(long)(folded & 1); // Deinterleave values from [0, -1, 1, -2, 2...] to [..., -2, -1, -0, 1, 2, ...]
479 }
480
481 /// <summary>
482 /// Reads a 4-byte floating point value from the data stream using a <see cref="StreamCompressionModel"/>.
483 /// </summary>
484 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
485 /// <returns>A 4-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
486 public float ReadPackedFloat(in StreamCompressionModel model)
487 {
488 return ReadPackedFloatDelta(0, model);
489 }
490
491 /// <summary>
492 /// Reads a 8-byte floating point value from the data stream using a <see cref="StreamCompressionModel"/>.
493 /// </summary>
494 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
495 /// <returns>A 8-byte floating point value read from the current stream, or 0 if the end of the stream has been reached.</returns>
496 public double ReadPackedDouble(in StreamCompressionModel model)
497 {
498 return ReadPackedDoubleDelta(0, model);
499 }
500
501 /// <summary>
502 /// Reads a 4-byte signed integer delta value from the data stream using a <see cref="StreamCompressionModel"/>.
503 /// </summary>
504 /// <param name="baseline">The previous 4-byte signed integer value, used to compute the diff.</param>
505 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
506 /// <returns>A 4-byte signed integer read from the current stream, or 0 if the end of the stream has been reached.
507 /// If the data did not change, this also returns 0.
508 /// <br/>
509 /// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
510 public int ReadPackedIntDelta(int baseline, in StreamCompressionModel model)
511 {
512 int delta = ReadPackedInt(model);
513 return baseline - delta;
514 }
515
516 /// <summary>
517 /// Reads a 4-byte unsigned integer delta value from the data stream using a <see cref="StreamCompressionModel"/>.
518 /// </summary>
519 /// <param name="baseline">The previous 4-byte unsigned integer value, used to compute the diff.</param>
520 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
521 /// <returns>A 4-byte unsigned integer read from the current stream, or 0 if the end of the stream has been reached.
522 /// If the data did not change, this also returns 0.
523 /// <br/>
524 /// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
525 public uint ReadPackedUIntDelta(uint baseline, in StreamCompressionModel model)
526 {
527 uint delta = (uint)ReadPackedInt(model);
528 return baseline - delta;
529 }
530
531 /// <summary>
532 /// Reads an 8-byte signed long delta value from the data stream using a <see cref="StreamCompressionModel"/>.
533 /// </summary>
534 /// <param name="baseline">The previous 8-byte signed long value, used to compute the diff.</param>
535 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
536 /// <returns>An 8-byte signed long read from the current stream, or 0 if the end of the stream has been reached.
537 /// If the data did not change, this also returns 0.
538 /// <br/>
539 /// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
540 public long ReadPackedLongDelta(long baseline, in StreamCompressionModel model)
541 {
542 long delta = ReadPackedLong(model);
543 return baseline - delta;
544 }
545
546 /// <summary>
547 /// Reads an 8-byte unsigned long delta value from the data stream using a <see cref="StreamCompressionModel"/>.
548 /// </summary>
549 /// <param name="baseline">The previous 8-byte unsigned long value, used to compute the diff.</param>
550 /// <param name="model"><see cref="StreamCompressionModel"/> model for reading value in a packed manner.</param>
551 /// <returns>An 8-byte unsigned long read from the current stream, or 0 if the end of the stream has been reached.
552 /// If the data did not change, this also returns 0.
553 /// <br/>
554 /// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
555 public ulong ReadPackedULongDelta(ulong baseline, in StreamCompressionModel model)
556 {
557 ulong delta = (ulong)ReadPackedLong(model);
558 return baseline - delta;
559 }
560
561 /// <summary>
562 /// Reads a 4-byte floating point value from the data stream.
563 ///
564 /// If the first bit is 0, the data did not change and <paramref name="baseline"/> will be returned.
565 /// </summary>
566 /// <param name="baseline">The previous 4-byte floating point value.</param>
567 /// <param name="model">Not currently used.</param>
568 /// <returns>A 4-byte floating point value read from the current stream, or <paramref name="baseline"/> if there are no changes to the value.
569 /// <br/>
570 /// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
571 public float ReadPackedFloatDelta(float baseline, in StreamCompressionModel model)
572 {
573 CheckRead();
574 FillBitBuffer();
575 if (ReadRawBitsInternal(1) == 0)
576 return baseline;
577
578 var bits = 32;
579 UIntFloat uf = new UIntFloat();
580 uf.intValue = ReadRawBitsInternal(bits);
581 return uf.floatValue;
582 }
583
584 /// <summary>
585 /// Reads a 8-byte floating point value from the data stream.
586 ///
587 /// If the first bit is 0, the data did not change and <paramref name="baseline"/> will be returned.
588 /// </summary>
589 /// <param name="baseline">The previous 8-byte floating point value.</param>
590 /// <param name="model">Not currently used.</param>
591 /// <returns>A 8-byte floating point value read from the current stream, or <paramref name="baseline"/> if there are no changes to the value.
592 /// <br/>
593 /// See: <see cref="HasFailedReads"/> to verify if the read failed.</returns>
594 public double ReadPackedDoubleDelta(double baseline, in StreamCompressionModel model)
595 {
596 CheckRead();
597 FillBitBuffer();
598 if (ReadRawBitsInternal(1) == 0)
599 return baseline;
600
601 var bits = 32;
602 UIntFloat uf = new UIntFloat();
603 var data = (uint*)&uf.longValue;
604 data[0] = ReadRawBitsInternal(bits);
605 FillBitBuffer();
606 data[1] |= ReadRawBitsInternal(bits);
607 return uf.doubleValue;
608 }
609
610 /// <summary>
611 /// Reads a <c>FixedString32Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
612 /// </summary>
613 /// <returns>A <c>FixedString32Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
614 public unsafe FixedString32Bytes ReadFixedString32()
615 {
616 FixedString32Bytes str;
617 byte* data = ((byte*)&str) + 2;
618 *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
619 return str;
620 }
621
622 /// <summary>
623 /// Reads a <c>FixedString64Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
624 /// </summary>
625 /// <returns>A <c>FixedString64Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
626 public unsafe FixedString64Bytes ReadFixedString64()
627 {
628 FixedString64Bytes str;
629 byte* data = ((byte*)&str) + 2;
630 *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
631 return str;
632 }
633
634 /// <summary>
635 /// Reads a <c>FixedString128Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
636 /// </summary>
637 /// <returns>A <c>FixedString128Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
638 public unsafe FixedString128Bytes ReadFixedString128()
639 {
640 FixedString128Bytes str;
641 byte* data = ((byte*)&str) + 2;
642 *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
643 return str;
644 }
645
646 /// <summary>
647 /// Reads a <c>FixedString512Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
648 /// </summary>
649 /// <returns>A <c>FixedString512Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
650 public unsafe FixedString512Bytes ReadFixedString512()
651 {
652 FixedString512Bytes str;
653 byte* data = ((byte*)&str) + 2;
654 *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
655 return str;
656 }
657
658 /// <summary>
659 /// Reads a <c>FixedString4096Bytes</c> value from the current stream and advances the current position of the stream by the length of the string.
660 /// </summary>
661 /// <returns>A <c>FixedString4096Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
662 public unsafe FixedString4096Bytes ReadFixedString4096()
663 {
664 FixedString4096Bytes str;
665 byte* data = ((byte*)&str) + 2;
666 *(ushort*)&str = ReadFixedStringInternal(data, str.Capacity);
667 return str;
668 }
669
670 /// <summary>
671 /// Read and copy data into the given NativeArray of bytes, an error will
672 /// be logged if not enough bytes are available in the array.
673 /// </summary>
674 /// <param name="array">Buffer to write the string bytes to.</param>
675 /// <returns>Length of data read into byte array, or zero if error occurred.</returns>
676 public ushort ReadFixedString(NativeArray<byte> array)
677 {
678 return ReadFixedStringInternal((byte*)array.GetUnsafePtr(), array.Length);
679 }
680
681 unsafe ushort ReadFixedStringInternal(byte* data, int maxLength)
682 {
683 ushort length = ReadUShort();
684 if (length > maxLength)
685 {
686#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
687 UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}");
688#endif
689 return 0;
690 }
691 ReadBytesInternal(data, length);
692 return length;
693 }
694
695 /// <summary>
696 /// Reads a <c>FixedString32Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
697 /// </summary>
698 /// <param name="baseline">The previous <c>FixedString32Bytes</c> value, used to compute the diff.</param>
699 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
700 /// <returns>A <c>FixedString32Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
701 public unsafe FixedString32Bytes ReadPackedFixedString32Delta(FixedString32Bytes baseline, in StreamCompressionModel model)
702 {
703 FixedString32Bytes str;
704 byte* data = ((byte*)&str) + 2;
705 *(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
706 return str;
707 }
708
709 /// <summary>
710 /// Reads a <c>FixedString64Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
711 /// </summary>
712 /// <param name="baseline">The previous <c>FixedString64Bytes</c> value, used to compute the diff.</param>
713 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
714 /// <returns>A <c>FixedString64Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
715 public unsafe FixedString64Bytes ReadPackedFixedString64Delta(FixedString64Bytes baseline, in StreamCompressionModel model)
716 {
717 FixedString64Bytes str;
718 byte* data = ((byte*)&str) + 2;
719 *(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
720 return str;
721 }
722
723 /// <summary>
724 /// Reads a <c>FixedString128Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
725 /// </summary>
726 /// <param name="baseline">The previous <c>FixedString128Bytes</c> value, used to compute the diff.</param>
727 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
728 /// <returns>A <c>FixedString128Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
729 public unsafe FixedString128Bytes ReadPackedFixedString128Delta(FixedString128Bytes baseline, in StreamCompressionModel model)
730 {
731 FixedString128Bytes str;
732 byte* data = ((byte*)&str) + 2;
733 *(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
734 return str;
735 }
736
737 /// <summary>
738 /// Reads a <c>FixedString512Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
739 /// </summary>
740 /// <param name="baseline">The previous <c>FixedString512Bytes</c> value, used to compute the diff.</param>
741 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
742 /// <returns>A <c>FixedString512Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
743 public unsafe FixedString512Bytes ReadPackedFixedString512Delta(FixedString512Bytes baseline, in StreamCompressionModel model)
744 {
745 FixedString512Bytes str;
746 byte* data = ((byte*)&str) + 2;
747 *(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
748 return str;
749 }
750
751 /// <summary>
752 /// Reads a <c>FixedString4096Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
753 /// </summary>
754 /// <param name="baseline">The previous <c>FixedString4096Bytes</c> value, used to compute the diff.</param>
755 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
756 /// <returns>A <c>FixedString4096Bytes</c> value read from the current stream, or 0 if the end of the stream has been reached.</returns>
757 public unsafe FixedString4096Bytes ReadPackedFixedString4096Delta(FixedString4096Bytes baseline, in StreamCompressionModel model)
758 {
759 FixedString4096Bytes str;
760 byte* data = ((byte*)&str) + 2;
761 *(ushort*)&str = ReadPackedFixedStringDeltaInternal(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
762 return str;
763 }
764
765 /// <summary>
766 /// Read and copy data into the given NativeArray of bytes, an error will
767 /// be logged if not enough bytes are available in the array.
768 /// </summary>
769 /// <param name="data">Array for the current fixed string.</param>
770 /// <param name="baseData">Array containing the previous value, used to compute the diff.</param>
771 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
772 /// <returns>Length of data read into byte array, or zero if error occurred.</returns>
773 public ushort ReadPackedFixedStringDelta(NativeArray<byte> data, NativeArray<byte> baseData, in StreamCompressionModel model)
774 {
775 return ReadPackedFixedStringDeltaInternal((byte*)data.GetUnsafePtr(), data.Length, (byte*)baseData.GetUnsafePtr(), (ushort)baseData.Length, model);
776 }
777
778 unsafe ushort ReadPackedFixedStringDeltaInternal(byte* data, int maxLength, byte* baseData, ushort baseLength, in StreamCompressionModel model)
779 {
780 uint length = ReadPackedUIntDelta(baseLength, model);
781 if (length > (uint)maxLength)
782 {
783#if (ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG)
784 UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}");
785#endif
786 return 0;
787 }
788 if (length <= baseLength)
789 {
790 for (int i = 0; i < length; ++i)
791 data[i] = (byte)ReadPackedUIntDelta(baseData[i], model);
792 }
793 else
794 {
795 for (int i = 0; i < baseLength; ++i)
796 data[i] = (byte)ReadPackedUIntDelta(baseData[i], model);
797 for (int i = baseLength; i < length; ++i)
798 data[i] = (byte)ReadPackedUInt(model);
799 }
800 return (ushort)length;
801 }
802
803 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
804 internal readonly void CheckRead()
805 {
806#if ENABLE_UNITY_COLLECTIONS_CHECKS
807 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
808#endif
809 }
810
811 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
812 static void CheckBits(int numBits)
813 {
814 if (numBits < 0 || numBits > 32)
815 throw new ArgumentOutOfRangeException($"Invalid number of bits specified: {numBits}! Valid range is (0, 32) inclusive.");
816 }
817 }
818}