using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using Unity.Jobs; using Unity.Mathematics; using UnityEngine.Assertions; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; #pragma warning disable 618 // disable obsolete warnings namespace Unity.Collections.LowLevel.Unsafe { /// /// An unmanaged, untyped, resizable list, without any thread safety check features. /// [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] [StructLayout(LayoutKind.Sequential)] [Obsolete("Untyped UnsafeList is deprecated, please use UnsafeList instead. (RemovedAfter 2021-05-18)", false)] public unsafe struct UnsafeList : INativeDisposable { /// /// [NativeDisableUnsafePtrRestriction] public void* Ptr; /// /// public int Length; public readonly int unused; /// /// public int Capacity; /// /// public AllocatorManager.AllocatorHandle Allocator; /// /// Constructs a new container with type of memory allocation. /// /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// The list initially has a capacity of one. To avoid reallocating memory for the list, specify /// sufficient capacity up front. public UnsafeList(Allocator allocator) : this() { Ptr = null; Length = 0; Capacity = 0; Allocator = allocator; } /// /// Constructs container as view into memory. /// /// Pointer to data. /// Lenght of data in bytes. public UnsafeList(void* ptr, int length) : this() { Ptr = ptr; Length = length; Capacity = length; Allocator = Collections.Allocator.None; } internal void Initialize(int sizeOf, int alignOf, int initialCapacity, ref U allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) where U : unmanaged, AllocatorManager.IAllocator { Allocator = allocator.Handle; Ptr = null; Length = 0; Capacity = 0; if (initialCapacity != 0) { SetCapacity(ref allocator, sizeOf, alignOf, initialCapacity); } if (options == NativeArrayOptions.ClearMemory && Ptr != null) { UnsafeUtility.MemClear(Ptr, Capacity * sizeOf); } } internal static UnsafeList New(int sizeOf, int alignOf, int initialCapacity, ref U allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) where U : unmanaged, AllocatorManager.IAllocator { var temp = new UnsafeList(); temp.Initialize(sizeOf, alignOf, initialCapacity, ref allocator, options); return temp; } /// /// Constructs a new container with the specified initial capacity and type of memory allocation. /// /// Size of element. /// Alignment of element. /// The initial capacity of the list. If the list grows larger than its capacity, /// the internal array is copied to a new, larger array. /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// Memory should be cleared on allocation or left uninitialized. public UnsafeList(int sizeOf, int alignOf, int initialCapacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) : this() { this = default; Initialize(sizeOf, alignOf, initialCapacity, ref allocator, options); } /// /// Constructs a new container with the specified initial capacity and type of memory allocation. /// /// Size of element. /// Alignment of element. /// The initial capacity of the list. If the list grows larger than its capacity, /// the internal array is copied to a new, larger array. /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// Memory should be cleared on allocation or left uninitialized. public UnsafeList(int sizeOf, int alignOf, int initialCapacity, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) : this() { Allocator = allocator; Ptr = null; Length = 0; Capacity = 0; if (initialCapacity != 0) { SetCapacity(sizeOf, alignOf, initialCapacity); } if (options == NativeArrayOptions.ClearMemory && Ptr != null) { UnsafeUtility.MemClear(Ptr, Capacity * sizeOf); } } /// /// Creates a new container with the specified initial capacity and type of memory allocation. /// /// Size of element. /// Alignment of element. /// The initial capacity of the list. If the list grows larger than its capacity, /// the internal array is copied to a new, larger array. /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// Memory should be cleared on allocation or left uninitialized. /// New initialized container. public static UnsafeList* Create(int sizeOf, int alignOf, int initialCapacity, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) { var handle = (AllocatorManager.AllocatorHandle)allocator; UnsafeList* listData = AllocatorManager.Allocate(handle); UnsafeUtility.MemClear(listData, UnsafeUtility.SizeOf()); listData->Allocator = allocator; if (initialCapacity != 0) { listData->SetCapacity(sizeOf, alignOf, initialCapacity); } if (options == NativeArrayOptions.ClearMemory && listData->Ptr != null) { UnsafeUtility.MemClear(listData->Ptr, listData->Capacity * sizeOf); } return listData; } internal static UnsafeList* Create(int sizeOf, int alignOf, int initialCapacity, ref U allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) where U : unmanaged, AllocatorManager.IAllocator { UnsafeList* listData = allocator.Allocate(default(UnsafeList), 1); UnsafeUtility.MemClear(listData, UnsafeUtility.SizeOf()); listData->Allocator = allocator.Handle; if (initialCapacity != 0) { listData->SetCapacity(ref allocator, sizeOf, alignOf, initialCapacity); } if (options == NativeArrayOptions.ClearMemory && listData->Ptr != null) { UnsafeUtility.MemClear(listData->Ptr, listData->Capacity * sizeOf); } return listData; } internal static void Destroy(UnsafeList* listData, ref U allocator, int sizeOf, int alignOf) where U : unmanaged, AllocatorManager.IAllocator { CheckNull(listData); listData->Dispose(ref allocator, sizeOf, alignOf); allocator.Free(listData, UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), 1); } /// /// Destroys container. /// /// Container to destroy. public static void Destroy(UnsafeList* listData) { CheckNull(listData); var allocator = listData->Allocator; listData->Dispose(); AllocatorManager.Free(allocator, listData); } /// /// Reports whether container is empty. /// /// True if this string has no characters or if the container has not been constructed. public bool IsEmpty => !IsCreated || Length == 0; /// /// Reports whether memory for the container is allocated. /// /// True if this container object's internal storage has been allocated. /// /// Note that the container storage is not created if you use the default constructor. You must specify /// at least an allocation type to construct a usable container. /// /// *Warning:* the `IsCreated` property can't be used to determine whether a copy of a container is still valid. /// If you dispose any copy of the container, the container storage is deallocated. However, the properties of /// the other copies of the container (including the original) are not updated. As a result the `IsCreated` property /// of the copies still return `true` even though the container storage has been deallocated. /// public bool IsCreated => Ptr != null; /// /// Disposes of this container and deallocates its memory immediately. /// public void Dispose() { if (CollectionHelper.ShouldDeallocate(Allocator)) { AllocatorManager.Free(Allocator, Ptr); Allocator = AllocatorManager.Invalid; } Ptr = null; Length = 0; Capacity = 0; } internal void Dispose(ref U allocator, int sizeOf, int alignOf) where U : unmanaged, AllocatorManager.IAllocator { allocator.Free(Ptr, sizeOf, alignOf, Length); Ptr = null; Length = 0; Capacity = 0; } /// /// Safely disposes of this container and deallocates its memory when the jobs that use it have completed. /// /// You can call this function dispose of the container immediately after scheduling the job. Pass /// the [JobHandle](https://docs.unity3d.com/ScriptReference/Unity.Jobs.JobHandle.html) returned by /// the [Job.Schedule](https://docs.unity3d.com/ScriptReference/Unity.Jobs.IJobExtensions.Schedule.html) /// method using the `jobHandle` parameter so the job scheduler can dispose the container after all jobs /// using it have run. /// The job handle or handles for any scheduled jobs that use this container. /// A new job handle containing the prior handles as well as the handle for the job that deletes /// the container. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { if (CollectionHelper.ShouldDeallocate(Allocator)) { var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = (Allocator)Allocator.Value }.Schedule(inputDeps); Ptr = null; Allocator = AllocatorManager.Invalid; return jobHandle; } Ptr = null; return inputDeps; } /// /// Clears the container. /// /// The container capacity remains unchanged. public void Clear() { Length = 0; } /// /// Changes the list length, resizing if necessary. /// /// Size of element. /// Alignment of element. /// The new length of the list. /// Memory should be cleared on allocation or left uninitialized. public void Resize(int sizeOf, int alignOf, int length, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) { var oldLength = Length; if (length > Capacity) { SetCapacity(sizeOf, alignOf, length); } Length = length; if (options == NativeArrayOptions.ClearMemory && oldLength < length) { var num = length - oldLength; byte* ptr = (byte*)Ptr; UnsafeUtility.MemClear(ptr + oldLength * sizeOf, num * sizeOf); } } /// /// Changes the list length, resizing if necessary. /// /// Source type of elements /// The new length of the list. /// Memory should be cleared on allocation or left uninitialized. public void Resize(int length, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) where T : struct { Resize(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), length, options); } void Realloc(ref U allocator, int sizeOf, int alignOf, int capacity) where U : unmanaged, AllocatorManager.IAllocator { void* newPointer = null; if (capacity > 0) { newPointer = allocator.Allocate(sizeOf, alignOf, capacity); if (Capacity > 0) { var itemsToCopy = math.min(capacity, Capacity); var bytesToCopy = itemsToCopy * sizeOf; UnsafeUtility.MemCpy(newPointer, Ptr, bytesToCopy); } } allocator.Free(Ptr, sizeOf, alignOf, Capacity); Ptr = newPointer; Capacity = capacity; Length = math.min(Length, capacity); } void Realloc(int sizeOf, int alignOf, int capacity) { Realloc(ref Allocator, sizeOf, alignOf, capacity); } void SetCapacity(ref U allocator, int sizeOf, int alignOf, int capacity) where U : unmanaged, AllocatorManager.IAllocator { var newCapacity = math.max(capacity, 64 / sizeOf); newCapacity = math.ceilpow2(newCapacity); if (newCapacity == Capacity) { return; } Realloc(ref allocator, sizeOf, alignOf, newCapacity); } void SetCapacity(int sizeOf, int alignOf, int capacity) { SetCapacity(ref Allocator, sizeOf, alignOf, capacity); } /// /// Set the number of items that can fit in the container. /// /// Source type of elements /// The number of items that the container can hold before it resizes its internal storage. public void SetCapacity(int capacity) where T : struct { SetCapacity(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), capacity); } /// /// Sets the capacity to the actual number of elements in the container. /// /// Source type of elements public void TrimExcess() where T : struct { if (Capacity != Length) { Realloc(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), Length); } } /// /// Searches for the specified element in list. /// /// Source type of elements /// /// The zero-based index of the first occurrence element if found, otherwise returns -1. public int IndexOf(T value) where T : struct, IEquatable { return NativeArrayExtensions.IndexOf(Ptr, Length, value); } /// /// Determines whether an element is in the list. /// /// Source type of elements /// /// True, if element is found. public bool Contains(T value) where T : struct, IEquatable { return IndexOf(value) != -1; } /// /// Adds an element to the list. /// /// Source type of elements /// The value to be added at the end of the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddNoResize(T value) where T : struct { CheckNoResizeHasEnoughCapacity(1); UnsafeUtility.WriteArrayElement(Ptr, Length, value); Length += 1; } void AddRangeNoResize(int sizeOf, void* ptr, int length) { CheckNoResizeHasEnoughCapacity(length); void* dst = (byte*)Ptr + Length * sizeOf; UnsafeUtility.MemCpy(dst, ptr, length * sizeOf); Length += length; } /// /// Adds elements from a buffer to this list. /// /// Source type of elements /// A pointer to the buffer. /// The number of elements to add to the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(void* ptr, int length) where T : struct { AddRangeNoResize(UnsafeUtility.SizeOf(), ptr, length); } /// /// Adds elements from a list to this list. /// /// Source type of elements /// Other container to copy elements from. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(UnsafeList list) where T : struct { AddRangeNoResize(UnsafeUtility.SizeOf(), list.Ptr, CollectionHelper.AssumePositive(list.Length)); } /// /// Adds an element to the list. /// /// Source type of elements /// The value to be added at the end of the list. /// /// If the list has reached its current capacity, it copies the original, internal array to /// a new, larger array, and then deallocates the original. /// public void Add(T value) where T : struct { var idx = Length; if (Length + 1 > Capacity) { Resize(idx + 1); } else { Length += 1; } UnsafeUtility.WriteArrayElement(Ptr, idx, value); } void AddRange(int sizeOf, int alignOf, void* ptr, int length) { var idx = Length; if (Length + length > Capacity) { Resize(sizeOf, alignOf, Length + length); } else { Length += length; } void* dst = (byte*)Ptr + idx * sizeOf; UnsafeUtility.MemCpy(dst, ptr, length * sizeOf); } /// /// Adds elements from a buffer to this list. /// /// Source type of elements /// A pointer to the buffer. /// The number of elements to add to the list. public void AddRange(void* ptr, int length) where T : struct { AddRange(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), ptr, length); } /// /// Adds elements from a list to this list. /// /// /// If the list has reached its current capacity, it copies the original, internal array to /// a new, larger array, and then deallocates the original. /// /// Source type of elements /// Other container to copy elements from. public void AddRange(UnsafeList list) where T : struct { AddRange(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), list.Ptr, list.Length); } void InsertRangeWithBeginEnd(int sizeOf, int alignOf, int begin, int end) { CheckBeginEnd(begin, end); int items = end - begin; if (items < 1) { return; } var oldLength = Length; if (Length + items > Capacity) { Resize(sizeOf, alignOf, Length + items); } else { Length += items; } var itemsToCopy = oldLength - begin; if (itemsToCopy < 1) { return; } var bytesToCopy = itemsToCopy * sizeOf; unsafe { byte* ptr = (byte*)Ptr; byte* dest = ptr + end * sizeOf; byte* src = ptr + begin * sizeOf; UnsafeUtility.MemMove(dest, src, bytesToCopy); } } /// /// Inserts a number of items into a container at a specified zero-based index. /// /// Source type of elements /// The zero-based index at which the new elements should be inserted. /// The zero-based index just after where the elements should be removed. /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void InsertRangeWithBeginEnd(int begin, int end) where T : struct { InsertRangeWithBeginEnd(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), begin, end); } void RemoveRangeSwapBackWithBeginEnd(int sizeOf, int begin, int end) { CheckBeginEnd(begin, end); int itemsToRemove = end - begin; if (itemsToRemove > 0) { int copyFrom = math.max(Length - itemsToRemove, end); void* dst = (byte*)Ptr + begin * sizeOf; void* src = (byte*)Ptr + copyFrom * sizeOf; UnsafeUtility.MemCpy(dst, src, (Length - copyFrom) * sizeOf); Length -= itemsToRemove; } } /// /// Truncates the list by replacing the item at the specified index with the last item in the list. The list /// is shortened by one. /// /// Source type of elements /// The index of the item to delete. /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void RemoveAtSwapBack(int index) where T : struct { RemoveRangeSwapBackWithBeginEnd(index, index + 1); } /// /// Truncates the list by replacing the item at the specified index range with the items from the end the list. The list /// is shortened by number of elements in range. /// /// Source type of elements /// The first index of the item to remove. /// The index past-the-last item to remove. /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void RemoveRangeSwapBackWithBeginEnd(int begin, int end) where T : struct { RemoveRangeSwapBackWithBeginEnd(UnsafeUtility.SizeOf(), begin, end); } void RemoveRangeWithBeginEnd(int sizeOf, int begin, int end) { CheckBeginEnd(begin, end); int itemsToRemove = end - begin; if (itemsToRemove > 0) { int copyFrom = math.min(begin + itemsToRemove, Length); void* dst = (byte*)Ptr + begin * sizeOf; void* src = (byte*)Ptr + copyFrom * sizeOf; UnsafeUtility.MemCpy(dst, src, (Length - copyFrom) * sizeOf); Length -= itemsToRemove; } } /// /// Truncates the list by removing the item at the specified index, and shifting all remaining items to replace removed item. The list /// is shortened by one. /// /// Source type of elements /// The index of the item to delete. /// /// This method of removing item is useful only in case when list is ordered and user wants to preserve order /// in list after removal In majority of cases is not important and user should use more performant `RemoveAtSwapBack`. /// public void RemoveAt(int index) where T : struct { RemoveRangeWithBeginEnd(index, index + 1); } /// /// Truncates the list by removing the items at the specified index range, and shifting all remaining items to replace removed items. The list /// is shortened by number of elements in range. /// /// Source type of elements /// The first index of the item to remove. /// The index past-the-last item to remove. /// /// This method of removing item(s) is useful only in case when list is ordered and user wants to preserve order /// in list after removal In majority of cases is not important and user should use more performant `RemoveRangeSwapBackWithBeginEnd`. /// /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void RemoveRangeWithBeginEnd(int begin, int end) where T : struct { RemoveRangeWithBeginEnd(UnsafeUtility.SizeOf(), begin, end); } /// /// Returns parallel reader instance. /// /// Parallel reader instance. public ParallelReader AsParallelReader() { return new ParallelReader(Ptr, Length); } /// /// Implements parallel reader. Use AsParallelReader to obtain it from container. /// public unsafe struct ParallelReader { /// /// /// [NativeDisableUnsafePtrRestriction] public readonly void* Ptr; /// /// /// public readonly int Length; internal ParallelReader(void* ptr, int length) { Ptr = ptr; Length = length; } /// /// /// /// /// /// public int IndexOf(T value) where T : struct, IEquatable { return NativeArrayExtensions.IndexOf(Ptr, Length, value); } /// /// /// /// /// /// public bool Contains(T value) where T : struct, IEquatable { return IndexOf(value) != -1; } } /// /// Returns parallel writer instance. /// /// Parallel writer instance. public ParallelWriter AsParallelWriter() { return new ParallelWriter(Ptr, (UnsafeList*)UnsafeUtility.AddressOf(ref this)); } /// /// /// public unsafe struct ParallelWriter { /// /// /// [NativeDisableUnsafePtrRestriction] public readonly void* Ptr; /// /// /// [NativeDisableUnsafePtrRestriction] public UnsafeList* ListData; internal unsafe ParallelWriter(void* ptr, UnsafeList* listData) { Ptr = ptr; ListData = listData; } /// /// Adds an element to the list. /// /// Source type of elements /// The value to be added at the end of the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddNoResize(T value) where T : struct { var idx = Interlocked.Increment(ref ListData->Length) - 1; ListData->CheckNoResizeHasEnoughCapacity(idx, 1); UnsafeUtility.WriteArrayElement(Ptr, idx, value); } void AddRangeNoResize(int sizeOf, int alignOf, void* ptr, int length) { var idx = Interlocked.Add(ref ListData->Length, length) - length; ListData->CheckNoResizeHasEnoughCapacity(idx, length); void* dst = (byte*)Ptr + idx * sizeOf; UnsafeUtility.MemCpy(dst, ptr, length * sizeOf); } /// /// Adds elements from a buffer to this list. /// /// Source type of elements /// A pointer to the buffer. /// The number of elements to add to the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(void* ptr, int length) where T : struct { AddRangeNoResize(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), ptr, length); } /// /// Adds elements from a list to this list. /// /// Source type of elements /// Other container to copy elements from. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(UnsafeList list) where T : struct { AddRangeNoResize(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), list.Ptr, list.Length); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] internal static void CheckNull(void* listData) { if (listData == null) { throw new Exception("UnsafeList has yet to be created or has been destroyed!"); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckAllocator(Allocator a) { if (!CollectionHelper.ShouldDeallocate(a)) { throw new Exception("UnsafeList is not initialized, it must be initialized with allocator before use."); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckAllocator(AllocatorManager.AllocatorHandle a) { if (!CollectionHelper.ShouldDeallocate(a)) { throw new Exception("UnsafeList is not initialized, it must be initialized with allocator before use."); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckBeginEnd(int begin, int end) { if (begin > end) { throw new ArgumentException($"Value for begin {begin} index must less or equal to end {end}."); } if (begin < 0) { throw new ArgumentOutOfRangeException($"Value for begin {begin} must be positive."); } if (begin > Length) { throw new ArgumentOutOfRangeException($"Value for begin {begin} is out of bounds."); } if (end > Length) { throw new ArgumentOutOfRangeException($"Value for end {end} is out of bounds."); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckNoResizeHasEnoughCapacity(int length) { CheckNoResizeHasEnoughCapacity(length, Length); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckNoResizeHasEnoughCapacity(int length, int index) { if (Capacity < index + length) { throw new Exception($"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Length {Length}), requested length {length}!"); } } } /// /// Provides extension methods for UnsafeList. /// public static class UnsafeListExtension { [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] internal static ref UnsafeList ListData(ref this UnsafeList from) where T : unmanaged => ref UnsafeUtility.As, UnsafeList>(ref from); /// /// Sorts a list in ascending order. /// /// Source type of elements /// List to perform sort. public unsafe static void Sort(this UnsafeList list) where T : unmanaged, IComparable { list.Sort>(new NativeSortExtension.DefaultComparer()); } /// /// Sorts a list using a custom comparison function. /// /// Source type of elements /// The comparer type. /// List to perform sort. /// A comparison function that indicates whether one element in the array is less than, equal to, or greater than another element. public unsafe static void Sort(this UnsafeList list, U comp) where T : unmanaged where U : IComparer { NativeSortExtension.IntroSort(list.Ptr, list.Length, comp); } /// /// Sorts the container in ascending order. /// /// Source type of elements /// The container to perform sort. /// The job handle or handles for any scheduled jobs that use this container. /// A new job handle containing the prior handles as well as the handle for the job that sorts /// the container. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] [Obsolete("Instead call SortJob(this UnsafeList).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort(this UnsafeList container, JobHandle inputDeps) where T : unmanaged, IComparable { return container.Sort>(new NativeSortExtension.DefaultComparer(), inputDeps); } /// /// Creates a job that will sort a list in ascending order. /// /// Source type of elements /// List to sort. /// The job that will sort the list. Scheduling the job is left to the user. public unsafe static SortJob> SortJob(this UnsafeList list) where T : unmanaged, IComparable { return NativeSortExtension.SortJob((T*)list.Ptr, list.Length, new NativeSortExtension.DefaultComparer()); } /// /// Sorts the container using a custom comparison function. /// /// Source type of elements /// The comparer type. /// The container to perform sort. /// A comparison function that indicates whether one element in the array is less than, equal to, or greater than another element. /// The job handle or handles for any scheduled jobs that use this container. /// A new job handle containing the prior handles as well as the handle for the job that sorts /// the container. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] [Obsolete("Instead call SortJob(this UnsafeList, U).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort(this UnsafeList container, U comp, JobHandle inputDeps) where T : unmanaged where U : IComparer { return NativeSortExtension.Sort((T*)container.Ptr, container.Length, comp, inputDeps); } /// /// Creates a job that will sort a list using a comparison function. /// /// Source type of elements /// The comparer type. /// List to sort. /// A comparison function that indicates whether one element in the array is less than, equal to, or greater than another element. /// The job that will sort the list. Scheduling the job is left to the user. public unsafe static SortJob SortJob(this UnsafeList list, U comp) where T : unmanaged where U : IComparer { return NativeSortExtension.SortJob((T*)list.Ptr, list.Length, comp); } /// /// Binary search for the value in the sorted container. /// /// Source type of elements /// The container to perform search. /// The value to search for. /// Positive index of the specified value if value is found. Otherwise bitwise complement of index of first greater value. /// Array must be sorted, otherwise value searched might not be found even when it is in array. IComparer corresponds to IComparer used by sort. public static int BinarySearch(this UnsafeList container, T value) where T : unmanaged, IComparable { return container.BinarySearch(value, new NativeSortExtension.DefaultComparer()); } /// /// Binary search for the value in the sorted container. /// /// Source type of elements /// The comparer type. /// The container to perform search. /// The value to search for. /// A comparison function that indicates whether one element in the array is less than, equal to, or greater than another element. /// Positive index of the specified value if value is found. Otherwise bitwise complement of index of first greater value. /// Array must be sorted, otherwise value searched might not be found even when it is in array. IComparer corresponds to IComparer used by sort. public unsafe static int BinarySearch(this UnsafeList container, T value, U comp) where T : unmanaged where U : IComparer { return NativeSortExtension.BinarySearch((T*)container.Ptr, container.Length, value, comp); } } /// /// An unmanaged, resizable list, without any thread safety check features. /// [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] [DebuggerTypeProxy(typeof(UnsafePtrListDebugView))] [Obsolete("Untyped UnsafePtrList is deprecated, please use UnsafePtrList instead. (RemovedAfter 2021-05-18)", false)] public unsafe struct UnsafePtrList : INativeDisposable , INativeList , IEnumerable // Used by collection initializers. { /// /// /// [NativeDisableUnsafePtrRestriction] public readonly void** Ptr; /// /// /// public readonly int length; public readonly int unused; /// /// /// public readonly int capacity; /// /// /// public readonly AllocatorManager.AllocatorHandle Allocator; /// /// /// public int Length { get { return length; } set { } } /// /// /// public int Capacity { get { return capacity; } set { } } /// /// /// /// /// public IntPtr this[int index] { get { return new IntPtr(Ptr[index]); } set { Ptr[index] = (void*)value; } } /// /// /// /// /// public ref IntPtr ElementAt(int index) { return ref ((IntPtr*)Ptr)[index]; } /// /// Constructs list as view into memory. /// /// /// public unsafe UnsafePtrList(void** ptr, int length) : this() { Ptr = ptr; this.length = length; this.capacity = length; Allocator = AllocatorManager.None; } /// /// Constructs a new list using the specified type of memory allocation. /// /// The initial capacity of the list. If the list grows larger than its capacity, /// the internal array is copied to a new, larger array. /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// Memory should be cleared on allocation or left uninitialized. /// The list initially has a capacity of one. To avoid reallocating memory for the list, specify /// sufficient capacity up front. public unsafe UnsafePtrList(int initialCapacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) : this() { Ptr = null; length = 0; capacity = 0; Allocator = AllocatorManager.None; var sizeOf = IntPtr.Size; this.ListData() = new UnsafeList(sizeOf, sizeOf, initialCapacity, allocator, options); } /// /// Constructs a new list using the specified type of memory allocation. /// /// The initial capacity of the list. If the list grows larger than its capacity, /// the internal array is copied to a new, larger array. /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// Memory should be cleared on allocation or left uninitialized. /// The list initially has a capacity of one. To avoid reallocating memory for the list, specify /// sufficient capacity up front. public unsafe UnsafePtrList(int initialCapacity, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) : this() { Ptr = null; length = 0; capacity = 0; Allocator = AllocatorManager.None; var sizeOf = IntPtr.Size; this.ListData() = new UnsafeList(sizeOf, sizeOf, initialCapacity, allocator, options); } /// /// /// /// /// /// New initialized container. public static UnsafePtrList* Create(void** ptr, int length) { UnsafePtrList* listData = AllocatorManager.Allocate(AllocatorManager.Persistent); *listData = new UnsafePtrList(ptr, length); return listData; } /// /// Creates a new list with the specified initial capacity and type of memory allocation. /// /// The initial capacity of the list. If the list grows larger than its capacity, /// the internal array is copied to a new, larger array. /// A member of the /// [Unity.Collections.Allocator](https://docs.unity3d.com/ScriptReference/Unity.Collections.Allocator.html) enumeration. /// Memory should be cleared on allocation or left uninitialized. /// New initialized container. public static UnsafePtrList* Create(int initialCapacity, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) { UnsafePtrList* listData = AllocatorManager.Allocate(allocator); *listData = new UnsafePtrList(initialCapacity, allocator, options); return listData; } /// /// Destroys list. /// /// Container to destroy. public static void Destroy(UnsafePtrList* listData) { UnsafeList.CheckNull(listData); var allocator = listData->ListData().Allocator.Value == AllocatorManager.Invalid.Value ? AllocatorManager.Persistent : listData->ListData().Allocator ; listData->Dispose(); AllocatorManager.Free(allocator, listData); } /// /// Reports whether container is empty. /// /// True if this string has no characters or if the container has not been constructed. public bool IsEmpty => !IsCreated || Length == 0; /// /// Reports whether memory for the container is allocated. /// /// True if this container object's internal storage has been allocated. /// /// Note that the container storage is not created if you use the default constructor. You must specify /// at least an allocation type to construct a usable container. /// /// *Warning:* the `IsCreated` property can't be used to determine whether a copy of a container is still valid. /// If you dispose any copy of the container, the container storage is deallocated. However, the properties of /// the other copies of the container (including the original) are not updated. As a result the `IsCreated` property /// of the copies still return `true` even though the container storage has been deallocated. /// public bool IsCreated => Ptr != null; /// /// Disposes of this container and deallocates its memory immediately. /// public void Dispose() { this.ListData().Dispose(); } /// /// Safely disposes of this container and deallocates its memory when the jobs that use it have completed. /// /// You can call this function dispose of the container immediately after scheduling the job. Pass /// the [JobHandle](https://docs.unity3d.com/ScriptReference/Unity.Jobs.JobHandle.html) returned by /// the [Job.Schedule](https://docs.unity3d.com/ScriptReference/Unity.Jobs.IJobExtensions.Schedule.html) /// method using the `jobHandle` parameter so the job scheduler can dispose the container after all jobs /// using it have run. /// The job handle or handles for any scheduled jobs that use this container. /// A new job handle containing the prior handles as well as the handle for the job that deletes /// the container. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { return this.ListData().Dispose(inputDeps); } /// /// Clears the list. /// /// List Capacity remains unchanged. public void Clear() { this.ListData().Clear(); } /// /// Changes the list length, resizing if necessary. /// /// The new length of the list. /// Memory should be cleared on allocation or left uninitialized. public void Resize(int length, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) { this.ListData().Resize(length, options); } /// /// Set the number of items that can fit in the list. /// /// The number of items that the list can hold before it resizes its internal storage. public void SetCapacity(int capacity) { this.ListData().SetCapacity(capacity); } /// /// Sets the capacity to the actual number of elements in the container. /// public void TrimExcess() { this.ListData().TrimExcess(); } /// /// Searches for the specified element in list. /// /// /// The zero-based index of the first occurrence element if found, otherwise returns -1. public int IndexOf(void* value) { for (int i = 0; i < Length; ++i) { if (Ptr[i] == value) return i; } return -1; } /// /// Determines whether an element is in the list. /// /// /// True, if element is found. public bool Contains(void* value) { return IndexOf(value) != -1; } /// /// Adds an element to the list. /// /// The value to be added at the end of the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddNoResize(void* value) { this.ListData().AddNoResize((IntPtr)value); } /// /// Adds elements from a buffer to this list. /// /// A pointer to the buffer. /// The number of elements to add to the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(void** ptr, int length) { this.ListData().AddRangeNoResize(ptr, length); } /// /// Adds elements from a list to this list. /// /// Other container to copy elements from. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(UnsafePtrList list) { this.ListData().AddRangeNoResize(list.Ptr, list.Length); } /// /// Adds an element to the list. /// /// The struct to be added at the end of the list. public void Add(in IntPtr value) { this.ListData().Add(value); } /// /// Adds an element to the list. /// /// The struct to be added at the end of the list. public void Add(void* value) { this.ListData().Add((IntPtr)value); } /// /// Adds elements from a buffer to this list. /// /// A pointer to the buffer. /// The number of elements to add to the list. public void AddRange(void* ptr, int length) { this.ListData().AddRange(ptr, length); } /// /// Adds the elements of a UnsafePtrList to this list. /// /// Other container to copy elements from. public void AddRange(UnsafePtrList list) { this.ListData().AddRange(list.ListData()); } /// /// Inserts a number of items into a container at a specified zero-based index. /// /// The zero-based index at which the new elements should be inserted. /// The zero-based index just after where the elements should be removed. /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void InsertRangeWithBeginEnd(int begin, int end) { this.ListData().InsertRangeWithBeginEnd(begin, end); } /// /// Truncates the list by replacing the item at the specified index with the last item in the list. The list /// is shortened by one. /// /// The index of the item to delete. public void RemoveAtSwapBack(int index) { this.ListData().RemoveAtSwapBack(index); } /// /// Truncates the list by replacing the item at the specified index range with the items from the end the list. The list /// is shortened by number of elements in range. /// /// The first index of the item to remove. /// The index past-the-last item to remove. /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void RemoveRangeSwapBackWithBeginEnd(int begin, int end) { this.ListData().RemoveRangeSwapBackWithBeginEnd(begin, end); } /// /// Truncates the list by removing the item at the specified index, and shifting all remaining items to replace removed item. The list /// is shortened by one. /// /// The index of the item to delete. /// /// This method of removing item is useful only in case when list is ordered and user wants to preserve order /// in list after removal In majority of cases is not important and user should use more performant `RemoveAtSwapBack`. /// public void RemoveAt(int index) { this.ListData().RemoveAt(index); } /// /// Truncates the list by removing the items at the specified index range, and shifting all remaining items to replace removed items. The list /// is shortened by number of elements in range. /// /// The first index of the item to remove. /// The index past-the-last item to remove. /// /// This method of removing item(s) is useful only in case when list is ordered and user wants to preserve order /// in list after removal In majority of cases is not important and user should use more performant `RemoveRangeSwapBackWithBeginEnd`. /// /// Thrown if end argument is less than begin argument. /// Thrown if begin or end arguments are not positive or out of bounds. public void RemoveRangeWithBeginEnd(int begin, int end) { this.ListData().RemoveRangeWithBeginEnd(begin, end); } /// /// This method is not implemented. It will throw NotImplementedException if it is used. /// /// Use Enumerator GetEnumerator() instead. /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// This method is not implemented. It will throw NotImplementedException if it is used. /// /// Use Enumerator GetEnumerator() instead. /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// Returns parallel reader instance. /// /// Parallel reader instance. public ParallelReader AsParallelReader() { return new ParallelReader(Ptr, Length); } /// /// Implements parallel reader. Use AsParallelReader to obtain it from container. /// public unsafe struct ParallelReader { /// /// /// [NativeDisableUnsafePtrRestriction] public readonly void** Ptr; /// /// /// public readonly int Length; internal ParallelReader(void** ptr, int length) { Ptr = ptr; Length = length; } /// /// /// /// /// public int IndexOf(void* value) { for (int i = 0; i < Length; ++i) { if (Ptr[i] == value) return i; } return -1; } /// /// /// /// /// public bool Contains(void* value) { return IndexOf(value) != -1; } } /// /// Returns parallel writer instance. /// /// Parallel writer instance. public ParallelWriter AsParallelWriter() { return new ParallelWriter(Ptr, (UnsafeList*)UnsafeUtility.AddressOf(ref this)); } /// /// /// public unsafe struct ParallelWriter { /// /// /// [NativeDisableUnsafePtrRestriction] public readonly void* Ptr; /// /// /// [NativeDisableUnsafePtrRestriction] public UnsafeList* ListData; internal unsafe ParallelWriter(void* ptr, UnsafeList* listData) { Ptr = ptr; ListData = listData; } /// /// Adds an element to the list. /// /// The value to be added at the end of the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddNoResize(void* value) { ListData->AddNoResize((IntPtr)value); } /// /// Adds elements from a buffer to this list. /// /// A pointer to the buffer. /// The number of elements to add to the list. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(void** ptr, int length) { ListData->AddRangeNoResize(ptr, length); } /// /// Adds elements from a list to this list. /// /// Other container to copy elements from. /// /// If the list has reached its current capacity, internal array won't be resized, and exception will be thrown. /// public void AddRangeNoResize(UnsafePtrList list) { ListData->AddRangeNoResize(list.Ptr, list.Length); } } } internal static class UnsafePtrListExtensions { public static ref UnsafeList ListData(ref this UnsafePtrList from) => ref UnsafeUtility.As(ref from); } internal sealed class UnsafePtrListDebugView { UnsafePtrList Data; public UnsafePtrListDebugView(UnsafePtrList data) { Data = data; } public unsafe IntPtr[] Items { get { IntPtr[] result = new IntPtr[Data.Length]; for (var i = 0; i < result.Length; ++i) { result[i] = (IntPtr)Data.Ptr[i]; } return result; } } } [Obsolete("This storage will no longer be used. (RemovedAfter 2021-06-01)")] sealed class WordStorageDebugView { WordStorage m_wordStorage; public WordStorageDebugView(WordStorage wordStorage) { m_wordStorage = wordStorage; } public FixedString128Bytes[] Table { get { var table = new FixedString128Bytes[m_wordStorage.Entries]; for (var i = 0; i < m_wordStorage.Entries; ++i) m_wordStorage.GetFixedString(i, ref table[i]); return table; } } } [Obsolete("This storage will no longer be used. (RemovedAfter 2021-06-01)")] sealed class WordStorageStatic { private WordStorageStatic() { } public struct Thing { public WordStorage Data; } public static Thing Ref = default; } /// /// /// [Obsolete("This storage will no longer be used. (RemovedAfter 2021-06-01)")] [DebuggerTypeProxy(typeof(WordStorageDebugView))] public struct WordStorage { struct Entry { public int offset; public int length; } NativeArray buffer; // all the UTF-8 encoded bytes in one place NativeArray entry; // one offset for each text in "buffer" NativeParallelMultiHashMap hash; // from string hash to table entry int chars; // bytes in buffer allocated so far int entries; // number of strings allocated so far /// /// For internal use only. /// [NotBurstCompatible /* Deprecated */] public static ref WordStorage Instance { get { Initialize(); return ref WordStorageStatic.Ref.Data; } } const int kMaxEntries = 16 << 10; const int kMaxChars = kMaxEntries * 128; /// /// /// public const int kMaxCharsPerEntry = 4096; /// /// /// public int Entries => entries; /// /// /// [NotBurstCompatible /* Deprecated */] public static void Initialize() { if (WordStorageStatic.Ref.Data.buffer.IsCreated) return; WordStorageStatic.Ref.Data.buffer = new NativeArray(kMaxChars, Allocator.Persistent); WordStorageStatic.Ref.Data.entry = new NativeArray(kMaxEntries, Allocator.Persistent); WordStorageStatic.Ref.Data.hash = new NativeParallelMultiHashMap(kMaxEntries, Allocator.Persistent); Clear(); #if !UNITY_DOTSRUNTIME // Free storage on domain unload, which happens when iterating on the Entities module a lot. AppDomain.CurrentDomain.DomainUnload += (_, __) => { Shutdown(); }; // There is no domain unload in player builds, so we must be sure to shutdown when the process exits. AppDomain.CurrentDomain.ProcessExit += (_, __) => { Shutdown(); }; #endif } /// /// /// [NotBurstCompatible /* Deprecated */] public static void Shutdown() { if (!WordStorageStatic.Ref.Data.buffer.IsCreated) return; WordStorageStatic.Ref.Data.buffer.Dispose(); WordStorageStatic.Ref.Data.entry.Dispose(); WordStorageStatic.Ref.Data.hash.Dispose(); WordStorageStatic.Ref.Data = default; } /// /// /// [NotBurstCompatible /* Deprecated */] public static void Clear() { Initialize(); WordStorageStatic.Ref.Data.chars = 0; WordStorageStatic.Ref.Data.entries = 0; WordStorageStatic.Ref.Data.hash.Clear(); var temp = new FixedString32Bytes(); WordStorageStatic.Ref.Data.GetOrCreateIndex(ref temp); // make sure that Index=0 means empty string } /// /// /// [NotBurstCompatible /* Deprecated */] public static void Setup() { Clear(); } /// /// /// /// /// /// public unsafe void GetFixedString(int index, ref T temp) where T : IUTF8Bytes, INativeList { Assert.IsTrue(index < entries); var e = entry[index]; Assert.IsTrue(e.length <= kMaxCharsPerEntry); temp.Length = e.length; UnsafeUtility.MemCpy(temp.GetUnsafePtr(), (byte*)buffer.GetUnsafePtr() + e.offset, temp.Length); } /// /// /// /// /// /// /// public int GetIndexFromHashAndFixedString(int h, ref T temp) where T : IUTF8Bytes, INativeList { Assert.IsTrue(temp.Length <= kMaxCharsPerEntry); // about one printed page of text int itemIndex; NativeParallelMultiHashMapIterator iter; if (hash.TryGetFirstValue(h, out itemIndex, out iter)) { do { var e = entry[itemIndex]; Assert.IsTrue(e.length <= kMaxCharsPerEntry); if (e.length == temp.Length) { int matches; for (matches = 0; matches < e.length; ++matches) if (temp[matches] != buffer[e.offset + matches]) break; if (matches == temp.Length) return itemIndex; } } while (hash.TryGetNextValue(out itemIndex, ref iter)); } return -1; } /// /// /// /// /// /// public bool Contains(ref T value) where T : IUTF8Bytes, INativeList { int h = value.GetHashCode(); return GetIndexFromHashAndFixedString(h, ref value) != -1; } /// /// /// /// /// [NotBurstCompatible /* Deprecated */] public unsafe bool Contains(string value) { FixedString512Bytes temp = value; return Contains(ref temp); } /// /// /// /// /// /// public int GetOrCreateIndex(ref T value) where T : IUTF8Bytes, INativeList { int h = value.GetHashCode(); var itemIndex = GetIndexFromHashAndFixedString(h, ref value); if (itemIndex != -1) return itemIndex; Assert.IsTrue(entries < kMaxEntries); Assert.IsTrue(chars + value.Length <= kMaxChars); var o = chars; var l = (ushort)value.Length; for (var i = 0; i < l; ++i) buffer[chars++] = value[i]; entry[entries] = new Entry { offset = o, length = l }; hash.Add(h, entries); return entries++; } } /// /// /// /// /// A "Words" is an integer that refers to 4,096 or fewer chars of UTF-16 text in a global storage blob. /// Each should refer to *at most* about one printed page of text. /// /// If you need more text, consider using one Words struct for each printed page's worth. /// /// Each Words instance that you create is stored in a single, internally-managed WordStorage object, /// which can hold up to 16,384 Words entries. Once added, the entries in WordStorage cannot be modified /// or removed. /// [Obsolete("This storage will no longer be used. (RemovedAfter 2021-06-01)")] public struct Words { int Index; /// /// /// /// /// public void ToFixedString(ref T value) where T : IUTF8Bytes, INativeList { WordStorage.Instance.GetFixedString(Index, ref value); } /// /// /// /// public override string ToString() { FixedString512Bytes temp = default; ToFixedString(ref temp); return temp.ToString(); } /// /// /// /// /// public void SetFixedString(ref T value) where T : IUTF8Bytes, INativeList { Index = WordStorage.Instance.GetOrCreateIndex(ref value); } /// /// /// /// public unsafe void SetString(string value) { FixedString512Bytes temp = value; SetFixedString(ref temp); } } /// /// /// /// /// A "NumberedWords" is a "Words", plus possibly a string of leading zeroes, followed by /// possibly a positive integer. /// The zeroes and integer aren't stored centrally as a string, they're stored as an int. /// Therefore, 1,000,000 items with names from FooBarBazBifBoo000000 to FooBarBazBifBoo999999 /// Will cost 8MB + a single copy of "FooBarBazBifBoo", instead of ~48MB. /// They say that this is a thing, too. /// [Obsolete("This storage will no longer be used. (RemovedAfter 2021-06-01)")] public struct NumberedWords { int Index; int Suffix; const int kPositiveNumericSuffixShift = 0; const int kPositiveNumericSuffixBits = 29; const int kMaxPositiveNumericSuffix = (1 << kPositiveNumericSuffixBits) - 1; const int kPositiveNumericSuffixMask = (1 << kPositiveNumericSuffixBits) - 1; const int kLeadingZeroesShift = 29; const int kLeadingZeroesBits = 3; const int kMaxLeadingZeroes = (1 << kLeadingZeroesBits) - 1; const int kLeadingZeroesMask = (1 << kLeadingZeroesBits) - 1; int LeadingZeroes { get => (Suffix >> kLeadingZeroesShift) & kLeadingZeroesMask; set { Suffix &= ~(kLeadingZeroesMask << kLeadingZeroesShift); Suffix |= (value & kLeadingZeroesMask) << kLeadingZeroesShift; } } int PositiveNumericSuffix { get => (Suffix >> kPositiveNumericSuffixShift) & kPositiveNumericSuffixMask; set { Suffix &= ~(kPositiveNumericSuffixMask << kPositiveNumericSuffixShift); Suffix |= (value & kPositiveNumericSuffixMask) << kPositiveNumericSuffixShift; } } bool HasPositiveNumericSuffix => PositiveNumericSuffix != 0; [NotBurstCompatible /* Deprecated */] string NewString(char c, int count) { char[] temp = new char[count]; for (var i = 0; i < count; ++i) temp[i] = c; return new string(temp, 0, count); } /// /// /// /// /// /// [NotBurstCompatible /* Deprecated */] public int ToFixedString(ref T result) where T : IUTF8Bytes, INativeList { unsafe { var positiveNumericSuffix = PositiveNumericSuffix; var leadingZeroes = LeadingZeroes; WordStorage.Instance.GetFixedString(Index, ref result); if (positiveNumericSuffix == 0 && leadingZeroes == 0) return 0; // print the numeric suffix, if any, backwards, as ASCII, to a little buffer. const int maximumDigits = kMaxLeadingZeroes + 10; var buffer = stackalloc byte[maximumDigits]; var firstDigit = maximumDigits; while (positiveNumericSuffix > 0) { buffer[--firstDigit] = (byte)('0' + positiveNumericSuffix % 10); positiveNumericSuffix /= 10; } while (leadingZeroes-- > 0) buffer[--firstDigit] = (byte)'0'; // make space in the output for leading zeroes if any, followed by the positive numeric index if any. var dest = result.GetUnsafePtr() + result.Length; result.Length += maximumDigits - firstDigit; while (firstDigit < maximumDigits) *dest++ = buffer[firstDigit++]; return 0; } } /// /// /// /// [NotBurstCompatible /* Deprecated */] public override string ToString() { FixedString512Bytes temp = default; ToFixedString(ref temp); return temp.ToString(); } bool IsDigit(byte b) { return b >= '0' && b <= '9'; } /// /// /// /// /// [NotBurstCompatible /* Deprecated */] public void SetString(ref T value) where T : IUTF8Bytes, INativeList { int beginningOfDigits = value.Length; // as long as there are digits at the end, // look back for more digits. while (beginningOfDigits > 0 && IsDigit(value[beginningOfDigits - 1])) --beginningOfDigits; // as long as the first digit is a zero, it's not the beginning of the positive integer - it's a leading zero. var beginningOfPositiveNumericSuffix = beginningOfDigits; while (beginningOfPositiveNumericSuffix < value.Length && value[beginningOfPositiveNumericSuffix] == '0') ++beginningOfPositiveNumericSuffix; // now we know where the leading zeroes begin, and then where the positive integer begins after them. // but if there are too many leading zeroes to encode, the excess ones become part of the string. var leadingZeroes = beginningOfPositiveNumericSuffix - beginningOfDigits; if (leadingZeroes > kMaxLeadingZeroes) { var excessLeadingZeroes = leadingZeroes - kMaxLeadingZeroes; beginningOfDigits += excessLeadingZeroes; leadingZeroes -= excessLeadingZeroes; } // if there is a positive integer after the zeroes, here's where we compute it and store it for later. PositiveNumericSuffix = 0; { int number = 0; for (var i = beginningOfPositiveNumericSuffix; i < value.Length; ++i) { number *= 10; number += value[i] - '0'; } // an intrepid user may attempt to encode a positive integer with 20 digits or something. // they are rewarded with a string that is encoded wholesale without any optimizations. if (number <= kMaxPositiveNumericSuffix) PositiveNumericSuffix = number; else { beginningOfDigits = value.Length; leadingZeroes = 0; // and your dog Toto, too. } } // set the leading zero count in the Suffix member. LeadingZeroes = leadingZeroes; // truncate the string, if there were digits at the end that we encoded. var truncated = value; int length = truncated.Length; if (beginningOfDigits != truncated.Length) truncated.Length = beginningOfDigits; // finally, set the string to its index in the global string blob thing. unsafe { Index = WordStorage.Instance.GetOrCreateIndex(ref truncated); } } /// /// /// /// [NotBurstCompatible /* Deprecated */] public void SetString(string value) { FixedString512Bytes temp = value; SetString(ref temp); } } [Obsolete("UntypedUnsafeHashMap is renamed to UntypedUnsafeParallelHashMap. (UnityUpgradable) -> UntypedUnsafeParallelHashMap", false)] [StructLayout(LayoutKind.Sequential)] public unsafe struct UntypedUnsafeHashMap { #pragma warning disable 169 [NativeDisableUnsafePtrRestriction] UnsafeParallelHashMapData* m_Buffer; AllocatorManager.AllocatorHandle m_AllocatorLabel; #pragma warning restore 169 } [Obsolete("UnsafeHashMap is renamed to UnsafeParallelHashMap. (UnityUpgradable) -> UnsafeParallelHashMap", false)] [StructLayout(LayoutKind.Sequential)] public unsafe struct UnsafeHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : struct, IEquatable where TValue : struct { [NativeDisableUnsafePtrRestriction] internal UnsafeParallelHashMapData* m_Buffer; internal AllocatorManager.AllocatorHandle m_AllocatorLabel; /// /// Initializes and returns an instance of UnsafeHashMap. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public UnsafeHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) { CollectionHelper.CheckIsUnmanaged(); CollectionHelper.CheckIsUnmanaged(); m_AllocatorLabel = allocator; // Bucket size if bigger to reduce collisions UnsafeParallelHashMapData.AllocateHashMap(capacity, capacity * 2, allocator, out m_Buffer); Clear(); } /// /// Whether this hash map is empty. /// /// True if this hash map is empty or the hash map has not been constructed. public bool IsEmpty => !IsCreated || UnsafeParallelHashMapData.IsEmpty(m_Buffer); /// /// The current number of key-value pairs in this hash map. /// /// The current number of key-value pairs in this hash map. public int Count() => UnsafeParallelHashMapData.GetCount(m_Buffer); /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. /// A new capacity. Must be larger than the current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { get { UnsafeParallelHashMapData* data = m_Buffer; return data->keyCapacity; } set { UnsafeParallelHashMapData* data = m_Buffer; UnsafeParallelHashMapData.ReallocateHashMap(data, value, UnsafeParallelHashMapData.GetBucketSize(value), m_AllocatorLabel); } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { UnsafeParallelHashMapBase.Clear(m_Buffer); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying the hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. public bool TryAdd(TKey key, TValue item) { return UnsafeParallelHashMapBase.TryAdd(m_Buffer, key, item, false, m_AllocatorLabel); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method throws without modifying the hash map. /// The key to add. /// The value to add. /// Thrown if the key was already present. public void Add(TKey key, TValue item) { TryAdd(key, item); } /// /// Removes a key-value pair. /// /// The key to remove. /// True if a key-value pair was removed. public bool Remove(TKey key) { return UnsafeParallelHashMapBase.Remove(m_Buffer, key, false) != 0; } /// /// Returns the value associated with a key. /// /// The key to look up. /// Outputs the value associated with the key. Outputs default if the key was not present. /// True if the key was present. public bool TryGetValue(TKey key, out TValue item) { NativeParallelMultiHashMapIterator tempIt; return UnsafeParallelHashMapBase.TryGetFirstValueAtomic(m_Buffer, key, out item, out tempIt); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present. public bool ContainsKey(TKey key) { return UnsafeParallelHashMapBase.TryGetFirstValueAtomic(m_Buffer, key, out var tempValue, out var tempIt); } /// /// Gets and sets values by key. /// /// Getting a key that is not present will throw. Setting a key that is not already present will add the key. /// The key to look up. /// The value associated with the key. /// For getting, thrown if the key was not present. public TValue this[TKey key] { get { TValue res; TryGetValue(key, out res); return res; } set { if (UnsafeParallelHashMapBase.TryGetFirstValueAtomic(m_Buffer, key, out var item, out var iterator)) { UnsafeParallelHashMapBase.SetValue(m_Buffer, ref iterator, ref value); } else { UnsafeParallelHashMapBase.TryAdd(m_Buffer, key, value, false, m_AllocatorLabel); } } } /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public bool IsCreated => m_Buffer != null; /// /// Releases all resources (memory). /// public void Dispose() { UnsafeParallelHashMapData.DeallocateHashMap(m_Buffer, m_AllocatorLabel); m_Buffer = null; } /// /// Creates and schedules a job that will dispose this hash map. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this hash map. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { var jobHandle = new UnsafeParallelHashMapDisposeJob { Data = m_Buffer, Allocator = m_AllocatorLabel }.Schedule(inputDeps); m_Buffer = null; return jobHandle; } /// /// Returns an array with a copy of all this hash map's keys (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's keys (in no particular order). public NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { var result = CollectionHelper.CreateNativeArray(UnsafeParallelHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetKeyArray(m_Buffer, result); return result; } /// /// Returns an array with a copy of all this hash map's values (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's values (in no particular order). public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { var result = CollectionHelper.CreateNativeArray(UnsafeParallelHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetValueArray(m_Buffer, result); return result; } /// /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values. /// /// The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`. /// The allocator to use. /// A NativeKeyValueArrays with a copy of all this hash map's keys and values. public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { var result = new NativeKeyValueArrays(UnsafeParallelHashMapData.GetCount(m_Buffer), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetKeyValueArrays(m_Buffer, result); return result; } /// /// Returns a parallel writer for this hash map. /// /// A parallel writer for this hash map. public ParallelWriter AsParallelWriter() { ParallelWriter writer; writer.m_ThreadIndex = 0; writer.m_Buffer = m_Buffer; return writer; } /// /// A parallel writer for a UnsafeParallelHashMap. /// /// /// Use to create a parallel writer for a UnsafeParallelHashMap. /// [NativeContainerIsAtomicWriteOnly] public unsafe struct ParallelWriter { [NativeDisableUnsafePtrRestriction] internal UnsafeParallelHashMapData* m_Buffer; [NativeSetThreadIndex] internal int m_ThreadIndex; /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. public int Capacity { get { UnsafeParallelHashMapData* data = m_Buffer; return data->keyCapacity; } } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying the hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. public bool TryAdd(TKey key, TValue item) { Assert.IsTrue(m_ThreadIndex >= 0); return UnsafeParallelHashMapBase.TryAddAtomic(m_Buffer, key, item, m_ThreadIndex); } } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// An enumerator over the key-value pairs of this hash map. public Enumerator GetEnumerator() { return new Enumerator { m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_Buffer) }; } /// /// 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(); } /// /// An enumerator over the key-value pairs of a hash map. /// /// /// In an enumerator's initial state, is not valid to read. /// From this state, the first call advances the enumerator to the first key-value pair. /// public struct Enumerator : IEnumerator> { internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next key-value pair. /// /// True if is valid to read after the call. public bool MoveNext() => m_Enumerator.MoveNext(); /// /// Resets the enumerator to its initial state. /// public void Reset() => m_Enumerator.Reset(); /// /// The current key-value pair. /// /// The current key-value pair. public KeyValue Current => m_Enumerator.GetCurrent(); object IEnumerator.Current => Current; } } [Obsolete("UnsafeMultiHashMap is renamed to UnsafeParallelMultiHashMap. (UnityUpgradable) -> UnsafeParallelMultiHashMap", false)] [StructLayout(LayoutKind.Sequential)] public unsafe struct UnsafeMultiHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : struct, IEquatable where TValue : struct { [NativeDisableUnsafePtrRestriction] internal UnsafeParallelHashMapData* m_Buffer; internal AllocatorManager.AllocatorHandle m_AllocatorLabel; /// /// Initializes and returns an instance of UnsafeParallelMultiHashMap. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public UnsafeMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) { m_AllocatorLabel = allocator; // Bucket size if bigger to reduce collisions UnsafeParallelHashMapData.AllocateHashMap(capacity, capacity * 2, allocator, out m_Buffer); Clear(); } /// /// Whether this hash map is empty. /// /// True if this hash map is empty or the hash map has not been constructed. public bool IsEmpty => !IsCreated || UnsafeParallelHashMapData.IsEmpty(m_Buffer); /// /// Returns the current number of key-value pairs in this hash map. /// /// Key-value pairs with matching keys are counted as separate, individual pairs. /// The current number of key-value pairs in this hash map. public int Count() { if (m_Buffer->allocatedIndexLength <= 0) { return 0; } return UnsafeParallelHashMapData.GetCount(m_Buffer); } /// /// Returns the number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. /// A new capacity. Must be larger than the current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { get { UnsafeParallelHashMapData* data = m_Buffer; return data->keyCapacity; } set { UnsafeParallelHashMapData* data = m_Buffer; UnsafeParallelHashMapData.ReallocateHashMap(data, value, UnsafeParallelHashMapData.GetBucketSize(value), m_AllocatorLabel); } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { UnsafeParallelHashMapBase.Clear(m_Buffer); } /// /// Adds a new key-value pair. /// /// /// If a key-value pair with this key is already present, an additional separate key-value pair is added. /// /// The key to add. /// The value to add. public void Add(TKey key, TValue item) { UnsafeParallelHashMapBase.TryAdd(m_Buffer, key, item, true, m_AllocatorLabel); } /// /// Removes a key and its associated value(s). /// /// The key to remove. /// The number of removed key-value pairs. If the key was not present, returns 0. public int Remove(TKey key) { return UnsafeParallelHashMapBase.Remove(m_Buffer, key, true); } /// /// Removes all key-value pairs with a particular key and a particular value. /// /// Removes all key-value pairs which have a particular key and which *also have* a particular value. /// In other words: (key *AND* value) rather than (key *OR* value). /// The type of the value. /// The key of the key-value pairs to remove. /// The value of the key-value pairs to remove. public void Remove(TKey key, TValueEQ value) where TValueEQ : struct, IEquatable { UnsafeParallelHashMapBase.RemoveKeyValue(m_Buffer, key, value); } /// /// Removes a single key-value pair. /// /// An iterator representing the key-value pair to remove. /// Thrown if the iterator is invalid. public void Remove(NativeParallelMultiHashMapIterator it) { UnsafeParallelHashMapBase.Remove(m_Buffer, it); } /// /// Gets an iterator for a key. /// /// The key. /// Outputs the associated value represented by the iterator. /// Outputs an iterator. /// True if the key was present. public bool TryGetFirstValue(TKey key, out TValue item, out NativeParallelMultiHashMapIterator it) { return UnsafeParallelHashMapBase.TryGetFirstValueAtomic(m_Buffer, key, out item, out it); } /// /// Advances an iterator to the next value associated with its key. /// /// Outputs the next value. /// A reference to the iterator to advance. /// True if the key was present and had another value. public bool TryGetNextValue(out TValue item, ref NativeParallelMultiHashMapIterator it) { return UnsafeParallelHashMapBase.TryGetNextValueAtomic(m_Buffer, out item, ref it); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present in this hash map. public bool ContainsKey(TKey key) { return TryGetFirstValue(key, out var temp0, out var temp1); } /// /// Returns the number of values associated with a given key. /// /// The key to look up. /// The number of values associated with the key. Returns 0 if the key was not present. public int CountValuesForKey(TKey key) { if (!TryGetFirstValue(key, out var value, out var iterator)) { return 0; } var count = 1; while (TryGetNextValue(out value, ref iterator)) { count++; } return count; } /// /// Sets a new value for an existing key-value pair. /// /// The new value. /// The iterator representing a key-value pair. /// True if a value was overwritten. public bool SetValue(TValue item, NativeParallelMultiHashMapIterator it) { return UnsafeParallelHashMapBase.SetValue(m_Buffer, ref it, ref item); } /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public bool IsCreated => m_Buffer != null; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { UnsafeParallelHashMapData.DeallocateHashMap(m_Buffer, m_AllocatorLabel); m_Buffer = null; } /// /// Creates and schedules a job that will dispose this hash map. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this hash map. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { var jobHandle = new UnsafeParallelHashMapDisposeJob { Data = m_Buffer, Allocator = m_AllocatorLabel }.Schedule(inputDeps); m_Buffer = null; return jobHandle; } /// /// Returns an array with a copy of all the keys (in no particular order). /// /// A key with *N* values is included *N* times in the array. /// /// Use `GetUniqueKeyArray` of instead if you only want one occurrence of each key. /// The allocator to use. /// An array with a copy of all the keys (in no particular order). public NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { var result = CollectionHelper.CreateNativeArray(Count(), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetKeyArray(m_Buffer, result); return result; } /// /// Returns an array with a copy of all the values (in no particular order). /// /// The values are not deduplicated. If you sort the returned array, /// you can use to remove duplicate values. /// The allocator to use. /// An array with a copy of all the values (in no particular order). public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { var result = CollectionHelper.CreateNativeArray(Count(), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetValueArray(m_Buffer, result); return result; } /// /// Returns a NativeKeyValueArrays with a copy of all the keys and values (in no particular order). /// /// A key with *N* values is included *N* times in the array. /// /// The allocator to use. /// A NativeKeyValueArrays with a copy of all the keys and values (in no particular order). public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { var result = new NativeKeyValueArrays(Count(), allocator, NativeArrayOptions.UninitializedMemory); UnsafeParallelHashMapData.GetKeyValueArrays(m_Buffer, result); return result; } /// /// Returns an enumerator over the values of an individual key. /// /// The key to get an enumerator for. /// An enumerator over the values of a key. public Enumerator GetValuesForKey(TKey key) { return new Enumerator { hashmap = this, key = key, isFirst = true }; } /// /// An enumerator over the values of an individual key in a multi hash map. /// /// /// In an enumerator's initial state, is not valid to read. /// The first call advances the enumerator to the first value of the key. /// public struct Enumerator : IEnumerator { internal UnsafeMultiHashMap hashmap; internal TKey key; internal bool isFirst; TValue value; NativeParallelMultiHashMapIterator iterator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value of the key. /// /// True if is valid to read after the call. public bool MoveNext() { //Avoids going beyond the end of the collection. if (isFirst) { isFirst = false; return hashmap.TryGetFirstValue(key, out value, out iterator); } return hashmap.TryGetNextValue(out value, ref iterator); } /// /// Resets the enumerator to its initial state. /// public void Reset() => isFirst = true; /// /// The current value. /// /// The current value. public TValue Current => value; object IEnumerator.Current => Current; /// /// Returns this enumerator. /// /// This enumerator. public Enumerator GetEnumerator() { return this; } } /// /// Returns a parallel writer for this hash map. /// /// A parallel writer for this hash map. public ParallelWriter AsParallelWriter() { ParallelWriter writer; #if UNITY_DOTSRUNTIME writer.m_ThreadIndex = -1; // aggressively check that code-gen has patched the ThreadIndex #else writer.m_ThreadIndex = 0; #endif writer.m_Buffer = m_Buffer; return writer; } /// /// A parallel writer for an UnsafeParallelMultiHashMap. /// /// /// Use to create a parallel writer for a UnsafeParallelMultiHashMap. /// [NativeContainerIsAtomicWriteOnly] public unsafe struct ParallelWriter { [NativeDisableUnsafePtrRestriction] internal UnsafeParallelHashMapData* m_Buffer; [NativeSetThreadIndex] internal int m_ThreadIndex; /// /// Returns the number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. public int Capacity { get { return m_Buffer->keyCapacity; } } /// /// Adds a new key-value pair. /// /// /// If a key-value pair with this key is already present, an additional separate key-value pair is added. /// /// The key to add. /// The value to add. public void Add(TKey key, TValue item) { Assert.IsTrue(m_ThreadIndex >= 0); UnsafeParallelHashMapBase.AddAtomicMulti(m_Buffer, key, item, m_ThreadIndex); } } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// A key with *N* values is visited by the enumerator *N* times. /// An enumerator over the key-value pairs of this hash map. public KeyValueEnumerator GetEnumerator() { return new KeyValueEnumerator { m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_Buffer) }; } /// /// 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(); } /// /// An enumerator over the key-value pairs of a multi hash map. /// /// A key with *N* values is visited by the enumerator *N* times. /// /// In an enumerator's initial state, is not valid to read. /// The first call advances the enumerator to the first key-value pair. /// public struct KeyValueEnumerator : IEnumerator> { internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next key-value pair. /// /// True if is valid to read after the call. public bool MoveNext() => m_Enumerator.MoveNext(); /// /// Resets the enumerator to its initial state. /// public void Reset() => m_Enumerator.Reset(); /// /// The current key-value pair. /// /// The current key-value pair. public KeyValue Current => m_Enumerator.GetCurrent(); object IEnumerator.Current => Current; } } [Obsolete("UnsafeHashSet is renamed to UnsafeParallelHashSet. (UnityUpgradable) -> UnsafeParallelHashSet", false)] public unsafe struct UnsafeHashSet : INativeDisposable , IEnumerable // Used by collection initializers. where T : unmanaged, IEquatable { internal UnsafeParallelHashMap m_Data; /// /// Initializes and returns an instance of UnsafeHashSet. /// /// The number of values that should fit in the initial allocation. /// The allocator to use. public UnsafeHashSet(int capacity, AllocatorManager.AllocatorHandle allocator) { m_Data = new UnsafeParallelHashMap(capacity, allocator); } /// /// Whether this set is empty. /// /// True if this set is empty. public bool IsEmpty => m_Data.IsEmpty; /// /// Returns the current number of values in this set. /// /// The current number of values in this set. public int Count() => m_Data.Count(); /// /// The number of values that fit in the current allocation. /// /// The number of values that fit in the current allocation. /// A new capacity. Must be larger than current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { get => m_Data.Capacity; set => m_Data.Capacity = value; } /// /// Whether this set has been allocated (and not yet deallocated). /// /// True if this set has been allocated (and not yet deallocated). public bool IsCreated => m_Data.IsCreated; /// /// Releases all resources (memory). /// public void Dispose() => m_Data.Dispose(); /// /// Creates and schedules a job that will dispose this set. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this set. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) => m_Data.Dispose(inputDeps); /// /// Removes all values. /// /// Does not change the capacity. public void Clear() => m_Data.Clear(); /// /// Adds a new value (unless it is already present). /// /// The value to add. /// True if the value was not already present. public bool Add(T item) => m_Data.TryAdd(item, false); /// /// Removes a particular value. /// /// The value to remove. /// True if the value was present. public bool Remove(T item) => m_Data.Remove(item); /// /// Returns true if a particular value is present. /// /// The value to check for. /// True if the value was present. public bool Contains(T item) => m_Data.ContainsKey(item); /// /// Returns an array with a copy of this set's values (in no particular order). /// /// The allocator to use. /// An array with a copy of the set's values. public NativeArray ToNativeArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyArray(allocator); /// /// Returns a parallel writer. /// /// A parallel writer. public ParallelWriter AsParallelWriter() { return new ParallelWriter { m_Data = m_Data.AsParallelWriter() }; } /// /// A parallel writer for an UnsafeHashSet. /// /// /// Use to create a parallel writer for a set. /// [NativeContainerIsAtomicWriteOnly] public struct ParallelWriter { internal UnsafeParallelHashMap.ParallelWriter m_Data; /// /// The number of values that fit in the current allocation. /// /// The number of values that fit in the current allocation. public int Capacity => m_Data.Capacity; /// /// Adds a new value (unless it is already present). /// /// The value to add. /// True if the value is not already present. public bool Add(T item) => m_Data.TryAdd(item, false); } /// /// Returns an enumerator over the values of this set. /// /// An enumerator over the values of this set. public Enumerator GetEnumerator() { return new Enumerator { m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_Data.m_Buffer) }; } /// /// 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(); } /// /// An enumerator over the values of a set. /// /// /// In an enumerator's initial state, is invalid. /// The first call advances the enumerator to the first value. /// public struct Enumerator : IEnumerator { internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value. /// /// True if `Current` is valid to read after the call. public bool MoveNext() => m_Enumerator.MoveNext(); /// /// Resets the enumerator to its initial state. /// public void Reset() => m_Enumerator.Reset(); /// /// The current value. /// /// The current value. public T Current => m_Enumerator.GetCurrentKey(); object IEnumerator.Current => Current; } } } namespace Unity.Collections { [Obsolete("NativeMultiHashMapIterator is renamed to NativeParallelMultiHashMapIterator. (UnityUpgradable) -> NativeParallelMultiHashMapIterator", false)] public struct NativeMultiHashMapIterator where TKey : struct { internal TKey key; internal int NextEntryIndex; internal int EntryIndex; /// /// Returns the entry index. /// /// The entry index. public int GetEntryIndex() => EntryIndex; } [Obsolete("NativeHashMap is renamed to NativeParallelHashMap. (UnityUpgradable) -> NativeParallelHashMap", false)] [StructLayout(LayoutKind.Sequential)] [NativeContainer] public unsafe struct NativeHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : struct, IEquatable where TValue : struct { internal UnsafeParallelHashMap m_HashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #if REMOVE_DISPOSE_SENTINEL #else [NativeSetClassTypeToNullOnSchedule] DisposeSentinel m_DisposeSentinel; #endif #endif /// /// Initializes and returns an instance of NativeParallelHashMap. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public NativeHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) : this(capacity, allocator, 2) { } NativeHashMap(int capacity, AllocatorManager.AllocatorHandle allocator, int disposeSentinelStackDepth) { m_HashMapData = new UnsafeParallelHashMap(capacity, allocator); #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL m_Safety = CollectionHelper.CreateSafetyHandle(allocator); #else if (AllocatorManager.IsCustomAllocator(allocator.ToAllocator)) { m_Safety = AtomicSafetyHandle.Create(); m_DisposeSentinel = null; } else { DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator); } #endif CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); #endif } /// /// Whether this hash map is empty. /// /// True if this hash map is empty or if the map has not been constructed. public bool IsEmpty { get { if (!IsCreated) { return true; } CheckRead(); return m_HashMapData.IsEmpty; } } /// /// The current number of key-value pairs in this hash map. /// /// The current number of key-value pairs in this hash map. public int Count() { CheckRead(); return m_HashMapData.Count(); } /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. /// A new capacity. Must be larger than the current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { get { CheckRead(); return m_HashMapData.Capacity; } set { CheckWrite(); m_HashMapData.Capacity = value; } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { CheckWrite(); m_HashMapData.Clear(); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying the hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. public bool TryAdd(TKey key, TValue item) { CheckWrite(); return m_HashMapData.TryAdd(key, item); } /// /// Adds a new key-value pair. /// /// If the key is already present, this method throws without modifying the hash map. /// The key to add. /// The value to add. /// Thrown if the key was already present. public void Add(TKey key, TValue item) { var added = TryAdd(key, item); if (!added) { ThrowKeyAlreadyAdded(key); } } /// /// Removes a key-value pair. /// /// The key to remove. /// True if a key-value pair was removed. public bool Remove(TKey key) { CheckWrite(); return m_HashMapData.Remove(key); } /// /// Returns the value associated with a key. /// /// The key to look up. /// Outputs the value associated with the key. Outputs default if the key was not present. /// True if the key was present. public bool TryGetValue(TKey key, out TValue item) { CheckRead(); return m_HashMapData.TryGetValue(key, out item); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present. public bool ContainsKey(TKey key) { CheckRead(); return m_HashMapData.ContainsKey(key); } /// /// Gets and sets values by key. /// /// Getting a key that is not present will throw. Setting a key that is not already present will add the key. /// The key to look up. /// The value associated with the key. /// For getting, thrown if the key was not present. public TValue this[TKey key] { get { CheckRead(); TValue res; if (m_HashMapData.TryGetValue(key, out res)) { return res; } ThrowKeyNotPresent(key); return default; } set { CheckWrite(); m_HashMapData[key] = value; } } /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public bool IsCreated => m_HashMapData.IsCreated; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL CollectionHelper.DisposeSafetyHandle(ref m_Safety); #else DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); #endif #endif m_HashMapData.Dispose(); } /// /// Creates and schedules a job that will dispose this hash map. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this hash map. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL #else // [DeallocateOnJobCompletion] is not supported, but we want the deallocation // to happen in a thread. DisposeSentinel needs to be cleared on main thread. // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling // will check that no jobs are writing to the container). DisposeSentinel.Clear(ref m_DisposeSentinel); #endif var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_HashMapData.m_Buffer, m_AllocatorLabel = m_HashMapData.m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_HashMapData.m_Buffer, m_AllocatorLabel = m_HashMapData.m_AllocatorLabel } }.Schedule(inputDeps); #endif m_HashMapData.m_Buffer = null; return jobHandle; } /// /// Returns an array with a copy of all this hash map's keys (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's keys (in no particular order). public NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetKeyArray(allocator); } /// /// Returns an array with a copy of all this hash map's values (in no particular order). /// /// The allocator to use. /// An array with a copy of all this hash map's values (in no particular order). public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetValueArray(allocator); } /// /// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values. /// /// The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`. /// The allocator to use. /// A NativeKeyValueArrays with a copy of all this hash map's keys and values. public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_HashMapData.GetKeyValueArrays(allocator); } /// /// Returns a parallel writer for this hash map. /// /// A parallel writer for this hash map. public ParallelWriter AsParallelWriter() { ParallelWriter writer; writer.m_Writer = m_HashMapData.AsParallelWriter(); #if ENABLE_UNITY_COLLECTIONS_CHECKS writer.m_Safety = m_Safety; CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data); #endif return writer; } /// /// A parallel writer for a NativeParallelHashMap. /// /// /// Use to create a parallel writer for a NativeParallelHashMap. /// [NativeContainer] [NativeContainerIsAtomicWriteOnly] [DebuggerDisplay("Capacity = {m_Writer.Capacity}")] public unsafe struct ParallelWriter { internal UnsafeParallelHashMap.ParallelWriter m_Writer; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif /// /// Returns the index of the current thread. /// /// In a job, each thread gets its own copy of the ParallelWriter struct, and the job system assigns /// each copy the index of its thread. /// The index of the current thread. public int m_ThreadIndex => m_Writer.m_ThreadIndex; /// /// The number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. public int Capacity { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Writer.Capacity; } } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying this hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. public bool TryAdd(TKey key, TValue item) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif return m_Writer.TryAdd(key, item); } } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// An enumerator over the key-value pairs of this hash map. public Enumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety); var ash = m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new Enumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_HashMapData.m_Buffer), }; } /// /// 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(); } /// /// An enumerator over the key-value pairs of a hash map. /// /// /// In an enumerator's initial state, is not valid to read. /// From this state, the first call advances the enumerator to the first key-value pair. /// [NativeContainer] [NativeContainerIsReadOnly] public struct Enumerator : IEnumerator> { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next key-value pair. /// /// True if is valid to read after the call. public bool MoveNext() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.MoveNext(); } /// /// Resets the enumerator to its initial state. /// public void Reset() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif m_Enumerator.Reset(); } /// /// The current key-value pair. /// /// The current key-value pair. public KeyValue Current => m_Enumerator.GetCurrent(); object IEnumerator.Current => Current; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckWrite() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void ThrowKeyNotPresent(TKey key) { throw new ArgumentException($"Key: {key} is not present in the NativeParallelHashMap."); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void ThrowKeyAlreadyAdded(TKey key) { throw new ArgumentException("An item with the same key has already been added", nameof(key)); } } [Obsolete("NativeMultiHashMap is renamed to NativeParallelMultiHashMap. (UnityUpgradable) -> NativeParallelMultiHashMap", false)] [StructLayout(LayoutKind.Sequential)] [NativeContainer] public unsafe struct NativeMultiHashMap : INativeDisposable , IEnumerable> // Used by collection initializers. where TKey : struct, IEquatable where TValue : struct { internal UnsafeParallelMultiHashMap m_MultiHashMapData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #if REMOVE_DISPOSE_SENTINEL #else [NativeSetClassTypeToNullOnSchedule] internal DisposeSentinel m_DisposeSentinel; #endif #endif /// /// Returns a newly allocated multi hash map. /// /// The number of key-value pairs that should fit in the initial allocation. /// The allocator to use. public NativeMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator) : this(capacity, allocator, 2) { } internal void Initialize(int capacity, ref U allocator, int disposeSentinelStackDepth) where U : unmanaged, AllocatorManager.IAllocator { m_MultiHashMapData = new UnsafeParallelMultiHashMap(capacity, allocator.Handle); #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL m_Safety = CollectionHelper.CreateSafetyHandle(allocator); #else if (allocator.IsCustomAllocator) { m_Safety = AtomicSafetyHandle.Create(); m_DisposeSentinel = null; } else { DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator); } #endif CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); #endif } NativeMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator, int disposeSentinelStackDepth) { this = default; Initialize(capacity, ref allocator, disposeSentinelStackDepth); } /// /// Whether this hash map is empty. /// /// True if the hash map is empty or if the hash map has not been constructed. public bool IsEmpty { get { CheckRead(); return m_MultiHashMapData.IsEmpty; } } /// /// Returns the current number of key-value pairs in this hash map. /// /// Key-value pairs with matching keys are counted as separate, individual pairs. /// The current number of key-value pairs in this hash map. public int Count() { CheckRead(); return m_MultiHashMapData.Count(); } /// /// Returns the number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. /// A new capacity. Must be larger than the current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { get { CheckRead(); return m_MultiHashMapData.Capacity; } set { CheckWrite(); m_MultiHashMapData.Capacity = value; } } /// /// Removes all key-value pairs. /// /// Does not change the capacity. public void Clear() { CheckWrite(); m_MultiHashMapData.Clear(); } /// /// Adds a new key-value pair. /// /// /// If a key-value pair with this key is already present, an additional separate key-value pair is added. /// /// The key to add. /// The value to add. public void Add(TKey key, TValue item) { CheckWrite(); m_MultiHashMapData.Add(key, item); } /// /// Removes a key and its associated value(s). /// /// The key to remove. /// The number of removed key-value pairs. If the key was not present, returns 0. public int Remove(TKey key) { CheckWrite(); return m_MultiHashMapData.Remove(key); } /// /// Removes a single key-value pair. /// /// An iterator representing the key-value pair to remove. /// Thrown if the iterator is invalid. public void Remove(NativeParallelMultiHashMapIterator it) { CheckWrite(); m_MultiHashMapData.Remove(it); } /// /// Gets an iterator for a key. /// /// The key. /// Outputs the associated value represented by the iterator. /// Outputs an iterator. /// True if the key was present. public bool TryGetFirstValue(TKey key, out TValue item, out NativeParallelMultiHashMapIterator it) { CheckRead(); return m_MultiHashMapData.TryGetFirstValue(key, out item, out it); } /// /// Advances an iterator to the next value associated with its key. /// /// Outputs the next value. /// A reference to the iterator to advance. /// True if the key was present and had another value. public bool TryGetNextValue(out TValue item, ref NativeParallelMultiHashMapIterator it) { CheckRead(); return m_MultiHashMapData.TryGetNextValue(out item, ref it); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present in this hash map. public bool ContainsKey(TKey key) { return TryGetFirstValue(key, out var temp0, out var temp1); } /// /// Returns the number of values associated with a given key. /// /// The key to look up. /// The number of values associated with the key. Returns 0 if the key was not present. public int CountValuesForKey(TKey key) { if (!TryGetFirstValue(key, out var value, out var iterator)) { return 0; } var count = 1; while (TryGetNextValue(out value, ref iterator)) { count++; } return count; } /// /// Sets a new value for an existing key-value pair. /// /// The new value. /// The iterator representing a key-value pair. /// True if a value was overwritten. public bool SetValue(TValue item, NativeParallelMultiHashMapIterator it) { CheckWrite(); return m_MultiHashMapData.SetValue(item, it); } /// /// Whether this hash map has been allocated (and not yet deallocated). /// /// True if this hash map has been allocated (and not yet deallocated). public bool IsCreated => m_MultiHashMapData.IsCreated; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL CollectionHelper.DisposeSafetyHandle(ref m_Safety); #else DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); #endif #endif m_MultiHashMapData.Dispose(); } /// /// Creates and schedules a job that will dispose this hash map. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this hash map. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL #else // [DeallocateOnJobCompletion] is not supported, but we want the deallocation // to happen in a thread. DisposeSentinel needs to be cleared on main thread. // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling // will check that no jobs are writing to the container). DisposeSentinel.Clear(ref m_DisposeSentinel); #endif var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_MultiHashMapData.m_Buffer, m_AllocatorLabel = m_MultiHashMapData.m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new UnsafeParallelHashMapDataDisposeJob { Data = new UnsafeParallelHashMapDataDispose { m_Buffer = m_MultiHashMapData.m_Buffer, m_AllocatorLabel = m_MultiHashMapData.m_AllocatorLabel } }.Schedule(inputDeps); #endif m_MultiHashMapData.m_Buffer = null; return jobHandle; } /// /// Returns an array with a copy of all the keys (in no particular order). /// /// A key with *N* values is included *N* times in the array. /// /// Use `GetUniqueKeyArray` of instead if you only want one occurrence of each key. /// The allocator to use. /// An array with a copy of all the keys (in no particular order). public NativeArray GetKeyArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_MultiHashMapData.GetKeyArray(allocator); } /// /// Returns an array with a copy of all the values (in no particular order). /// /// The values are not deduplicated. If you sort the returned array, /// you can use to remove duplicate values. /// The allocator to use. /// An array with a copy of all the values (in no particular order). public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_MultiHashMapData.GetValueArray(allocator); } /// /// Returns a NativeKeyValueArrays with a copy of all the keys and values (in no particular order). /// /// A key with *N* values is included *N* times in the array. /// /// The allocator to use. /// A NativeKeyValueArrays with a copy of all the keys and values (in no particular order). public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_MultiHashMapData.GetKeyValueArrays(allocator); } /// /// Returns a parallel writer for this hash map. /// /// A parallel writer for this hash map. public ParallelWriter AsParallelWriter() { ParallelWriter writer; writer.m_Writer = m_MultiHashMapData.AsParallelWriter(); #if ENABLE_UNITY_COLLECTIONS_CHECKS writer.m_Safety = m_Safety; CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref s_staticSafetyId.Data); #endif return writer; } /// /// A parallel writer for a NativeParallelMultiHashMap. /// /// /// Use to create a parallel writer for a NativeParallelMultiHashMap. /// [NativeContainer] [NativeContainerIsAtomicWriteOnly] public unsafe struct ParallelWriter { internal UnsafeParallelMultiHashMap.ParallelWriter m_Writer; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif /// /// Returns the index of the current thread. /// /// In a job, each thread gets its own copy of the ParallelWriter struct, and the job system assigns /// each copy the index of its thread. /// The index of the current thread. public int m_ThreadIndex => m_Writer.m_ThreadIndex; /// /// Returns the number of key-value pairs that fit in the current allocation. /// /// The number of key-value pairs that fit in the current allocation. public int Capacity { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Writer.Capacity; } } /// /// Adds a new key-value pair. /// /// /// If a key-value pair with this key is already present, an additional separate key-value pair is added. /// /// The key to add. /// The value to add. public void Add(TKey key, TValue item) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif m_Writer.Add(key, item); } } /// /// Returns an enumerator over the values of an individual key. /// /// The key to get an enumerator for. /// An enumerator over the values of a key. public Enumerator GetValuesForKey(TKey key) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return new Enumerator { hashmap = this, key = key, isFirst = true }; } /// /// An enumerator over the values of an individual key in a multi hash map. /// /// /// In an enumerator's initial state, is not valid to read. /// The first call advances the enumerator to the first value of the key. /// public struct Enumerator : IEnumerator { internal NativeMultiHashMap hashmap; internal TKey key; internal bool isFirst; TValue value; NativeParallelMultiHashMapIterator iterator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value of the key. /// /// True if is valid to read after the call. public bool MoveNext() { //Avoids going beyond the end of the collection. if (isFirst) { isFirst = false; return hashmap.TryGetFirstValue(key, out value, out iterator); } return hashmap.TryGetNextValue(out value, ref iterator); } /// /// Resets the enumerator to its initial state. /// public void Reset() => isFirst = true; /// /// The current value. /// /// The current value. public TValue Current => value; object IEnumerator.Current => Current; /// /// Returns this enumerator. /// /// This enumerator. public Enumerator GetEnumerator() { return this; } } /// /// Returns an enumerator over the key-value pairs of this hash map. /// /// A key with *N* values is visited by the enumerator *N* times. /// An enumerator over the key-value pairs of this hash map. public KeyValueEnumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety); var ash = m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new KeyValueEnumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_MultiHashMapData.m_Buffer), }; } /// /// 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(); } /// /// An enumerator over the key-value pairs of a multi hash map. /// /// A key with *N* values is visited by the enumerator *N* times. /// /// In an enumerator's initial state, is not valid to read. /// The first call advances the enumerator to the first key-value pair. /// [NativeContainer] [NativeContainerIsReadOnly] public struct KeyValueEnumerator : IEnumerator> { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next key-value pair. /// /// True if is valid to read after the call. public unsafe bool MoveNext() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.MoveNext(); } /// /// Resets the enumerator to its initial state. /// public void Reset() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif m_Enumerator.Reset(); } /// /// The current key-value pair. /// /// The current key-value pair. public KeyValue Current { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.GetCurrent(); } } object IEnumerator.Current => Current; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckWrite() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); #endif } } [Obsolete("NativeHashSet is renamed to NativeParallelHashSet. (UnityUpgradable) -> NativeParallelHashSet", false)] public unsafe struct NativeHashSet : INativeDisposable , IEnumerable // Used by collection initializers. where T : unmanaged, IEquatable { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #endif internal NativeParallelHashMap m_Data; /// /// Initializes and returns an instance of NativeHashSet. /// /// The number of values that should fit in the initial allocation. /// The allocator to use. public NativeHashSet(int capacity, AllocatorManager.AllocatorHandle allocator) { m_Data = new NativeParallelHashMap(capacity, allocator); #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.SetStaticSafetyId>(ref m_Data.m_Safety, ref s_staticSafetyId.Data); #endif } /// /// Whether this set is empty. /// /// True if this set is empty or if the set has not been constructed. public bool IsEmpty => m_Data.IsEmpty; /// /// Returns the current number of values in this set. /// /// The current number of values in this set. public int Count() => m_Data.Count(); /// /// The number of values that fit in the current allocation. /// /// The number of values that fit in the current allocation. /// A new capacity. Must be larger than current capacity. /// Thrown if `value` is less than the current capacity. public int Capacity { get => m_Data.Capacity; set => m_Data.Capacity = value; } /// /// Whether this set has been allocated (and not yet deallocated). /// /// True if this set has been allocated (and not yet deallocated). public bool IsCreated => m_Data.IsCreated; /// /// Releases all resources (memory and safety handles). /// public void Dispose() => m_Data.Dispose(); /// /// Creates and schedules a job that will dispose this set. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will dispose this set. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) => m_Data.Dispose(inputDeps); /// /// Removes all values. /// /// Does not change the capacity. public void Clear() => m_Data.Clear(); /// /// Adds a new value (unless it is already present). /// /// The value to add. /// True if the value was not already present. public bool Add(T item) => m_Data.TryAdd(item, false); /// /// Removes a particular value. /// /// The value to remove. /// True if the value was present. public bool Remove(T item) => m_Data.Remove(item); /// /// Returns true if a particular value is present. /// /// The value to check for. /// True if the value was present. public bool Contains(T item) => m_Data.ContainsKey(item); /// /// Returns an array with a copy of this set's values (in no particular order). /// /// The allocator to use. /// An array with a copy of the set's values. public NativeArray ToNativeArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyArray(allocator); /// /// Returns a parallel writer. /// /// A parallel writer. public ParallelWriter AsParallelWriter() { ParallelWriter writer; writer.m_Data = m_Data.AsParallelWriter(); #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.SetStaticSafetyId(ref writer.m_Data.m_Safety, ref ParallelWriter.s_staticSafetyId.Data); #endif return writer; } /// /// A parallel writer for a NativeHashSet. /// /// /// Use to create a parallel writer for a set. /// [NativeContainerIsAtomicWriteOnly] public struct ParallelWriter { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif internal NativeParallelHashMap.ParallelWriter m_Data; /// /// The number of values that fit in the current allocation. /// /// The number of values that fit in the current allocation. public int Capacity => m_Data.Capacity; /// /// Adds a new value (unless it is already present). /// /// The value to add. /// True if the value is not already present. public bool Add(T item) => m_Data.TryAdd(item, false); } /// /// Returns an enumerator over the values of this set. /// /// An enumerator over the values of this set. public Enumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Data.m_Safety); var ash = m_Data.m_Safety; AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new Enumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = new UnsafeParallelHashMapDataEnumerator(m_Data.m_HashMapData.m_Buffer), }; } /// /// 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(); } /// /// An enumerator over the values of a set. /// /// /// In an enumerator's initial state, is invalid. /// The first call advances the enumerator to the first value. /// [NativeContainer] [NativeContainerIsReadOnly] public struct Enumerator : IEnumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif internal UnsafeParallelHashMapDataEnumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value. /// /// True if `Current` is valid to read after the call. public bool MoveNext() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.MoveNext(); } /// /// Resets the enumerator to its initial state. /// public void Reset() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif m_Enumerator.Reset(); } /// /// The current value. /// /// The current value. public T Current { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.GetCurrentKey(); } } /// /// Gets the element at the current position of the enumerator in the container. /// object IEnumerator.Current => Current; } } }