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 UnityEngine; using static Unity.Baselib.LowLevel.Binding; #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" NativeMultiHashMap 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 NativeMultiHashMap(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; NativeMultiHashMapIterator 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); } } }