/* 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
}
}