using System; using System.Diagnostics; namespace UnityEngine.Rendering { /// /// Generic growable array. /// /// Type of the array. [DebuggerDisplay("Size = {size} Capacity = {capacity}")] public class DynamicArray where T : new() { T[] m_Array = null; /// /// Number of elements in the array. /// public int size { get; private 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 /// internal int version { get; private set; } #endif /// /// Constructor. /// Defaults to a size of 32 elements. /// public DynamicArray() { m_Array = new T[32]; size = 0; #if DEVELOPMENT_BUILD || UNITY_EDITOR version = 0; #endif } /// /// Constructor /// /// Number of elements. public DynamicArray(int size) { m_Array = new T[size]; this.size = 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[m_Array.Length * 2]; 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) { Reserve(size + array.size, true); for (int i = 0; i < array.size; ++i) m_Array[size++] = array[i]; BumpVersion(); } /// /// 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 (index < 0 || index >= size) throw new IndexOutOfRangeException(); 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 (index < 0 || index >= size || count < 0 || index + count > size) throw new ArgumentOutOfRangeException(); 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. /// 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. /// /// 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(); } /// /// 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 >= size) throw new IndexOutOfRangeException(); #endif return ref m_Array[index]; } } /// /// Implicit conversion to regular array. /// /// Input DynamicArray. /// The internal array. public static implicit operator T[](DynamicArray array) => array.m_Array; /// /// IEnumerator-like struct used to loop over this entire array. See the IEnumerator docs for more info: /// /// /// /// 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: /// /// /// 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: /// /// /// 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: /// /// /// 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; } internal void BumpVersion() { #if DEVELOPMENT_BUILD || UNITY_EDITOR version++; #endif } } /// /// Extension class for DynamicArray /// public static class DynamicArrayExtensions { static int Partition(T[] 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(T[] 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); } } /// /// 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(); } } }