using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace Unity.Collections { /// /// An indexable collection. /// /// The type of the elements in the collection. public interface IIndexable where T : unmanaged { /// /// The current number of elements in the collection. /// /// The current number of elements in the collection. int Length { get; set; } /// /// Returns a reference to the element at a given index. /// /// The index to access. Must be in the range of [0..Length). /// A reference to the element at the index. ref T ElementAt(int index); } /// /// A resizable list. /// /// The type of the elements. public interface INativeList : IIndexable where T : unmanaged { /// /// The number of elements that fit in the current allocation. /// /// The number of elements that fit in the current allocation. /// A new capacity. int Capacity { get; set; } /// /// Whether this list is empty. /// /// True if this list is empty. bool IsEmpty { get; } /// /// The element at an index. /// /// An index. /// The element at the index. /// Thrown if index is out of bounds. T this[int index] { get; set; } /// /// Sets the length to 0. /// /// Does not change the capacity. void Clear(); } /// /// An unmanaged, resizable list. /// /// The elements are stored contiguously in a buffer rather than as linked nodes. /// The type of the elements. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [DebuggerDisplay("Length = {m_ListData == null ? default : m_ListData->Length}, Capacity = {m_ListData == null ? default : m_ListData->Capacity}")] [DebuggerTypeProxy(typeof(NativeListDebugView<>))] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public unsafe struct NativeList : INativeDisposable , INativeList , IEnumerable // Used by collection initializers. where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal int m_SafetyIndexHint; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #endif [NativeDisableUnsafePtrRestriction] internal UnsafeList* m_ListData; /// /// Initializes and returns a NativeList with a capacity of one. /// /// The allocator to use. public NativeList(AllocatorManager.AllocatorHandle allocator) : this(1, allocator) { } /// /// Initializes and returns a NativeList. /// /// The initial capacity of the list. /// The allocator to use. public NativeList(int initialCapacity, AllocatorManager.AllocatorHandle allocator) { this = default; AllocatorManager.AllocatorHandle temp = allocator; Initialize(initialCapacity, ref temp); } [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(AllocatorManager.AllocatorHandle) })] internal void Initialize(int initialCapacity, ref U allocator) where U : unmanaged, AllocatorManager.IAllocator { var totalSize = sizeof(T) * (long)initialCapacity; #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.CheckAllocator(allocator.Handle); CheckInitialCapacity(initialCapacity); CheckTotalSize(initialCapacity, totalSize); m_Safety = CollectionHelper.CreateSafetyHandle(allocator.Handle); CollectionHelper.InitNativeContainer(m_Safety); CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); m_SafetyIndexHint = (allocator.Handle).AddSafetyHandle(m_Safety); AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); #endif m_ListData = UnsafeList.Create(initialCapacity, ref allocator, NativeArrayOptions.UninitializedMemory); } [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(AllocatorManager.AllocatorHandle) })] internal static NativeList New(int initialCapacity, ref U allocator) where U : unmanaged, AllocatorManager.IAllocator { var nativelist = new NativeList(); nativelist.Initialize(initialCapacity, ref allocator); return nativelist; } /// /// The element at a given index. /// /// An index into this list. /// The value to store at the `index`. /// Thrown if `index` is out of bounds. public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return (*m_ListData)[index]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif (*m_ListData)[index] = value; } } /// /// Returns a reference to the element at an index. /// /// An index. /// A reference to the element at the index. /// Thrown if index is out of bounds. public ref T ElementAt(int index) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif return ref m_ListData->ElementAt(index); } /// /// The count of elements. /// /// The current count of elements. Always less than or equal to the capacity. /// To decrease the memory used by a list, set after reducing the length of the list. /// The new length. If the new length is greater than the current capacity, the capacity is increased. /// Newly allocated memory is cleared. public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return CollectionHelper.AssumePositive(m_ListData->Length); } set { // Unity 2022.2.16f1 removes the global temp safety handle so only // from this version onward is it not a breaking change to perform this check // since all previous versions did not have this safety check and will // likely break from safe usage that is blurred by sharing the temp safety handle // between multiple containers #if ENABLE_UNITY_COLLECTIONS_CHECKS && UNITY_2022_2_16F1_OR_NEWER AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->Resize(value, NativeArrayOptions.ClearMemory); } } /// /// The number of elements that fit in the current allocation. /// /// The number of elements that fit in the current allocation. /// The new capacity. Must be greater or equal to the length. /// Thrown if the new capacity is smaller than the length. public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_ListData->Capacity; } set { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->Capacity = value; } } /// /// Returns the internal unsafe list. /// /// Internally, the elements of a NativeList are stored in an UnsafeList. /// The internal unsafe list. public UnsafeList* GetUnsafeList() => m_ListData; /// /// Appends an element to the end of this list. /// /// The value to add to the end of this list. /// /// Length is incremented by 1. Will not increase the capacity. /// /// Thrown if incrementing the length would exceed the capacity. public void AddNoResize(T value) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif m_ListData->AddNoResize(value); } /// /// Appends elements from a buffer to the end of this list. /// /// The buffer to copy from. /// The number of elements to copy from the buffer. /// /// Length is increased by the count. Will not increase the capacity. /// /// Thrown if the increased length would exceed the capacity. public void AddRangeNoResize(void* ptr, int count) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif CheckArgPositive(count); m_ListData->AddRangeNoResize(ptr, count); } /// /// Appends the elements of another list to the end of this list. /// /// The other list to copy from. /// /// Length is increased by the length of the other list. Will not increase the capacity. /// /// Thrown if the increased length would exceed the capacity. public void AddRangeNoResize(NativeList list) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif m_ListData->AddRangeNoResize(*list.m_ListData); } /// /// Appends an element to the end of this list. /// /// The value to add to the end of this list. /// /// Length is incremented by 1. If necessary, the capacity is increased. /// public void Add(in T value) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->Add(in value); } /// /// Appends the elements of an array to the end of this list. /// /// The array to copy from. /// /// Length is increased by the number of new elements. Does not increase the capacity. /// /// Thrown if the increased length would exceed the capacity. public void AddRange(NativeArray array) { AddRange(array.GetUnsafeReadOnlyPtr(), array.Length); } /// /// Appends the elements of a buffer to the end of this list. /// /// The buffer to copy from. /// The number of elements to copy from the buffer. /// Thrown if count is negative. public void AddRange(void* ptr, int count) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif CheckArgPositive(count); m_ListData->AddRange(ptr, CollectionHelper.AssumePositive(count)); } /// /// Appends value count times to the end of this list. /// /// The value to add to the end of this list. /// The number of times to replicate the value. /// /// Length is incremented by count. If necessary, the capacity is increased. /// /// Thrown if count is negative. public void AddReplicate(in T value, int count) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif CheckArgPositive(count); m_ListData->AddReplicate(in value, CollectionHelper.AssumePositive(count)); } /// /// Shifts elements toward the end of this list, increasing its length. /// /// /// Right-shifts elements in the list so as to create 'free' slots at the beginning or in the middle. /// /// The length is increased by `end - begin`. If necessary, the capacity will be increased accordingly. /// /// If `end` equals `begin`, the method does nothing. /// /// The element at index `begin` will be copied to index `end`, the element at index `begin + 1` will be copied to `end + 1`, and so forth. /// /// The indexes `begin` up to `end` are not cleared: they will contain whatever values they held prior. /// /// The index of the first element that will be shifted up. /// The index where the first shifted element will end up. /// Thrown if `end < begin`. /// Thrown if `begin` or `end` are out of bounds. public void InsertRangeWithBeginEnd(int begin, int end) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->InsertRangeWithBeginEnd(begin, end); } /// /// Shifts elements toward the end of this list, increasing its length. /// /// /// Right-shifts elements in the list so as to create 'free' slots at the beginning or in the middle. /// /// The length is increased by `count`. If necessary, the capacity will be increased accordingly. /// /// If `count` equals `0`, the method does nothing. /// /// The element at index `index` will be copied to index `index + count`, the element at index `index + 1` will be copied to `index + count + 1`, and so forth. /// /// The indexes `index` up to `index + count` are not cleared: they will contain whatever values they held prior. /// /// The index of the first element that will be shifted up. /// The number of elements to insert. /// Thrown if `count` is negative. /// Thrown if `index` is out of bounds. public void InsertRange(int index, int count) => InsertRangeWithBeginEnd(index, index + count); /// /// Copies the last element of this list to the specified index. Decrements the length by 1. /// /// Useful as a cheap way to remove an element from this list when you don't care about preserving order. /// The index to overwrite with the last element. /// Thrown if `index` is out of bounds. public void RemoveAtSwapBack(int index) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->RemoveAtSwapBack(index); } /// /// Copies the last *N* elements of this list to a range in this list. Decrements the length by *N*. /// /// /// Copies the last `count` elements to the indexes `index` up to `index + count`. /// /// Useful as a cheap way to remove elements from a list when you don't care about preserving order. /// /// The index of the first element to overwrite. /// The number of elements to copy and remove. /// Thrown if `index` is out of bounds, `count` is negative, /// or `index + count` exceeds the length. public void RemoveRangeSwapBack(int index, int count) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->RemoveRangeSwapBack(index, count); } /// /// Removes the element at an index, shifting everything above it down by one. Decrements the length by 1. /// /// The index of the item to remove. /// /// If you don't care about preserving the order of the elements, is a more efficient way to remove elements. /// /// Thrown if `index` is out of bounds. public void RemoveAt(int index) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->RemoveAt(index); } /// /// Removes *N* elements in a range, shifting everything above the range down by *N*. Decrements the length by *N*. /// /// The index of the first element to remove. /// The number of elements to remove. /// /// If you don't care about preserving the order of the elements, `RemoveRangeSwapBackWithBeginEnd` /// is a more efficient way to remove elements. /// /// Thrown if `index` is out of bounds, `count` is negative, /// or `index + count` exceeds the length. public void RemoveRange(int index, int count) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->RemoveRange(index, count); } /// /// Whether this list is empty. /// /// True if the list is empty or if the list has not been constructed. public readonly bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_ListData == null || m_ListData->Length == 0; } /// /// Whether this list has been allocated (and not yet deallocated). /// /// True if this list has been allocated (and not yet deallocated). public readonly bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_ListData != null; } /// /// Releases all resources (memory and safety handles). /// public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return; } #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.DisposeSafetyHandle(ref m_Safety); #endif UnsafeList.Destroy(m_ListData); m_ListData = null; } /// /// Releases all resources (memory and safety handles). /// The type of allocator. /// The allocator that was used to allocate this list. /// [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })] internal void Dispose(ref U allocator) where U : unmanaged, AllocatorManager.IAllocator { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return; } CheckHandleMatches(allocator.Handle); #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.DisposeSafetyHandle(ref m_Safety); #endif UnsafeList.Destroy(m_ListData, ref allocator); m_ListData = null; } /// /// Creates and schedules a job that releases all resources (memory and safety handles) of this list. /// /// The dependency for the new job. /// The handle of the new job. The job depends upon `inputDeps` and releases all resources (memory and safety handles) of this list. public JobHandle Dispose(JobHandle inputDeps) { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return inputDeps; } #if ENABLE_UNITY_COLLECTIONS_CHECKS var jobHandle = new NativeListDisposeJob { Data = new NativeListDispose { m_ListData = (UntypedUnsafeList*)m_ListData, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new NativeListDisposeJob { Data = new NativeListDispose { m_ListData = (UntypedUnsafeList*)m_ListData } }.Schedule(inputDeps); #endif m_ListData = null; return jobHandle; } /// /// Sets the length to 0. /// /// Does not change the capacity. public void Clear() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->Clear(); } /// /// **Obsolete.** Use method to do explicit cast instead. /// /// /// Returns a native array that aliases the content of a list. /// /// The list to alias. /// A native array that aliases the content of the list. [Obsolete("Implicit cast from `NativeList` to `NativeArray` has been deprecated; Use '.AsArray()' method to do explicit cast instead.", false)] public static implicit operator NativeArray(NativeList nativeList) { return nativeList.AsArray(); } /// /// Returns a native array that aliases the content of this list. /// /// A native array that aliases the content of this list. public NativeArray AsArray() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety); var arraySafety = m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref arraySafety); #endif var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_ListData->Ptr, m_ListData->Length, Allocator.None); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, arraySafety); #endif return array; } /// /// Returns an array that aliases this list. The length of the array is updated when the length of /// this array is updated in a prior job. /// /// /// Useful when a job populates a list that is then used by another job. /// /// If you pass both jobs the same list, you have to complete the first job before you schedule the second: /// otherwise, the second job doesn't see the first job's changes to the list's length. /// /// If instead you pass the second job a deferred array that aliases the list, the array's length is kept in sync with /// the first job's changes to the list's length. Consequently, the first job doesn't have to /// be completed before you can schedule the second: the second job simply has to depend upon the first. /// /// An array that aliases this list and whose length can be specially modified across jobs. /// /// The following example populates a list with integers in one job and passes that data to a second job as /// a deferred array. If we tried to pass the list directly to the second job, that job would not see any /// modifications made to the list by the first job. To avoid this, we instead pass the second job a deferred array that aliases the list. /// /// using UnityEngine; /// using Unity.Jobs; /// using Unity.Collections; /// /// public class DeferredArraySum : MonoBehaviour ///{ /// public struct Populate : IJob /// { /// public NativeList<int> list; /// /// public void Execute() /// { /// for (int i = list.Length; i < list.Capacity; i++) /// { /// list.Add(i); /// } /// } /// } /// /// // Sums all numbers from deferred. /// public struct Sum : IJob /// { /// [ReadOnly] public NativeArray<int> deferred; /// public NativeArray<int> sum; /// /// public void Execute() /// { /// sum[0] = 0; /// for (int i = 0; i < deferred.Length; i++) /// { /// sum[0] += deferred[i]; /// } /// } /// } /// /// void Start() /// { /// var list = new NativeList<int>(100, Allocator.TempJob); /// var deferred = list.AsDeferredJobArray(), /// var output = new NativeArray<int>(1, Allocator.TempJob); /// /// // The Populate job increases the list's length from 0 to 100. /// var populate = new Populate { list = list }.Schedule(); /// /// // At time of scheduling, the length of the deferred array given to Sum is 0. /// // When Populate increases the list's length, the deferred array's length field in the /// // Sum job is also modified, even though it has already been scheduled. /// var sum = new Sum { deferred = deferred, sum = output }.Schedule(populate); /// /// sum.Complete(); /// /// Debug.Log("Result: " + output[0]); /// /// list.Dispose(); /// output.Dispose(); /// } /// } /// /// public NativeArray AsDeferredJobArray() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); #endif byte* buffer = (byte*)m_ListData; // We use the first bit of the pointer to infer that the array is in list mode // Thus the job scheduling code will need to patch it. buffer += 1; var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(buffer, 0, Allocator.Invalid); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_Safety); #endif return array; } /// /// Returns an array containing a copy of this list's content. /// /// The allocator to use. /// An array containing a copy of this list's content. public NativeArray ToArray(AllocatorManager.AllocatorHandle allocator) { NativeArray result = CollectionHelper.CreateNativeArray(Length, allocator, NativeArrayOptions.UninitializedMemory); #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); AtomicSafetyHandle.CheckWriteAndThrow(result.m_Safety); #endif UnsafeUtility.MemCpy((byte*)result.m_Buffer, (byte*)m_ListData->Ptr, Length * UnsafeUtility.SizeOf()); return result; } /// /// Copies all elements of specified container to this container. /// /// An container to copy into this container. public void CopyFrom(in NativeArray other) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); AtomicSafetyHandle.CheckReadAndThrow(other.m_Safety); #endif m_ListData->CopyFrom(other); } /// /// Copies all elements of specified container to this container. /// /// An container to copy into this container. public void CopyFrom(in UnsafeList other) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->CopyFrom(other); } /// /// Copies all elements of specified container to this container. /// /// An container to copy into this container. public void CopyFrom(in NativeList other) { CopyFrom(*other.m_ListData); } /// /// Returns an enumerator over the elements of this list. /// /// An enumerator over the elements of this list. public NativeArray.Enumerator GetEnumerator() { var array = AsArray(); return new NativeArray.Enumerator(ref array); } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// Sets the length of this list, increasing the capacity if necessary. /// /// The new length of this list. /// Whether to clear any newly allocated bytes to all zeroes. public void Resize(int length, NativeArrayOptions options) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_ListData->Resize(length, options); } /// /// Sets the length of this list, increasing the capacity if necessary. /// /// Does not clear newly allocated bytes. /// The new length of this list. public void ResizeUninitialized(int length) { Resize(length, NativeArrayOptions.UninitializedMemory); } /// /// Sets the capacity. /// /// The new capacity. public void SetCapacity(int capacity) { m_ListData->SetCapacity(capacity); } /// /// Sets the capacity to match the length. /// public void TrimExcess() { m_ListData->TrimExcess(); } /// /// Returns a read only of this list. /// /// A read only of this list. public NativeArray.ReadOnly AsReadOnly() { #if ENABLE_UNITY_COLLECTIONS_CHECKS return new NativeArray.ReadOnly(m_ListData->Ptr, m_ListData->Length, ref m_Safety); #else return new NativeArray.ReadOnly(m_ListData->Ptr, m_ListData->Length); #endif } /// /// Returns a parallel reader of this list. /// /// A parallel reader of this list. // [Obsolete("'AsParallelReader' has been deprecated; use 'AsReadOnly' instead. (UnityUpgradable) -> AsReadOnly")] public NativeArray.ReadOnly AsParallelReader() { #if ENABLE_UNITY_COLLECTIONS_CHECKS return new NativeArray.ReadOnly(m_ListData->Ptr, m_ListData->Length, ref m_Safety); #else return new NativeArray.ReadOnly(m_ListData->Ptr, m_ListData->Length); #endif } /// /// Returns a parallel writer of this list. /// /// A parallel writer of this list. public ParallelWriter AsParallelWriter() { #if ENABLE_UNITY_COLLECTIONS_CHECKS return new ParallelWriter(m_ListData, ref m_Safety); #else return new ParallelWriter(m_ListData); #endif } /// /// A parallel writer for a NativeList. /// /// /// Use to create a parallel writer for a list. /// [NativeContainer] [NativeContainerIsAtomicWriteOnly] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public unsafe struct ParallelWriter { /// /// The data of the list. /// public readonly void* Ptr { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ListData->Ptr; } /// /// The internal unsafe list. /// /// The internal unsafe list. [NativeDisableUnsafePtrRestriction] public UnsafeList* ListData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); [GenerateTestsForBurstCompatibility(CompileTarget = GenerateTestsForBurstCompatibilityAttribute.BurstCompatibleCompileTarget.Editor)] internal unsafe ParallelWriter(UnsafeList* listData, ref AtomicSafetyHandle safety) { ListData = listData; m_Safety = safety; CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); } #else internal unsafe ParallelWriter(UnsafeList* listData) { ListData = listData; } #endif /// /// Appends an element to the end of this list. /// /// The value to add to the end of this list. /// /// Increments the length by 1 unless doing so would exceed the current capacity. /// /// Thrown if adding an element would exceed the capacity. public void AddNoResize(T value) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif var idx = Interlocked.Increment(ref ListData->m_length) - 1; CheckSufficientCapacity(ListData->Capacity, idx + 1); UnsafeUtility.WriteArrayElement(ListData->Ptr, idx, value); } /// /// Appends elements from a buffer to the end of this list. /// /// The buffer to copy from. /// The number of elements to copy from the buffer. /// /// Increments the length by `count` unless doing so would exceed the current capacity. /// /// Thrown if adding the elements would exceed the capacity. public void AddRangeNoResize(void* ptr, int count) { CheckArgPositive(count); #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif var idx = Interlocked.Add(ref ListData->m_length, count) - count; CheckSufficientCapacity(ListData->Capacity, idx + count); var sizeOf = sizeof(T); void* dst = (byte*)ListData->Ptr + idx * sizeOf; UnsafeUtility.MemCpy(dst, ptr, count * sizeOf); } /// /// Appends the elements of another list to the end of this list. /// /// The other list to copy from. /// /// Increments the length of this list by the length of the other list unless doing so would exceed the current capacity. /// /// Thrown if adding the elements would exceed the capacity. public void AddRangeNoResize(UnsafeList list) { AddRangeNoResize(list.Ptr, list.Length); } /// /// Appends the elements of another list to the end of this list. /// /// The other list to copy from. /// /// Increments the length of this list by the length of the other list unless doing so would exceed the current capacity. /// /// Thrown if adding the elements would exceed the capacity. public void AddRangeNoResize(NativeList list) { AddRangeNoResize(*list.m_ListData); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckInitialCapacity(int initialCapacity) { if (initialCapacity < 0) throw new ArgumentOutOfRangeException(nameof(initialCapacity), "Capacity must be >= 0"); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckTotalSize(int initialCapacity, long totalSize) { // Make sure we cannot allocate more than int.MaxValue (2,147,483,647 bytes) // because the underlying UnsafeUtility.Malloc is expecting a int. // TODO: change UnsafeUtility.Malloc to accept a UIntPtr length instead to match C++ API if (totalSize > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(initialCapacity), $"Capacity * sizeof(T) cannot exceed {int.MaxValue} bytes"); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckSufficientCapacity(int capacity, int length) { if (capacity < length) throw new InvalidOperationException($"Length {length} exceeds Capacity {capacity}"); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckIndexInRange(int value, int length) { if (value < 0) throw new IndexOutOfRangeException($"Value {value} must be positive."); if ((uint)value >= (uint)length) throw new IndexOutOfRangeException( $"Value {value} is out of range in NativeList of '{length}' Length."); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckArgPositive(int value) { if (value < 0) throw new ArgumentOutOfRangeException($"Value {value} must be positive."); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] void CheckHandleMatches(AllocatorManager.AllocatorHandle handle) { if(m_ListData == null) throw new ArgumentOutOfRangeException($"Allocator handle {handle} can't match because container is not initialized."); if(m_ListData->Allocator.Index != handle.Index) throw new ArgumentOutOfRangeException($"Allocator handle {handle} can't match because container handle index doesn't match."); if(m_ListData->Allocator.Version != handle.Version) throw new ArgumentOutOfRangeException($"Allocator handle {handle} matches container handle index, but has different version."); } } [NativeContainer] [GenerateTestsForBurstCompatibility] internal unsafe struct NativeListDispose { [NativeDisableUnsafePtrRestriction] public UntypedUnsafeList* m_ListData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif public void Dispose() { var listData = (UnsafeList*)m_ListData; UnsafeList.Destroy(listData); } } [BurstCompile] [GenerateTestsForBurstCompatibility] internal unsafe struct NativeListDisposeJob : IJob { internal NativeListDispose Data; public void Execute() { Data.Dispose(); } } sealed unsafe class NativeListDebugView where T : unmanaged { UnsafeList* Data; public NativeListDebugView(NativeList array) { Data = array.m_ListData; } public T[] Items { get { if (Data == null) { return default; } // Trying to avoid safety checks, so that container can be read in debugger if it's safety handle // is in write-only mode. var length = Data->Length; var dst = new T[length]; fixed (T* pDst = &dst[0]) { UnsafeUtility.MemCpy(pDst, Data->Ptr, length * UnsafeUtility.SizeOf()); } return dst; } } } /// /// Provides extension methods for UnsafeList. /// [GenerateTestsForBurstCompatibility] public unsafe static class NativeListExtensions { /// /// Returns true if a particular value is present in this list. /// /// The type of elements in this list. /// The value type. /// The list to search. /// The value to locate. /// True if the value is present in this list. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })] public static bool Contains(this NativeList list, U value) where T : unmanaged, IEquatable { return NativeArrayExtensions.IndexOf(list.GetUnsafeReadOnlyPtr(), list.Length, value) != -1; } /// /// Finds the index of the first occurrence of a particular value in this list. /// /// The type of elements in the list. /// The value type. /// The list to search. /// The value to locate. /// The index of the first occurrence of the value in this list. Returns -1 if no occurrence is found. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int) })] public static int IndexOf(this NativeList list, U value) where T : unmanaged, IEquatable { return NativeArrayExtensions.IndexOf(list.GetUnsafeReadOnlyPtr(), list.Length, value); } /// /// Returns true if this container and another have equal length and content. /// /// The type of the source container's elements. /// The container to compare for equality. /// The other container to compare for equality. /// True if the containers have equal length and content. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static bool ArraysEqual(this NativeArray container, in NativeList other) where T : unmanaged, IEquatable { return container.ArraysEqual(other.AsArray()); } /// /// Returns true if this container and another have equal length and content. /// /// The type of the source container's elements. /// The container to compare for equality. /// The other container to compare for equality. /// True if the containers have equal length and content. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static bool ArraysEqual(this NativeList container, in NativeArray other) where T : unmanaged, IEquatable { return other.ArraysEqual(container); } /// /// Returns true if this container and another have equal length and content. /// /// The type of the source container's elements. /// The container to compare for equality. /// The other container to compare for equality. /// True if the containers have equal length and content. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static bool ArraysEqual(this NativeList container, in NativeList other) where T : unmanaged, IEquatable { return container.AsArray().ArraysEqual(other.AsArray()); } /// /// Returns true if this container and another have equal length and content. /// /// The type of the source container's elements. /// The container to compare for equality. /// The other container to compare for equality. /// True if the containers have equal length and content. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static bool ArraysEqual(this NativeList container, in UnsafeList other) where T : unmanaged, IEquatable { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(container.m_Safety); #endif return container.m_ListData->ArraysEqual(other); } } } namespace Unity.Collections.LowLevel.Unsafe { /// /// Provides unsafe utility methods for NativeList. /// [GenerateTestsForBurstCompatibility] public unsafe static class NativeListUnsafeUtility { /// /// Returns a pointer to this list's internal buffer. /// /// Performs a job safety check for read-write access. /// The list. /// The type of the elements. /// A pointer to this list's internal buffer. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public static T* GetUnsafePtr(this NativeList list) where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(list.m_Safety); #endif return list.m_ListData->Ptr; } /// /// Returns a pointer to this list's internal buffer. /// /// Performs a job safety check for read-only access. /// The list. /// The type of the elements. /// A pointer to this list's internal buffer. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public static unsafe T* GetUnsafeReadOnlyPtr(this NativeList list) where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(list.m_Safety); #endif return list.m_ListData->Ptr; } #if ENABLE_UNITY_COLLECTIONS_CHECKS /// /// Returns this list's . /// /// The list. /// The type of the elements. /// The atomic safety handle for this list. /// /// The job safety checks use a native collection's atomic safety handle to assert safety. /// /// This method is only available if the symbol `ENABLE_UNITY_COLLECTIONS_CHECKS` is defined. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) }, RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", CompileTarget = GenerateTestsForBurstCompatibilityAttribute.BurstCompatibleCompileTarget.Editor)] public static AtomicSafetyHandle GetAtomicSafetyHandle(ref NativeList list) where T : unmanaged { return list.m_Safety; } #endif /// /// Returns a pointer to this list's internal unsafe list. /// /// Performs no job safety checks. /// The list. /// The type of the elements. /// A pointer to this list's internal unsafe list. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public static void* GetInternalListDataPtrUnchecked(ref NativeList list) where T : unmanaged { return list.m_ListData; } } }