/* Copyright 2013-2014 Daikon Forge */ namespace DaikonForge.VoIP { using System; using System.Collections; using System.Collections.Generic; /// /// Defines a simplified Generic List customized specifically for /// game development - Implements object pooling, minimizes memory /// allocations during common operations, replaces common extension /// methods with bespoke implementations that do not allocate /// iterators, etc. /// public class FastList : IList, IDisposable { #region Object pooling #region Static variables // NOTE: Switched to Queue in an attempt to work around // a bug in the version of Mono used by Unity on iOS - http://stackoverflow.com/q/16542915 private static Queue pool = new Queue( 1024 ); #endregion /// /// Releases all instances in the object pool. /// public static void ClearPool() { lock( pool ) { pool.Clear(); pool.TrimExcess(); } } /// /// Returns a reference to a instance. If there are /// available instances in the object pool, the first available instance will /// be returned. If there are no instances available, then a new instance /// will be created. Use the function to return the instance /// to the object pool. /// public static FastList Obtain() { lock( pool ) { if( pool.Count == 0 ) return new FastList(); return (FastList)pool.Dequeue(); } } /// /// Returns a reference to a instance. If there are /// available instances in the object pool, the first available instance will /// be returned. If there are no instances available, then a new instance /// will be created. Use the function to return the instance /// to the object pool. /// public static FastList Obtain( int capacity ) { var list = Obtain(); list.EnsureCapacity( capacity ); return list; } /// /// Releases the back to the object pool /// public void Release() { Clear(); lock( pool ) { pool.Enqueue( this ); } } #endregion #region Private instance fields private const int DEFAULT_CAPACITY = 128; private T[] items = new T[ DEFAULT_CAPACITY ]; private int count = 0; private bool isElementTypeValueType = false; //private bool isElementTypePoolable = false; #endregion #region Constructor internal FastList() { #if !UNITY_EDITOR && UNITY_METRO isElementTypeValueType = typeof( T ).GetTypeInfo().IsValueType; isElementTypePoolable = typeof( IPoolable ).GetTypeInfo().IsAssignableFrom( typeof( T ).GetTypeInfo() ); #else isElementTypeValueType = typeof( T ).IsValueType; //isElementTypePoolable = typeof( IPoolable ).IsAssignableFrom( typeof( T ) ); #endif } internal FastList( IList listToClone ) : this() { AddRange( listToClone ); } internal FastList( int capacity ) : this() { EnsureCapacity( capacity ); } #endregion #region Public properties /// /// Returns the number of items in the list /// public int Count { get { return this.count; } } /// /// Returns the number of items this list can hold without needing to /// resize the internal array (for internal use only) /// internal int Capacity { get { return this.items.Length; } } /// /// Gets a value indicating whether the list is read-only. Inherited from IList<> /// public bool IsReadOnly { get { return false; } } /// /// Gets/Sets the item at the specified index /// public T this[ int index ] { get { if( index < 0 || index > this.count - 1 ) throw new IndexOutOfRangeException(); return this.items[ index ]; } set { if( index < 0 || index > this.count - 1 ) throw new IndexOutOfRangeException(); this.items[ index ] = value; } } /// /// Allows direct access to the underlying /// containing this list's data. This array will most likely contain more /// elements than the list reports via the property. /// This property is intended for internal use by the UI library and should /// not be accessed by other code. /// internal T[] Items { get { return this.items; } } #endregion #region Public methods /// /// Adds a new item to the end of the list. Provided only for call-level /// compatability with code that treats this collection as a Queue. /// public void Enqueue( T item ) { lock( items ) { this.Add( item ); } } /// /// Returns the first item in the collection and removes it from the list. /// Provided only for call-level compatability with code that treats this /// collection as a Queue. /// public T Dequeue() { lock( items ) { if( this.count == 0 ) throw new IndexOutOfRangeException(); var item = this.items[ 0 ]; this.RemoveAt( 0 ); return item; } } /// /// Returns the last item in the collection and removes it frm the list. /// Provided only for call-level compatibility with code that treats this /// collection as a Stack. /// /// public T Pop() { lock( items ) { if( this.count == 0 ) throw new IndexOutOfRangeException(); var item = this.items[ this.count - 1 ]; this.count -= 1; return item; } } /// /// Returns a shallow copy of this instance /// /// public FastList Clone() { var clone = Obtain( this.count ); Array.Copy( this.items, 0, clone.items, 0, this.count ); clone.count = this.count; return clone; } /// /// Reverses the order of the elements in the list /// public void Reverse() { Array.Reverse( this.items, 0, this.count ); } /// /// Sorts the elements in the entire using the default comparer. /// public void Sort() { Array.Sort( this.items, 0, this.count, null ); } /// /// Sorts the elements in the entire using the specified comparer. /// public void Sort( IComparer comparer ) { Array.Sort( this.items, 0, this.count, comparer ); } /// /// Sorts the elements in the entire using the specified . /// /// The to use when comparing elements. public void Sort( Comparison comparison ) { if( comparison == null ) { throw new ArgumentNullException( "comparison" ); } if( this.count > 0 ) { using( var comparer = FunctorComparer.Obtain( comparison ) ) { Array.Sort( this.items, 0, this.count, comparer ); } } } /// /// Ensures that the has enough capacity to store elements /// /// public void EnsureCapacity( int Size ) { if( items.Length < Size ) { var newSize = ( Size / DEFAULT_CAPACITY ) * DEFAULT_CAPACITY + DEFAULT_CAPACITY; Array.Resize( ref this.items, newSize ); } } public void ForceCount( int count ) { this.count = count; } /// /// Adds the elements of the specified collection to the end of the /// public void AddRange( FastList list ) { var listCount = list.count; EnsureCapacity( this.count + listCount ); Array.Copy( list.items, 0, this.items, this.count, listCount ); this.count += listCount; } /// /// Adds the elements of the specified collection to the end of the /// public void AddRange( IList list ) { var listCount = list.Count; EnsureCapacity( this.count + listCount ); for( int i = 0; i < listCount; i++ ) { this.items[ count++ ] = list[ i ]; } } /// /// Adds the elements of the specified collection to the end of the /// public void AddRange( T[] list ) { var listLength = list.Length; EnsureCapacity( this.count + listLength ); Array.Copy( list, 0, this.items, this.count, listLength ); this.count += listLength; } /// /// Determines the index of a specific item in the collection /// public int IndexOf( T item ) { return Array.IndexOf( this.items, item, 0, this.count ); } /// /// Inserts an item to the collection at the specified index /// /// /// public void Insert( int index, T item ) { EnsureCapacity( this.count + 1 ); if( index < this.count ) { Array.Copy( this.items, index, this.items, index + 1, this.count - index ); } this.items[ index ] = item; this.count += 1; } /// /// Inserts an array of items at the specified index /// public void InsertRange( int index, T[] array ) { if( array == null ) throw new ArgumentNullException( "items" ); if( index < 0 || index > this.count ) throw new ArgumentOutOfRangeException( "index" ); EnsureCapacity( this.count + array.Length ); if( index < this.count ) { Array.Copy( this.items, index, this.items, index + array.Length, this.count - index ); } array.CopyTo( this.items, index ); this.count += array.Length; } /// /// Inserts a collection of items at the specified index /// public void InsertRange( int index, FastList list ) { if( list == null ) throw new ArgumentNullException( "items" ); if( index < 0 || index > this.count ) throw new ArgumentOutOfRangeException( "index" ); EnsureCapacity( this.count + list.count ); if( index < this.count ) { Array.Copy( this.items, index, this.items, index + list.count, this.count - index ); } Array.Copy( list.items, 0, this.items, index, list.count ); this.count += list.count; } /// /// Removes all items matching the predicate condition from the list /// public void RemoveAll( Predicate predicate ) { var index = 0; while( index < this.count ) { if( predicate( items[ index ] ) ) { RemoveAt( index ); } else { index += 1; } } } /// /// Removes the item at the specified index /// public void RemoveAt( int index ) { if( index >= this.count ) { throw new ArgumentOutOfRangeException(); } this.count -= 1; if( index < this.count ) { Array.Copy( this.items, index + 1, this.items, index, this.count - index ); } this.items[ this.count ] = default( T ); } /// /// Removes items from the collection at the specified index /// public void RemoveRange( int index, int length ) { if( index < 0 || length < 0 || this.count - index < length ) { throw new ArgumentOutOfRangeException(); } if( count > 0 ) { this.count -= length; if( index < this.count ) { Array.Copy( this.items, index + length, this.items, index, this.count - index ); } Array.Clear( this.items, this.count, length ); } } /// /// Adds an item to the collection /// public void Add( T item ) { EnsureCapacity( this.count + 1 ); this.items[ this.count++ ] = item; } /// /// Adds two items to the collection /// public void Add( T item0, T item1 ) { EnsureCapacity( this.count + 2 ); this.items[ this.count++ ] = item0; this.items[ this.count++ ] = item1; } /// /// Adds three items to the collection /// public void Add( T item0, T item1, T item2 ) { EnsureCapacity( this.count + 3 ); this.items[ this.count++ ] = item0; this.items[ this.count++ ] = item1; this.items[ this.count++ ] = item2; } /// /// Adds four items to the collection /// public void Add( T item0, T item1, T item2, T item3 ) { EnsureCapacity( this.count + 3 ); this.items[ this.count++ ] = item0; this.items[ this.count++ ] = item1; this.items[ this.count++ ] = item2; this.items[ this.count++ ] = item3; } /// /// Removes all items from the collection /// public void Clear() { if( !isElementTypeValueType ) { Array.Clear( this.items, 0, this.items.Length ); } this.count = 0; } /// /// Resizes the internal buffer to exactly match the number of elements in the collection /// public void TrimExcess() { Array.Resize( ref this.items, this.count ); } /// /// Determines whether the collection contains the specified value /// public bool Contains( T item ) { if( item == null ) { for( int i = 0; i < this.count; i++ ) { if( this.items[ i ] == null ) { return true; } } return false; } EqualityComparer comparer = EqualityComparer.Default; for( int j = 0; j < this.count; j++ ) { if( comparer.Equals( this.items[ j ], item ) ) { return true; } } return false; } /// /// Copies the elements of the collection to a instance /// /// public void CopyTo( T[] array ) { CopyTo( array, 0 ); } /// /// Copies the elements of the collection to an starting at the specified index /// public void CopyTo( T[] array, int arrayIndex ) { Array.Copy( this.items, 0, array, arrayIndex, this.count ); } /// /// Copies the elements of the collection to an /// /// The starting position in the collection /// The destination array /// The position in the array to start copying to /// How many elements to copy public void CopyTo( int sourceIndex, T[] dest, int destIndex, int length ) { if( sourceIndex + length > this.count ) throw new IndexOutOfRangeException( "sourceIndex" ); if( dest == null ) throw new ArgumentNullException( "dest" ); if( destIndex + length > dest.Length ) throw new IndexOutOfRangeException( "destIndex" ); Array.Copy( this.items, sourceIndex, dest, destIndex, length ); } /// /// Removes the first occurrence of a specific object from the collection /// public bool Remove( T item ) { var index = IndexOf( item ); if( index == -1 ) return false; RemoveAt( index ); return true; } /// /// Returns a List<T> collection containing all elements of this collection /// /// public List ToList() { var list = new List( this.count ); list.AddRange( this.ToArray() ); return list; } /// /// For internal use only. /// // @private internal T[] ToTempArray() { var result = TempArray.Obtain( this.count ); Array.Copy( items, 0, result, 0, this.count ); return result; } /// /// Returns an array containing all elements of this collection /// public T[] ToArray() { var array = new T[ this.count ]; Array.Copy( this.items, 0, array, 0, this.count ); return array; } /// /// Returns a subset of the collection's items as an array /// public T[] ToArray( int index, int length ) { var array = new T[ this.count ]; if( this.count > 0 ) { CopyTo( index, array, 0, length ); } return array; } /// /// Returns a subset of the collection's items as another dfList /// public FastList GetRange( int index, int length ) { var range = Obtain( length ); CopyTo( 0, range.items, index, length ); return range; } #endregion #region LINQ replacement methods (avoids allocating enumerators) /// /// Returns whether any items in the collection match the condition /// defined by the predicate. /// /// A function to test each element for a condition. /// true if any elements in the source sequence pass the test in the specified /// predicate; otherwise, false. public bool Any( Func predicate ) { for( int i = 0; i < this.count; i++ ) { if( predicate( this.items[ i ] ) ) return true; } return false; } /// /// Returns the first element in the list. Throws an exception if the list is empty. /// /// public T First() { if( this.count == 0 ) { throw new IndexOutOfRangeException(); } return this.items[ 0 ]; } /// /// Returns the first element of the collection, or a default value /// if the collection contains no elements. /// public T FirstOrDefault() { if( this.count > 0 ) return this.items[ 0 ]; return default( T ); } /// /// Returns the first element of the collection matching the condition defined by /// , or the default value for the element type if the /// collection contains no elements. /// public T FirstOrDefault( Func predicate ) { for( int i = 0; i < this.count; i++ ) { if( predicate( items[ i ] ) ) return items[ i ]; } return default( T ); } /// /// Returns the last element in the list. Throws an exception if the list is empty. /// public T Last() { if( this.count == 0 ) { throw new IndexOutOfRangeException(); } return this.items[ this.count - 1 ]; } /// /// Returns the last element of the collection, or a default value /// if the collection contains no elements. /// public T LastOrDefault() { if( this.count == 0 ) { return default( T ); } return this.items[ this.count - 1 ]; } /// /// Returns the last element of the collection matching the condition defined by /// , or the default value for the element type if the /// collection contains no elements. /// public T LastOrDefault( Func predicate ) { var result = default( T ); for( int i = 0; i < this.count; i++ ) { if( predicate( items[ i ] ) ) { result = items[ i ]; } } return result; } /// /// Returns a list containing all elements /// of the collection matching the condition specified by /// public FastList Where( Func predicate ) { var result = FastList.Obtain( this.count ); for( int i = 0; i < this.count; i++ ) { if( predicate( items[ i ] ) ) { result.Add( items[ i ] ); } } return result; } /// /// Returns the count of elements in the list that satisfy the /// condition defined by /// public int Matching( Func predicate ) { var matching = 0; for( int i = 0; i < this.count; i++ ) { if( predicate( items[ i ] ) ) matching += 1; } return matching; } /// /// Projects each element of a sequence into a new form defined by /// public FastList Select( Func selector ) { var result = FastList.Obtain( this.count ); for( int i = 0; i < this.count; i++ ) { result.Add( selector( items[ i ] ) ); } return result; } /// /// Returns a concatenated list containing all elements both lists /// public FastList Concat( FastList list ) { var result = FastList.Obtain( this.count + list.count ); result.AddRange( this ); result.AddRange( list ); return result; } /// /// Converts all elements of the list to the specified target type /// public FastList Convert() { var result = FastList.Obtain( this.count ); for( int i = 0; i < this.count; i++ ) { result.Add( (TResult)System.Convert.ChangeType( this.items[ i ], typeof( TResult ) ) ); } return result; } /// /// Performs an action on each element of the list /// /// The action to be performed on each element public void ForEach( Action action ) { var index = 0; while( index < this.Count ) { action( items[ index++ ] ); } } #endregion #region IEnumerable implementation // NOTE: The IEnumerable implementation here is horribly broken on iOS, and until // I can figure out a way to implement typed enumerators that do work on iOS, please // use a for(;;) loop instead of foreach(). Note that this may also apply to using // LINQ queries, which may use foreach() or an GetEnumerator() internally. /// /// Returns an IEnumerator instance that can be used to iterate through /// the elements in this list. /// public IEnumerator GetEnumerator() { return PooledEnumerator.Obtain( this, null ); } /// /// Returns an IEnumerator instance that can be used to iterate through /// the elements in this list. /// IEnumerator IEnumerable.GetEnumerator() { return PooledEnumerator.Obtain( this, null ); } #endregion #region IDisposable implementation /// /// Releases the memory used by this object and returns it to the object pool /// public void Dispose() { Release(); } #endregion #region Nested classes /// /// This custom enumerator class implements object pooling in order /// to reduce the number and frequency of memory allocations. It is /// primarily expected to be used by framework and library code which /// treats the associated collection as an IEnumerable<T> rather /// than as a . For instance, LINQ contains /// several extension methods which will treat the collection as /// an IEnumerable<T> and will create, use, and Dispose() of /// the enumerator while performing the query. Similarly, many /// third-party libraries will use a foreach() loop over a collection /// rather than using for() with numeric indices, and the object pooling /// implemented by this class will help to mitigate the number of /// allocations typically caused by using such code. /// private class PooledEnumerator : IEnumerator, IEnumerable { #region Static variables private static Queue pool = new Queue(); #endregion #region Private variables private FastList list; private Func predicate; private int currentIndex; private T currentValue; private bool isValid = false; #endregion #region Pooling public static PooledEnumerator Obtain( FastList list, Func predicate ) { var enumerator = ( pool.Count > 0 ) ? pool.Dequeue() : new PooledEnumerator(); enumerator.ResetInternal( list, predicate ); return enumerator; } public void Release() { if( this.isValid ) { this.isValid = false; pool.Enqueue( this ); } } #endregion #region IEnumerator Members public T Current { get { if( !this.isValid ) throw new InvalidOperationException( "The enumerator is no longer valid" ); return this.currentValue; } } #endregion #region Private utility methods private void ResetInternal( FastList list, Func predicate ) { this.isValid = true; this.list = list; this.predicate = predicate; this.currentIndex = 0; this.currentValue = default( T ); } #endregion #region IDisposable Members public void Dispose() { Release(); } #endregion #region IEnumerator Members object IEnumerator.Current { get { return this.Current; } } public bool MoveNext() { if( !this.isValid ) throw new InvalidOperationException( "The enumerator is no longer valid" ); while( this.currentIndex < this.list.Count ) { var valueAtIndex = this.list[ currentIndex++ ]; if( predicate != null ) { if( !predicate( valueAtIndex ) ) continue; } this.currentValue = valueAtIndex; return true; } Release(); this.currentValue = default( T ); return false; } public void Reset() { throw new NotImplementedException(); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return this; } IEnumerator IEnumerable.GetEnumerator() { return this; } #endregion } private class FunctorComparer : IComparer, IDisposable { #region Static variables private static Queue pool = new Queue(); #endregion #region Private instance variables private Comparison comparison; #endregion #region Object pooling public static FunctorComparer Obtain( Comparison comparison ) { var comparer = ( pool.Count > 0 ) ? pool.Dequeue() : new FunctorComparer(); comparer.comparison = comparison; return comparer; } public void Release() { this.comparison = null; if( !pool.Contains( this ) ) { pool.Enqueue( this ); } } #endregion #region IComparer implementation public int Compare( T x, T y ) { return this.comparison( x, y ); } #endregion #region IDisposable implementation public void Dispose() { this.Release(); } #endregion } #endregion } }