A game about forced loneliness, made by TACStudios
1using System; 2using System.Runtime.InteropServices; 3using System.Threading; 4using Unity.Collections.LowLevel.Unsafe; 5using Unity.Burst; 6using Unity.Jobs; 7using Unity.Jobs.LowLevel.Unsafe; 8using System.Diagnostics; 9using System.Runtime.CompilerServices; 10using System.Collections; 11using System.Collections.Generic; 12 13namespace Unity.Collections 14{ 15 [StructLayout(LayoutKind.Sequential)] 16 unsafe struct UnsafeQueueBlockHeader 17 { 18 public UnsafeQueueBlockHeader* m_NextBlock; 19 public int m_NumItems; 20 } 21 22 [StructLayout(LayoutKind.Sequential)] 23 [GenerateTestsForBurstCompatibility] 24 internal unsafe struct UnsafeQueueBlockPoolData 25 { 26 internal IntPtr m_FirstBlock; 27 internal int m_NumBlocks; 28 internal int m_MaxBlocks; 29 internal const int m_BlockSize = 16 * 1024; 30 internal int m_AllocLock; 31 32 public UnsafeQueueBlockHeader* AllocateBlock() 33 { 34 // There can only ever be a single thread allocating an entry from the free list since it needs to 35 // access the content of the block (the next pointer) before doing the CAS. 36 // If there was no lock thread A could read the next pointer, thread B could quickly allocate 37 // the same block then free it with another next pointer before thread A performs the CAS which 38 // leads to an invalid free list potentially causing memory corruption. 39 // Having multiple threads freeing data concurrently to each other while another thread is allocating 40 // is no problems since there is only ever a single thread modifying global data in that case. 41 while (Interlocked.CompareExchange(ref m_AllocLock, 1, 0) != 0) 42 { 43 } 44 45 UnsafeQueueBlockHeader* checkBlock = (UnsafeQueueBlockHeader*)m_FirstBlock; 46 UnsafeQueueBlockHeader* block; 47 48 do 49 { 50 block = checkBlock; 51 if (block == null) 52 { 53 Interlocked.Exchange(ref m_AllocLock, 0); 54 Interlocked.Increment(ref m_NumBlocks); 55 block = (UnsafeQueueBlockHeader*)Memory.Unmanaged.Allocate(m_BlockSize, 16, Allocator.Persistent); 56 return block; 57 } 58 59 checkBlock = (UnsafeQueueBlockHeader*)Interlocked.CompareExchange(ref m_FirstBlock, (IntPtr)block->m_NextBlock, (IntPtr)block); 60 } 61 while (checkBlock != block); 62 63 Interlocked.Exchange(ref m_AllocLock, 0); 64 65 return block; 66 } 67 68 public void FreeBlock(UnsafeQueueBlockHeader* block) 69 { 70 if (m_NumBlocks > m_MaxBlocks) 71 { 72 if (Interlocked.Decrement(ref m_NumBlocks) + 1 > m_MaxBlocks) 73 { 74 Memory.Unmanaged.Free(block, Allocator.Persistent); 75 return; 76 } 77 78 Interlocked.Increment(ref m_NumBlocks); 79 } 80 81 UnsafeQueueBlockHeader* checkBlock = (UnsafeQueueBlockHeader*)m_FirstBlock; 82 UnsafeQueueBlockHeader* nextPtr; 83 84 do 85 { 86 nextPtr = checkBlock; 87 block->m_NextBlock = checkBlock; 88 checkBlock = (UnsafeQueueBlockHeader*)Interlocked.CompareExchange(ref m_FirstBlock, (IntPtr)block, (IntPtr)checkBlock); 89 } 90 while (checkBlock != nextPtr); 91 } 92 } 93 94 internal unsafe class UnsafeQueueBlockPool 95 { 96 static readonly SharedStatic<IntPtr> Data = SharedStatic<IntPtr>.GetOrCreate<UnsafeQueueBlockPool>(); 97 98 internal static UnsafeQueueBlockPoolData* GetQueueBlockPool() 99 { 100 var pData = (UnsafeQueueBlockPoolData**)Data.UnsafeDataPointer; 101 var data = *pData; 102 103 if (data == null) 104 { 105 data = (UnsafeQueueBlockPoolData*)Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<UnsafeQueueBlockPoolData>(), 8, Allocator.Persistent); 106 *pData = data; 107 data->m_NumBlocks = data->m_MaxBlocks = 256; 108 data->m_AllocLock = 0; 109 // Allocate MaxBlocks items 110 UnsafeQueueBlockHeader* prev = null; 111 112 for (int i = 0; i < data->m_MaxBlocks; ++i) 113 { 114 UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)Memory.Unmanaged.Allocate(UnsafeQueueBlockPoolData.m_BlockSize, 16, Allocator.Persistent); 115 block->m_NextBlock = prev; 116 prev = block; 117 } 118 data->m_FirstBlock = (IntPtr)prev; 119 120 AppDomainOnDomainUnload(); 121 } 122 return data; 123 } 124 125 [BurstDiscard] 126 static void AppDomainOnDomainUnload() 127 { 128 AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; 129 } 130 131 static void OnDomainUnload(object sender, EventArgs e) 132 { 133 var pData = (UnsafeQueueBlockPoolData**)Data.UnsafeDataPointer; 134 var data = *pData; 135 136 while (data->m_FirstBlock != IntPtr.Zero) 137 { 138 UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)data->m_FirstBlock; 139 data->m_FirstBlock = (IntPtr)block->m_NextBlock; 140 Memory.Unmanaged.Free(block, Allocator.Persistent); 141 --data->m_NumBlocks; 142 } 143 Memory.Unmanaged.Free(data, Allocator.Persistent); 144 *pData = null; 145 } 146 } 147 148 [StructLayout(LayoutKind.Sequential)] 149 [GenerateTestsForBurstCompatibility] 150 internal unsafe struct UnsafeQueueData 151 { 152 public IntPtr m_FirstBlock; 153 public IntPtr m_LastBlock; 154 public int m_MaxItems; 155 public int m_CurrentRead; 156 public byte* m_CurrentWriteBlockTLS; 157 158 [MethodImpl(MethodImplOptions.AggressiveInlining)] 159 internal UnsafeQueueBlockHeader* GetCurrentWriteBlockTLS(int threadIndex) 160 { 161 var data = (UnsafeQueueBlockHeader**)&m_CurrentWriteBlockTLS[threadIndex * JobsUtility.CacheLineSize]; 162 return *data; 163 } 164 165 [MethodImpl(MethodImplOptions.AggressiveInlining)] 166 internal void SetCurrentWriteBlockTLS(int threadIndex, UnsafeQueueBlockHeader* currentWriteBlock) 167 { 168 var data = (UnsafeQueueBlockHeader**)&m_CurrentWriteBlockTLS[threadIndex * JobsUtility.CacheLineSize]; 169 *data = currentWriteBlock; 170 } 171 172 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] 173 public static UnsafeQueueBlockHeader* AllocateWriteBlockMT<T>(UnsafeQueueData* data, UnsafeQueueBlockPoolData* pool, int threadIndex) where T : unmanaged 174 { 175 UnsafeQueueBlockHeader* currentWriteBlock = data->GetCurrentWriteBlockTLS(threadIndex); 176 177 if (currentWriteBlock != null) 178 { 179 if (currentWriteBlock->m_NumItems != data->m_MaxItems) 180 { 181 return currentWriteBlock; 182 } 183 currentWriteBlock = null; 184 } 185 186 currentWriteBlock = pool->AllocateBlock(); 187 currentWriteBlock->m_NextBlock = null; 188 currentWriteBlock->m_NumItems = 0; 189 UnsafeQueueBlockHeader* prevLast = (UnsafeQueueBlockHeader*)Interlocked.Exchange(ref data->m_LastBlock, (IntPtr)currentWriteBlock); 190 191 if (prevLast == null) 192 { 193 data->m_FirstBlock = (IntPtr)currentWriteBlock; 194 } 195 else 196 { 197 prevLast->m_NextBlock = currentWriteBlock; 198 } 199 200 data->SetCurrentWriteBlockTLS(threadIndex, currentWriteBlock); 201 return currentWriteBlock; 202 } 203 204 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] 205 public unsafe static void AllocateQueue<T>(AllocatorManager.AllocatorHandle label, out UnsafeQueueData* outBuf) where T : unmanaged 206 { 207#if UNITY_2022_2_14F1_OR_NEWER 208 int maxThreadCount = JobsUtility.ThreadIndexCount; 209#else 210 int maxThreadCount = JobsUtility.MaxJobThreadCount; 211#endif 212 213 var queueDataSize = CollectionHelper.Align(UnsafeUtility.SizeOf<UnsafeQueueData>(), JobsUtility.CacheLineSize); 214 215 var data = (UnsafeQueueData*)Memory.Unmanaged.Allocate( 216 queueDataSize 217 + JobsUtility.CacheLineSize * maxThreadCount 218 , JobsUtility.CacheLineSize 219 , label 220 ); 221 222 data->m_CurrentWriteBlockTLS = (((byte*)data) + queueDataSize); 223 224 data->m_FirstBlock = IntPtr.Zero; 225 data->m_LastBlock = IntPtr.Zero; 226 data->m_MaxItems = (UnsafeQueueBlockPoolData.m_BlockSize - UnsafeUtility.SizeOf<UnsafeQueueBlockHeader>()) / UnsafeUtility.SizeOf<T>(); 227 228 data->m_CurrentRead = 0; 229 for (int threadIndex = 0; threadIndex < maxThreadCount; ++threadIndex) 230 { 231 data->SetCurrentWriteBlockTLS(threadIndex, null); 232 } 233 234 outBuf = data; 235 } 236 237 public unsafe static void DeallocateQueue(UnsafeQueueData* data, UnsafeQueueBlockPoolData* pool, AllocatorManager.AllocatorHandle allocation) 238 { 239 UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)data->m_FirstBlock; 240 241 while (firstBlock != null) 242 { 243 UnsafeQueueBlockHeader* next = firstBlock->m_NextBlock; 244 pool->FreeBlock(firstBlock); 245 firstBlock = next; 246 } 247 248 Memory.Unmanaged.Free(data, allocation); 249 } 250 } 251 252 /// <summary> 253 /// An unmanaged queue. 254 /// </summary> 255 /// <typeparam name="T">The type of the elements.</typeparam> 256 [StructLayout(LayoutKind.Sequential)] 257 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] 258 public unsafe struct UnsafeQueue<T> 259 : INativeDisposable 260 where T : unmanaged 261 { 262 [NativeDisableUnsafePtrRestriction] 263 internal UnsafeQueueData* m_Buffer; 264 [NativeDisableUnsafePtrRestriction] 265 internal UnsafeQueueBlockPoolData* m_QueuePool; 266 internal AllocatorManager.AllocatorHandle m_AllocatorLabel; 267 268 /// <summary> 269 /// Initializes and returns an instance of UnsafeQueue. 270 /// </summary> 271 /// <param name="allocator">The allocator to use.</param> 272 public UnsafeQueue(AllocatorManager.AllocatorHandle allocator) 273 { 274 m_QueuePool = UnsafeQueueBlockPool.GetQueueBlockPool(); 275 m_AllocatorLabel = allocator; 276 UnsafeQueueData.AllocateQueue<T>(allocator, out m_Buffer); 277 } 278 279 internal static UnsafeQueue<T>* Alloc(AllocatorManager.AllocatorHandle allocator) 280 { 281 UnsafeQueue<T>* data = (UnsafeQueue<T>*)Memory.Unmanaged.Allocate(sizeof(UnsafeQueue<T>), UnsafeUtility.AlignOf<UnsafeQueue<T>>(), allocator); 282 return data; 283 } 284 285 internal static void Free(UnsafeQueue<T>* data) 286 { 287 if (data == null) 288 { 289 throw new InvalidOperationException("UnsafeQueue has yet to be created or has been destroyed!"); 290 } 291 var allocator = data->m_AllocatorLabel; 292 data->Dispose(); 293 Memory.Unmanaged.Free(data, allocator); 294 } 295 296 /// <summary> 297 /// Returns true if this queue is empty. 298 /// </summary> 299 /// <returns>True if this queue has no items or if the queue has not been constructed.</returns> 300 public readonly bool IsEmpty() 301 { 302 if (IsCreated) 303 { 304 int count = 0; 305 var currentRead = m_Buffer->m_CurrentRead; 306 307 for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock 308 ; block != null 309 ; block = block->m_NextBlock 310 ) 311 { 312 count += block->m_NumItems; 313 314 if (count > currentRead) 315 { 316 return false; 317 } 318 } 319 320 return count == currentRead; 321 } 322 return true; 323 } 324 325 /// <summary> 326 /// Returns the current number of elements in this queue. 327 /// </summary> 328 /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks. 329 /// Where possible, cache this value instead of reading the property repeatedly.</remarks> 330 /// <returns>The current number of elements in this queue.</returns> 331 public readonly int Count 332 { 333 get 334 { 335 int count = 0; 336 337 for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock 338 ; block != null 339 ; block = block->m_NextBlock 340 ) 341 { 342 count += block->m_NumItems; 343 } 344 345 return count - m_Buffer->m_CurrentRead; 346 } 347 } 348 349 internal static int PersistentMemoryBlockCount 350 { 351 get { return UnsafeQueueBlockPool.GetQueueBlockPool()->m_MaxBlocks; } 352 set { Interlocked.Exchange(ref UnsafeQueueBlockPool.GetQueueBlockPool()->m_MaxBlocks, value); } 353 } 354 355 internal static int MemoryBlockSize 356 { 357 get { return UnsafeQueueBlockPoolData.m_BlockSize; } 358 } 359 360 /// <summary> 361 /// Returns the element at the front of this queue without removing it. 362 /// </summary> 363 /// <returns>The element at the front of this queue.</returns> 364 [MethodImpl(MethodImplOptions.AggressiveInlining)] 365 public T Peek() 366 { 367 CheckNotEmpty(); 368 369 UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock; 370 return UnsafeUtility.ReadArrayElement<T>(firstBlock + 1, m_Buffer->m_CurrentRead); 371 } 372 373 /// <summary> 374 /// Adds an element at the back of this queue. 375 /// </summary> 376 /// <param name="value">The value to be enqueued.</param> 377 public void Enqueue(T value) 378 { 379 UnsafeQueueBlockHeader* writeBlock = UnsafeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, 0); 380 UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value); 381 ++writeBlock->m_NumItems; 382 } 383 384 /// <summary> 385 /// Removes and returns the element at the front of this queue. 386 /// </summary> 387 /// <exception cref="InvalidOperationException">Thrown if this queue is empty.</exception> 388 /// <returns>The element at the front of this queue.</returns> 389 public T Dequeue() 390 { 391 if (!TryDequeue(out T item)) 392 { 393 ThrowEmpty(); 394 } 395 396 return item; 397 } 398 399 /// <summary> 400 /// Removes and outputs the element at the front of this queue. 401 /// </summary> 402 /// <param name="item">Outputs the removed element.</param> 403 /// <returns>True if this queue was not empty.</returns> 404 public bool TryDequeue(out T item) 405 { 406 UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock; 407 408 if (firstBlock != null) 409 { 410 var currentRead = m_Buffer->m_CurrentRead++; 411 var numItems = firstBlock->m_NumItems; 412 item = UnsafeUtility.ReadArrayElement<T>(firstBlock + 1, currentRead); 413 414 if (currentRead + 1 >= numItems) 415 { 416 m_Buffer->m_CurrentRead = 0; 417 m_Buffer->m_FirstBlock = (IntPtr)firstBlock->m_NextBlock; 418 419 if (m_Buffer->m_FirstBlock == IntPtr.Zero) 420 { 421 m_Buffer->m_LastBlock = IntPtr.Zero; 422 } 423 424#if UNITY_2022_2_14F1_OR_NEWER 425 int maxThreadCount = JobsUtility.ThreadIndexCount; 426#else 427 int maxThreadCount = JobsUtility.MaxJobThreadCount; 428#endif 429 for (int threadIndex = 0; threadIndex < maxThreadCount; ++threadIndex) 430 { 431 if (m_Buffer->GetCurrentWriteBlockTLS(threadIndex) == firstBlock) 432 { 433 m_Buffer->SetCurrentWriteBlockTLS(threadIndex, null); 434 } 435 } 436 437 m_QueuePool->FreeBlock(firstBlock); 438 } 439 return true; 440 } 441 442 item = default(T); 443 return false; 444 } 445 446 /// <summary> 447 /// Returns an array containing a copy of this queue's content. 448 /// </summary> 449 /// <param name="allocator">The allocator to use.</param> 450 /// <returns>An array containing a copy of this queue's content. The elements are ordered in the same order they were 451 /// enqueued, *e.g.* the earliest enqueued element is copied to index 0 of the array.</returns> 452 public NativeArray<T> ToArray(AllocatorManager.AllocatorHandle allocator) 453 { 454 UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock; 455 var outputArray = CollectionHelper.CreateNativeArray<T>(Count, allocator, NativeArrayOptions.UninitializedMemory); 456 457 UnsafeQueueBlockHeader* currentBlock = firstBlock; 458 var arrayPtr = (byte*)outputArray.GetUnsafePtr(); 459 int size = UnsafeUtility.SizeOf<T>(); 460 int dstOffset = 0; 461 int srcOffset = m_Buffer->m_CurrentRead * size; 462 int srcOffsetElements = m_Buffer->m_CurrentRead; 463 while (currentBlock != null) 464 { 465 int bytesToCopy = (currentBlock->m_NumItems - srcOffsetElements) * size; 466 UnsafeUtility.MemCpy(arrayPtr + dstOffset, (byte*)(currentBlock + 1) + srcOffset, bytesToCopy); 467 srcOffset = srcOffsetElements = 0; 468 dstOffset += bytesToCopy; 469 currentBlock = currentBlock->m_NextBlock; 470 } 471 472 return outputArray; 473 } 474 475 /// <summary> 476 /// Removes all elements of this queue. 477 /// </summary> 478 public void Clear() 479 { 480 UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock; 481 482 while (firstBlock != null) 483 { 484 UnsafeQueueBlockHeader* next = firstBlock->m_NextBlock; 485 m_QueuePool->FreeBlock(firstBlock); 486 firstBlock = next; 487 } 488 489 m_Buffer->m_FirstBlock = IntPtr.Zero; 490 m_Buffer->m_LastBlock = IntPtr.Zero; 491 m_Buffer->m_CurrentRead = 0; 492 493#if UNITY_2022_2_14F1_OR_NEWER 494 int maxThreadCount = JobsUtility.ThreadIndexCount; 495#else 496 int maxThreadCount = JobsUtility.MaxJobThreadCount; 497#endif 498 for (int threadIndex = 0; threadIndex < maxThreadCount; ++threadIndex) 499 { 500 m_Buffer->SetCurrentWriteBlockTLS(threadIndex, null); 501 } 502 } 503 504 /// <summary> 505 /// Whether this queue has been allocated (and not yet deallocated). 506 /// </summary> 507 /// <value>True if this queue has been allocated (and not yet deallocated).</value> 508 public readonly bool IsCreated 509 { 510 [MethodImpl(MethodImplOptions.AggressiveInlining)] 511 get => m_Buffer != null; 512 } 513 514 /// <summary> 515 /// Releases all resources (memory and safety handles). 516 /// </summary> 517 public void Dispose() 518 { 519 if (!IsCreated) 520 { 521 return; 522 } 523 524 UnsafeQueueData.DeallocateQueue(m_Buffer, m_QueuePool, m_AllocatorLabel); 525 m_Buffer = null; 526 m_QueuePool = null; 527 } 528 529 /// <summary> 530 /// Creates and schedules a job that releases all resources (memory and safety handles) of this queue. 531 /// </summary> 532 /// <param name="inputDeps">The dependency for the new job.</param> 533 /// <returns>The handle of the new job. The job depends upon `inputDeps` and releases all resources (memory and safety handles) of this queue.</returns> 534 public JobHandle Dispose(JobHandle inputDeps) 535 { 536 if (!IsCreated) 537 { 538 return inputDeps; 539 } 540 541 var jobHandle = new UnsafeQueueDisposeJob { Data = new UnsafeQueueDispose { m_Buffer = m_Buffer, m_QueuePool = m_QueuePool, m_AllocatorLabel = m_AllocatorLabel } }.Schedule(inputDeps); 542 m_Buffer = null; 543 m_QueuePool = null; 544 545 return jobHandle; 546 } 547 548 /// <summary> 549 /// An enumerator over the values of a container. 550 /// </summary> 551 /// <remarks> 552 /// In an enumerator's initial state, <see cref="Current"/> is invalid. 553 /// The first <see cref="MoveNext"/> call advances the enumerator to the first value. 554 /// </remarks> 555 public struct Enumerator : IEnumerator<T> 556 { 557 [NativeDisableUnsafePtrRestriction] 558 internal UnsafeQueueBlockHeader* m_FirstBlock; 559 560 [NativeDisableUnsafePtrRestriction] 561 internal UnsafeQueueBlockHeader* m_Block; 562 563 internal int m_Index; 564 T value; 565 566 /// <summary> 567 /// Does nothing. 568 /// </summary> 569 public void Dispose() { } 570 571 /// <summary> 572 /// Advances the enumerator to the next value. 573 /// </summary> 574 /// <returns>True if `Current` is valid to read after the call.</returns> 575 [MethodImpl(MethodImplOptions.AggressiveInlining)] 576 public bool MoveNext() 577 { 578 m_Index++; 579 580 for (; m_Block != null 581 ; m_Block = m_Block->m_NextBlock 582 ) 583 { 584 var numItems = m_Block->m_NumItems; 585 586 if (m_Index < numItems) 587 { 588 value = UnsafeUtility.ReadArrayElement<T>(m_Block + 1, m_Index); 589 return true; 590 } 591 592 m_Index -= numItems; 593 } 594 595 value = default; 596 return false; 597 } 598 599 /// <summary> 600 /// Resets the enumerator to its initial state. 601 /// </summary> 602 public void Reset() 603 { 604 m_Block = m_FirstBlock; 605 m_Index = -1; 606 } 607 608 /// <summary> 609 /// The current value. 610 /// </summary> 611 /// <value>The current value.</value> 612 public T Current 613 { 614 [MethodImpl(MethodImplOptions.AggressiveInlining)] 615 get => value; 616 } 617 618 object IEnumerator.Current => Current; 619 } 620 621 /// <summary> 622 /// Returns a readonly version of this UnsafeQueue instance. 623 /// </summary> 624 /// <remarks>ReadOnly containers point to the same underlying data as the UnsafeQueue it is made from.</remarks> 625 /// <returns>ReadOnly instance for this.</returns> 626 public ReadOnly AsReadOnly() 627 { 628 return new ReadOnly(ref this); 629 } 630 631 /// <summary> 632 /// A read-only alias for the value of a UnsafeQueue. Does not have its own allocated storage. 633 /// </summary> 634 public struct ReadOnly 635 : IEnumerable<T> 636 { 637 [NativeDisableUnsafePtrRestriction] 638 UnsafeQueueData* m_Buffer; 639 640 internal ReadOnly(ref UnsafeQueue<T> data) 641 { 642 m_Buffer = data.m_Buffer; 643 } 644 645 /// <summary> 646 /// Whether this container been allocated (and not yet deallocated). 647 /// </summary> 648 /// <value>True if this container has been allocated (and not yet deallocated).</value> 649 public readonly bool IsCreated 650 { 651 [MethodImpl(MethodImplOptions.AggressiveInlining)] 652 get 653 { 654 return m_Buffer != null; 655 } 656 } 657 658 /// <summary> 659 /// Returns true if this queue is empty. 660 /// </summary> 661 /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks. 662 /// Where possible, cache this value instead of reading the property repeatedly.</remarks> 663 /// <returns>True if this queue has no items or if the queue has not been constructed.</returns> 664 public readonly bool IsEmpty() 665 { 666 int count = 0; 667 var currentRead = m_Buffer->m_CurrentRead; 668 669 for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock 670 ; block != null 671 ; block = block->m_NextBlock 672 ) 673 { 674 count += block->m_NumItems; 675 676 if (count > currentRead) 677 { 678 return false; 679 } 680 } 681 682 return count == currentRead; 683 } 684 685 /// <summary> 686 /// Returns the current number of elements in this queue. 687 /// </summary> 688 /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks. 689 /// Where possible, cache this value instead of reading the property repeatedly.</remarks> 690 /// <returns>The current number of elements in this queue.</returns> 691 public readonly int Count 692 { 693 get 694 { 695 int count = 0; 696 697 for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock 698 ; block != null 699 ; block = block->m_NextBlock 700 ) 701 { 702 count += block->m_NumItems; 703 } 704 705 return count - m_Buffer->m_CurrentRead; 706 } 707 } 708 709 /// <summary> 710 /// The element at an index. 711 /// </summary> 712 /// <param name="index">An index.</param> 713 /// <value>The element at the index.</value> 714 /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception> 715 public readonly T this[int index] 716 { 717 get 718 { 719 T result; 720 if (!TryGetValue(index, out result)) 721 { 722 ThrowIndexOutOfRangeException(index); 723 } 724 725 return result; 726 } 727 } 728 729 readonly bool TryGetValue(int index, out T item) 730 { 731 if (index >= 0) 732 { 733 var idx = index; 734 735 for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock 736 ; block != null 737 ; block = block->m_NextBlock 738 ) 739 { 740 var numItems = block->m_NumItems; 741 742 if (idx < numItems) 743 { 744 item = UnsafeUtility.ReadArrayElement<T>(block + 1, idx); 745 return true; 746 } 747 748 idx -= numItems; 749 } 750 } 751 752 item = default; 753 return false; 754 } 755 756 /// <summary> 757 /// Returns an enumerator over the items of this container. 758 /// </summary> 759 /// <returns>An enumerator over the items of this container.</returns> 760 public readonly Enumerator GetEnumerator() 761 { 762 return new Enumerator 763 { 764 m_FirstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock, 765 m_Block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock, 766 m_Index = -1, 767 }; 768 } 769 770 /// <summary> 771 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead. 772 /// </summary> 773 /// <returns>Throws NotImplementedException.</returns> 774 /// <exception cref="NotImplementedException">Method is not implemented.</exception> 775 IEnumerator<T> IEnumerable<T>.GetEnumerator() 776 { 777 throw new NotImplementedException(); 778 } 779 780 /// <summary> 781 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead. 782 /// </summary> 783 /// <returns>Throws NotImplementedException.</returns> 784 /// <exception cref="NotImplementedException">Method is not implemented.</exception> 785 IEnumerator IEnumerable.GetEnumerator() 786 { 787 throw new NotImplementedException(); 788 } 789 790 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 791 [MethodImpl(MethodImplOptions.AggressiveInlining)] 792 readonly void ThrowIndexOutOfRangeException(int index) 793 { 794 throw new IndexOutOfRangeException($"Index {index} is out of bounds [0-{Count}]."); 795 } 796 } 797 798 /// <summary> 799 /// Returns a parallel writer for this queue. 800 /// </summary> 801 /// <returns>A parallel writer for this queue.</returns> 802 public ParallelWriter AsParallelWriter() 803 { 804 ParallelWriter writer; 805 806 writer.m_Buffer = m_Buffer; 807 writer.m_QueuePool = m_QueuePool; 808 writer.m_ThreadIndex = 0; 809 810 return writer; 811 } 812 813 /// <summary> 814 /// A parallel writer for a UnsafeQueue. 815 /// </summary> 816 /// <remarks> 817 /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a UnsafeQueue. 818 /// </remarks> 819 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] 820 public unsafe struct ParallelWriter 821 { 822 [NativeDisableUnsafePtrRestriction] 823 internal UnsafeQueueData* m_Buffer; 824 825 [NativeDisableUnsafePtrRestriction] 826 internal UnsafeQueueBlockPoolData* m_QueuePool; 827 828 [NativeSetThreadIndex] 829 internal int m_ThreadIndex; 830 831 /// <summary> 832 /// Adds an element at the back of the queue. 833 /// </summary> 834 /// <param name="value">The value to be enqueued.</param> 835 public void Enqueue(T value) 836 { 837 UnsafeQueueBlockHeader* writeBlock = UnsafeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, m_ThreadIndex); 838 UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value); 839 ++writeBlock->m_NumItems; 840 } 841 842 /// <summary> 843 /// Adds an element at the back of the queue. 844 /// </summary> 845 /// <param name="value">The value to be enqueued.</param> 846 /// <param name="threadIndexOverride">The thread index which must be set by a field from a job struct with the <see cref="NativeSetThreadIndexAttribute"/> attribute.</param> 847 internal void Enqueue(T value, int threadIndexOverride) 848 { 849 UnsafeQueueBlockHeader* writeBlock = UnsafeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, threadIndexOverride); 850 UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value); 851 ++writeBlock->m_NumItems; 852 } 853 } 854 855 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 856 [MethodImpl(MethodImplOptions.AggressiveInlining)] 857 void CheckNotEmpty() 858 { 859 if (m_Buffer->m_FirstBlock == (IntPtr)0) 860 { 861 ThrowEmpty(); 862 } 863 } 864 865 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 866 static void ThrowEmpty() 867 { 868 throw new InvalidOperationException("Trying to read from an empty queue."); 869 } 870 } 871 872 [GenerateTestsForBurstCompatibility] 873 internal unsafe struct UnsafeQueueDispose 874 { 875 [NativeDisableUnsafePtrRestriction] 876 internal UnsafeQueueData* m_Buffer; 877 878 [NativeDisableUnsafePtrRestriction] 879 internal UnsafeQueueBlockPoolData* m_QueuePool; 880 881 internal AllocatorManager.AllocatorHandle m_AllocatorLabel; 882 883 public void Dispose() 884 { 885 UnsafeQueueData.DeallocateQueue(m_Buffer, m_QueuePool, m_AllocatorLabel); 886 } 887 } 888 889 [BurstCompile] 890 struct UnsafeQueueDisposeJob : IJob 891 { 892 public UnsafeQueueDispose Data; 893 894 public void Execute() 895 { 896 Data.Dispose(); 897 } 898 } 899}