A game about forced loneliness, made by TACStudios
1using System; 2using System.Runtime.CompilerServices; 3using Unity.Burst; 4using Unity.Jobs; 5using Unity.Jobs.LowLevel.Unsafe; 6using UnityEngine.Assertions; 7 8namespace Unity.Collections.LowLevel.Unsafe 9{ 10 [GenerateTestsForBurstCompatibility] 11 internal unsafe struct UnsafeStreamBlock 12 { 13 internal UnsafeStreamBlock* Next; 14 internal fixed byte Data[1]; 15 } 16 17 [GenerateTestsForBurstCompatibility] 18 internal unsafe struct UnsafeStreamRange 19 { 20 internal UnsafeStreamBlock* Block; 21 internal int OffsetInFirstBlock; 22 internal int ElementCount; 23 24 /// One byte past the end of the last byte written 25 internal int LastOffset; 26 internal int NumberOfBlocks; 27 } 28 29 [GenerateTestsForBurstCompatibility] 30 internal unsafe struct UnsafeStreamBlockData 31 { 32 internal const int AllocationSize = 4 * 1024; 33 internal AllocatorManager.AllocatorHandle Allocator; 34 35 internal UnsafeStreamBlock** Blocks; 36 internal int BlockCount; 37 38 internal AllocatorManager.Block Ranges; 39 internal int RangeCount; 40 41 internal UnsafeStreamBlock* Allocate(UnsafeStreamBlock* oldBlock, int threadIndex) 42 { 43 Assert.IsTrue(threadIndex < BlockCount && threadIndex >= 0); 44 45 UnsafeStreamBlock* block = (UnsafeStreamBlock*)Memory.Unmanaged.Array.Resize(null, 0, AllocationSize, Allocator, 1, 16); 46 block->Next = null; 47 48 if (oldBlock == null) 49 { 50 // Append our new block in front of the previous head. 51 block->Next = Blocks[threadIndex]; 52 Blocks[threadIndex] = block; 53 } 54 else 55 { 56 block->Next = oldBlock->Next; 57 oldBlock->Next = block; 58 } 59 60 return block; 61 } 62 63 internal void Free(UnsafeStreamBlock* oldBlock) 64 { 65 Memory.Unmanaged.Array.Resize(oldBlock, AllocationSize, 0, Allocator, 1, 16); 66 } 67 } 68 69 /// <summary> 70 /// A set of untyped, append-only buffers. Allows for concurrent reading and concurrent writing without synchronization. 71 /// </summary> 72 /// <remarks> 73 /// As long as each individual buffer is written in one thread and read in one thread, multiple 74 /// threads can read and write the stream concurrently, *e.g.* 75 /// while thread *A* reads from buffer *X* of a stream, thread *B* can read from 76 /// buffer *Y* of the same stream. 77 /// 78 /// Each buffer is stored as a chain of blocks. When a write exceeds a buffer's current capacity, another block 79 /// is allocated and added to the end of the chain. Effectively, expanding the buffer never requires copying the existing 80 /// data (unlike, for example, with <see cref="NativeList{T}"/>). 81 /// 82 /// **All writing to a stream should be completed before the stream is first read. Do not write to a stream after the first read.** 83 /// 84 /// Writing is done with <see cref="NativeStream.Writer"/>, and reading is done with <see cref="NativeStream.Reader"/>. 85 /// An individual reader or writer cannot be used concurrently across threads. Each thread must use its own. 86 /// 87 /// The data written to an individual buffer can be heterogeneous in type, and the data written 88 /// to different buffers of a stream can be entirely different in type, number, and order. Just make sure 89 /// that the code reading from a particular buffer knows what to expect to read from it. 90 /// </remarks> 91 [GenerateTestsForBurstCompatibility] 92 public unsafe struct UnsafeStream 93 : INativeDisposable 94 { 95 [NativeDisableUnsafePtrRestriction] 96 internal AllocatorManager.Block m_BlockData; 97 98 /// <summary> 99 /// Initializes and returns an instance of UnsafeStream. 100 /// </summary> 101 /// <param name="bufferCount">The number of buffers to give the stream. You usually want 102 /// one buffer for each thread that will read or write the stream.</param> 103 /// <param name="allocator">The allocator to use.</param> 104 public UnsafeStream(int bufferCount, AllocatorManager.AllocatorHandle allocator) 105 { 106 AllocateBlock(out this, allocator); 107 AllocateForEach(bufferCount); 108 } 109 110 /// <summary> 111 /// Creates and schedules a job to allocate a new stream. 112 /// </summary> 113 /// <remarks>The stream can be used on the main thread after completing the returned job or used in other jobs that depend upon the returned job. 114 /// 115 /// Using a job to allocate the buffers can be more efficient, particularly for a stream with many buffers. 116 /// </remarks> 117 /// <typeparam name="T">Ignored.</typeparam> 118 /// <param name="stream">Outputs the new stream.</param> 119 /// <param name="bufferCount">A list whose length determines the number of buffers in the stream.</param> 120 /// <param name="dependency">A job handle. The new job will depend upon this handle.</param> 121 /// <param name="allocator">The allocator to use.</param> 122 /// <returns>The handle of the new job.</returns> 123 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] 124 public static JobHandle ScheduleConstruct<T>(out UnsafeStream stream, NativeList<T> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator) 125 where T : unmanaged 126 { 127 AllocateBlock(out stream, allocator); 128 var jobData = new ConstructJobList { List = (UntypedUnsafeList*)bufferCount.GetUnsafeList(), Container = stream }; 129 return jobData.Schedule(dependency); 130 } 131 132 /// <summary> 133 /// Creates and schedules a job to allocate a new stream. 134 /// </summary> 135 /// <remarks>The stream can be used on the main thread after completing the returned job or used in other jobs that depend upon the returned job. 136 /// 137 /// Allocating the buffers in a job can be more efficient, particularly for a stream with many buffers. 138 /// </remarks> 139 /// <param name="stream">Outputs the new stream.</param> 140 /// <param name="bufferCount">An array whose value at index 0 determines the number of buffers in the stream.</param> 141 /// <param name="dependency">A job handle. The new job will depend upon this handle.</param> 142 /// <param name="allocator">The allocator to use.</param> 143 /// <returns>The handle of the new job.</returns> 144 public static JobHandle ScheduleConstruct(out UnsafeStream stream, NativeArray<int> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator) 145 { 146 AllocateBlock(out stream, allocator); 147 var jobData = new ConstructJob { Length = bufferCount, Container = stream }; 148 return jobData.Schedule(dependency); 149 } 150 151 internal static void AllocateBlock(out UnsafeStream stream, AllocatorManager.AllocatorHandle allocator) 152 { 153#if UNITY_2022_2_14F1_OR_NEWER 154 int maxThreadCount = JobsUtility.ThreadIndexCount; 155#else 156 int maxThreadCount = JobsUtility.MaxJobThreadCount; 157#endif 158 159 int blockCount = maxThreadCount; 160 161 int allocationSize = sizeof(UnsafeStreamBlockData) + sizeof(UnsafeStreamBlock*) * blockCount; 162 163 AllocatorManager.Block blk = AllocatorManager.AllocateBlock(ref allocator, allocationSize, 16, 1); 164 UnsafeUtility.MemClear( (void*)blk.Range.Pointer, blk.AllocatedBytes); 165 166 stream.m_BlockData = blk; 167 168 var blockData = (UnsafeStreamBlockData*)blk.Range.Pointer; 169 blockData->Allocator = allocator; 170 blockData->BlockCount = blockCount; 171 blockData->Blocks = (UnsafeStreamBlock**)(blk.Range.Pointer + sizeof(UnsafeStreamBlockData)); 172 173 blockData->Ranges = default; 174 blockData->RangeCount = 0; 175 } 176 177 internal void AllocateForEach(int forEachCount) 178 { 179 long allocationSize = sizeof(UnsafeStreamRange) * forEachCount; 180 181 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 182 blockData->Ranges = AllocatorManager.AllocateBlock(ref m_BlockData.Range.Allocator, sizeof(UnsafeStreamRange), 16, forEachCount); 183 blockData->RangeCount = forEachCount; 184 UnsafeUtility.MemClear((void*)blockData->Ranges.Range.Pointer, blockData->Ranges.AllocatedBytes); 185 } 186 187 /// <summary> 188 /// Returns true if this stream is empty. 189 /// </summary> 190 /// <returns>True if this stream is empty or the stream has not been constructed.</returns> 191 public readonly bool IsEmpty() 192 { 193 if (!IsCreated) 194 { 195 return true; 196 } 197 198 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 199 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; 200 201 for (int i = 0; i != blockData->RangeCount; i++) 202 { 203 if (ranges[i].ElementCount > 0) 204 { 205 return false; 206 } 207 } 208 209 return true; 210 } 211 212 /// <summary> 213 /// Whether this stream has been allocated (and not yet deallocated). 214 /// </summary> 215 /// <remarks>Does not necessarily reflect whether the buffers of the stream have themselves been allocated.</remarks> 216 /// <value>True if this stream has been allocated (and not yet deallocated).</value> 217 public readonly bool IsCreated 218 { 219 [MethodImpl(MethodImplOptions.AggressiveInlining)] 220 get => m_BlockData.Range.Pointer != IntPtr.Zero; 221 } 222 223 /// <summary> 224 /// The number of buffers in this stream. 225 /// </summary> 226 /// <value>The number of buffers in this stream.</value> 227 public readonly int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount; 228 229 /// <summary> 230 /// Returns a reader of this stream. 231 /// </summary> 232 /// <returns>A reader of this stream.</returns> 233 public Reader AsReader() 234 { 235 return new Reader(ref this); 236 } 237 238 /// <summary> 239 /// Returns a writer of this stream. 240 /// </summary> 241 /// <returns>A writer of this stream.</returns> 242 public Writer AsWriter() 243 { 244 return new Writer(ref this); 245 } 246 247 /// <summary> 248 /// Returns the total number of items in the buffers of this stream. 249 /// </summary> 250 /// <remarks>Each <see cref="Writer.Write{T}"/> and <see cref="Writer.Allocate"/> call increments this number.</remarks> 251 /// <returns>The total number of items in the buffers of this stream.</returns> 252 public int Count() 253 { 254 int itemCount = 0; 255 256 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 257 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; 258 259 for (int i = 0; i != blockData->RangeCount; i++) 260 { 261 itemCount += ranges[i].ElementCount; 262 } 263 264 return itemCount; 265 } 266 267 /// <summary> 268 /// Returns a new NativeArray copy of this stream's data. 269 /// </summary> 270 /// <remarks>The length of the array will equal the count of this stream. 271 /// 272 /// Each buffer of this stream is copied to the array, one after the other. 273 /// </remarks> 274 /// <typeparam name="T">The type of values in the array.</typeparam> 275 /// <param name="allocator">The allocator to use.</param> 276 /// <returns>A new NativeArray copy of this stream's data.</returns> 277 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] 278 public NativeArray<T> ToNativeArray<T>(AllocatorManager.AllocatorHandle allocator) where T : unmanaged 279 { 280 var array = CollectionHelper.CreateNativeArray<T>(Count(), allocator, NativeArrayOptions.UninitializedMemory); 281 var reader = AsReader(); 282 283 int offset = 0; 284 for (int i = 0; i != reader.ForEachCount; i++) 285 { 286 reader.BeginForEachIndex(i); 287 int rangeItemCount = reader.RemainingItemCount; 288 for (int j = 0; j < rangeItemCount; ++j) 289 { 290 array[offset] = reader.Read<T>(); 291 offset++; 292 } 293 reader.EndForEachIndex(); 294 } 295 296 return array; 297 } 298 299 void Deallocate() 300 { 301 if (!IsCreated) 302 { 303 return; 304 } 305 306 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 307 308 for (int i = 0; i != blockData->BlockCount; i++) 309 { 310 UnsafeStreamBlock* block = blockData->Blocks[i]; 311 while (block != null) 312 { 313 UnsafeStreamBlock* next = block->Next; 314 blockData->Free(block); 315 block = next; 316 } 317 } 318 319 blockData->Ranges.Dispose(); 320 321 m_BlockData.Dispose(); 322 m_BlockData = default; 323 } 324 325 /// <summary> 326 /// Releases all resources (memory). 327 /// </summary> 328 public void Dispose() 329 { 330 if (!IsCreated) 331 { 332 return; 333 } 334 335 Deallocate(); 336 } 337 338 /// <summary> 339 /// Creates and schedules a job that will release all resources (memory and safety handles) of this stream. 340 /// </summary> 341 /// <param name="inputDeps">A job handle which the newly scheduled job will depend upon.</param> 342 /// <returns>The handle of a new job that will release all resources (memory and safety handles) of this stream.</returns> 343 public JobHandle Dispose(JobHandle inputDeps) 344 { 345 if (!IsCreated) 346 { 347 return inputDeps; 348 } 349 350 var jobHandle = new DisposeJob { Container = this }.Schedule(inputDeps); 351 352 m_BlockData = default; 353 354 return jobHandle; 355 } 356 357 [BurstCompile] 358 struct DisposeJob : IJob 359 { 360 public UnsafeStream Container; 361 362 public void Execute() 363 { 364 Container.Deallocate(); 365 } 366 } 367 368 [BurstCompile] 369 struct ConstructJobList : IJob 370 { 371 public UnsafeStream Container; 372 373 [ReadOnly] 374 [NativeDisableUnsafePtrRestriction] 375 public UntypedUnsafeList* List; 376 377 public void Execute() 378 { 379 Container.AllocateForEach(List->m_length); 380 } 381 } 382 383 [BurstCompile] 384 struct ConstructJob : IJob 385 { 386 public UnsafeStream Container; 387 388 [ReadOnly] 389 public NativeArray<int> Length; 390 391 public void Execute() 392 { 393 Container.AllocateForEach(Length[0]); 394 } 395 } 396 397 /// <summary> 398 /// Writes data into a buffer of an <see cref="UnsafeStream"/>. 399 /// </summary> 400 /// <remarks>An individual writer can only be used for one buffer of one stream. 401 /// Do not create more than one writer for an individual buffer.</remarks> 402 [GenerateTestsForBurstCompatibility] 403 public unsafe struct Writer 404 { 405 [NativeDisableUnsafePtrRestriction] 406 internal AllocatorManager.Block m_BlockData; 407 408 [NativeDisableUnsafePtrRestriction] 409 UnsafeStreamBlock* m_CurrentBlock; 410 411 [NativeDisableUnsafePtrRestriction] 412 byte* m_CurrentPtr; 413 414 [NativeDisableUnsafePtrRestriction] 415 byte* m_CurrentBlockEnd; 416 417 internal int m_ForeachIndex; 418 int m_ElementCount; 419 420 [NativeDisableUnsafePtrRestriction] 421 UnsafeStreamBlock* m_FirstBlock; 422 423 int m_FirstOffset; 424 int m_NumberOfBlocks; 425 426 [NativeSetThreadIndex] 427 int m_ThreadIndex; 428 429 internal Writer(ref UnsafeStream stream) 430 { 431 m_BlockData = stream.m_BlockData; 432 m_ForeachIndex = int.MinValue; 433 m_ElementCount = -1; 434 m_CurrentBlock = null; 435 m_CurrentBlockEnd = null; 436 m_CurrentPtr = null; 437 m_FirstBlock = null; 438 m_NumberOfBlocks = 0; 439 m_FirstOffset = 0; 440 m_ThreadIndex = 0; 441 } 442 443 /// <summary> 444 /// The number of buffers in the stream of this writer. 445 /// </summary> 446 /// <value>The number of buffers in the stream of this writer.</value> 447 public int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount; 448 449 /// <summary> 450 /// Readies this writer to write to a particular buffer of the stream. 451 /// </summary> 452 /// <remarks>Must be called before using this writer. For an individual writer, call this method only once. 453 /// 454 /// When done using this writer, you must call <see cref="EndForEachIndex"/>.</remarks> 455 /// <param name="foreachIndex">The index of the buffer to write.</param> 456 public void BeginForEachIndex(int foreachIndex) 457 { 458 m_ForeachIndex = foreachIndex; 459 m_ElementCount = 0; 460 m_NumberOfBlocks = 0; 461 m_FirstBlock = m_CurrentBlock; 462 m_FirstOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock); 463 } 464 465 /// <summary> 466 /// Readies the buffer written by this writer for reading. 467 /// </summary> 468 /// <remarks>Must be called before reading the buffer written by this writer.</remarks> 469 public void EndForEachIndex() 470 { 471 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 472 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; 473 474 ranges[m_ForeachIndex].ElementCount = m_ElementCount; 475 ranges[m_ForeachIndex].OffsetInFirstBlock = m_FirstOffset; 476 ranges[m_ForeachIndex].Block = m_FirstBlock; 477 478 ranges[m_ForeachIndex].LastOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock); 479 ranges[m_ForeachIndex].NumberOfBlocks = m_NumberOfBlocks; 480 } 481 482 /// <summary> 483 /// Write a value to a buffer. 484 /// </summary> 485 /// <remarks>The value is written to the buffer which was specified 486 /// with <see cref="BeginForEachIndex"/>.</remarks> 487 /// <typeparam name="T">The type of value to write.</typeparam> 488 /// <param name="value">The value to write.</param> 489 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] 490 public void Write<T>(T value) where T : unmanaged 491 { 492 ref T dst = ref Allocate<T>(); 493 dst = value; 494 } 495 496 /// <summary> 497 /// Allocate space in a buffer. 498 /// </summary> 499 /// <remarks>The space is allocated in the buffer which was specified 500 /// with <see cref="BeginForEachIndex"/>.</remarks> 501 /// <typeparam name="T">The type of value to allocate space for.</typeparam> 502 /// <returns>A reference to the allocation.</returns> 503 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] 504 public ref T Allocate<T>() where T : unmanaged 505 { 506 int size = UnsafeUtility.SizeOf<T>(); 507 return ref UnsafeUtility.AsRef<T>(Allocate(size)); 508 } 509 510 /// <summary> 511 /// Allocate space in a buffer. 512 /// </summary> 513 /// <remarks>The space is allocated in the buffer which was specified 514 /// with <see cref="BeginForEachIndex"/>.</remarks> 515 /// <param name="size">The number of bytes to allocate.</param> 516 /// <returns>The allocation.</returns> 517 public byte* Allocate(int size) 518 { 519 byte* ptr = m_CurrentPtr; 520 m_CurrentPtr += size; 521 522 if (m_CurrentPtr > m_CurrentBlockEnd) 523 { 524 UnsafeStreamBlock* oldBlock = m_CurrentBlock; 525 526 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 527 528 m_CurrentBlock = blockData->Allocate(oldBlock, m_ThreadIndex); 529 m_CurrentPtr = m_CurrentBlock->Data; 530 531 if (m_FirstBlock == null) 532 { 533 m_FirstOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock); 534 m_FirstBlock = m_CurrentBlock; 535 } 536 else 537 { 538 m_NumberOfBlocks++; 539 } 540 541 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize; 542 ptr = m_CurrentPtr; 543 m_CurrentPtr += size; 544 } 545 546 m_ElementCount++; 547 548 return ptr; 549 } 550 } 551 552 /// <summary> 553 /// Reads data from a buffer of an <see cref="UnsafeStream"/>. 554 /// </summary> 555 /// <remarks>An individual reader can only be used for one buffer of one stream. 556 /// Do not create more than one reader for an individual buffer.</remarks> 557 [GenerateTestsForBurstCompatibility] 558 public unsafe struct Reader 559 { 560 [NativeDisableUnsafePtrRestriction] 561 internal AllocatorManager.Block m_BlockData; 562 563 [NativeDisableUnsafePtrRestriction] 564 internal UnsafeStreamBlock* m_CurrentBlock; 565 566 [NativeDisableUnsafePtrRestriction] 567 internal byte* m_CurrentPtr; 568 569 [NativeDisableUnsafePtrRestriction] 570 internal byte* m_CurrentBlockEnd; 571 572 internal int m_RemainingItemCount; 573 internal int m_LastBlockSize; 574 575 internal Reader(ref UnsafeStream stream) 576 { 577 m_BlockData = stream.m_BlockData; 578 m_CurrentBlock = null; 579 m_CurrentPtr = null; 580 m_CurrentBlockEnd = null; 581 m_RemainingItemCount = 0; 582 m_LastBlockSize = 0; 583 } 584 585 /// <summary> 586 /// Readies this reader to read a particular buffer of the stream. 587 /// </summary> 588 /// <remarks>Must be called before using this reader. For an individual reader, call this method only once. 589 /// 590 /// When done using this reader, you must call <see cref="EndForEachIndex"/>.</remarks> 591 /// <param name="foreachIndex">The index of the buffer to read.</param> 592 /// <returns>The number of remaining elements to read from the buffer.</returns> 593 public int BeginForEachIndex(int foreachIndex) 594 { 595 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 596 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; 597 598 m_RemainingItemCount = ranges[foreachIndex].ElementCount; 599 m_LastBlockSize = ranges[foreachIndex].LastOffset; 600 601 m_CurrentBlock = ranges[foreachIndex].Block; 602 m_CurrentPtr = (byte*)m_CurrentBlock + ranges[foreachIndex].OffsetInFirstBlock; 603 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize; 604 605 return m_RemainingItemCount; 606 } 607 608 /// <summary> 609 /// Does nothing. 610 /// </summary> 611 /// <remarks>Included only for consistency with <see cref="NativeStream"/>.</remarks> 612 public void EndForEachIndex() 613 { 614 } 615 616 /// <summary> 617 /// The number of buffers in the stream of this reader. 618 /// </summary> 619 /// <value>The number of buffers in the stream of this reader.</value> 620 public int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount; 621 622 /// <summary> 623 /// The number of items not yet read from the buffer. 624 /// </summary> 625 /// <value>The number of items not yet read from the buffer.</value> 626 public int RemainingItemCount => m_RemainingItemCount; 627 628 /// <summary> 629 /// Returns a pointer to the next position to read from the buffer. Advances the reader some number of bytes. 630 /// </summary> 631 /// <param name="size">The number of bytes to advance the reader.</param> 632 /// <returns>A pointer to the next position to read from the buffer.</returns> 633 /// <exception cref="System.ArgumentException">Thrown if the reader has been advanced past the end of the buffer.</exception> 634 public byte* ReadUnsafePtr(int size) 635 { 636 m_RemainingItemCount--; 637 638 byte* ptr = m_CurrentPtr; 639 m_CurrentPtr += size; 640 641 if (m_CurrentPtr > m_CurrentBlockEnd) 642 { 643 m_CurrentBlock = m_CurrentBlock->Next; 644 m_CurrentPtr = m_CurrentBlock->Data; 645 646 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize; 647 648 ptr = m_CurrentPtr; 649 m_CurrentPtr += size; 650 } 651 652 return ptr; 653 } 654 655 /// <summary> 656 /// Reads the next value from the buffer. 657 /// </summary> 658 /// <remarks>Each read advances the reader to the next item in the buffer.</remarks> 659 /// <typeparam name="T">The type of value to read.</typeparam> 660 /// <returns>A reference to the next value from the buffer.</returns> 661 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] 662 public ref T Read<T>() where T : unmanaged 663 { 664 int size = UnsafeUtility.SizeOf<T>(); 665 return ref UnsafeUtility.AsRef<T>(ReadUnsafePtr(size)); 666 } 667 668 /// <summary> 669 /// Reads the next value from the buffer. Does not advance the reader. 670 /// </summary> 671 /// <typeparam name="T">The type of value to read.</typeparam> 672 /// <returns>A reference to the next value from the buffer.</returns> 673 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] 674 public ref T Peek<T>() where T : unmanaged 675 { 676 int size = UnsafeUtility.SizeOf<T>(); 677 678 byte* ptr = m_CurrentPtr; 679 if (ptr + size > m_CurrentBlockEnd) 680 { 681 ptr = m_CurrentBlock->Next->Data; 682 } 683 684 return ref UnsafeUtility.AsRef<T>(ptr); 685 } 686 687 /// <summary> 688 /// Returns the total number of items in the buffers of the stream. 689 /// </summary> 690 /// <returns>The total number of items in the buffers of the stream.</returns> 691 public int Count() 692 { 693 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; 694 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; 695 696 int itemCount = 0; 697 for (int i = 0; i != blockData->RangeCount; i++) 698 { 699 itemCount += ranges[i].ElementCount; 700 } 701 702 return itemCount; 703 } 704 } 705 } 706}