123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 |
- using System;
- using System.Runtime.InteropServices;
- using System.Threading;
- using Unity.Collections.LowLevel.Unsafe;
- using Unity.Burst;
- using Unity.Jobs;
- using Unity.Jobs.LowLevel.Unsafe;
- using System.Diagnostics;
- using System.Runtime.CompilerServices;
- using System.Collections;
- using System.Collections.Generic;
-
- namespace Unity.Collections
- {
- [StructLayout(LayoutKind.Sequential)]
- unsafe struct UnsafeQueueBlockHeader
- {
- public UnsafeQueueBlockHeader* m_NextBlock;
- public int m_NumItems;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- [GenerateTestsForBurstCompatibility]
- internal unsafe struct UnsafeQueueBlockPoolData
- {
- internal IntPtr m_FirstBlock;
- internal int m_NumBlocks;
- internal int m_MaxBlocks;
- internal const int m_BlockSize = 16 * 1024;
- internal int m_AllocLock;
-
- public UnsafeQueueBlockHeader* AllocateBlock()
- {
- // There can only ever be a single thread allocating an entry from the free list since it needs to
- // access the content of the block (the next pointer) before doing the CAS.
- // If there was no lock thread A could read the next pointer, thread B could quickly allocate
- // the same block then free it with another next pointer before thread A performs the CAS which
- // leads to an invalid free list potentially causing memory corruption.
- // Having multiple threads freeing data concurrently to each other while another thread is allocating
- // is no problems since there is only ever a single thread modifying global data in that case.
- while (Interlocked.CompareExchange(ref m_AllocLock, 1, 0) != 0)
- {
- }
-
- UnsafeQueueBlockHeader* checkBlock = (UnsafeQueueBlockHeader*)m_FirstBlock;
- UnsafeQueueBlockHeader* block;
-
- do
- {
- block = checkBlock;
- if (block == null)
- {
- Interlocked.Exchange(ref m_AllocLock, 0);
- Interlocked.Increment(ref m_NumBlocks);
- block = (UnsafeQueueBlockHeader*)Memory.Unmanaged.Allocate(m_BlockSize, 16, Allocator.Persistent);
- return block;
- }
-
- checkBlock = (UnsafeQueueBlockHeader*)Interlocked.CompareExchange(ref m_FirstBlock, (IntPtr)block->m_NextBlock, (IntPtr)block);
- }
- while (checkBlock != block);
-
- Interlocked.Exchange(ref m_AllocLock, 0);
-
- return block;
- }
-
- public void FreeBlock(UnsafeQueueBlockHeader* block)
- {
- if (m_NumBlocks > m_MaxBlocks)
- {
- if (Interlocked.Decrement(ref m_NumBlocks) + 1 > m_MaxBlocks)
- {
- Memory.Unmanaged.Free(block, Allocator.Persistent);
- return;
- }
-
- Interlocked.Increment(ref m_NumBlocks);
- }
-
- UnsafeQueueBlockHeader* checkBlock = (UnsafeQueueBlockHeader*)m_FirstBlock;
- UnsafeQueueBlockHeader* nextPtr;
-
- do
- {
- nextPtr = checkBlock;
- block->m_NextBlock = checkBlock;
- checkBlock = (UnsafeQueueBlockHeader*)Interlocked.CompareExchange(ref m_FirstBlock, (IntPtr)block, (IntPtr)checkBlock);
- }
- while (checkBlock != nextPtr);
- }
- }
-
- internal unsafe class UnsafeQueueBlockPool
- {
- static readonly SharedStatic<IntPtr> Data = SharedStatic<IntPtr>.GetOrCreate<UnsafeQueueBlockPool>();
-
- internal static UnsafeQueueBlockPoolData* GetQueueBlockPool()
- {
- var pData = (UnsafeQueueBlockPoolData**)Data.UnsafeDataPointer;
- var data = *pData;
-
- if (data == null)
- {
- data = (UnsafeQueueBlockPoolData*)Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<UnsafeQueueBlockPoolData>(), 8, Allocator.Persistent);
- *pData = data;
- data->m_NumBlocks = data->m_MaxBlocks = 256;
- data->m_AllocLock = 0;
- // Allocate MaxBlocks items
- UnsafeQueueBlockHeader* prev = null;
-
- for (int i = 0; i < data->m_MaxBlocks; ++i)
- {
- UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)Memory.Unmanaged.Allocate(UnsafeQueueBlockPoolData.m_BlockSize, 16, Allocator.Persistent);
- block->m_NextBlock = prev;
- prev = block;
- }
- data->m_FirstBlock = (IntPtr)prev;
-
- AppDomainOnDomainUnload();
- }
- return data;
- }
-
- [BurstDiscard]
- static void AppDomainOnDomainUnload()
- {
- AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
- }
-
- static void OnDomainUnload(object sender, EventArgs e)
- {
- var pData = (UnsafeQueueBlockPoolData**)Data.UnsafeDataPointer;
- var data = *pData;
-
- while (data->m_FirstBlock != IntPtr.Zero)
- {
- UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)data->m_FirstBlock;
- data->m_FirstBlock = (IntPtr)block->m_NextBlock;
- Memory.Unmanaged.Free(block, Allocator.Persistent);
- --data->m_NumBlocks;
- }
- Memory.Unmanaged.Free(data, Allocator.Persistent);
- *pData = null;
- }
- }
-
- [StructLayout(LayoutKind.Sequential)]
- [GenerateTestsForBurstCompatibility]
- internal unsafe struct UnsafeQueueData
- {
- public IntPtr m_FirstBlock;
- public IntPtr m_LastBlock;
- public int m_MaxItems;
- public int m_CurrentRead;
- public byte* m_CurrentWriteBlockTLS;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal UnsafeQueueBlockHeader* GetCurrentWriteBlockTLS(int threadIndex)
- {
- var data = (UnsafeQueueBlockHeader**)&m_CurrentWriteBlockTLS[threadIndex * JobsUtility.CacheLineSize];
- return *data;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void SetCurrentWriteBlockTLS(int threadIndex, UnsafeQueueBlockHeader* currentWriteBlock)
- {
- var data = (UnsafeQueueBlockHeader**)&m_CurrentWriteBlockTLS[threadIndex * JobsUtility.CacheLineSize];
- *data = currentWriteBlock;
- }
-
- [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
- public static UnsafeQueueBlockHeader* AllocateWriteBlockMT<T>(UnsafeQueueData* data, UnsafeQueueBlockPoolData* pool, int threadIndex) where T : unmanaged
- {
- UnsafeQueueBlockHeader* currentWriteBlock = data->GetCurrentWriteBlockTLS(threadIndex);
-
- if (currentWriteBlock != null)
- {
- if (currentWriteBlock->m_NumItems != data->m_MaxItems)
- {
- return currentWriteBlock;
- }
- currentWriteBlock = null;
- }
-
- currentWriteBlock = pool->AllocateBlock();
- currentWriteBlock->m_NextBlock = null;
- currentWriteBlock->m_NumItems = 0;
- UnsafeQueueBlockHeader* prevLast = (UnsafeQueueBlockHeader*)Interlocked.Exchange(ref data->m_LastBlock, (IntPtr)currentWriteBlock);
-
- if (prevLast == null)
- {
- data->m_FirstBlock = (IntPtr)currentWriteBlock;
- }
- else
- {
- prevLast->m_NextBlock = currentWriteBlock;
- }
-
- data->SetCurrentWriteBlockTLS(threadIndex, currentWriteBlock);
- return currentWriteBlock;
- }
-
- [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
- public unsafe static void AllocateQueue<T>(AllocatorManager.AllocatorHandle label, out UnsafeQueueData* outBuf) where T : unmanaged
- {
- #if UNITY_2022_2_14F1_OR_NEWER
- int maxThreadCount = JobsUtility.ThreadIndexCount;
- #else
- int maxThreadCount = JobsUtility.MaxJobThreadCount;
- #endif
-
- var queueDataSize = CollectionHelper.Align(UnsafeUtility.SizeOf<UnsafeQueueData>(), JobsUtility.CacheLineSize);
-
- var data = (UnsafeQueueData*)Memory.Unmanaged.Allocate(
- queueDataSize
- + JobsUtility.CacheLineSize * maxThreadCount
- , JobsUtility.CacheLineSize
- , label
- );
-
- data->m_CurrentWriteBlockTLS = (((byte*)data) + queueDataSize);
-
- data->m_FirstBlock = IntPtr.Zero;
- data->m_LastBlock = IntPtr.Zero;
- data->m_MaxItems = (UnsafeQueueBlockPoolData.m_BlockSize - UnsafeUtility.SizeOf<UnsafeQueueBlockHeader>()) / UnsafeUtility.SizeOf<T>();
-
- data->m_CurrentRead = 0;
- for (int threadIndex = 0; threadIndex < maxThreadCount; ++threadIndex)
- {
- data->SetCurrentWriteBlockTLS(threadIndex, null);
- }
-
- outBuf = data;
- }
-
- public unsafe static void DeallocateQueue(UnsafeQueueData* data, UnsafeQueueBlockPoolData* pool, AllocatorManager.AllocatorHandle allocation)
- {
- UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)data->m_FirstBlock;
-
- while (firstBlock != null)
- {
- UnsafeQueueBlockHeader* next = firstBlock->m_NextBlock;
- pool->FreeBlock(firstBlock);
- firstBlock = next;
- }
-
- Memory.Unmanaged.Free(data, allocation);
- }
- }
-
- /// <summary>
- /// An unmanaged queue.
- /// </summary>
- /// <typeparam name="T">The type of the elements.</typeparam>
- [StructLayout(LayoutKind.Sequential)]
- [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
- public unsafe struct UnsafeQueue<T>
- : INativeDisposable
- where T : unmanaged
- {
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueData* m_Buffer;
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueBlockPoolData* m_QueuePool;
- internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
-
- /// <summary>
- /// Initializes and returns an instance of UnsafeQueue.
- /// </summary>
- /// <param name="allocator">The allocator to use.</param>
- public UnsafeQueue(AllocatorManager.AllocatorHandle allocator)
- {
- m_QueuePool = UnsafeQueueBlockPool.GetQueueBlockPool();
- m_AllocatorLabel = allocator;
- UnsafeQueueData.AllocateQueue<T>(allocator, out m_Buffer);
- }
-
- internal static UnsafeQueue<T>* Alloc(AllocatorManager.AllocatorHandle allocator)
- {
- UnsafeQueue<T>* data = (UnsafeQueue<T>*)Memory.Unmanaged.Allocate(sizeof(UnsafeQueue<T>), UnsafeUtility.AlignOf<UnsafeQueue<T>>(), allocator);
- return data;
- }
-
- internal static void Free(UnsafeQueue<T>* data)
- {
- if (data == null)
- {
- throw new InvalidOperationException("UnsafeQueue has yet to be created or has been destroyed!");
- }
- var allocator = data->m_AllocatorLabel;
- data->Dispose();
- Memory.Unmanaged.Free(data, allocator);
- }
-
- /// <summary>
- /// Returns true if this queue is empty.
- /// </summary>
- /// <returns>True if this queue has no items or if the queue has not been constructed.</returns>
- public readonly bool IsEmpty()
- {
- if (IsCreated)
- {
- int count = 0;
- var currentRead = m_Buffer->m_CurrentRead;
-
- for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock
- ; block != null
- ; block = block->m_NextBlock
- )
- {
- count += block->m_NumItems;
-
- if (count > currentRead)
- {
- return false;
- }
- }
-
- return count == currentRead;
- }
- return true;
- }
-
- /// <summary>
- /// Returns the current number of elements in this queue.
- /// </summary>
- /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
- /// Where possible, cache this value instead of reading the property repeatedly.</remarks>
- /// <returns>The current number of elements in this queue.</returns>
- public readonly int Count
- {
- get
- {
- int count = 0;
-
- for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock
- ; block != null
- ; block = block->m_NextBlock
- )
- {
- count += block->m_NumItems;
- }
-
- return count - m_Buffer->m_CurrentRead;
- }
- }
-
- internal static int PersistentMemoryBlockCount
- {
- get { return UnsafeQueueBlockPool.GetQueueBlockPool()->m_MaxBlocks; }
- set { Interlocked.Exchange(ref UnsafeQueueBlockPool.GetQueueBlockPool()->m_MaxBlocks, value); }
- }
-
- internal static int MemoryBlockSize
- {
- get { return UnsafeQueueBlockPoolData.m_BlockSize; }
- }
-
- /// <summary>
- /// Returns the element at the end of this queue without removing it.
- /// </summary>
- /// <returns>The element at the end of this queue.</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public T Peek()
- {
- CheckNotEmpty();
-
- UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock;
- return UnsafeUtility.ReadArrayElement<T>(firstBlock + 1, m_Buffer->m_CurrentRead);
- }
-
- /// <summary>
- /// Adds an element at the front of this queue.
- /// </summary>
- /// <param name="value">The value to be enqueued.</param>
- public void Enqueue(T value)
- {
- UnsafeQueueBlockHeader* writeBlock = UnsafeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, 0);
- UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value);
- ++writeBlock->m_NumItems;
- }
-
- /// <summary>
- /// Removes and returns the element at the end of this queue.
- /// </summary>
- /// <exception cref="InvalidOperationException">Thrown if this queue is empty.</exception>
- /// <returns>The element at the end of this queue.</returns>
- public T Dequeue()
- {
- if (!TryDequeue(out T item))
- {
- ThrowEmpty();
- }
-
- return item;
- }
-
- /// <summary>
- /// Removes and outputs the element at the end of this queue.
- /// </summary>
- /// <param name="item">Outputs the removed element.</param>
- /// <returns>True if this queue was not empty.</returns>
- public bool TryDequeue(out T item)
- {
- UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock;
-
- if (firstBlock != null)
- {
- var currentRead = m_Buffer->m_CurrentRead++;
- var numItems = firstBlock->m_NumItems;
- item = UnsafeUtility.ReadArrayElement<T>(firstBlock + 1, currentRead);
-
- if (currentRead + 1 >= numItems)
- {
- m_Buffer->m_CurrentRead = 0;
- m_Buffer->m_FirstBlock = (IntPtr)firstBlock->m_NextBlock;
-
- if (m_Buffer->m_FirstBlock == IntPtr.Zero)
- {
- m_Buffer->m_LastBlock = IntPtr.Zero;
- }
-
- #if UNITY_2022_2_14F1_OR_NEWER
- int maxThreadCount = JobsUtility.ThreadIndexCount;
- #else
- int maxThreadCount = JobsUtility.MaxJobThreadCount;
- #endif
- for (int threadIndex = 0; threadIndex < maxThreadCount; ++threadIndex)
- {
- if (m_Buffer->GetCurrentWriteBlockTLS(threadIndex) == firstBlock)
- {
- m_Buffer->SetCurrentWriteBlockTLS(threadIndex, null);
- }
- }
-
- m_QueuePool->FreeBlock(firstBlock);
- }
- return true;
- }
-
- item = default(T);
- return false;
- }
-
- /// <summary>
- /// Returns an array containing a copy of this queue's content.
- /// </summary>
- /// <param name="allocator">The allocator to use.</param>
- /// <returns>An array containing a copy of this queue's content. The elements are ordered in the same order they were
- /// enqueued, *e.g.* the earliest enqueued element is copied to index 0 of the array.</returns>
- public NativeArray<T> ToArray(AllocatorManager.AllocatorHandle allocator)
- {
- UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock;
- var outputArray = CollectionHelper.CreateNativeArray<T>(Count, allocator, NativeArrayOptions.UninitializedMemory);
-
- UnsafeQueueBlockHeader* currentBlock = firstBlock;
- var arrayPtr = (byte*)outputArray.GetUnsafePtr();
- int size = UnsafeUtility.SizeOf<T>();
- int dstOffset = 0;
- int srcOffset = m_Buffer->m_CurrentRead * size;
- int srcOffsetElements = m_Buffer->m_CurrentRead;
- while (currentBlock != null)
- {
- int bytesToCopy = (currentBlock->m_NumItems - srcOffsetElements) * size;
- UnsafeUtility.MemCpy(arrayPtr + dstOffset, (byte*)(currentBlock + 1) + srcOffset, bytesToCopy);
- srcOffset = srcOffsetElements = 0;
- dstOffset += bytesToCopy;
- currentBlock = currentBlock->m_NextBlock;
- }
-
- return outputArray;
- }
-
- /// <summary>
- /// Removes all elements of this queue.
- /// </summary>
- public void Clear()
- {
- UnsafeQueueBlockHeader* firstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock;
-
- while (firstBlock != null)
- {
- UnsafeQueueBlockHeader* next = firstBlock->m_NextBlock;
- m_QueuePool->FreeBlock(firstBlock);
- firstBlock = next;
- }
-
- m_Buffer->m_FirstBlock = IntPtr.Zero;
- m_Buffer->m_LastBlock = IntPtr.Zero;
- m_Buffer->m_CurrentRead = 0;
-
- #if UNITY_2022_2_14F1_OR_NEWER
- int maxThreadCount = JobsUtility.ThreadIndexCount;
- #else
- int maxThreadCount = JobsUtility.MaxJobThreadCount;
- #endif
- for (int threadIndex = 0; threadIndex < maxThreadCount; ++threadIndex)
- {
- m_Buffer->SetCurrentWriteBlockTLS(threadIndex, null);
- }
- }
-
- /// <summary>
- /// Whether this queue has been allocated (and not yet deallocated).
- /// </summary>
- /// <value>True if this queue has been allocated (and not yet deallocated).</value>
- public readonly bool IsCreated
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => m_Buffer != null;
- }
-
- /// <summary>
- /// Releases all resources (memory and safety handles).
- /// </summary>
- public void Dispose()
- {
- if (!IsCreated)
- {
- return;
- }
-
- UnsafeQueueData.DeallocateQueue(m_Buffer, m_QueuePool, m_AllocatorLabel);
- m_Buffer = null;
- m_QueuePool = null;
- }
-
- /// <summary>
- /// Creates and schedules a job that releases all resources (memory and safety handles) of this queue.
- /// </summary>
- /// <param name="inputDeps">The dependency for the new job.</param>
- /// <returns>The handle of the new job. The job depends upon `inputDeps` and releases all resources (memory and safety handles) of this queue.</returns>
- public JobHandle Dispose(JobHandle inputDeps)
- {
- if (!IsCreated)
- {
- return inputDeps;
- }
-
- var jobHandle = new UnsafeQueueDisposeJob { Data = new UnsafeQueueDispose { m_Buffer = m_Buffer, m_QueuePool = m_QueuePool, m_AllocatorLabel = m_AllocatorLabel } }.Schedule(inputDeps);
- m_Buffer = null;
- m_QueuePool = null;
-
- return jobHandle;
- }
-
- /// <summary>
- /// An enumerator over the values of a container.
- /// </summary>
- /// <remarks>
- /// In an enumerator's initial state, <see cref="Current"/> is invalid.
- /// The first <see cref="MoveNext"/> call advances the enumerator to the first value.
- /// </remarks>
- public struct Enumerator : IEnumerator<T>
- {
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueBlockHeader* m_FirstBlock;
-
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueBlockHeader* m_Block;
-
- internal int m_Index;
- T value;
-
- /// <summary>
- /// Does nothing.
- /// </summary>
- public void Dispose() { }
-
- /// <summary>
- /// Advances the enumerator to the next value.
- /// </summary>
- /// <returns>True if `Current` is valid to read after the call.</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool MoveNext()
- {
- m_Index++;
-
- for (; m_Block != null
- ; m_Block = m_Block->m_NextBlock
- )
- {
- var numItems = m_Block->m_NumItems;
-
- if (m_Index < numItems)
- {
- value = UnsafeUtility.ReadArrayElement<T>(m_Block + 1, m_Index);
- return true;
- }
-
- m_Index -= numItems;
- }
-
- value = default;
- return false;
- }
-
- /// <summary>
- /// Resets the enumerator to its initial state.
- /// </summary>
- public void Reset()
- {
- m_Block = m_FirstBlock;
- m_Index = -1;
- }
-
- /// <summary>
- /// The current value.
- /// </summary>
- /// <value>The current value.</value>
- public T Current
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => value;
- }
-
- object IEnumerator.Current => Current;
- }
-
- /// <summary>
- /// Returns a readonly version of this UnsafeQueue instance.
- /// </summary>
- /// <remarks>ReadOnly containers point to the same underlying data as the UnsafeQueue it is made from.</remarks>
- /// <returns>ReadOnly instance for this.</returns>
- public ReadOnly AsReadOnly()
- {
- return new ReadOnly(ref this);
- }
-
- /// <summary>
- /// A read-only alias for the value of a UnsafeQueue. Does not have its own allocated storage.
- /// </summary>
- public struct ReadOnly
- : IEnumerable<T>
- {
- [NativeDisableUnsafePtrRestriction]
- UnsafeQueueData* m_Buffer;
-
- internal ReadOnly(ref UnsafeQueue<T> data)
- {
- m_Buffer = data.m_Buffer;
- }
-
- /// <summary>
- /// Whether this container been allocated (and not yet deallocated).
- /// </summary>
- /// <value>True if this container has been allocated (and not yet deallocated).</value>
- public readonly bool IsCreated
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- return m_Buffer != null;
- }
- }
-
- /// <summary>
- /// Returns true if this queue is empty.
- /// </summary>
- /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
- /// Where possible, cache this value instead of reading the property repeatedly.</remarks>
- /// <returns>True if this queue has no items or if the queue has not been constructed.</returns>
- public readonly bool IsEmpty()
- {
- int count = 0;
- var currentRead = m_Buffer->m_CurrentRead;
-
- for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock
- ; block != null
- ; block = block->m_NextBlock
- )
- {
- count += block->m_NumItems;
-
- if (count > currentRead)
- {
- return false;
- }
- }
-
- return count == currentRead;
- }
-
- /// <summary>
- /// Returns the current number of elements in this queue.
- /// </summary>
- /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
- /// Where possible, cache this value instead of reading the property repeatedly.</remarks>
- /// <returns>The current number of elements in this queue.</returns>
- public readonly int Count
- {
- get
- {
- int count = 0;
-
- for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock
- ; block != null
- ; block = block->m_NextBlock
- )
- {
- count += block->m_NumItems;
- }
-
- return count - m_Buffer->m_CurrentRead;
- }
- }
-
- /// <summary>
- /// The element at an index.
- /// </summary>
- /// <param name="index">An index.</param>
- /// <value>The element at the index.</value>
- /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception>
- public readonly T this[int index]
- {
- get
- {
- T result;
- if (!TryGetValue(index, out result))
- {
- ThrowIndexOutOfRangeException(index);
- }
-
- return result;
- }
- }
-
- readonly bool TryGetValue(int index, out T item)
- {
- if (index >= 0)
- {
- var idx = index;
-
- for (UnsafeQueueBlockHeader* block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock
- ; block != null
- ; block = block->m_NextBlock
- )
- {
- var numItems = block->m_NumItems;
-
- if (idx < numItems)
- {
- item = UnsafeUtility.ReadArrayElement<T>(block + 1, idx);
- return true;
- }
-
- idx -= numItems;
- }
- }
-
- item = default;
- return false;
- }
-
- /// <summary>
- /// Returns an enumerator over the items of this container.
- /// </summary>
- /// <returns>An enumerator over the items of this container.</returns>
- public readonly Enumerator GetEnumerator()
- {
- return new Enumerator
- {
- m_FirstBlock = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock,
- m_Block = (UnsafeQueueBlockHeader*)m_Buffer->m_FirstBlock,
- m_Index = -1,
- };
- }
-
- /// <summary>
- /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
- /// </summary>
- /// <returns>Throws NotImplementedException.</returns>
- /// <exception cref="NotImplementedException">Method is not implemented.</exception>
- IEnumerator<T> IEnumerable<T>.GetEnumerator()
- {
- throw new NotImplementedException();
- }
-
- /// <summary>
- /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
- /// </summary>
- /// <returns>Throws NotImplementedException.</returns>
- /// <exception cref="NotImplementedException">Method is not implemented.</exception>
- IEnumerator IEnumerable.GetEnumerator()
- {
- throw new NotImplementedException();
- }
-
- [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- readonly void ThrowIndexOutOfRangeException(int index)
- {
- throw new IndexOutOfRangeException($"Index {index} is out of bounds [0-{Count}].");
- }
- }
-
- /// <summary>
- /// Returns a parallel writer for this queue.
- /// </summary>
- /// <returns>A parallel writer for this queue.</returns>
- public ParallelWriter AsParallelWriter()
- {
- ParallelWriter writer;
-
- writer.m_Buffer = m_Buffer;
- writer.m_QueuePool = m_QueuePool;
- writer.m_ThreadIndex = 0;
-
- return writer;
- }
-
- /// <summary>
- /// A parallel writer for a UnsafeQueue.
- /// </summary>
- /// <remarks>
- /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a UnsafeQueue.
- /// </remarks>
- [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
- public unsafe struct ParallelWriter
- {
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueData* m_Buffer;
-
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueBlockPoolData* m_QueuePool;
-
- [NativeSetThreadIndex]
- internal int m_ThreadIndex;
-
- /// <summary>
- /// Adds an element at the front of the queue.
- /// </summary>
- /// <param name="value">The value to be enqueued.</param>
- public void Enqueue(T value)
- {
- UnsafeQueueBlockHeader* writeBlock = UnsafeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, m_ThreadIndex);
- UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value);
- ++writeBlock->m_NumItems;
- }
-
- /// <summary>
- /// Adds an element at the front of the queue.
- /// </summary>
- /// <param name="value">The value to be enqueued.</param>
- /// <param name="threadIndexOverride">The thread index which must be set by a field from a job struct with the <see cref="NativeSetThreadIndexAttribute"/> attribute.</param>
- internal void Enqueue(T value, int threadIndexOverride)
- {
- UnsafeQueueBlockHeader* writeBlock = UnsafeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, threadIndexOverride);
- UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value);
- ++writeBlock->m_NumItems;
- }
- }
-
- [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- void CheckNotEmpty()
- {
- if (m_Buffer->m_FirstBlock == (IntPtr)0)
- {
- ThrowEmpty();
- }
- }
-
- [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
- static void ThrowEmpty()
- {
- throw new InvalidOperationException("Trying to read from an empty queue.");
- }
- }
-
- [GenerateTestsForBurstCompatibility]
- internal unsafe struct UnsafeQueueDispose
- {
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueData* m_Buffer;
-
- [NativeDisableUnsafePtrRestriction]
- internal UnsafeQueueBlockPoolData* m_QueuePool;
-
- internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
-
- public void Dispose()
- {
- UnsafeQueueData.DeallocateQueue(m_Buffer, m_QueuePool, m_AllocatorLabel);
- }
- }
-
- [BurstCompile]
- struct UnsafeQueueDisposeJob : IJob
- {
- public UnsafeQueueDispose Data;
-
- public void Execute()
- {
- Data.Dispose();
- }
- }
- }
|