using System; using System.Collections.Generic; using System.Diagnostics; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Jobs.LowLevel.Unsafe; using Unity.Mathematics; namespace Unity.Collections { /// <summary> /// Extension methods for sorting collections. /// </summary> [BurstCompatible] public static class NativeSortExtension { /// <summary> /// A comparer that uses IComparable.CompareTo(). For primitive types, this is an ascending sort. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public struct DefaultComparer<T> : IComparer<T> where T : IComparable<T> { /// <summary> /// Compares two values. /// </summary> /// <param name="x">First value to compare.</param> /// <param name="y">Second value to compare.</param> /// <returns>A signed integer that denotes the relative values of `x` and `y`: /// 0 if they're equal, negative if `x < y`, and positive if `x > y`.</returns> public int Compare(T x, T y) => x.CompareTo(y); } /// <summary> /// Sorts an array in ascending order. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="length">The number of elements to sort in the array. /// Indexes greater than or equal to `length` won't be included in the sort.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public unsafe static void Sort<T>(T* array, int length) where T : unmanaged, IComparable<T> { IntroSort<T, DefaultComparer<T>>(array, length, new DefaultComparer<T>()); } /// <summary> /// Sorts an array using a custom comparison. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="length">The number of elements to sort in the array. /// Indexes greater than or equal to `length` won't be included in the sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static void Sort<T, U>(T* array, int length, U comp) where T : unmanaged where U : IComparer<T> { IntroSort<T, U>(array, length, comp); } /// <summary> /// Creates and schedules a job that will sort an array in ascending order. /// </summary> /// <typeparam name="T">The type of elements.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="length">The number of elements to sort in the array. /// Indexes greater than or equal to `length` won't be included in the sort.</param> /// <param name="inputDeps">A job handle which the newly created job will depend upon.</param> /// <returns>The handle of the new job which will sort the array.</returns> [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(T*, int).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T>(T* array, int length, JobHandle inputDeps) where T : unmanaged, IComparable<T> { return Sort(array, length, new DefaultComparer<T>(), inputDeps); } /// <summary> /// Returns a job which will sort an array in ascending order. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="length">The number of elements to sort in the array. /// Indexes greater than or equal to `length` won't be included in the sort.</param> /// <returns>A job for sorting the array.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, DefaultComparer<T>> SortJob<T>(T* array, int length) where T : unmanaged, IComparable<T> { return new SortJob<T, DefaultComparer<T>> {Data = array, Length = length, Comp = new DefaultComparer<T>()}; } /// <summary> /// Sorts an array using a custom comparison function. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <typeparam name="U">The comparer type.</typeparam> /// <param name="array">Array to perform sort.</param> /// <param name="length">Number of elements to perform sort.</param> /// <param name="comp">A comparison function that determines whether one element in the array is less than, equal to, or greater than another element.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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(T*, int, U).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T, U>(T* array, int length, U comp, JobHandle inputDeps) where T : unmanaged where U : IComparer<T> { if (length == 0) { return inputDeps; } var segmentCount = (length + 1023) / 1024; var workerCount = math.max(1, JobsUtility.MaxJobThreadCount); var workerSegmentCount = segmentCount / workerCount; var segmentSortJob = new SegmentSort<T, U> { Data = array, Comp = comp, Length = length, SegmentWidth = 1024 }; var segmentSortJobHandle = segmentSortJob.Schedule(segmentCount, workerSegmentCount, inputDeps); var segmentSortMergeJob = new SegmentSortMerge<T, U> { Data = array, Comp = comp, Length = length, SegmentWidth = 1024 }; var segmentSortMergeJobHandle = segmentSortMergeJob.Schedule(segmentSortJobHandle); return segmentSortMergeJobHandle; } /// <summary> /// Returns a job which will sort an array using a custom comparison. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="length">The number of elements to sort in the array. /// Indexes greater than or equal to `length` won't be included in the sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>A job for sorting the array.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, U> SortJob<T, U>(T* array, int length, U comp) where T : unmanaged where U : IComparer<T> { return new SortJob<T, U>() {Data = array, Length = length, Comp = comp}; } /// <summary> /// Finds a value in a sorted array by binary search. /// </summary> /// <remarks>If the array is not sorted, the value might not be found, even if it's present in the array.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="ptr">The array to search.</param> /// <param name="value">The value to locate.</param> /// <param name="length">The number of elements to search. Indexes greater than or equal to `length` won't be searched.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public unsafe static int BinarySearch<T>(T* ptr, int length, T value) where T : unmanaged, IComparable<T> { return BinarySearch(ptr, length, value, new DefaultComparer<T>()); } /// <summary> /// Finds a value in a sorted array by binary search using a custom comparison. /// </summary> /// <remarks>If the array is not sorted, the value might not be found, even if it's present in the array.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="ptr">The array to search.</param> /// <param name="value">The value to locate.</param> /// <param name="length">The number of elements to search. Indexes greater than or equal to `length` won't be searched.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static int BinarySearch<T, U>(T* ptr, int length, T value, U comp) where T : unmanaged where U : IComparer<T> { var offset = 0; for (var l = length; l != 0; l >>= 1) { var idx = offset + (l >> 1); var curr = ptr[idx]; var r = comp.Compare(value, curr); if (r == 0) { return idx; } if (r > 0) { offset = idx + 1; --l; } } return ~offset; } /// <summary> /// Sorts this array in ascending order. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="array">The array to sort.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public unsafe static void Sort<T>(this NativeArray<T> array) where T : struct, IComparable<T> { IntroSortStruct<T, DefaultComparer<T>>(array.GetUnsafePtr(), array.Length, new DefaultComparer<T>()); } /// <summary> /// Sorts this array using a custom comparison. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static void Sort<T, U>(this NativeArray<T> array, U comp) where T : struct where U : IComparer<T> { IntroSortStruct<T, U>(array.GetUnsafePtr(), array.Length, comp); } /// <summary> /// Sorts this array in ascending order. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <param name="array">The array to sort.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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 NativeArray<T>).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T>(this NativeArray<T> array, JobHandle inputDeps) where T : unmanaged, IComparable<T> { return Sort((T*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array), array.Length, new DefaultComparer<T>(), inputDeps); } /// <summary> /// Returns a job which will sort this array in ascending order. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="array">The array to sort.</param> /// <returns>A job for sorting this array.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, DefaultComparer<T>> SortJob<T>(this NativeArray<T> array) where T : unmanaged, IComparable<T> { return SortJob((T*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array), array.Length, new DefaultComparer<T>()); } /// <summary> /// Sorts this array using a custom comparison function. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <typeparam name="U">The comparer type.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="comp">A comparison function that determines whether one element in the array is less than, equal to, or greater than another element.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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 NativeArray<T>, U).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T, U>(this NativeArray<T> array, U comp, JobHandle inputDeps) where T : unmanaged where U : IComparer<T> { return Sort((T*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array), array.Length, comp, inputDeps); } /// <summary> /// Returns a job which will sort this array using a custom comparison. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="array">The array to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>A job for sorting the array.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, U> SortJob<T, U>(this NativeArray<T> array, U comp) where T : unmanaged where U : IComparer<T> { return new SortJob<T, U> { Data = (T*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array), Length = array.Length, Comp = comp }; } /// <summary> /// Finds a value in this sorted array by binary search. /// </summary> /// <remarks>If the array is not sorted, the value might not be found, even if it's present in this array.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="array">The array to search.</param> /// <param name="value">The value to locate.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public static int BinarySearch<T>(this NativeArray<T> array, T value) where T : unmanaged, IComparable<T> { return array.BinarySearch(value, new DefaultComparer<T>()); } /// <summary> /// Finds a value in this sorted array by binary search using a custom comparison. /// </summary> /// <remarks>If the array is not sorted, the value might not be found, even if it's present in this array. /// </remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The comparer type.</typeparam> /// <param name="array">The array to search.</param> /// <param name="value">The value to locate.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static int BinarySearch<T, U>(this NativeArray<T> array, T value, U comp) where T : unmanaged where U : IComparer<T> { return BinarySearch((T*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array), array.Length, value, comp); } /// <summary> /// Sorts this list in ascending order. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="list">The list to sort.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public unsafe static void Sort<T>(this NativeList<T> list) where T : unmanaged, IComparable<T> { list.Sort(new DefaultComparer<T>()); } /// <summary> /// Sorts this list using a custom comparison. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="list">The list to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static void Sort<T, U>(this NativeList<T> list, U comp) where T : unmanaged where U : IComparer<T> { IntroSort<T, U>(list.GetUnsafePtr(), list.Length, comp); } /// <summary> /// Sorts this list in ascending order. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <param name="array">The container to perform sort.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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 NativeList<T>).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T>(this NativeList<T> array, JobHandle inputDeps) where T : unmanaged, IComparable<T> { return array.Sort(new DefaultComparer<T>(), inputDeps); } /// <summary> /// Returns a job which will sort this list in ascending order. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="list">The list to sort.</param> /// <returns>A job for sorting this list.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, DefaultComparer<T>> SortJob<T>(this NativeList<T> list) where T : unmanaged, IComparable<T> { return SortJob((T*)list.GetUnsafePtr(), list.Length,new DefaultComparer<T>()); } /// <summary> /// Sorts this list using a custom comparison function. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <typeparam name="U">The comparer type.</typeparam> /// <param name="list">The container to perform sort.</param> /// <param name="comp">A comparison function that determines whether one element in the array is less than, equal to, or greater than another element.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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 NativeList<T>, U).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T, U>(this NativeList<T> list, U comp, JobHandle inputDeps) where T : unmanaged where U : IComparer<T> { return Sort((T*)list.GetUnsafePtr(), list.Length, comp, inputDeps); } /// <summary> /// Returns a job which will sort this list using a custom comparison. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="list">The list to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>A job for sorting this list.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, U> SortJob<T, U>(this NativeList<T> list, U comp) where T : unmanaged where U : IComparer<T> { return SortJob((T*)list.GetUnsafePtr(), list.Length, comp); } /// <summary> /// Finds a value in this sorted list by binary search. /// </summary> /// <remarks>If this list is not sorted, the value might not be found, even if it's present in this list.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="list">The list to search.</param> /// <param name="value">The value to locate.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public static int BinarySearch<T>(this NativeList<T> list, T value) where T : unmanaged, IComparable<T> { return list.BinarySearch(value, new DefaultComparer<T>()); } /// <summary> /// Finds a value in this sorted list by binary search using a custom comparison. /// </summary> /// <remarks>If this list is not sorted, the value may not be found, even if it's present in this list.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="list">The list to search.</param> /// <param name="value">The value to locate.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static int BinarySearch<T, U>(this NativeList<T> list, T value, U comp) where T : unmanaged where U : IComparer<T> { return BinarySearch((T*)list.GetUnsafePtr(), list.Length, value, comp); } /// <summary> /// Sorts this list in ascending order. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="list">The list to sort.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public unsafe static void Sort<T>(this UnsafeList<T> list) where T : unmanaged, IComparable<T> { list.Sort(new DefaultComparer<T>()); } /// <summary> /// Sorts the list using a custom comparison. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="list">The list to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static void Sort<T, U>(this UnsafeList<T> list, U comp) where T : unmanaged where U : IComparer<T> { IntroSort<T, U>(list.Ptr, list.Length, comp); } /// <summary> /// Sorts this list in ascending order. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <param name="list">The container to perform sort.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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<T>).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T>(this UnsafeList<T> list, JobHandle inputDeps) where T : unmanaged, IComparable<T> { return list.Sort(new DefaultComparer<T>(), inputDeps); } /// <summary> /// Returns a job which will sort this list in ascending order. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="list">The list to sort.</param> /// <returns>A job for sorting this list.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, DefaultComparer<T>> SortJob<T>(this UnsafeList<T> list) where T : unmanaged, IComparable<T> { return SortJob((T*)list.Ptr, list.Length, new DefaultComparer<T>()); } /// <summary> /// Sorts this list using a custom comparison function. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <typeparam name="U">The comparer type.</typeparam> /// <param name="list">The container to perform sort.</param> /// <param name="comp">A comparison function that determines whether one element in the array is less than, equal to, or greater than another element.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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<T>, U).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T, U>(this UnsafeList<T> list, U comp, JobHandle inputDeps) where T : unmanaged where U : IComparer<T> { return Sort(list.Ptr, list.Length, comp, inputDeps); } /// <summary> /// Returns a job which will sort this list using a custom comparison. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="list">The list to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>A job for sorting this list.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, U> SortJob<T, U>(this UnsafeList<T> list, U comp) where T : unmanaged where U : IComparer<T> { return SortJob(list.Ptr, list.Length, comp); } /// <summary> /// Finds a value in this sorted list by binary search. /// </summary> /// <remarks>If this list is not sorted, the value might not be found, even if it's present in this list.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="list">The list to search.</param> /// <param name="value">The value to locate.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public static int BinarySearch<T>(this UnsafeList<T> list, T value) where T : unmanaged, IComparable<T> { return list.BinarySearch(value, new DefaultComparer<T>()); } /// <summary> /// Finds a value in this sorted list by binary search using a custom comparison. /// </summary> /// <remarks>If this list is not sorted, the value might not be found, even if it's present in this list.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="list">The list to search.</param> /// <param name="value">The value to locate.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static int BinarySearch<T, U>(this UnsafeList<T> list, T value, U comp) where T : unmanaged where U : IComparer<T> { return BinarySearch(list.Ptr, list.Length, value, comp); } /// <summary> /// Sorts this slice in ascending order. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="slice">The slice to sort.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public unsafe static void Sort<T>(this NativeSlice<T> slice) where T : struct, IComparable<T> { slice.Sort(new DefaultComparer<T>()); } /// <summary> /// Sorts this slice using a custom comparison. /// </summary> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="slice">The slice to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static void Sort<T, U>(this NativeSlice<T> slice, U comp) where T : struct where U : IComparer<T> { CheckStrideMatchesSize<T>(slice.Stride); IntroSortStruct<T, U>(slice.GetUnsafePtr(), slice.Length, comp); } /// <summary> /// Sorts this slice in ascending order. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <param name="slice">The container to perform sort.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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 NativeSlice<T>).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T>(this NativeSlice<T> slice, JobHandle inputDeps) where T : unmanaged, IComparable<T> { return slice.Sort(new DefaultComparer<T>(), inputDeps); } /// <summary> /// Returns a job which will sort this slice in ascending order. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="slice">The slice to sort.</param> /// <returns>A job for sorting this slice.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, DefaultComparer<T>> SortJob<T>(this NativeSlice<T> slice) where T : unmanaged, IComparable<T> { CheckStrideMatchesSize<T>(slice.Stride); return SortJob((T*)slice.GetUnsafePtr(), slice.Length, new DefaultComparer<T>()); } /// <summary> /// Sorts this slice using a custom comparison function. /// </summary> /// <typeparam name="T">Source type of elements</typeparam> /// <typeparam name="U">The comparer type.</typeparam> /// <param name="slice">The container to perform sort.</param> /// <param name="comp">A comparison function that determines whether one element in the array is less than, equal to, or greater than another element.</param> /// <param name="inputDeps">The job handle or handles for any scheduled jobs that use this container.</param> /// <returns>A new job handle containing the prior handles as well as the handle for the job that sorts /// the container.</returns> [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 NativeSlice<T>, U).Schedule(JobHandle). (RemovedAfter 2021-06-20)", false)] public unsafe static JobHandle Sort<T, U>(this NativeSlice<T> slice, U comp, JobHandle inputDeps) where T : unmanaged where U : IComparer<T> { return Sort((T*)slice.GetUnsafePtr(), slice.Length, comp, inputDeps); } /// <summary> /// Returns a job which will sort this slice using a custom comparison. /// </summary> /// <remarks>This method does not schedule the job. Scheduling the job is left to you.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="slice">The slice to sort.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>A job for sorting this slice.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe static SortJob<T, U> SortJob<T, U>(this NativeSlice<T> slice, U comp) where T : unmanaged where U : IComparer<T> { CheckStrideMatchesSize<T>(slice.Stride); return SortJob((T*)slice.GetUnsafePtr(), slice.Length, comp); } /// <summary> /// Finds a value in this sorted slice by binary search. /// </summary> /// <remarks>If this slice is not sorted, the value might not be found, even if it's present in this slice.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <param name="slice">The slice to search.</param> /// <param name="value">The value to locate.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int) })] public static int BinarySearch<T>(this NativeSlice<T> slice, T value) where T : unmanaged, IComparable<T> { return slice.BinarySearch(value, new DefaultComparer<T>()); } /// <summary> /// Finds a value in this sorted slice by binary search using a custom comparison. /// </summary> /// <remarks>If this slice is not sorted, the value might not be found, even if it's present in this slice.</remarks> /// <typeparam name="T">The type of the elements.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> /// <param name="slice">The slice to search.</param> /// <param name="value">The value to locate.</param> /// <param name="comp">The comparison function used to determine the relative order of the elements.</param> /// <returns>If found, the index of the located value. If not found, the return value is negative.</returns> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] public unsafe static int BinarySearch<T, U>(this NativeSlice<T> slice, T value, U comp) where T : unmanaged where U : IComparer<T> { return BinarySearch((T*)slice.GetUnsafePtr(), slice.Length, value, comp); } /// -- Internals [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(DefaultComparer<int>) })] unsafe internal static void IntroSort<T, U>(void* array, int length, U comp) where T : unmanaged where U : IComparer<T> { IntroSort<T, U>(array, 0, length - 1, 2 * CollectionHelper.Log2Floor(length), comp); } const int k_IntrosortSizeThreshold = 16; unsafe static void IntroSort<T, U>(void* array, int lo, int hi, int depth, U comp) where T : unmanaged where U : IComparer<T> { while (hi > lo) { int partitionSize = hi - lo + 1; if (partitionSize <= k_IntrosortSizeThreshold) { if (partitionSize == 1) { return; } if (partitionSize == 2) { SwapIfGreaterWithItems<T, U>(array, lo, hi, comp); return; } if (partitionSize == 3) { SwapIfGreaterWithItems<T, U>(array, lo, hi - 1, comp); SwapIfGreaterWithItems<T, U>(array, lo, hi, comp); SwapIfGreaterWithItems<T, U>(array, hi - 1, hi, comp); return; } InsertionSort<T, U>(array, lo, hi, comp); return; } if (depth == 0) { HeapSort<T, U>(array, lo, hi, comp); return; } depth--; int p = Partition<T, U>(array, lo, hi, comp); IntroSort<T, U>(array, p + 1, hi, depth, comp); hi = p - 1; } } unsafe static void InsertionSort<T, U>(void* array, int lo, int hi, U comp) where T : unmanaged where U : IComparer<T> { int i, j; T t; for (i = lo; i < hi; i++) { j = i; t = UnsafeUtility.ReadArrayElement<T>(array, i + 1); while (j >= lo && comp.Compare(t, UnsafeUtility.ReadArrayElement<T>(array, j)) < 0) { UnsafeUtility.WriteArrayElement<T>(array, j + 1, UnsafeUtility.ReadArrayElement<T>(array, j)); j--; } UnsafeUtility.WriteArrayElement<T>(array, j + 1, t); } } unsafe static int Partition<T, U>(void* array, int lo, int hi, U comp) where T : unmanaged where U : IComparer<T> { int mid = lo + ((hi - lo) / 2); SwapIfGreaterWithItems<T, U>(array, lo, mid, comp); SwapIfGreaterWithItems<T, U>(array, lo, hi, comp); SwapIfGreaterWithItems<T, U>(array, mid, hi, comp); T pivot = UnsafeUtility.ReadArrayElement<T>(array, mid); Swap<T>(array, mid, hi - 1); int left = lo, right = hi - 1; while (left < right) { while (comp.Compare(pivot, UnsafeUtility.ReadArrayElement<T>(array, ++left)) > 0) ; while (comp.Compare(pivot, UnsafeUtility.ReadArrayElement<T>(array, --right)) < 0) ; if (left >= right) break; Swap<T>(array, left, right); } Swap<T>(array, left, (hi - 1)); return left; } unsafe static void HeapSort<T, U>(void* array, int lo, int hi, U comp) where T : unmanaged where U : IComparer<T> { int n = hi - lo + 1; for (int i = n / 2; i >= 1; i--) { Heapify<T, U>(array, i, n, lo, comp); } for (int i = n; i > 1; i--) { Swap<T>(array, lo, lo + i - 1); Heapify<T, U>(array, 1, i - 1, lo, comp); } } unsafe static void Heapify<T, U>(void* array, int i, int n, int lo, U comp) where T : unmanaged where U : IComparer<T> { T val = UnsafeUtility.ReadArrayElement<T>(array, lo + i - 1); int child; while (i <= n / 2) { child = 2 * i; if (child < n && (comp.Compare(UnsafeUtility.ReadArrayElement<T>(array, lo + child - 1), UnsafeUtility.ReadArrayElement<T>(array, (lo + child))) < 0)) { child++; } if (comp.Compare(UnsafeUtility.ReadArrayElement<T>(array, (lo + child - 1)), val) < 0) break; UnsafeUtility.WriteArrayElement(array, lo + i - 1, UnsafeUtility.ReadArrayElement<T>(array, lo + child - 1)); i = child; } UnsafeUtility.WriteArrayElement(array, lo + i - 1, val); } unsafe static void Swap<T>(void* array, int lhs, int rhs) where T : unmanaged { T val = UnsafeUtility.ReadArrayElement<T>(array, lhs); UnsafeUtility.WriteArrayElement(array, lhs, UnsafeUtility.ReadArrayElement<T>(array, rhs)); UnsafeUtility.WriteArrayElement(array, rhs, val); } unsafe static void SwapIfGreaterWithItems<T, U>(void* array, int lhs, int rhs, U comp) where T : unmanaged where U : IComparer<T> { if (lhs != rhs) { if (comp.Compare(UnsafeUtility.ReadArrayElement<T>(array, lhs), UnsafeUtility.ReadArrayElement<T>(array, rhs)) > 0) { Swap<T>(array, lhs, rhs); } } } unsafe static void IntroSortStruct<T, U>(void* array, int length, U comp) where T : struct where U : IComparer<T> { IntroSortStruct<T, U>(array, 0, length - 1, 2 * CollectionHelper.Log2Floor(length), comp); } unsafe static void IntroSortStruct<T, U>(void* array, int lo, int hi, int depth, U comp) where T : struct where U : IComparer<T> { while (hi > lo) { int partitionSize = hi - lo + 1; if (partitionSize <= k_IntrosortSizeThreshold) { if (partitionSize == 1) { return; } if (partitionSize == 2) { SwapIfGreaterWithItemsStruct<T, U>(array, lo, hi, comp); return; } if (partitionSize == 3) { SwapIfGreaterWithItemsStruct<T, U>(array, lo, hi - 1, comp); SwapIfGreaterWithItemsStruct<T, U>(array, lo, hi, comp); SwapIfGreaterWithItemsStruct<T, U>(array, hi - 1, hi, comp); return; } InsertionSortStruct<T, U>(array, lo, hi, comp); return; } if (depth == 0) { HeapSortStruct<T, U>(array, lo, hi, comp); return; } depth--; int p = PartitionStruct<T, U>(array, lo, hi, comp); IntroSortStruct<T, U>(array, p + 1, hi, depth, comp); hi = p - 1; } } unsafe static void InsertionSortStruct<T, U>(void* array, int lo, int hi, U comp) where T : struct where U : IComparer<T> { int i, j; T t; for (i = lo; i < hi; i++) { j = i; t = UnsafeUtility.ReadArrayElement<T>(array, i + 1); while (j >= lo && comp.Compare(t, UnsafeUtility.ReadArrayElement<T>(array, j)) < 0) { UnsafeUtility.WriteArrayElement<T>(array, j + 1, UnsafeUtility.ReadArrayElement<T>(array, j)); j--; } UnsafeUtility.WriteArrayElement<T>(array, j + 1, t); } } unsafe static int PartitionStruct<T, U>(void* array, int lo, int hi, U comp) where T : struct where U : IComparer<T> { int mid = lo + ((hi - lo) / 2); SwapIfGreaterWithItemsStruct<T, U>(array, lo, mid, comp); SwapIfGreaterWithItemsStruct<T, U>(array, lo, hi, comp); SwapIfGreaterWithItemsStruct<T, U>(array, mid, hi, comp); T pivot = UnsafeUtility.ReadArrayElement<T>(array, mid); SwapStruct<T>(array, mid, hi - 1); int left = lo, right = hi - 1; while (left < right) { while (comp.Compare(pivot, UnsafeUtility.ReadArrayElement<T>(array, ++left)) > 0) ; while (comp.Compare(pivot, UnsafeUtility.ReadArrayElement<T>(array, --right)) < 0) ; if (left >= right) break; SwapStruct<T>(array, left, right); } SwapStruct<T>(array, left, (hi - 1)); return left; } unsafe static void HeapSortStruct<T, U>(void* array, int lo, int hi, U comp) where T : struct where U : IComparer<T> { int n = hi - lo + 1; for (int i = n / 2; i >= 1; i--) { HeapifyStruct<T, U>(array, i, n, lo, comp); } for (int i = n; i > 1; i--) { SwapStruct<T>(array, lo, lo + i - 1); HeapifyStruct<T, U>(array, 1, i - 1, lo, comp); } } unsafe static void HeapifyStruct<T, U>(void* array, int i, int n, int lo, U comp) where T : struct where U : IComparer<T> { T val = UnsafeUtility.ReadArrayElement<T>(array, lo + i - 1); int child; while (i <= n / 2) { child = 2 * i; if (child < n && (comp.Compare(UnsafeUtility.ReadArrayElement<T>(array, lo + child - 1), UnsafeUtility.ReadArrayElement<T>(array, (lo + child))) < 0)) { child++; } if (comp.Compare(UnsafeUtility.ReadArrayElement<T>(array, (lo + child - 1)), val) < 0) break; UnsafeUtility.WriteArrayElement(array, lo + i - 1, UnsafeUtility.ReadArrayElement<T>(array, lo + child - 1)); i = child; } UnsafeUtility.WriteArrayElement(array, lo + i - 1, val); } unsafe static void SwapStruct<T>(void* array, int lhs, int rhs) where T : struct { T val = UnsafeUtility.ReadArrayElement<T>(array, lhs); UnsafeUtility.WriteArrayElement(array, lhs, UnsafeUtility.ReadArrayElement<T>(array, rhs)); UnsafeUtility.WriteArrayElement(array, rhs, val); } unsafe static void SwapIfGreaterWithItemsStruct<T, U>(void* array, int lhs, int rhs, U comp) where T : struct where U : IComparer<T> { if (lhs != rhs) { if (comp.Compare(UnsafeUtility.ReadArrayElement<T>(array, lhs), UnsafeUtility.ReadArrayElement<T>(array, rhs)) > 0) { SwapStruct<T>(array, lhs, rhs); } } } [BurstCompile] unsafe struct SegmentSort<T, U> : IJobParallelFor where T : unmanaged where U : IComparer<T> { [NativeDisableUnsafePtrRestriction] public T* Data; public U Comp; public int Length; public int SegmentWidth; public void Execute(int index) { var startIndex = index * SegmentWidth; var segmentLength = ((Length - startIndex) < SegmentWidth) ? (Length - startIndex) : SegmentWidth; Sort(Data + startIndex, segmentLength, Comp); } } [BurstCompile] unsafe struct SegmentSortMerge<T, U> : IJob where T : unmanaged where U : IComparer<T> { [NativeDisableUnsafePtrRestriction] public T* Data; public U Comp; public int Length; public int SegmentWidth; public void Execute() { var segmentCount = (Length + (SegmentWidth - 1)) / SegmentWidth; var segmentIndex = stackalloc int[segmentCount]; var resultCopy = (T*)Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<T>() * Length, 16, Allocator.Temp); for (int sortIndex = 0; sortIndex < Length; sortIndex++) { // find next best int bestSegmentIndex = -1; T bestValue = default(T); for (int i = 0; i < segmentCount; i++) { var startIndex = i * SegmentWidth; var offset = segmentIndex[i]; var segmentLength = ((Length - startIndex) < SegmentWidth) ? (Length - startIndex) : SegmentWidth; if (offset == segmentLength) continue; var nextValue = Data[startIndex + offset]; if (bestSegmentIndex != -1) { if (Comp.Compare(nextValue, bestValue) > 0) continue; } bestValue = nextValue; bestSegmentIndex = i; } segmentIndex[bestSegmentIndex]++; resultCopy[sortIndex] = bestValue; } UnsafeUtility.MemCpy(Data, resultCopy, UnsafeUtility.SizeOf<T>() * Length); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckStrideMatchesSize<T>(int stride) where T : struct { if (stride != UnsafeUtility.SizeOf<T>()) { throw new InvalidOperationException("Sort requires that stride matches the size of the source type"); } } } /// <summary> /// Returned by the `SortJob` methods of <see cref="Unity.Collections.NativeSortExtension"/>. Call `Schedule` to schedule the sorting. /// </summary> /// <typeparam name="T">The type of the elements to sort.</typeparam> /// <typeparam name="U">The type of the comparer.</typeparam> [BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(NativeSortExtension.DefaultComparer<int>) }, RequiredUnityDefine = "UNITY_2020_2_OR_NEWER" /* Due to job scheduling on 2020.1 using statics */)] public unsafe struct SortJob<T, U> where T : unmanaged where U : IComparer<T> { /// <summary> /// The data to sort. /// </summary> public T* Data; /// <summary> /// Comparison function. /// </summary> public U Comp; /// <summary> /// The length to sort. /// </summary> public int Length; [BurstCompile] struct SegmentSort : IJobParallelFor { [NativeDisableUnsafePtrRestriction] public T* Data; public U Comp; public int Length; public int SegmentWidth; public void Execute(int index) { var startIndex = index * SegmentWidth; var segmentLength = ((Length - startIndex) < SegmentWidth) ? (Length - startIndex) : SegmentWidth; NativeSortExtension.Sort(Data + startIndex, segmentLength, Comp); } } [BurstCompile] struct SegmentSortMerge : IJob { [NativeDisableUnsafePtrRestriction] public T* Data; public U Comp; public int Length; public int SegmentWidth; public void Execute() { var segmentCount = (Length + (SegmentWidth - 1)) / SegmentWidth; var segmentIndex = stackalloc int[segmentCount]; var resultCopy = (T*)Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<T>() * Length, 16, Allocator.Temp); for (int sortIndex = 0; sortIndex < Length; sortIndex++) { // find next best int bestSegmentIndex = -1; T bestValue = default(T); for (int i = 0; i < segmentCount; i++) { var startIndex = i * SegmentWidth; var offset = segmentIndex[i]; var segmentLength = ((Length - startIndex) < SegmentWidth) ? (Length - startIndex) : SegmentWidth; if (offset == segmentLength) continue; var nextValue = Data[startIndex + offset]; if (bestSegmentIndex != -1) { if (Comp.Compare(nextValue, bestValue) > 0) continue; } bestValue = nextValue; bestSegmentIndex = i; } segmentIndex[bestSegmentIndex]++; resultCopy[sortIndex] = bestValue; } UnsafeUtility.MemCpy(Data, resultCopy, UnsafeUtility.SizeOf<T>() * Length); } } /// <summary> /// Schedules this job. /// </summary> /// <param name="inputDeps">Handle of a job to depend upon.</param> /// <returns>The handle of this newly scheduled job.</returns> [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Schedule(JobHandle inputDeps = default) { if (Length == 0) return inputDeps; var segmentCount = (Length + 1023) / 1024; var workerCount = math.max(1, JobsUtility.MaxJobThreadCount); var workerSegmentCount = segmentCount / workerCount; var segmentSortJob = new SegmentSort { Data = Data, Comp = Comp, Length = Length, SegmentWidth = 1024 }; var segmentSortJobHandle = segmentSortJob.Schedule(segmentCount, workerSegmentCount, inputDeps); var segmentSortMergeJob = new SegmentSortMerge { Data = Data, Comp = Comp, Length = Length, SegmentWidth = 1024 }; var segmentSortMergeJobHandle = segmentSortMergeJob.Schedule(segmentSortJobHandle); return segmentSortMergeJobHandle; } } }