using System; using System.Collections.Generic; using System.Diagnostics; namespace UnityEngine.Rendering { /// /// Generic growable array. /// /// Type of the array. [DebuggerDisplay("Size = {size} Capacity = {capacity}")] public class DynamicArray where T : new() { /// /// The C# array memory used to store the DynamicArray values in. This array's Length may be longer than the DynamicArrayLength. Objects beyond the length should not be referenced. /// protected T[] m_Array = null; /// /// Number of elements in the array. There may be more elements allocated. Use `capacity` to query the number of allocated items. /// public int size { get; protected set; } /// /// Allocated size of the array. /// public int capacity { get { return m_Array.Length; } } #if DEVELOPMENT_BUILD || UNITY_EDITOR /// /// This keeps track of structural modifications to this array and allows us to raise exceptions when modifying during enumeration /// protected internal int version { get; protected set; } #endif /// /// Constructor. /// Defaults to a capacity of 32 elements. The size will be 0. /// public DynamicArray() { m_Array = new T[32]; size = 0; #if DEVELOPMENT_BUILD || UNITY_EDITOR version = 0; #endif } /// /// Constructor. This constructor allocates memory and sets the size of the array to the specified number of elements. /// /// Number of elements. The elements are initialized to the default value of the element type, 0 for integers. public DynamicArray(int size) { m_Array = new T[size]; this.size = size; #if DEVELOPMENT_BUILD || UNITY_EDITOR version = 0; #endif } /// /// Constructor. This overload allows you to only allocate memory without setting the size. /// /// The number of elements to allocate. /// If true, also set the size of the array to the passed in capacity. If false, only allocate data but keep the size at 0. public DynamicArray(int capacity, bool resize) { m_Array = new T[capacity]; this.size = (resize) ? capacity : 0; #if DEVELOPMENT_BUILD || UNITY_EDITOR version = 0; #endif } /// /// Constructor. This constructor allocates memory and does a deep copy of the provided array. /// /// Array to be copied public DynamicArray(DynamicArray deepCopy) { m_Array = new T[deepCopy.size]; size = deepCopy.size; Array.Copy(deepCopy.m_Array, m_Array, size); #if DEVELOPMENT_BUILD || UNITY_EDITOR version = 0; #endif } /// /// Clear the array of all elements. /// public void Clear() { size = 0; } /// /// Determines whether the DynamicArray contains a specific value. /// /// The object to locate in the DynamicArray. /// true if item is found in the DynamicArray; otherwise, false. public bool Contains(T item) { return IndexOf(item) != -1; } /// /// Add an element to the array. /// /// Element to add to the array. /// The index of the element. public int Add(in T value) { int index = size; // Grow array if needed; if (index >= m_Array.Length) { var newArray = new T[Math.Max(m_Array.Length * 2,1)]; Array.Copy(m_Array, newArray, m_Array.Length); m_Array = newArray; } m_Array[index] = value; size++; BumpVersion(); return index; } /// /// Adds the elements of the specified collection to the end of the DynamicArray. /// /// The array whose elements should be added to the end of the DynamicArray. The array itself cannot be null, but it can contain elements that are null, if type T is a reference type. public void AddRange(DynamicArray array) { // Save the size before reserve. Otherwise things break when self-appending i.e. `a.AddRange(a)` var addedSize = array.size; Reserve(size + addedSize, true); for (int i = 0; i < addedSize; ++i) m_Array[size++] = array[i]; BumpVersion(); } /// /// Insert an item in the DynamicArray. /// /// Index where the item should be inserted. /// Item to be inserted in the DynamicArray. public void Insert(int index, T item) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (index < 0 || index > size) throw new IndexOutOfRangeException(); #endif if (index == size) Add(item); else { Resize(size + 1, true); Array.Copy(m_Array, index, m_Array, index + 1, size - index); m_Array[index] = item; } } /// /// Removes the first occurrence of a specific object from the DynamicArray. /// /// The object to remove from the DynamicArray. The value can be null for reference types. /// true if item is successfully removed; otherwise, false. This method also returns false if item was not found in the DynamicArray. public bool Remove(T item) { int index = IndexOf(item); if (index != -1) { RemoveAt(index); return true; } return false; } /// /// Removes the element at the specified index of the DynamicArray. /// /// The zero-based index of the element to remove. public void RemoveAt(int index) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (index < 0 || index >= size) throw new IndexOutOfRangeException(); #endif if (index != size - 1) Array.Copy(m_Array, index + 1, m_Array, index, size - index - 1); size--; BumpVersion(); } /// /// Removes a range of elements from the DynamicArray. /// /// The zero-based starting index of the range of elements to remove. /// The number of elements to remove. public void RemoveRange(int index, int count) { if (count == 0) return; #if DEVELOPMENT_BUILD || UNITY_EDITOR if (index < 0 || index >= size || count < 0 || index + count > size) throw new ArgumentOutOfRangeException(); #endif Array.Copy(m_Array, index + count, m_Array, index, size - index - count); size -= count; BumpVersion(); } /// /// Searches for an element that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the range of elements in the DynamicArray that starts at the specified index and contains the specified number of elements. /// /// The zero-based starting index of the search. /// The number of elements in the section to search. /// The Predicate delegate that defines the conditions of the element to search for. /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, -1. public int FindIndex(int startIndex, int count, Predicate match) { for (int i = startIndex; i < size; ++i) { if (match(m_Array[i])) { return i; } } return -1; } /// /// Searches for the specified object and returns the zero-based index of the first occurrence within the range of elements in the DynamicArray that starts at the specified index and contains the specified number of elements. /// /// The object to locate in the DynamicArray. The value can be null for reference types. /// The zero-based starting index of the search. 0 (zero) is valid in an empty list. /// The number of elements in the section to search. /// The index of the first occurrence of the object within the range of elements, or -1 if not found. public int IndexOf(T item, int index, int count) { for (int i = index; i < size && count > 0; ++i, --count) { if (m_Array[i].Equals(item)) { return i; } } return -1; } /// /// Searches for the specified object and returns the zero-based index of the first occurrence within the range of elements in the DynamicArray that extends from the specified index to the last element. /// /// The object to locate in the DynamicArray. The value can be null for reference types. /// The zero-based starting index of the search. 0 (zero) is valid in an empty list. /// The zero-based index of the first occurrence of item within the range of elements in the DynamicArray that extends from index to the last element, if found; otherwise, -1. public int IndexOf(T item, int index) { for (int i = index; i < size; ++i) { if (m_Array[i].Equals(item)) { return i; } } return -1; } /// /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire DynamicArray. /// /// The object to locate in the DynamicArray. The value can be null for reference types. /// he zero-based index of the first occurrence of item within the entire DynamicArray, if found; otherwise, -1. public int IndexOf(T item) { return IndexOf(item, 0); } /// /// Resize the Dynamic Array. /// This will reallocate memory if necessary and set the current size of the array to the provided size. /// Note: The memory is not cleared so the elements may contain invalid data. /// /// New size for the array. /// Set to true if you want the current content of the array to be kept. public void Resize(int newSize, bool keepContent = false) { Reserve(newSize, keepContent); size = newSize; BumpVersion(); } /// /// Resize the Dynamic Array. /// This will reallocate memory if necessary and set the current size of the array to the provided size. /// The elements are initialized to the default value of the element type, e.g. 0 for integers. /// /// New size for the array. public void ResizeAndClear(int newSize) { if (newSize > m_Array.Length) { // Reserve will allocate a whole new array that is cleared as part of the allocation Reserve(newSize, false); } else { // We're not reallocating anything we need to clear the old values to the default. Array.Clear(m_Array, 0, newSize); } size = newSize; BumpVersion(); } /// /// Sets the total number of elements the internal data structure can hold without resizing. /// /// New capacity for the array. /// Set to true if you want the current content of the array to be kept. public void Reserve(int newCapacity, bool keepContent = false) { if (newCapacity > m_Array.Length) { if (keepContent) { var newArray = new T[newCapacity]; Array.Copy(m_Array, newArray, m_Array.Length); m_Array = newArray; } else { m_Array = new T[newCapacity]; } } } /// /// ref access to an element. /// /// Element index /// The requested element. public ref T this[int index] { get { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (index < 0 || index >= size) throw new IndexOutOfRangeException(); #endif return ref m_Array[index]; } } /// /// Implicit conversion to regular array. /// /// Input DynamicArray. /// The internal array. [Obsolete("This is deprecated because it returns an incorrect value. It may returns an array with elements beyond the size. Please use Span/ReadOnly if you want safe raw access to the DynamicArray memory.",false)] public static implicit operator T[](DynamicArray array) => array.m_Array; /// /// Implicit conversion to ReadOnlySpan. /// /// Input DynamicArray. /// The internal array. public static implicit operator ReadOnlySpan(DynamicArray array) => new ReadOnlySpan(array.m_Array, 0, array.size); /// /// Implicit conversion to Span. /// /// Input DynamicArray. /// The internal array. public static implicit operator Span(DynamicArray array) => new Span(array.m_Array, 0, array.size); /// /// IEnumerator-like struct used to loop over this entire array. See the IEnumerator docs for more info: /// IEnumerator /// /// /// This struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows /// the same function signatures. This means the duck typing used by foreach on the compiler level will /// pick it up as IEnumerable but at the same time avoids generating Garbage. /// For more info, see the C# language specification of the foreach statement. /// /// public struct Iterator { private readonly DynamicArray owner; private int index; #if DEVELOPMENT_BUILD || UNITY_EDITOR private int localVersion; #endif /// /// Creates an iterator to iterate over an array. /// /// The array to iterate over. /// Thrown if the array is null. public Iterator(DynamicArray setOwner) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (setOwner == null) throw new ArgumentNullException(); #endif owner = setOwner; index = -1; #if DEVELOPMENT_BUILD || UNITY_EDITOR localVersion = owner.version; #endif } /// /// Gets the element in the DynamicArray at the current position of the iterator. /// public ref T Current { get { return ref owner[index]; } } /// /// Advances the iterator to the next element of the DynamicArray. /// /// Returns true if the iterator has successfully advanced to the next element; false if the iterator has passed the end of the DynamicArray. /// An operation changed the DynamicArray after the creation of this iterator. public bool MoveNext() { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (owner.version != localVersion) { throw new InvalidOperationException("DynamicArray was modified during enumeration"); } #endif index++; return index < owner.size; } /// /// Sets the iterator to its initial position, which is before the first element in the DynamicArray. /// public void Reset() { index = -1; } } /// /// Returns an enumerator that iterates through of this array. /// See the IEnumerable docs for more info: IEnumerable /// /// /// The returned struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows /// the same function signatures. This means the duck typing used by foreach on the compiler level will /// pick it up as IEnumerable but at the same time avoids generating Garbage. /// For more info, see the C# language specification of the foreach statement. /// /// Iterator pointing before the first element in the array. public Iterator GetEnumerator() { return new Iterator(this); } /// /// IEnumerable-like struct used to iterate through a subsection of this array. /// See the IEnumerable docs for more info: IEnumerable /// /// /// This struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows /// the same function signatures. This means the duck typing used by foreach on the compiler level will /// pick it up as IEnumerable but at the same time avoids generating Garbage. /// For more info, see the C# language specification of the foreach statement. /// /// public struct RangeEnumerable { /// /// IEnumerator-like struct used to iterate through a subsection of this array. /// See the IEnumerator docs for more info: IEnumerator /// /// /// This struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows /// the same function signatures. This means the duck typing used by foreach on the compiler level will /// pick it up as IEnumarable but at the same time avoids generating Garbage. /// For more info, see the C# language specification of the foreach statement. /// /// public struct RangeIterator { private readonly DynamicArray owner; private int index; private int first; private int last; #if DEVELOPMENT_BUILD || UNITY_EDITOR private int localVersion; #endif /// /// Create an iterator to iterate over the given range in the array. /// /// The array to iterate over. /// The index of the first item in the array. /// The number of array members to iterate through. /// Thrown if the array is null. public RangeIterator(DynamicArray setOwner, int first, int numItems) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (setOwner == null) throw new ArgumentNullException(); if (first < 0 || first > setOwner.size || (first + numItems) > setOwner.size) throw new IndexOutOfRangeException(); #endif owner = setOwner; this.first = first; index = first-1; last = first + numItems; #if DEVELOPMENT_BUILD || UNITY_EDITOR localVersion = owner.version; #endif } /// /// Gets the element in the DynamicArray at the current position of the iterator. /// public ref T Current { get { return ref owner[index]; } } /// /// Advances the iterator to the next element of the DynamicArray. /// /// Returs true if the iterator successfully advanced to the next element; returns false if the iterator has passed the end of the range. /// The DynamicArray was modified after the iterator was created. public bool MoveNext() { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (owner.version != localVersion) { throw new InvalidOperationException("DynamicArray was modified during enumeration"); } #endif index++; return index < last; } /// /// Sets the iterator to its initial position, which is before the first element in the range. /// public void Reset() { index = first-1; } } /// /// The iterator associated with this Enumerable. /// public RangeIterator iterator; /// /// Returns an enumerator that iterates through this array. /// /// /// The returned struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows /// the same function signatures. This means the duck typing used by foreach on the compiler level will /// pick it up as IEnumerable but at the same time avoids generating Garbage. /// For more info, see the C# language specification of the foreach statement. /// /// Iterator pointing before the first element in the range. public RangeIterator GetEnumerator() { return iterator; } } /// /// Returns an IEnumeralbe-Like object that iterates through a subsection of this array. /// /// /// The returned struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows /// the same function signatures. This means the duck typing used by foreach on the compiler level will /// pick it up as IEnumerable but at the same time avoids generating Garbage. /// For more info, see the C# language specification of the foreach statement. /// /// The index of the first item /// The number of items to iterate /// RangeEnumerable that can be used to enumerate the given range. /// public RangeEnumerable SubRange(int first, int numItems) { RangeEnumerable r = new RangeEnumerable { iterator = new RangeEnumerable.RangeIterator(this, first, numItems) }; return r; } /// /// Increments the internal version counter. /// protected internal void BumpVersion() { #if DEVELOPMENT_BUILD || UNITY_EDITOR version++; #endif } /// /// Delegate for custom sorting comparison. /// /// First object. /// Second object. /// -1 if x smaller than y, 1 if x bigger than y and 0 otherwise. public delegate int SortComparer(T x, T y); } /// /// Extension class for DynamicArray /// public static class DynamicArrayExtensions { static int Partition(Span data, int left, int right) where T : IComparable, new() { var pivot = data[left]; --left; ++right; while (true) { var c = 0; var lvalue = default(T); do { ++left; lvalue = data[left]; c = lvalue.CompareTo(pivot); } while (c < 0); var rvalue = default(T); do { --right; rvalue = data[right]; c = rvalue.CompareTo(pivot); } while (c > 0); if (left < right) { data[right] = lvalue; data[left] = rvalue; } else { return right; } } } static void QuickSort(Span data, int left, int right) where T : IComparable, new() { 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); } } // C# SUCKS // Had to copy paste because it's apparently impossible to pass a sort delegate where T is Comparable, otherwise some boxing happens and allocates... // So two identical versions of the function, one with delegate but no Comparable and the other with just the comparable. static int Partition(Span data, int left, int right, DynamicArray.SortComparer comparer) where T : new() { var pivot = data[left]; --left; ++right; while (true) { var c = 0; var lvalue = default(T); do { ++left; lvalue = data[left]; c = comparer(lvalue, pivot); } while (c < 0); var rvalue = default(T); do { --right; rvalue = data[right]; c = comparer(rvalue, pivot); } while (c > 0); if (left < right) { data[right] = lvalue; data[left] = rvalue; } else { return right; } } } static void QuickSort(Span data, int left, int right, DynamicArray.SortComparer comparer) where T : new() { if (left < right) { int pivot = Partition(data, left, right, comparer); if (pivot >= 1) QuickSort(data, left, pivot, comparer); if (pivot + 1 < right) QuickSort(data, pivot + 1, right, comparer); } } /// /// Perform a quick sort on the DynamicArray /// /// Type of the array. /// Array on which to perform the quick sort. public static void QuickSort(this DynamicArray array) where T : IComparable, new() { QuickSort(array, 0, array.size - 1); array.BumpVersion(); } /// /// Perform a quick sort on the DynamicArray /// /// Type of the array. /// Array on which to perform the quick sort. /// Comparer used for sorting. public static void QuickSort(this DynamicArray array, DynamicArray.SortComparer comparer) where T : new() { QuickSort(array, 0, array.size - 1, comparer); array.BumpVersion(); } } }