A game about forced loneliness, made by TACStudios
at master 920 lines 41 kB view raw
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&lt;byte&gt;, writing data 23 /// to a NativeArray&lt;byte&gt; and reading it back can be done like this: 24 /// <code> 25 /// using (var data = new NativeArray&lt;byte&gt;(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&lt;byte&gt;(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&lt;byte&gt; 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}