using System; using System.Collections.Generic; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.Rendering { /// /// Static class with unsafe utility functions. /// public static unsafe class CoreUnsafeUtils { /// /// Fixed Buffer String Queue class. /// public struct FixedBufferStringQueue { byte* m_ReadCursor; byte* m_WriteCursor; readonly byte* m_BufferEnd; readonly byte* m_BufferStart; readonly int m_BufferLength; /// /// Number of element in the queue. /// public int Count { get; private set; } /// /// Constructor. /// /// Buffer pointer. /// Length of the provided allocated buffer in byte. public FixedBufferStringQueue(byte* ptr, int length) { m_BufferStart = ptr; m_BufferLength = length; m_BufferEnd = m_BufferStart + m_BufferLength; m_ReadCursor = m_BufferStart; m_WriteCursor = m_BufferStart; Count = 0; Clear(); } /// /// Try to push a new element in the queue. /// /// Element to push in the queue. /// True if the new element could be pushed in the queue. False if reserved memory was not enough. public bool TryPush(string v) { var size = v.Length * sizeof(char) + sizeof(int); if (m_WriteCursor + size >= m_BufferEnd) return false; *(int*)m_WriteCursor = v.Length; m_WriteCursor += sizeof(int); var charPtr = (char*)m_WriteCursor; for (int i = 0; i < v.Length; ++i, ++charPtr) *charPtr = v[i]; m_WriteCursor += sizeof(char) * v.Length; ++Count; return true; } /// /// Pop an element of the queue. /// /// Output result string. /// True if an element was succesfuly poped. public bool TryPop(out string v) { var size = *(int*)m_ReadCursor; if (size != 0) { m_ReadCursor += sizeof(int); v = new string((char*)m_ReadCursor, 0, size); m_ReadCursor += size * sizeof(char); return true; } v = default; return false; } /// /// Clear the queue. /// public void Clear() { m_WriteCursor = m_BufferStart; m_ReadCursor = m_BufferStart; Count = 0; UnsafeUtility.MemClear(m_BufferStart, m_BufferLength); } } /// /// Key Getter interface. /// /// Value /// Key public interface IKeyGetter { /// Getter /// The value /// The key TKey Get(ref TValue v); } internal struct DefaultKeyGetter : IKeyGetter { public T Get(ref T v) { return v; } } // Note: this is a workaround needed to circumvent some AOT issues when building for xbox internal struct UintKeyGetter : IKeyGetter { public uint Get(ref uint v) { return v; } } /// /// Extension method to copy elements of a list into a buffer. /// /// Type of the provided List. /// Input List. /// Destination buffer. /// Number of elements to copy. public static void CopyTo(this List list, void* dest, int count) where T : struct { var c = Mathf.Min(count, list.Count); for (int i = 0; i < c; ++i) UnsafeUtility.WriteArrayElement(dest, i, list[i]); } /// /// Extension method to copy elements of an array into a buffer. /// /// Type of the provided array. /// Input List. /// Destination buffer. /// Number of elements to copy. public static void CopyTo(this T[] list, void* dest, int count) where T : struct { var c = Mathf.Min(count, list.Length); for (int i = 0; i < c; ++i) UnsafeUtility.WriteArrayElement(dest, i, list[i]); } /// /// Quick Sort /// /// uint array. /// Left boundary. /// Left boundary. public static unsafe void QuickSort(uint[] arr, int left, int right) { fixed (uint* ptr = arr) CoreUnsafeUtils.QuickSort(ptr, left, right); } /// /// Quick sort. /// /// Type to compare. /// Number of element. /// Buffer to sort. public static void QuickSort(int count, void* data) where T : struct, IComparable { QuickSort>(data, 0, count - 1); } /// /// Quick sort. /// /// Value type. /// Key Type. /// Getter type. /// Number of element. /// Data to sort. public static void QuickSort(int count, void* data) where TKey : struct, IComparable where TValue : struct where TGetter : struct, IKeyGetter { QuickSort(data, 0, count - 1); } /// /// Quick sort. /// /// Value type. /// Key Type. /// Getter type. /// Data to sort. /// Left boundary. /// Right boundary. public static void QuickSort(void* data, int left, int right) where TKey : struct, IComparable where TValue : struct where TGetter : struct, IKeyGetter { // For Recursion if (left < right) { int pivot = Partition(data, left, right); if (pivot >= 1) QuickSort(data, left, pivot); if (pivot + 1 < right) QuickSort(data, pivot + 1, right); } } /// /// Index of an element in a buffer. /// /// Data type. /// Data buffer. /// Number of elements. /// Element to test against. /// The first index of the provided element. public static int IndexOf(void* data, int count, T v) where T : struct, IEquatable { for (int i = 0; i < count; ++i) { if (UnsafeUtility.ReadArrayElement(data, i).Equals(v)) return i; } return -1; } /// /// Compare hashes of two collections and provide /// a list of indices to remove in /// and a list of indices to add in . /// /// Assumes that and are sorted. /// /// Old value type. /// Old getter type. /// New value type. /// New getter type. /// Number of hashes in . /// Previous hashes to compare. /// Number of hashes in . /// New hashes to compare. /// Indices of element to add in will be written here. /// Indices of element to remove in will be written here. /// Number of elements to add will be written here. /// Number of elements to remove will be written here. /// The number of operation to perform ( + ) public static int CompareHashes( int oldHashCount, void* oldHashes, int newHashCount, void* newHashes, // assume that the capacity of indices is >= max(oldHashCount, newHashCount) int* addIndices, int* removeIndices, out int addCount, out int remCount ) where TOldValue : struct where TNewValue : struct where TOldGetter : struct, IKeyGetter where TNewGetter : struct, IKeyGetter { var oldGetter = new TOldGetter(); var newGetter = new TNewGetter(); addCount = 0; remCount = 0; // Check combined hashes if (oldHashCount == newHashCount) { var oldHash = new Hash128(); var newHash = new Hash128(); CombineHashes(oldHashCount, oldHashes, &oldHash); CombineHashes(newHashCount, newHashes, &newHash); if (oldHash == newHash) return 0; } var numOperations = 0; var oldI = 0; var newI = 0; while (oldI < oldHashCount || newI < newHashCount) { // At the end of old array. if (oldI == oldHashCount) { // No more hashes in old array. Add remaining entries from new array. for (; newI < newHashCount; ++newI) { addIndices[addCount++] = newI; ++numOperations; } continue; } // At end of new array. if (newI == newHashCount) { // No more hashes in old array. Remove remaining entries from old array. for (; oldI < oldHashCount; ++oldI) { removeIndices[remCount++] = oldI; ++numOperations; } continue; } // Both arrays have data. var newVal = UnsafeUtility.ReadArrayElement(newHashes, newI); var oldVal = UnsafeUtility.ReadArrayElement(oldHashes, oldI); var newKey = newGetter.Get(ref newVal); var oldKey = oldGetter.Get(ref oldVal); if (newKey == oldKey) { // Matching hash, skip. ++newI; ++oldI; continue; } // Both arrays have data, but hashes do not match. if (newKey < oldKey) { // oldIter is the greater hash. Push "add" jobs from the new array until reaching the oldIter hash. while (newI < newHashCount && newKey < oldKey) { addIndices[addCount++] = newI; ++newI; ++numOperations; newVal = UnsafeUtility.ReadArrayElement(newHashes, newI); newKey = newGetter.Get(ref newVal); } } else { // newIter is the greater hash. Push "remove" jobs from the old array until reaching the newIter hash. while (oldI < oldHashCount && oldKey < newKey) { removeIndices[remCount++] = oldI; ++numOperations; ++oldI; } } } return numOperations; } /// /// Compare hashes. /// /// Number of hashes in . /// Previous hashes to compare. /// Number of hashes in . /// New hashes to compare. /// Indices of element to add in will be written here. /// Indices of element to remove in will be written here. /// Number of elements to add will be written here. /// Number of elements to remove will be written here. /// The number of operation to perform ( + ) public static int CompareHashes( int oldHashCount, Hash128* oldHashes, int newHashCount, Hash128* newHashes, // assume that the capacity of indices is >= max(oldHashCount, newHashCount) int* addIndices, int* removeIndices, out int addCount, out int remCount ) { return CompareHashes, Hash128, DefaultKeyGetter>( oldHashCount, oldHashes, newHashCount, newHashes, addIndices, removeIndices, out addCount, out remCount ); } /// Combine all of the hashes of a collection of hashes. /// Value type. /// Getter type. /// Number of hash to combine. /// Hashes to combine. /// Hash to update. public static void CombineHashes(int count, void* hashes, Hash128* outHash) where TValue : struct where TGetter : struct, IKeyGetter { var getter = new TGetter(); for (int i = 0; i < count; ++i) { var v = UnsafeUtility.ReadArrayElement(hashes, i); var h = getter.Get(ref v); HashUtilities.AppendHash(ref h, ref *outHash); } } /// /// Combine hashes. /// /// Number of hash to combine. /// Hashes to combine. /// Hash to update. public static void CombineHashes(int count, Hash128* hashes, Hash128* outHash) { CombineHashes>(count, hashes, outHash); } // Just a sort function that doesn't allocate memory // Note: Should be replace by a radix sort for positive integer static int Partition(void* data, int left, int right) where TKey : struct, IComparable where TValue : struct where TGetter : struct, IKeyGetter { var getter = default(TGetter); var pivotvalue = UnsafeUtility.ReadArrayElement(data, left); var pivot = getter.Get(ref pivotvalue); --left; ++right; while (true) { var c = 0; var lvalue = default(TValue); var lkey = default(TKey); do { ++left; lvalue = UnsafeUtility.ReadArrayElement(data, left); lkey = getter.Get(ref lvalue); c = lkey.CompareTo(pivot); } while (c < 0); var rvalue = default(TValue); var rkey = default(TKey); do { --right; rvalue = UnsafeUtility.ReadArrayElement(data, right); rkey = getter.Get(ref rvalue); c = rkey.CompareTo(pivot); } while (c > 0); if (left < right) { UnsafeUtility.WriteArrayElement(data, right, lvalue); UnsafeUtility.WriteArrayElement(data, left, rvalue); } else { return right; } } } /// /// Checks for duplicates in an array. /// /// Input array. /// True if there is any duplicate in the input array. public static unsafe bool HaveDuplicates(int[] arr) { int* copy = stackalloc int[arr.Length]; arr.CopyTo(copy, arr.Length); QuickSort(arr.Length, copy); for (int i = arr.Length - 1; i > 0; --i) { if (UnsafeUtility.ReadArrayElement(copy, i).CompareTo(UnsafeUtility.ReadArrayElement(copy, i - 1)) == 0) { return true; } } return false; } } }