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}