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();
}
}
}