using System; using NUnit.Framework; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.Tests; using Unity.Jobs; using Assert = FastAssert; [BurstCompile] internal class NativeQueueTests : CollectionsTestCommonBase { static void ExpectedCount(ref NativeQueue container, int expected) where T : unmanaged { Assert.AreEqual(expected == 0, container.IsEmpty()); Assert.AreEqual(expected, container.Count); } [Test] public void Enqueue_Dequeue() { var queue = new NativeQueue(Allocator.Temp); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif for (int i = 0; i < 16; ++i) queue.Enqueue(i); ExpectedCount(ref queue, 16); for (int i = 0; i < 16; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif queue.Dispose(); } [Test] public void ConcurrentEnqueue_Dequeue() { var queue = new NativeQueue(Allocator.Temp); var cQueue = queue.AsParallelWriter(); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif for (int i = 0; i < 16; ++i) cQueue.Enqueue(i); ExpectedCount(ref queue, 16); for (int i = 0; i < 16; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif queue.Dispose(); } [Test] public void Enqueue_Dequeue_Peek() { var queue = new NativeQueue(Allocator.Temp); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif for (int i = 0; i < 16; ++i) queue.Enqueue(i); ExpectedCount(ref queue, 16); for (int i = 0; i < 16; ++i) { Assert.AreEqual(i, queue.Peek(), "Got the wrong value from the queue"); queue.Dequeue(); } ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif queue.Dispose(); } [Test] public void Enqueue_Dequeue_Clear() { var queue = new NativeQueue(Allocator.Temp); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif for (int i = 0; i < 16; ++i) queue.Enqueue(i); ExpectedCount(ref queue, 16); for (int i = 0; i < 8; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 8); queue.Clear(); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif queue.Dispose(); } [Test] [TestRequiresCollectionChecks] public void Double_Deallocate_Throws() { var queue = new NativeQueue(CommonRwdAllocator.Handle); queue.Dispose(); Assert.Throws( () => { queue.Dispose(); }); } [Test] public void EnqueueScalability() { var queue = new NativeQueue(Allocator.Persistent); for (int i = 0; i != 1000 * 100; i++) { queue.Enqueue(i); } ExpectedCount(ref queue, 1000 * 100); for (int i = 0; i != 1000 * 100; i++) Assert.AreEqual(i, queue.Dequeue()); ExpectedCount(ref queue, 0); queue.Dispose(); } [Test] public void Enqueue_Wrap() { var queue = new NativeQueue(Allocator.Temp); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif for (int i = 0; i < 256; ++i) queue.Enqueue(i); ExpectedCount(ref queue, 256); for (int i = 0; i < 128; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 128); for (int i = 0; i < 128; ++i) queue.Enqueue(i); ExpectedCount(ref queue, 256); for (int i = 128; i < 256; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 128); for (int i = 0; i < 128; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif queue.Dispose(); } [Test] public void ConcurrentEnqueue_Wrap() { var queue = new NativeQueue(Allocator.Temp); var cQueue = queue.AsParallelWriter(); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif for (int i = 0; i < 256; ++i) cQueue.Enqueue(i); ExpectedCount(ref queue, 256); for (int i = 0; i < 128; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 128); for (int i = 0; i < 128; ++i) cQueue.Enqueue(i); ExpectedCount(ref queue, 256); for (int i = 128; i < 256; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 128); for (int i = 0; i < 128; ++i) Assert.AreEqual(i, queue.Dequeue(), "Got the wrong value from the queue"); ExpectedCount(ref queue, 0); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { queue.Dequeue(); }); #endif queue.Dispose(); } [Test] public void NativeQueue_DisposeJob() { var container = new NativeQueue(Allocator.Persistent); Assert.True(container.IsCreated); Assert.DoesNotThrow(() => { container.Enqueue(0); }); var disposeJob = container.Dispose(default); Assert.False(container.IsCreated); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws( () => { container.Enqueue(0); }); #endif disposeJob.Complete(); } [Test] public void TryDequeue_OnEmptyQueueWhichHadElements_RetainsValidState() { using (var queue = new NativeQueue(Allocator.Temp)) { for (int i = 0; i < 3; i++) { queue.Enqueue(i); Assert.AreEqual(1, queue.Count); int value; while (queue.TryDequeue(out value)) { Assert.AreEqual(i, value); } Assert.AreEqual(0, queue.Count); } } } [Test] public void TryDequeue_OnEmptyQueue_RetainsValidState() { using (var queue = new NativeQueue(Allocator.Temp)) { Assert.IsFalse(queue.TryDequeue(out _)); queue.Enqueue(1); Assert.AreEqual(1, queue.Count); } } [Test] public void ToArray_ContainsCorrectElements() { using (var queue = new NativeQueue(Allocator.Temp)) { for (int i = 0; i < 100; i++) queue.Enqueue(i); using (var array = queue.ToArray(Allocator.Temp)) { Assert.AreEqual(queue.Count, array.Length); for (int i = 0; i < array.Length; i++) Assert.AreEqual(i, array[i]); } } } [Test] public void ToArray_RespectsDequeue() { using (var queue = new NativeQueue(Allocator.Temp)) { for (int i = 0; i < 100; i++) queue.Enqueue(i); for (int i = 0; i < 50; i++) queue.Dequeue(); using (var array = queue.ToArray(Allocator.Temp)) { Assert.AreEqual(queue.Count, array.Length); for (int i = 0; i < array.Length; i++) Assert.AreEqual(50 + i, array[i]); } } } [Test] [TestRequiresCollectionChecks] public void NativeQueue_UseAfterFree_UsesCustomOwnerTypeName() { var container = new NativeQueue(CommonRwdAllocator.Handle); container.Enqueue(123); container.Dispose(); NUnit.Framework.Assert.That(() => container.Dequeue(), Throws.Exception.TypeOf() .With.Message.Contains($"The {container.GetType()} has been deallocated")); } [Test] public void NativeQueue_CustomAllocatorTest() { AllocatorManager.Initialize(); var allocatorHelper = new AllocatorHelper(AllocatorManager.Persistent); ref var allocator = ref allocatorHelper.Allocator; allocator.Initialize(); using (var container = new NativeQueue(allocator.Handle)) { } Assert.IsTrue(allocator.WasUsed); allocator.Dispose(); allocatorHelper.Dispose(); AllocatorManager.Shutdown(); } [BurstCompile(CompileSynchronously = true)] struct BurstedCustomAllocatorJob : IJob { [NativeDisableUnsafePtrRestriction] public unsafe CustomAllocatorTests.CountingAllocator* Allocator; public void Execute() { unsafe { using (var container = new NativeQueue(Allocator->Handle)) { } } } } [Test] public unsafe void NativeQueue_BurstedCustomAllocatorTest() { AllocatorManager.Initialize(); var allocatorHelper = new AllocatorHelper(AllocatorManager.Persistent); ref var allocator = ref allocatorHelper.Allocator; allocator.Initialize(); var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf(ref allocator); unsafe { var handle = new BurstedCustomAllocatorJob { Allocator = allocatorPtr }.Schedule(); handle.Complete(); } Assert.IsTrue(allocator.WasUsed); allocator.Dispose(); allocatorHelper.Dispose(); AllocatorManager.Shutdown(); } public struct NestedContainer { public NativeQueue data; } [Test] public void NativeQueue_Nested() { var inner = new NativeQueue(CommonRwdAllocator.Handle); NestedContainer nestedStruct = new NestedContainer { data = inner }; var containerNestedStruct = new NativeQueue(CommonRwdAllocator.Handle); var containerNested = new NativeQueue>(CommonRwdAllocator.Handle); containerNested.Enqueue(inner); containerNestedStruct.Enqueue(nestedStruct); containerNested.Dispose(); containerNestedStruct.Dispose(); inner.Dispose(); } [Test] public void NativeQueue_ReadOnly() { var container = new NativeQueue(CommonRwdAllocator.Handle); container.Enqueue(123); container.Enqueue(456); container.Enqueue(789); var ro = container.AsReadOnly(); Assert.AreEqual(3, ro.Count); Assert.AreEqual(123, ro[0]); Assert.AreEqual(456, ro[1]); Assert.AreEqual(789, ro[2]); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { _ = ro[3]; }); Assert.Throws(() => { _ = ro[-1]; }); Assert.Throws(() => { _ = ro[int.MaxValue]; }); Assert.Throws(() => { _ = ro[int.MinValue]; }); #endif container.Dispose(); } // Burst error BC1071: Unsupported assert type // [BurstCompile(CompileSynchronously = true)] struct NativeQueueTestAsReadOnly : IJob { [ReadOnly] NativeQueue.ReadOnly container; public NativeQueueTestAsReadOnly(NativeQueue.ReadOnly container) { this.container = container; } public void Execute() { var ro = container; Assert.AreEqual(3, ro.Count); Assert.AreEqual(123, ro[0]); Assert.AreEqual(456, ro[1]); Assert.AreEqual(789, ro[2]); #if ENABLE_UNITY_COLLECTIONS_CHECKS Assert.Throws(() => { _ = ro[3]; }); Assert.Throws(() => { _ = ro[-1]; }); Assert.Throws(() => { _ = ro[int.MaxValue]; }); Assert.Throws(() => { _ = ro[int.MinValue]; }); #endif } } [Test] [TestRequiresCollectionChecks] public void NativeQueue_ReadOnlyJob() { var container = new NativeQueue(CommonRwdAllocator.Handle); container.Enqueue(123); container.Enqueue(456); container.Enqueue(789); var job = new NativeQueueTestAsReadOnly(container.AsReadOnly()).Schedule(); Assert.Throws(() => { container.Enqueue(987); }); Assert.Throws(() => { container.Dequeue(); }); Assert.Throws(() => { container.Dispose(); }); job.Complete(); Assert.DoesNotThrow(() => { container.Enqueue(987); }); Assert.DoesNotThrow(() => { container.Dequeue(); }); Assert.DoesNotThrow(() => { container.Dispose(); }); } struct NativeQueueTestWriteMappedToReadOnly : IJob { [WriteOnly] public NativeQueue.ParallelWriter Container; public void Execute() { } } [Test] [TestRequiresCollectionChecks] public void NativeQueue_ReadOnlyCannotScheduledForWrite() { var container = new NativeQueue(CommonRwdAllocator.Handle); container.Enqueue(123); container.Enqueue(456); container.Enqueue(789); var ro = container.AsReadOnly(); var job = new NativeQueueTestWriteMappedToReadOnly { Container = container.AsParallelWriter() }.Schedule(); Assert.Throws(() => { _ = ro.Count; }); Assert.Throws(() => { _ = ro[0]; }); Assert.Throws(() => { _ = ro[1]; }); Assert.Throws(() => { _ = ro[2]; }); Assert.Throws(() => { foreach (var item in ro) { } }); job.Complete(); Assert.AreEqual(3, ro.Count); Assert.AreEqual(123, ro[0]); Assert.AreEqual(456, ro[1]); Assert.AreEqual(789, ro[2]); Assert.DoesNotThrow(() => { foreach (var item in ro) { } }); container.Dispose(); } [Test] public void NativeQueue_ReadOnlyForEach() { var container = new NativeQueue(CommonRwdAllocator.Handle); container.Enqueue(123); container.Enqueue(456); container.Enqueue(789); var ro = container.AsReadOnly(); var idx = 0; foreach (var item in ro) { Assert.AreEqual(item, ro[idx++]); } container.Dispose(); } struct NativeQueue_ForEachIterator : IJob { [ReadOnly] public NativeQueue.Enumerator Iter; public void Execute() { while (Iter.MoveNext()) { } } } [Test] [TestRequiresCollectionChecks] public void NativeQueue_ForEach_Throws_Job_Iterator() { using (var container = new NativeQueue(CommonRwdAllocator.Handle)) { var jobHandle = new NativeQueue_ForEachIterator { Iter = container.AsReadOnly().GetEnumerator() }.Schedule(); Assert.Throws(() => { container.Enqueue(987); }); jobHandle.Complete(); } } struct NativeQueueParallelWriteJob : IJobParallelFor { [WriteOnly] public NativeQueue.ParallelWriter Writer; public void Execute(int index) { Writer.Enqueue(index); } } [Test] [TestRequiresCollectionChecks] public void NativeQueue_ForEach_Throws() { using (var container = new NativeQueue(CommonRwdAllocator.Handle)) { var iter = container.AsReadOnly().GetEnumerator(); var jobHandle = new NativeQueueParallelWriteJob { Writer = container.AsParallelWriter() }.Schedule(1, 2); Assert.Throws(() => { while (iter.MoveNext()) { } }); jobHandle.Complete(); } } struct NativeQueue_ForEach_Job : IJob { public NativeQueue.ReadOnly Input; public void Execute() { var index = 0; foreach (var value in Input) { Assert.AreEqual(value, Input[index++]); } } } [Test] public void NativeQueue_ForEach_From_Job([Values(10, 1000)] int n) { var seen = new NativeArray(n, Allocator.Temp); using (var container = new NativeQueue(CommonRwdAllocator.Handle)) { for (int i = 0; i < n; i++) { container.Enqueue(i * 37); } new NativeQueue_ForEach_Job { Input = container.AsReadOnly(), }.Run(); } } }