A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using System.Runtime.CompilerServices;
4using System.Runtime.InteropServices;
5using Unity.Burst;
6using Unity.Collections.LowLevel.Unsafe;
7using UnityEngine.Scripting.APIUpdating;
8
9namespace Unity.Collections
10{
11 /// <summary>
12 /// Writes data in an endian format to serialize data.
13 /// </summary>
14 /// <remarks>
15 /// Data streams can be used to serialize data (e.g. over the network). The
16 /// DataStreamWriter and <see cref="DataStreamReader"/> classes work together
17 /// to serialize data for sending and then to deserialize when receiving.
18 ///
19 /// DataStreamWriter writes data in the endian format native to the current machine architecture.
20 /// For network byte order use the so named methods.
21 /// <br/>
22 /// The reader can be used to deserialize the data from a NativeArray<byte>, writing data
23 /// to a NativeArray<byte> and reading it back can be done like this:
24 /// <code>
25 /// using (var data = new NativeArray<byte>(16, Allocator.Persistent))
26 /// {
27 /// var dataWriter = new DataStreamWriter(data);
28 /// dataWriter.WriteInt(42);
29 /// dataWriter.WriteInt(1234);
30 /// // Length is the actual amount of data inside the writer,
31 /// // Capacity is the total amount.
32 /// var dataReader = new DataStreamReader(nativeArrayOfBytes.GetSubArray(0, dataWriter.Length));
33 /// var myFirstInt = dataReader.ReadInt();
34 /// var mySecondInt = dataReader.ReadInt();
35 /// }
36 /// </code>
37 ///
38 /// There are a number of functions for various data types. If a copy of the writer
39 /// is stored it can be used to overwrite the data later on. This is particularly useful when
40 /// the size of the data is written at the start and you want to write it at
41 /// the end when you know the value.
42 /// <seealso cref="IsLittleEndian"/>
43 ///
44 /// <code>
45 /// using (var data = new NativeArray<byte>(16, Allocator.Persistent))
46 /// {
47 /// var dataWriter = new DataStreamWriter(data);
48 /// // My header data
49 /// var headerSizeMark = dataWriter;
50 /// dataWriter.WriteUShort((ushort)0);
51 /// var payloadSizeMark = dataWriter;
52 /// dataWriter.WriteUShort((ushort)0);
53 /// dataWriter.WriteInt(42);
54 /// dataWriter.WriteInt(1234);
55 /// var headerSize = data.Length;
56 /// // Update header size to correct value
57 /// headerSizeMark.WriteUShort((ushort)headerSize);
58 /// // My payload data
59 /// byte[] someBytes = Encoding.ASCII.GetBytes("some string");
60 /// dataWriter.Write(someBytes, someBytes.Length);
61 /// // Update payload size to correct value
62 /// payloadSizeMark.WriteUShort((ushort)(dataWriter.Length - headerSize));
63 /// }
64 /// </code>
65 /// </remarks>
66 [MovedFrom(true, "Unity.Networking.Transport", "Unity.Networking.Transport")]
67 [StructLayout(LayoutKind.Sequential)]
68 [GenerateTestsForBurstCompatibility]
69 public unsafe struct DataStreamWriter
70 {
71 /// <summary>
72 /// Show the byte order in which the current computer architecture stores data.
73 /// </summary>
74 /// <remarks>
75 /// Different computer architectures store data using different byte orders.
76 /// <list type="bullet">
77 /// <item>Big-endian: the most significant byte is at the left end of a word.</item>
78 /// <item>Little-endian: means the most significant byte is at the right end of a word.</item>
79 /// </list>
80 /// </remarks>
81 public static bool IsLittleEndian
82 {
83 get
84 {
85 uint test = 1;
86 byte* testPtr = (byte*)&test;
87 return testPtr[0] == 1;
88 }
89 }
90
91 struct StreamData
92 {
93 public byte* buffer;
94 public int length;
95 public int capacity;
96 public ulong bitBuffer;
97 public int bitIndex;
98 public int failedWrites;
99 }
100
101 [NativeDisableUnsafePtrRestriction] StreamData m_Data;
102 /// <summary>
103 /// Used for sending data asynchronously.
104 /// </summary>
105 public IntPtr m_SendHandleData;
106
107#if ENABLE_UNITY_COLLECTIONS_CHECKS
108 AtomicSafetyHandle m_Safety;
109#endif
110
111 /// <summary>
112 /// Initializes a new instance of the DataStreamWriter struct.
113 /// </summary>
114 /// <param name="length">The number of bytes available in the buffer.</param>
115 /// <param name="allocator">The <see cref="Allocator"/> used to allocate the memory.</param>
116 public DataStreamWriter(int length, AllocatorManager.AllocatorHandle allocator)
117 {
118 CheckAllocator(allocator);
119 Initialize(out this, CollectionHelper.CreateNativeArray<byte>(length, allocator));
120 }
121
122 /// <summary>
123 /// Initializes a new instance of the DataStreamWriter struct with a NativeArray<byte>
124 /// </summary>
125 /// <param name="data">The buffer to attach to the DataStreamWriter.</param>
126 public DataStreamWriter(NativeArray<byte> data)
127 {
128 Initialize(out this, data);
129 }
130
131 /// <summary>
132 /// Initializes a new instance of the DataStreamWriter struct with a memory we don't own
133 /// </summary>
134 /// <param name="data">Pointer to the data</param>
135 /// <param name="length">Length of the data</param>
136 public DataStreamWriter(byte* data, int length)
137 {
138 var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(data, length, Allocator.Invalid);
139#if ENABLE_UNITY_COLLECTIONS_CHECKS
140 NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, AtomicSafetyHandle.GetTempMemoryHandle());
141#endif
142 Initialize(out this, na);
143 }
144
145 /// <summary>
146 /// Convert internal data buffer to NativeArray for use in entities APIs.
147 /// </summary>
148 /// <returns>NativeArray representation of internal buffer.</returns>
149 public NativeArray<byte> AsNativeArray()
150 {
151 var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(m_Data.buffer, Length, Allocator.Invalid);
152#if ENABLE_UNITY_COLLECTIONS_CHECKS
153 NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, m_Safety);
154#endif
155 return na;
156 }
157
158 static void Initialize(out DataStreamWriter self, NativeArray<byte> data)
159 {
160 self.m_SendHandleData = IntPtr.Zero;
161
162 self.m_Data.capacity = data.Length;
163 self.m_Data.length = 0;
164 self.m_Data.buffer = (byte*)data.GetUnsafePtr();
165 self.m_Data.bitBuffer = 0;
166 self.m_Data.bitIndex = 0;
167 self.m_Data.failedWrites = 0;
168
169#if ENABLE_UNITY_COLLECTIONS_CHECKS
170 self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(data);
171#endif
172 }
173
174 static short ByteSwap(short val)
175 {
176 return (short)(((val & 0xff) << 8) | ((val >> 8) & 0xff));
177 }
178
179 static int ByteSwap(int val)
180 {
181 return (int)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff));
182 }
183
184 /// <summary>
185 /// True if there is a valid data buffer present. This would be false
186 /// if the writer was created with no arguments.
187 /// </summary>
188 public readonly bool IsCreated
189 {
190 [MethodImpl(MethodImplOptions.AggressiveInlining)]
191 get { return m_Data.buffer != null; }
192 }
193
194 /// <summary>
195 /// If there is a write failure this returns true.
196 /// A failure might happen if an attempt is made to write more than there is capacity for.
197 /// </summary>
198 public readonly bool HasFailedWrites
199 {
200 [MethodImpl(MethodImplOptions.AggressiveInlining)]
201 get => m_Data.failedWrites > 0;
202 }
203
204 /// <summary>
205 /// The total size of the data buffer, see <see cref="Length"/> for
206 /// the size of space used in the buffer.
207 /// </summary>
208 public readonly int Capacity
209 {
210 [MethodImpl(MethodImplOptions.AggressiveInlining)]
211 get
212 {
213 CheckRead();
214 return m_Data.capacity;
215 }
216 }
217
218 /// <summary>
219 /// The size of the buffer used. See <see cref="Capacity"/> for the total size.
220 /// </summary>
221 public int Length
222 {
223 get
224 {
225 CheckRead();
226 SyncBitData();
227 return m_Data.length + ((m_Data.bitIndex + 7) >> 3);
228 }
229 }
230 /// <summary>
231 /// The size of the buffer used in bits. See <see cref="Length"/> for the length in bytes.
232 /// </summary>
233 public int LengthInBits
234 {
235 get
236 {
237 CheckRead();
238 SyncBitData();
239 return m_Data.length * 8 + m_Data.bitIndex;
240 }
241 }
242
243 void SyncBitData()
244 {
245 var bitIndex = m_Data.bitIndex;
246 if (bitIndex <= 0)
247 return;
248 CheckWrite();
249
250 var bitBuffer = m_Data.bitBuffer;
251 int offset = 0;
252 while (bitIndex > 0)
253 {
254 m_Data.buffer[m_Data.length + offset] = (byte)bitBuffer;
255 bitIndex -= 8;
256 bitBuffer >>= 8;
257 ++offset;
258 }
259 }
260
261 /// <summary>
262 /// Causes any buffered bits to be written to the data buffer.
263 /// Note this needs to be invoked after using methods that writes directly to the bit buffer.
264 /// </summary>
265 public void Flush()
266 {
267 while (m_Data.bitIndex > 0)
268 {
269 m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer;
270 m_Data.bitIndex -= 8;
271 m_Data.bitBuffer >>= 8;
272 }
273
274 m_Data.bitIndex = 0;
275 }
276
277 bool WriteBytesInternal(byte* data, int bytes)
278 {
279 CheckWrite();
280
281 if (m_Data.length + ((m_Data.bitIndex + 7) >> 3) + bytes > m_Data.capacity)
282 {
283 ++m_Data.failedWrites;
284 return false;
285 }
286 Flush();
287 UnsafeUtility.MemCpy(m_Data.buffer + m_Data.length, data, bytes);
288 m_Data.length += bytes;
289 return true;
290 }
291
292 /// <summary>
293 /// Writes an unsigned byte to the current stream and advances the stream position by one byte.
294 /// </summary>
295 /// <param name="value">The unsigned byte to write.</param>
296 /// <returns>Whether the write was successful</returns>
297 public bool WriteByte(byte value)
298 {
299 return WriteBytesInternal((byte*)&value, sizeof(byte));
300 }
301
302 /// <summary>
303 /// Copy NativeArray of bytes into the writer's data buffer.
304 /// </summary>
305 /// <param name="value">Source byte array</param>
306 /// <returns>Whether the write was successful</returns>
307 public bool WriteBytes(NativeArray<byte> value)
308 {
309 return WriteBytesInternal((byte*)value.GetUnsafeReadOnlyPtr(), value.Length);
310 }
311
312 /// <summary>
313 /// Copy <c>Span</c> of bytes into the writer's data buffer.
314 /// </summary>
315 /// <param name="value">Source byte span</param>
316 /// <returns>Whether the write was successful</returns>
317 public bool WriteBytes(Span<byte> value)
318 {
319 fixed (byte* data = value)
320 {
321 return WriteBytesInternal(data, value.Length);
322 }
323 }
324
325 /// <summary>
326 /// Writes a 2-byte signed short to the current stream and advances the stream position by two bytes.
327 /// </summary>
328 /// <param name="value">The 2-byte signed short to write.</param>
329 /// <returns>Whether the write was successful</returns>
330 public bool WriteShort(short value)
331 {
332 return WriteBytesInternal((byte*)&value, sizeof(short));
333 }
334
335 /// <summary>
336 /// Writes a 2-byte unsigned short to the current stream and advances the stream position by two bytes.
337 /// </summary>
338 /// <param name="value">The 2-byte unsigned short to write.</param>
339 /// <returns>Whether the write was successful</returns>
340 public bool WriteUShort(ushort value)
341 {
342 return WriteBytesInternal((byte*)&value, sizeof(ushort));
343 }
344
345 /// <summary>
346 /// Writes a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
347 /// </summary>
348 /// <param name="value">The 4-byte signed integer to write.</param>
349 /// <returns>Whether the write was successful</returns>
350 public bool WriteInt(int value)
351 {
352 return WriteBytesInternal((byte*)&value, sizeof(int));
353 }
354
355 /// <summary>
356 /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four bytes.
357 /// </summary>
358 /// <param name="value">The 4-byte unsigned integer to write.</param>
359 /// <returns>Whether the write was successful</returns>
360 public bool WriteUInt(uint value)
361 {
362 return WriteBytesInternal((byte*)&value, sizeof(uint));
363 }
364
365 /// <summary>
366 /// Writes an 8-byte signed long from the stream and advances the current position of the stream by eight bytes.
367 /// </summary>
368 /// <param name="value">The 8-byte signed long to write.</param>
369 /// <returns>Whether the write was successful</returns>
370 public bool WriteLong(long value)
371 {
372 return WriteBytesInternal((byte*)&value, sizeof(long));
373 }
374
375 /// <summary>
376 /// Reads an 8-byte unsigned long from the stream and advances the current position of the stream by eight bytes.
377 /// </summary>
378 /// <param name="value">The 8-byte unsigned long to write.</param>
379 /// <returns>Whether the write was successful</returns>
380 public bool WriteULong(ulong value)
381 {
382 return WriteBytesInternal((byte*)&value, sizeof(ulong));
383 }
384
385 /// <summary>
386 /// Writes a 2-byte signed short to the current stream using Big-endian byte order and advances the stream position by two bytes.
387 /// If the stream is in little-endian order, the byte order will be swapped.
388 /// </summary>
389 /// <param name="value">The 2-byte signed short to write.</param>
390 /// <returns>Whether the write was successful</returns>
391 public bool WriteShortNetworkByteOrder(short value)
392 {
393 short netValue = IsLittleEndian ? ByteSwap(value) : value;
394 return WriteBytesInternal((byte*)&netValue, sizeof(short));
395 }
396
397
398 /// <summary>
399 /// Writes a 2-byte unsigned short to the current stream using Big-endian byte order and advances the stream position by two bytes.
400 /// If the stream is in little-endian order, the byte order will be swapped.
401 /// </summary>
402 /// <param name="value">The 2-byte unsigned short to write.</param>
403 /// <returns>Whether the write was successful</returns>
404 public bool WriteUShortNetworkByteOrder(ushort value)
405 {
406 return WriteShortNetworkByteOrder((short)value);
407 }
408
409 /// <summary>
410 /// Writes a 4-byte signed integer from the current stream using Big-endian byte order and advances the current position of the stream by four bytes.
411 /// If the current machine is in little-endian order, the byte order will be swapped.
412 /// </summary>
413 /// <param name="value">The 4-byte signed integer to write.</param>
414 /// <returns>Whether the write was successful</returns>
415 public bool WriteIntNetworkByteOrder(int value)
416 {
417 int netValue = IsLittleEndian ? ByteSwap(value) : value;
418 return WriteBytesInternal((byte*)&netValue, sizeof(int));
419 }
420
421 /// <summary>
422 /// Writes a 4-byte unsigned integer from the current stream using Big-endian byte order and advances the current position of the stream by four bytes.
423 /// If the stream is in little-endian order, the byte order will be swapped.
424 /// </summary>
425 /// <param name="value">The 4-byte unsigned integer to write.</param>
426 /// <returns>Whether the write was successful</returns>
427 public bool WriteUIntNetworkByteOrder(uint value)
428 {
429 return WriteIntNetworkByteOrder((int)value);
430 }
431
432 /// <summary>
433 /// Writes a 4-byte floating point value to the data stream.
434 /// </summary>
435 /// <param name="value">The 4-byte floating point value to write.</param>
436 /// <returns>Whether the write was successful</returns>
437 public bool WriteFloat(float value)
438 {
439 UIntFloat uf = new UIntFloat();
440 uf.floatValue = value;
441 return WriteInt((int)uf.intValue);
442 }
443
444 /// <summary>
445 /// Writes a 8-byte floating point value to the data stream.
446 /// </summary>
447 /// <param name="value">The 8-byte floating point value to write.</param>
448 /// <returns>Whether the write was successful</returns>
449 public bool WriteDouble(double value)
450 {
451 UIntFloat uf = new UIntFloat();
452 uf.doubleValue = value;
453 return WriteLong((long)uf.longValue);
454 }
455
456 void FlushBits()
457 {
458 while (m_Data.bitIndex >= 8)
459 {
460 m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer;
461 m_Data.bitIndex -= 8;
462 m_Data.bitBuffer >>= 8;
463 }
464 }
465
466 void WriteRawBitsInternal(uint value, int numbits)
467 {
468 CheckBits(value, numbits);
469
470 m_Data.bitBuffer |= ((ulong)value << m_Data.bitIndex);
471 m_Data.bitIndex += numbits;
472 }
473
474 /// <summary>
475 /// Appends a specified number of bits to the data stream.
476 /// </summary>
477 /// <param name="value">The bits to write.</param>
478 /// <param name="numbits">A positive number of bytes to write.</param>
479 /// <returns>Whether the write was successful</returns>
480 public bool WriteRawBits(uint value, int numbits)
481 {
482 CheckWrite();
483
484 if (m_Data.length + ((m_Data.bitIndex + numbits + 7) >> 3) > m_Data.capacity)
485 {
486 ++m_Data.failedWrites;
487 return false;
488 }
489 WriteRawBitsInternal(value, numbits);
490 FlushBits();
491 return true;
492 }
493
494 /// <summary>
495 /// Writes a 4-byte unsigned integer value to the data stream using a <see cref="StreamCompressionModel"/>.
496 /// </summary>
497 /// <param name="value">The 4-byte unsigned integer to write.</param>
498 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
499 /// <returns>Whether the write was successful</returns>
500 public bool WritePackedUInt(uint value, in StreamCompressionModel model)
501 {
502 CheckWrite();
503 int bucket = model.CalculateBucket(value);
504 uint offset = model.bucketOffsets[bucket];
505 int bits = model.bucketSizes[bucket];
506 ushort encodeEntry = model.encodeTable[bucket];
507
508 if (m_Data.length + ((m_Data.bitIndex + (encodeEntry & 0xff) + bits + 7) >> 3) > m_Data.capacity)
509 {
510 ++m_Data.failedWrites;
511 return false;
512 }
513 WriteRawBitsInternal((uint)(encodeEntry >> 8), encodeEntry & 0xFF);
514 WriteRawBitsInternal(value - offset, bits);
515 FlushBits();
516 return true;
517 }
518
519 /// <summary>
520 /// Writes an 8-byte unsigned long value to the data stream using a <see cref="StreamCompressionModel"/>.
521 /// </summary>
522 /// <param name="value">The 8-byte unsigned long to write.</param>
523 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
524 /// <returns>Whether the write was successful</returns>
525 public bool WritePackedULong(ulong value, in StreamCompressionModel model)
526 {
527 var data = (uint*)&value;
528 return WritePackedUInt(data[0], model) &
529 WritePackedUInt(data[1], model);
530 }
531
532 /// <summary>
533 /// Writes a 4-byte signed integer value to the data stream using a <see cref="StreamCompressionModel"/>.
534 /// Negative values are interleaved between positive values, i.e. (0, -1, 1, -2, 2)
535 /// </summary>
536 /// <param name="value">The 4-byte signed integer to write.</param>
537 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
538 /// <returns>Whether the write was successful</returns>
539 public bool WritePackedInt(int value, in StreamCompressionModel model)
540 {
541 uint interleaved = (uint)((value >> 31) ^ (value << 1)); // interleave negative values between positive values: 0, -1, 1, -2, 2
542 return WritePackedUInt(interleaved, model);
543 }
544
545 /// <summary>
546 /// Writes a 8-byte signed long value to the data stream using a <see cref="StreamCompressionModel"/>.
547 /// </summary>
548 /// <param name="value">The 8-byte signed long to write.</param>
549 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
550 /// <returns>Whether the write was successful</returns>
551 public bool WritePackedLong(long value, in StreamCompressionModel model)
552 {
553 ulong interleaved = (ulong)((value >> 63) ^ (value << 1)); // interleave negative values between positive values: 0, -1, 1, -2, 2
554 return WritePackedULong(interleaved, model);
555 }
556
557 /// <summary>
558 /// Writes a 4-byte floating point value to the data stream using a <see cref="StreamCompressionModel"/>.
559 /// </summary>
560 /// <param name="value">The 4-byte floating point value to write.</param>
561 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
562 /// <returns>Whether the write was successful</returns>
563 public bool WritePackedFloat(float value, in StreamCompressionModel model)
564 {
565 return WritePackedFloatDelta(value, 0, model);
566 }
567
568 /// <summary>
569 /// Writes a 8-byte floating point value to the data stream using a <see cref="StreamCompressionModel"/>.
570 /// </summary>
571 /// <param name="value">The 8-byte floating point value to write.</param>
572 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
573 /// <returns>Whether the write was successful</returns>
574 public bool WritePackedDouble(double value, in StreamCompressionModel model)
575 {
576 return WritePackedDoubleDelta(value, 0, model);
577 }
578
579 /// <summary>
580 /// Writes a delta 4-byte unsigned integer value to the data stream using a <see cref="StreamCompressionModel"/>.
581 /// Note that the Uint values are cast to an Int after computing the diff.
582 /// </summary>
583 /// <param name="value">The current 4-byte unsigned integer value.</param>
584 /// <param name="baseline">The previous 4-byte unsigned integer value, used to compute the diff.</param>
585 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
586 /// <returns>Whether the write was successful</returns>
587 public bool WritePackedUIntDelta(uint value, uint baseline, in StreamCompressionModel model)
588 {
589 int diff = (int)(baseline - value);
590 return WritePackedInt(diff, model);
591 }
592
593 /// <summary>
594 /// Writes a delta 4-byte signed integer value to the data stream using a <see cref="StreamCompressionModel"/>.
595 /// </summary>
596 /// <param name="value">The current 4-byte signed integer value.</param>
597 /// <param name="baseline">The previous 4-byte signed integer value, used to compute the diff.</param>
598 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
599 /// <returns>Whether the write was successful</returns>
600 public bool WritePackedIntDelta(int value, int baseline, in StreamCompressionModel model)
601 {
602 int diff = (int)(baseline - value);
603 return WritePackedInt(diff, model);
604 }
605
606 /// <summary>
607 /// Writes a delta 8-byte signed long value to the data stream using a <see cref="StreamCompressionModel"/>.
608 /// </summary>
609 /// <param name="value">The current 8-byte signed long value.</param>
610 /// <param name="baseline">The previous 8-byte signed long value, used to compute the diff.</param>
611 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
612 /// <returns>Whether the write was successful</returns>
613 public bool WritePackedLongDelta(long value, long baseline, in StreamCompressionModel model)
614 {
615 long diff = (long)(baseline - value);
616 return WritePackedLong(diff, model);
617 }
618
619 /// <summary>
620 /// Writes a delta 8-byte unsigned long value to the data stream using a <see cref="StreamCompressionModel"/>.
621 /// Note that the unsigned long values are cast to a signed long after computing the diff.
622 /// </summary>
623 /// <param name="value">The current 8-byte unsigned long value.</param>
624 /// <param name="baseline">The previous 8-byte unsigned long, used to compute the diff.</param>
625 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
626 /// <returns>Whether the write was successful</returns>
627 public bool WritePackedULongDelta(ulong value, ulong baseline, in StreamCompressionModel model)
628 {
629 long diff = (long)(baseline - value);
630 return WritePackedLong(diff, model);
631 }
632
633 /// <summary>
634 /// Writes a 4-byte floating point value to the data stream.
635 ///
636 /// If the data did not change a zero bit is prepended, otherwise a 1 bit is prepended.
637 /// When reading back the data, the first bit is then checked for whether the data was changed or not.
638 /// </summary>
639 /// <param name="value">The current 4-byte floating point value.</param>
640 /// <param name="baseline">The previous 4-byte floating value, used to compute the diff.</param>
641 /// <param name="model">Not currently used.</param>
642 /// <returns>Whether the write was successful</returns>
643 public bool WritePackedFloatDelta(float value, float baseline, in StreamCompressionModel model)
644 {
645 CheckWrite();
646 var bits = 0;
647 if (value != baseline)
648 bits = 32;
649 if (m_Data.length + ((m_Data.bitIndex + 1 + bits + 7) >> 3) > m_Data.capacity)
650 {
651 ++m_Data.failedWrites;
652 return false;
653 }
654 if (bits == 0)
655 WriteRawBitsInternal(0, 1);
656 else
657 {
658 WriteRawBitsInternal(1, 1);
659 UIntFloat uf = new UIntFloat();
660 uf.floatValue = value;
661 WriteRawBitsInternal(uf.intValue, bits);
662 }
663 FlushBits();
664 return true;
665 }
666
667 /// <summary>
668 /// Writes a 8-byte floating point value to the data stream.
669 ///
670 /// If the data did not change a zero bit is prepended, otherwise a 1 bit is prepended.
671 /// When reading back the data, the first bit is then checked for whether the data was changed or not.
672 /// </summary>
673 /// <param name="value">The current 8-byte floating point value.</param>
674 /// <param name="baseline">The previous 8-byte floating value, used to compute the diff.</param>
675 /// <param name="model">Not currently used.</param>
676 /// <returns>Whether the write was successful</returns>
677 public bool WritePackedDoubleDelta(double value, double baseline, in StreamCompressionModel model)
678 {
679 CheckWrite();
680 var bits = 0;
681 if (value != baseline)
682 bits = 64;
683 if (m_Data.length + ((m_Data.bitIndex + 1 + bits + 7) >> 3) > m_Data.capacity)
684 {
685 ++m_Data.failedWrites;
686 return false;
687 }
688 if (bits == 0)
689 WriteRawBitsInternal(0, 1);
690 else
691 {
692 WriteRawBitsInternal(1, 1);
693 UIntFloat uf = new UIntFloat();
694 uf.doubleValue = value;
695 var data = (uint*)&uf.longValue;
696 WriteRawBitsInternal(data[0], 32);
697 FlushBits();
698 WriteRawBitsInternal(data[1], 32);
699 }
700 FlushBits();
701 return true;
702 }
703
704
705 /// <summary>
706 /// Writes a <c>FixedString32Bytes</c> value to the data stream.
707 /// </summary>
708 /// <param name="str">The <c>FixedString32Bytes</c> to write.</param>
709 /// <returns>Whether the write was successful</returns>
710 public unsafe bool WriteFixedString32(FixedString32Bytes str)
711 {
712 int length = (int)*((ushort*)&str) + 2;
713 byte* data = ((byte*)&str);
714 return WriteBytesInternal(data, length);
715 }
716
717 /// <summary>
718 /// Writes a <c>FixedString64Bytes</c> value to the data stream.
719 /// </summary>
720 /// <param name="str">The <c>FixedString64Bytes</c> to write.</param>
721 /// <returns>Whether the write was successful</returns>
722 public unsafe bool WriteFixedString64(FixedString64Bytes str)
723 {
724 int length = (int)*((ushort*)&str) + 2;
725 byte* data = ((byte*)&str);
726 return WriteBytesInternal(data, length);
727 }
728
729 /// <summary>
730 /// Writes a <c>FixedString128Bytes</c> value to the data stream.
731 /// </summary>
732 /// <param name="str">The <c>FixedString128Bytes</c> to write.</param>
733 /// <returns>Whether the write was successful</returns>
734 public unsafe bool WriteFixedString128(FixedString128Bytes str)
735 {
736 int length = (int)*((ushort*)&str) + 2;
737 byte* data = ((byte*)&str);
738 return WriteBytesInternal(data, length);
739 }
740
741 /// <summary>
742 /// Writes a <c>FixedString512Bytes</c> value to the data stream.
743 /// </summary>
744 /// <param name="str">The <c>FixedString512Bytes</c> to write.</param>
745 /// <returns>Whether the write was successful</returns>
746 public unsafe bool WriteFixedString512(FixedString512Bytes str)
747 {
748 int length = (int)*((ushort*)&str) + 2;
749 byte* data = ((byte*)&str);
750 return WriteBytesInternal(data, length);
751 }
752
753 /// <summary>
754 /// Writes a <c>FixedString4096Bytes</c> value to the data stream.
755 /// </summary>
756 /// <param name="str">The <c>FixedString4096Bytes</c> to write.</param>
757 /// <returns>Whether the write was successful</returns>
758 public unsafe bool WriteFixedString4096(FixedString4096Bytes str)
759 {
760 int length = (int)*((ushort*)&str) + 2;
761 byte* data = ((byte*)&str);
762 return WriteBytesInternal(data, length);
763 }
764
765 /// <summary>
766 /// Writes a <c>FixedString32Bytes</c> delta value to the data stream using a <see cref="StreamCompressionModel"/>.
767 /// </summary>
768 /// <param name="str">The current <c>FixedString32Bytes</c> value.</param>
769 /// <param name="baseline">The previous <c>FixedString32Bytes</c> value, used to compute the diff.</param>
770 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
771 /// <returns>Whether the write was successful</returns>
772 public unsafe bool WritePackedFixedString32Delta(FixedString32Bytes str, FixedString32Bytes baseline, in StreamCompressionModel model)
773 {
774 ushort length = *((ushort*)&str);
775 byte* data = ((byte*)&str) + 2;
776 return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
777 }
778
779 /// <summary>
780 /// Writes a delta <c>FixedString64Bytes</c> value to the data stream using a <see cref="StreamCompressionModel"/>.
781 /// </summary>
782 /// <param name="str">The current <c>FixedString64Bytes</c> value.</param>
783 /// <param name="baseline">The previous <c>FixedString64Bytes</c> value, used to compute the diff.</param>
784 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
785 /// <returns>Whether the write was successful</returns>
786 public unsafe bool WritePackedFixedString64Delta(FixedString64Bytes str, FixedString64Bytes baseline, in StreamCompressionModel model)
787 {
788 ushort length = *((ushort*)&str);
789 byte* data = ((byte*)&str) + 2;
790 return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
791 }
792
793 /// <summary>
794 /// Writes a delta <c>FixedString128Bytes</c> value to the data stream using a <see cref="StreamCompressionModel"/>.
795 /// </summary>
796 /// <param name="str">The current <c>FixedString128Bytes</c> value.</param>
797 /// <param name="baseline">The previous <c>FixedString128Bytes</c> value, used to compute the diff.</param>
798 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
799 /// <returns>Whether the write was successful</returns>
800 public unsafe bool WritePackedFixedString128Delta(FixedString128Bytes str, FixedString128Bytes baseline, in StreamCompressionModel model)
801 {
802 ushort length = *((ushort*)&str);
803 byte* data = ((byte*)&str) + 2;
804 return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
805 }
806
807 /// <summary>
808 /// Writes a delta <c>FixedString512Bytes</c> value to the data stream using a <see cref="StreamCompressionModel"/>.
809 /// </summary>
810 /// <param name="str">The current <c>FixedString512Bytes</c> value.</param>
811 /// <param name="baseline">The previous <c>FixedString512Bytes</c> value, used to compute the diff.</param>
812 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
813 /// <returns>Whether the write was successful</returns>
814 public unsafe bool WritePackedFixedString512Delta(FixedString512Bytes str, FixedString512Bytes baseline, in StreamCompressionModel model)
815 {
816 ushort length = *((ushort*)&str);
817 byte* data = ((byte*)&str) + 2;
818 return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
819 }
820
821 /// <summary>
822 /// Writes a delta <c>FixedString4096Bytes</c> value to the data stream using a <see cref="StreamCompressionModel"/>.
823 /// </summary>
824 /// <param name="str">The current <c>FixedString4096Bytes</c> value.</param>
825 /// <param name="baseline">The previous <c>FixedString4096Bytes</c> value, used to compute the diff.</param>
826 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
827 /// <returns>Whether the write was successful</returns>
828 public unsafe bool WritePackedFixedString4096Delta(FixedString4096Bytes str, FixedString4096Bytes baseline, in StreamCompressionModel model)
829 {
830 ushort length = *((ushort*)&str);
831 byte* data = ((byte*)&str) + 2;
832 return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
833 }
834
835 /// <summary>
836 /// Writes a delta FixedString value to the data stream using a <see cref="StreamCompressionModel"/>.
837 ///
838 /// If the value cannot be written <see cref="HasFailedWrites"/> will return true. This state can be cleared by
839 /// calling <see cref="Clear"/>.
840 /// </summary>
841 /// <param name="data">Pointer to a packed fixed string.</param>
842 /// <param name="length">The length of the new value.</param>
843 /// <param name="baseData">The previous value, used to compute the diff.</param>
844 /// <param name="baseLength">The length of the previous value.</param>
845 /// <param name="model"><see cref="StreamCompressionModel"/> model for writing value in a packed manner.</param>
846 /// <returns>Whether the write was successful</returns>
847 unsafe bool WritePackedFixedStringDelta(byte* data, uint length, byte* baseData, uint baseLength, in StreamCompressionModel model)
848 {
849 var oldData = m_Data;
850 if (!WritePackedUIntDelta(length, baseLength, model))
851 return false;
852 bool didFailWrite = false;
853 if (length <= baseLength)
854 {
855 for (uint i = 0; i < length; ++i)
856 didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model);
857 }
858 else
859 {
860 for (uint i = 0; i < baseLength; ++i)
861 didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model);
862 for (uint i = baseLength; i < length; ++i)
863 didFailWrite |= !WritePackedUInt(data[i], model);
864 }
865 // If anything was not written, rewind to the previous position
866 if (didFailWrite)
867 {
868 m_Data = oldData;
869 ++m_Data.failedWrites;
870 }
871 return !didFailWrite;
872 }
873
874 /// <summary>
875 /// Moves the write position to the start of the data buffer used.
876 /// </summary>
877 public void Clear()
878 {
879 m_Data.length = 0;
880 m_Data.bitIndex = 0;
881 m_Data.bitBuffer = 0;
882 m_Data.failedWrites = 0;
883 }
884
885 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
886 [MethodImpl(MethodImplOptions.AggressiveInlining)]
887 readonly void CheckRead()
888 {
889#if ENABLE_UNITY_COLLECTIONS_CHECKS
890 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
891#endif
892 }
893
894 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
895 [MethodImpl(MethodImplOptions.AggressiveInlining)]
896 void CheckWrite()
897 {
898#if ENABLE_UNITY_COLLECTIONS_CHECKS
899 AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
900#endif
901 }
902
903 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
904 static void CheckAllocator(AllocatorManager.AllocatorHandle allocator)
905 {
906 if (allocator.ToAllocator != Allocator.Temp)
907 throw new InvalidOperationException("DataStreamWriters can only be created with temp memory");
908 }
909
910 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
911 static void CheckBits(uint value, int numBits)
912 {
913 if (numBits < 0 || numBits > 32)
914 throw new ArgumentOutOfRangeException($"Invalid number of bits specified: {numBits}! Valid range is (0, 32) inclusive.");
915 var errValue = (1UL << numBits);
916 if (value >= errValue)
917 throw new ArgumentOutOfRangeException($"Value {value} does not fit in the specified number of bits: {numBits}! Range (inclusive) is (0, {errValue-1})!");
918 }
919 }
920}