summaryrefslogtreecommitdiff
path: root/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-06-03 10:15:45 +0800
committerchai <215380520@qq.com>2024-06-03 10:15:45 +0800
commitacea7b2e728787a0d83bbf83c8c1f042d2c32e7e (patch)
tree0bfec05c1ca2d71be2c337bcd110a0421f19318b /Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections
parent88febcb02bf127d961c6471d9e846c0e1315f5c3 (diff)
+ plugins project
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections')
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Bag.cs217
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Deque.cs837
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/DictionaryExtensions.cs18
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IObservableCollection.cs26
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IPoolable.cs12
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ItemEventArgs.cs20
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/KeyedCollection.cs68
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ListExtensions.cs22
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObjectPool.cs157
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObservableCollection.cs117
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Pool.cs51
11 files changed, 1545 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Bag.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Bag.cs
new file mode 100644
index 0000000..868b6d7
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Bag.cs
@@ -0,0 +1,217 @@
+// Original code dervied from:
+// https://github.com/thelinuxlich/artemis_CSharp/blob/master/Artemis_XNA_INDEPENDENT/Utils/Bag.cs
+
+// --------------------------------------------------------------------------------------------------------------------
+// <copyright file="Bag.cs" company="GAMADU.COM">
+// Copyright © 2013 GAMADU.COM. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other materials
+// provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY GAMADU.COM 'AS IS' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GAMADU.COM OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The views and conclusions contained in the software and documentation are those of the
+// authors and should not be interpreted as representing official policies, either expressed
+// or implied, of GAMADU.COM.
+// </copyright>
+// <summary>
+// Class Bag.
+// </summary>
+// --------------------------------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace MonoGame.Extended.Collections
+{
+ public class Bag<T> : IEnumerable<T>
+ {
+ private T[] _items;
+ private readonly bool _isPrimitive;
+
+ public int Capacity => _items.Length;
+ public bool IsEmpty => Count == 0;
+ public int Count { get; private set; }
+
+ public Bag(int capacity = 16)
+ {
+ _isPrimitive = typeof(T).IsPrimitive;
+ _items = new T[capacity];
+ }
+
+ public T this[int index]
+ {
+ get => index >= _items.Length ? default(T) : _items[index];
+ set
+ {
+ EnsureCapacity(index + 1);
+ if (index >= Count)
+ Count = index + 1;
+ _items[index] = value;
+ }
+ }
+
+ public void Add(T element)
+ {
+ EnsureCapacity(Count + 1);
+ _items[Count] = element;
+ ++Count;
+ }
+
+ public void AddRange(Bag<T> range)
+ {
+ for (int index = 0, j = range.Count; j > index; ++index)
+ Add(range[index]);
+ }
+
+ public void Clear()
+ {
+ if(Count == 0)
+ return;
+
+ // non-primitive types are cleared so the garbage collector can release them
+ if (!_isPrimitive)
+ Array.Clear(_items, 0, Count);
+
+ Count = 0;
+ }
+
+ public bool Contains(T element)
+ {
+ for (var index = Count - 1; index >= 0; --index)
+ {
+ if (element.Equals(_items[index]))
+ return true;
+ }
+
+ return false;
+ }
+
+ public T RemoveAt(int index)
+ {
+ var result = _items[index];
+ --Count;
+ _items[index] = _items[Count];
+ _items[Count] = default(T);
+ return result;
+ }
+
+ public bool Remove(T element)
+ {
+ for (var index = Count - 1; index >= 0; --index)
+ {
+ if (element.Equals(_items[index]))
+ {
+ --Count;
+ _items[index] = _items[Count];
+ _items[Count] = default(T);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public bool RemoveAll(Bag<T> bag)
+ {
+ var isResult = false;
+
+ for (var index = bag.Count - 1; index >= 0; --index)
+ {
+ if (Remove(bag[index]))
+ isResult = true;
+ }
+
+ return isResult;
+ }
+
+ private void EnsureCapacity(int capacity)
+ {
+ if (capacity < _items.Length)
+ return;
+
+ var newCapacity = Math.Max((int)(_items.Length * 1.5), capacity);
+ var oldElements = _items;
+ _items = new T[newCapacity];
+ Array.Copy(oldElements, 0, _items, 0, oldElements.Length);
+ }
+
+ IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ /// <summary>
+ /// Get the <see cref="BagEnumerator"/> for this <see cref="Bag{T}"/>.
+ /// </summary>
+ /// <returns></returns>
+ /// <remarks>
+ /// Use this method preferentially over <see cref="IEnumerable.GetEnumerator"/> while enumerating via foreach
+ /// to avoid boxing the enumerator on every iteration, which can be expensive in high-performance environments.
+ /// </remarks>
+ public BagEnumerator GetEnumerator()
+ {
+ return new BagEnumerator(this);
+ }
+
+ /// <summary>
+ /// Enumerates a Bag.
+ /// </summary>
+ public struct BagEnumerator : IEnumerator<T>
+ {
+ private readonly Bag<T> _bag;
+ private volatile int _index;
+
+ /// <summary>
+ /// Creates a new <see cref="BagEnumerator"/> for this <see cref="Bag{T}"/>.
+ /// </summary>
+ /// <param name="bag"></param>
+ public BagEnumerator(Bag<T> bag)
+ {
+ _bag = bag;
+ _index = -1;
+ }
+
+ readonly T IEnumerator<T>.Current => _bag[_index];
+
+ readonly object IEnumerator.Current => _bag[_index];
+
+ /// <summary>
+ /// Gets the element in the <see cref="Bag{T}"/> at the current position of the enumerator.
+ /// </summary>
+ public readonly T Current => _bag[_index];
+
+ /// <inheritdoc/>
+ public bool MoveNext()
+ {
+ return ++_index < _bag.Count;
+ }
+
+ /// <inheritdoc/>
+ public readonly void Dispose()
+ {
+ }
+
+ /// <inheritdoc/>
+ public readonly void Reset()
+ {
+ }
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Deque.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Deque.cs
new file mode 100644
index 0000000..fd59066
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Deque.cs
@@ -0,0 +1,837 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace MonoGame.Extended.Collections
+{
+ internal static class Deque
+ {
+ internal static readonly Func<int, int> DefaultResizeFunction = x => x * 2;
+ }
+
+ /// <summary>
+ /// Represents a collection of objects which elements can added to or removed either from the front or back; a
+ /// <a href="https://en.wikipedia.org/wiki/Double-ended_queue">double ended queue</a> (deque).
+ /// </summary>
+ /// <remarks>
+ /// <a href="https://en.wikipedia.org/wiki/Circular_buffer">circular array</a> is used as the internal data
+ /// structure for the <see cref="Deque{T}" />.
+ /// </remarks>
+ /// <typeparam name="T">The type of the elements in the deque.</typeparam>
+ public class Deque<T> : IList<T>
+ {
+ private const int _defaultCapacity = 4;
+ private static readonly T[] _emptyArray = new T[0];
+ private int _frontArrayIndex;
+ private T[] _items;
+ private Func<int, int> _resizeFunction = Deque.DefaultResizeFunction;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Deque{T}" /> class that is empty and has the default initial capacity.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// The capacity of a <see cref="Deque{T}" /> is the number of elements that the <see cref="Deque{T}" /> can
+ /// hold. As elements are added to a <see cref="Deque{T}" />, <see cref="Capacity" /> is automatically increased by
+ /// <see cref="ResizeFunction" /> as required by reallocating the internal array.
+ /// </para>
+ /// <para>
+ /// If the size of the collection can be estimated, using the <see cref="Deque{T}(int)" /> constructor and
+ /// specifying the initial capacity eliminates the need to perform a number of resizing operations while adding
+ /// elements to the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>
+ /// The capacity can be decreased by calling the <see cref="TrimExcess" /> method or by setting the
+ /// <see cref="Capacity" /> property explicitly. Decreasing, or increasing, the capacity reallocates memory and
+ /// copies all the
+ /// elements in the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>This constructor is an O(1) operation.</para>
+ /// </remarks>
+ public Deque()
+ {
+ _items = _emptyArray;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Deque{T}" /> class that contains elements copied from the specified
+ /// collection and has sufficient capacity to accommodate the number of elements copied.
+ /// </summary>
+ /// <param name="collection">The collection whose elements are copied to the new deque.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="collection" /> is null.</exception>
+ /// <remarks>
+ /// <para>
+ /// The elements are copied onto the <see cref="Deque{T}" /> in the same order they are read by the enumerator of
+ /// <paramref name="collection" />.
+ /// </para>
+ /// <para>This constructor is an O(n) operation, where n is the number of elements in <paramref name="collection" />.</para>
+ /// </remarks>
+ public Deque(IEnumerable<T> collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ var array = collection as T[] ?? collection.ToArray();
+ var count = array.Length;
+
+ if (count == 0)
+ _items = _emptyArray;
+ else
+ {
+ _items = new T[count];
+ array.CopyTo(_items, 0);
+ Count = count;
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Deque{T}" /> class that is empty and has the specified initial
+ /// capacity.
+ /// </summary>
+ /// <param name="capacity">The number of elements that the new <see cref="Deque{T}" /> can initially store.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity" /> is less than 0.</exception>
+ /// <remarks>
+ /// <para>
+ /// The capacity of a <see cref="Deque{T}" /> is the number of elements that the <see cref="Deque{T}" /> can
+ /// hold. As elements are added to a <see cref="Deque{T}" />, <see cref="Capacity" /> is automatically increased by
+ /// <see cref="ResizeFunction" /> as required by reallocating the internal array.
+ /// </para>
+ /// <para>
+ /// If the size of the collection can be estimated, specifying the initial capacity eliminates the need to
+ /// perform a number of resizing operations while adding elements to the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>
+ /// The capacity can be decreased by calling the <see cref="TrimExcess" /> method or by setting the
+ /// <see cref="Capacity" /> property explicitly. Decreasing, or increasing, the capacity reallocates memory and
+ /// copies all the elements in the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>This constructor is an O(n) operation, where n is <paramref name="capacity" />.</para>
+ /// </remarks>
+ public Deque(int capacity)
+ {
+ if (capacity < 0)
+ throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity was less than zero.");
+
+ _items = capacity == 0 ? _emptyArray : new T[capacity];
+ }
+
+ /// <summary>
+ /// Gets or sets the resize function used to calculate and set <see cref="Capacity" /> when a greater capacity is
+ /// required.
+ /// </summary>
+ /// <returns>
+ /// The <see cref="Func{T, TResult}" /> used to calculate and set <see cref="Capacity" /> when a greater capacity
+ /// is required.
+ /// </returns>
+ /// <remarks>
+ /// The default resize function is twice the <see cref="Capacity" />. Setting
+ /// <see cref="ResizeFunction" /> to <c>null</c> will set it back to the default.
+ /// </remarks>
+ public Func<int, int> ResizeFunction
+ {
+ get => _resizeFunction;
+ set => _resizeFunction = value ?? Deque.DefaultResizeFunction;
+ }
+
+ /// <summary>
+ /// Gets or sets the total number of elements the internal data structure can hold without resizing.
+ /// </summary>
+ /// <returns>The number of elements that the <see cref="Deque{T}" /> can contain before resizing is required.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <see cref="Capacity" /> cannot be set to a value less than <see cref="Count" />.
+ /// </exception>
+ /// <remarks>
+ /// Changing <see cref="Capacity" /> reallocates memory and copies all the
+ /// elements in the <see cref="Deque{T}" />.
+ /// </remarks>
+ public int Capacity
+ {
+ get => _items.Length;
+ set
+ {
+ if (value < Count)
+ throw new ArgumentOutOfRangeException(nameof(value), "capacity was less than the current size.");
+
+ if (value == Capacity)
+ return;
+
+ if (value == 0)
+ {
+ _items = _emptyArray;
+ return;
+ }
+
+ var newItems = new T[value];
+ CopyTo(newItems);
+
+ _frontArrayIndex = 0;
+ _items = null;
+ _items = newItems;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the element at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index of the element to get or set.</param>
+ /// <returns>The element at the specified index.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Index was out of range. Must be non-negative and less than <see cref="Count" />.
+ /// </exception>
+ /// <remarks>
+ /// <para></para>
+ /// <para>
+ /// Use <c>0</c> for the <paramref name="index" /> to get or set the element at the beginning of the
+ /// <see cref="Deque{T}" />, and use <c><see cref="Count" /> - 1</c> for the <paramref name="index" /> to get the
+ /// element at the end of the <see cref="Deque{T}" />.
+ /// </para>
+ /// </remarks>
+ public T this[int index]
+ {
+ get
+ {
+ var arrayIndex = GetArrayIndex(index);
+ if (arrayIndex == -1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index),
+ "Index was out of range. Must be non-negative and less than the size of the collection.");
+ }
+ return _items[arrayIndex];
+ }
+ set
+ {
+ var arrayIndex = GetArrayIndex(index);
+ if (arrayIndex == -1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index),
+ "Index was out of range. Must be non-negative and less than the size of the collection.");
+ }
+ _items[arrayIndex] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the number of elements contained in the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <returns>The number of elements contained in the <see cref="Deque{T}" />.</returns>
+ public int Count { get; private set; }
+
+ bool ICollection<T>.IsReadOnly => false;
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <returns>An <see cref="IEnumerator{T}" /> that can be used to iterate through the <see cref="Deque{T}" />.</returns>
+ public IEnumerator<T> GetEnumerator()
+ {
+ if (Count == 0)
+ yield break;
+
+ if (Count <= _items.Length - _frontArrayIndex)
+ {
+ for (var i = _frontArrayIndex; i < _frontArrayIndex + Count; i++)
+ yield return _items[i];
+ }
+ else
+ {
+ for (var i = _frontArrayIndex; i < Capacity; i++)
+ yield return _items[i];
+ for (var i = 0; i < (_frontArrayIndex + Count) % Capacity; i++)
+ yield return _items[i];
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ void ICollection<T>.Add(T item)
+ {
+ AddToBack(item);
+ }
+
+ /// <summary>
+ /// Searches for the specified element and returns the zero-based index of the first occurrence within the entire
+ /// <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// The element to locate in the <see cref="Deque{T}" />. The value can be <c>null</c> for reference
+ /// types.
+ /// </param>
+ /// <returns>
+ /// The zero-based index of the first occurrence of <paramref name="item" /> within the entire
+ /// <see cref="Deque{T}" />, if found; otherwise, <c>-1</c>.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// This method is an O(1) operation if <paramref name="item" /> is at the front or back of the
+ /// <see cref="Deque{T}" />; otherwise, this method is an O(n) operation where n is <see cref="Count" />.
+ /// </para>
+ /// </remarks>
+ public int IndexOf(T item)
+ {
+ var comparer = EqualityComparer<T>.Default;
+
+ if (Get(0, out var checkFrontBackItem) && comparer.Equals(checkFrontBackItem, item))
+ return 0;
+
+ var backIndex = Count - 1;
+
+ if (Get(backIndex, out checkFrontBackItem) && comparer.Equals(checkFrontBackItem, item))
+ return backIndex;
+
+ int index;
+
+ if (Count <= _items.Length - _frontArrayIndex)
+ index = Array.IndexOf(_items, item, _frontArrayIndex, Count);
+ else
+ {
+ index = Array.IndexOf(_items, item, _frontArrayIndex, _items.Length - _frontArrayIndex);
+ if (index < 0)
+ index = Array.IndexOf(_items, item, 0, _frontArrayIndex + Count - _items.Length);
+ }
+
+ var circularIndex = (index - _frontArrayIndex + _items.Length) % _items.Length;
+ return circularIndex;
+ }
+
+ void IList<T>.Insert(int index, T item)
+ {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Removes the first occurrence of a specific element from the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// The element to remove from the <see cref="Deque{T}" />. The value can be <c>null</c> for reference
+ /// types.
+ /// </param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="item" /> was successfully removed; otherwise, false. This method also returns false
+ /// if <paramref name="item" /> is not found in the <see cref="Deque{T}" />.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// This method is an O(1) operation if <paramref name="item" /> is at the front or back of the
+ /// <see cref="Deque{T}" />; otherwise, this method is an O(n) operation where n is <see cref="Count" />.
+ /// </para>
+ /// </remarks>
+ public bool Remove(T item)
+ {
+ var index = IndexOf(item);
+ if (index == -1)
+ return false;
+
+ RemoveAt(index);
+ return true;
+ }
+
+ /// <summary>
+ /// Removes the element at the specified index of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="index">The zero-based index of the element to remove.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <para><paramref name="index" /> is less than 0.</para>
+ /// <para>-or-</para>
+ /// <para><paramref name="index" /> is equal to or greater than <see cref="Count" />.</para>
+ /// </exception>
+ public void RemoveAt(int index)
+ {
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), index, "Index was less than zero.");
+
+ if (index >= Count)
+ throw new ArgumentOutOfRangeException(nameof(index), index, "Index was equal or greater than TotalCount.");
+
+ if (index == 0)
+ {
+ RemoveFromFront();
+ }
+ else
+ {
+ if (index == Count - 1)
+ {
+ RemoveFromBack();
+ }
+ else
+ {
+ if (index < Count / 2)
+ {
+ var arrayIndex = GetArrayIndex(index);
+ // shift the array from 0 to before the index to remove by 1 to the right
+ // the element to remove is replaced by the copy
+ Array.Copy(_items, 0, _items, 1, arrayIndex);
+ // the first element in the arrya is now either a duplicate or it's default value
+ // to be safe set it to it's default value regardless of circumstance
+ _items[0] = default(T);
+ // if we shifted the front element, adjust the front index
+ if (_frontArrayIndex < arrayIndex)
+ _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length;
+ // decrement the count so the back index is calculated correctly
+ Count--;
+ }
+ else
+ {
+ var arrayIndex = GetArrayIndex(index);
+ // shift the array from the center of the array to before the index to remove by 1 to the right
+ // the element to remove is replaced by the copy
+ var arrayCenterIndex = _items.Length / 2;
+ Array.Copy(_items, arrayCenterIndex, _items, arrayCenterIndex + 1, _items.Length - 1 - arrayIndex);
+ // the last element in the array is now either a duplicate or it's default value
+ // to be safe set it to it's default value regardless of circumstance
+ _items[_items.Length - 1] = default(T);
+ // if we shifted the front element, adjust the front index
+ if (_frontArrayIndex < arrayIndex)
+ _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length;
+ // decrement the count so the back index is calculated correctly
+ Count--;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes all elements from the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// <see cref="Count" /> is set to <c>0</c>, and references to other objects from elements of the collection are
+ /// also released.
+ /// </para>
+ /// <para>
+ /// <see cref="Capacity" /> remains unchanged. To reset the capacity of the <see cref="Deque{T}" />, call the
+ /// <see cref="TrimExcess" /> method or set the <see cref="Capacity" /> property explictly. Decreasing, or
+ /// increasing, the capacity reallocates memory and copies all the elements in the <see cref="Deque{T}" />.
+ /// Trimming an empty <see cref="Deque{T}" /> sets <see cref="Capacity" /> to the default capacity.
+ /// </para>
+ /// <para>This method is an O(n) operation, where n is <see cref="Count" />.</para>
+ /// </remarks>
+ public void Clear()
+ {
+ // allow the garbage collector to reclaim the references
+
+ if (Count == 0)
+ return;
+
+ if (Count > _items.Length - _frontArrayIndex)
+ {
+ Array.Clear(_items, _frontArrayIndex, _items.Length - _frontArrayIndex);
+ Array.Clear(_items, 0, _frontArrayIndex + Count - _items.Length);
+ }
+ else
+ Array.Clear(_items, _frontArrayIndex, Count);
+ Count = 0;
+ _frontArrayIndex = 0;
+ }
+
+ /// <summary>
+ /// Determines whether an element is in the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// The element to locate in the <see cref="Deque{T}" />. The value can be <c>null</c> for reference
+ /// types.
+ /// </param>
+ /// <returns><c>true</c> if <paramref name="item" /> is found in the <see cref="Deque{T}" />; otherwise, false.</returns>
+ /// <remarks>
+ /// <para>
+ /// This method determines equality by using the default equality comparer, as defined by the object's
+ /// implementation
+ /// of the <see cref="IEquatable{T}.Equals(T)" /> method for the type of values in the list.
+ /// </para>
+ /// <para>
+ /// This method performs a linear search; therefore, this method is an O(n) operation, where n is
+ /// <see cref="Count" />.
+ /// </para>
+ /// </remarks>
+ public bool Contains(T item)
+ {
+ return this.Contains(item, EqualityComparer<T>.Default);
+ }
+
+ /// <summary>
+ /// Copies the entire <see cref="Deque{T}" /> to a compatible one-dimensional array, starting at the specified index of
+ /// the target array.
+ /// </summary>
+ /// <param name="array">
+ /// The one-dimensional <see cref="Array" /> that is the destination of the elements copied from
+ /// <see cref="Deque{T}" />. The <see cref="Array" /> must have zero-based indexing.
+ /// </param>
+ /// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="array" /> is null.</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex" /> is less than 0.</exception>
+ /// <exception cref="ArgumentException">
+ /// The number of elements in the source <see cref="Deque{T}" /> is greater than the
+ /// available space from <paramref name="arrayIndex" /> to the end of the destination <paramref name="array" />.
+ /// </exception>
+ /// <remarks>
+ /// This method uses <see cref="Array.Copy(Array, int, Array, int, int)" /> to copy the elements. The elements are
+ /// copied to the <see cref="Array" /> in the same order in which the enumerator iterates
+ /// through the <see cref="Deque{T}" />. This method is an O(n) operation, where n is <see cref="Count" />.
+ /// </remarks>
+ public void CopyTo(T[] array, int arrayIndex = 0)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+
+ if (array.Rank != 1)
+ throw new ArgumentException("Only single dimensional arrays are supported for the requested action.");
+
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Index was less than the array's lower bound.");
+
+ if (arrayIndex >= array.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex),
+ "Index was greater than the array's upper bound.");
+ }
+
+ if (array.Length - arrayIndex < Count)
+ throw new ArgumentException("Destination array was not long enough.");
+
+ if (Count == 0)
+ return;
+
+
+ try
+ {
+ var loopsAround = Count > _items.Length - _frontArrayIndex;
+ if (!loopsAround)
+ Array.Copy(_items, _frontArrayIndex, array, arrayIndex, Count);
+ else
+ {
+ Array.Copy(_items, _frontArrayIndex, array, arrayIndex, Capacity - _frontArrayIndex);
+ Array.Copy(_items, 0, array, arrayIndex + Capacity - _frontArrayIndex,
+ _frontArrayIndex + (Count - Capacity));
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ throw new ArgumentException(
+ "Target array type is not compatible with the type of items in the collection.");
+ }
+ }
+
+ /// <summary>
+ /// Sets the capacity to the actual number of elements in the <see cref="Deque{T}" />, if that number is less than a
+ /// threshold value.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This method can be used to minimize the <see cref="Deque{T}" />'s memory overhead if no new elements will be
+ /// added. The cost of reallocating and copying the elements of a <see cref="Deque{T}" /> can be considerable.
+ /// However, the <see cref="TrimExcess" /> method does nothing if the <see cref="Count" /> is more than 90% of
+ /// <see cref="Capacity" />. This avoids incurring a large reallocation cost for a relatively small gain.
+ /// </para>
+ /// <para>
+ /// If <see cref="Count" /> is more than 90% of <see cref="Capacity" />, this method is an O(1) operation; O(n)
+ /// otherwise, where n is <see cref="Count" />.
+ /// </para>
+ /// <para>
+ /// To reset a <see cref="Deque{T}" /> to its initial state, call the <see cref="Clear" /> method before calling
+ /// the <see cref="TrimExcess" /> method. Trimming an empty <see cref="Deque{T}" /> sets <see cref="Capacity" /> to
+ /// the default capacity.
+ /// </para>
+ /// <para>The capacity can also be set using the <see cref="Capacity" /> property.</para>
+ /// </remarks>
+ public void TrimExcess()
+ {
+ if (Count > (int)(_items.Length * 0.9))
+ return;
+ Capacity = Count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetArrayIndex(int index)
+ {
+ if ((index < 0) || (index >= Count))
+ return -1;
+ return _items.Length != 0 ? (_frontArrayIndex + index) % _items.Length : 0;
+ }
+
+ private void EnsureCapacity(int minimumCapacity)
+ {
+ if (_items.Length >= minimumCapacity)
+ return;
+ var newCapacity = _defaultCapacity;
+ if (_items.Length > 0)
+ newCapacity = _resizeFunction(_items.Length);
+ newCapacity = Math.Max(newCapacity, minimumCapacity);
+ Capacity = newCapacity;
+ }
+
+ /// <summary>
+ /// Adds an element to the beginning of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">The element to add to the <see cref="Deque{T}" />. The value can be <c>null</c>.</param>
+ /// <remarks>
+ /// <para>
+ /// As elements are added to a <see cref="Deque{T}" />, <see cref="Capacity" /> is automatically increased by
+ /// <see cref="ResizeFunction" /> as required by reallocating the internal circular array.
+ /// </para>
+ /// <para>
+ /// If <see cref="Count" /> is less than <see cref="Capacity" />, this method is an O(1) operation. Otherwise the
+ /// internal circular array needs to be resized to accommodate the new element and this method becomes an O(n)
+ /// operation, where n is <see cref="Count" />.
+ /// </para>
+ /// </remarks>
+ public void AddToFront(T item)
+ {
+ EnsureCapacity(Count + 1);
+ _frontArrayIndex = (_frontArrayIndex - 1 + _items.Length) % _items.Length;
+ _items[_frontArrayIndex] = item;
+ Count++;
+ }
+
+ /// <summary>
+ /// Adds an element to the end of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">The element to add to the <see cref="Deque{T}" />. The value can be <c>null</c>.</param>
+ /// <remarks>
+ /// <para>
+ /// As elements are added to a <see cref="Deque{T}" />, <see cref="Capacity" /> is automatically increased by
+ /// <see cref="ResizeFunction" /> as required by reallocating the internal circular array.
+ /// </para>
+ /// <para>
+ /// If <see cref="Count" /> is less than <see cref="Capacity" />, this method is an O(1) operation. Otherwise the
+ /// internal circular array needs to be resized to accommodate the new element and this method becomes an O(n)
+ /// operation, where n is <see cref="Count" />.
+ /// </para>
+ /// </remarks>
+ public void AddToBack(T item)
+ {
+ EnsureCapacity(Count + 1);
+ var index = (_frontArrayIndex + Count++) % _items.Length;
+ _items[index] = item;
+ }
+
+ /// <summary>
+ /// Returns the element at the specified index of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="index">The zero-based index of the element to get.</param>
+ /// <param name="item">
+ /// When this method returns, contains the element at the specified index of the
+ /// <see cref="Deque{T}" />, if <paramref name="index" /> was non-negative and less than <see cref="Count" />;
+ /// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.
+ /// </param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="item" /> was successfully retrieved at <paramref name="index" /> from the of the
+ /// <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if <paramref name="index" /> was non-negative and less than <see cref="Count" />.
+ /// </returns>
+ public bool Get(int index, out T item)
+ {
+ var arrayIndex = GetArrayIndex(index);
+ if (arrayIndex == -1)
+ {
+ item = default(T);
+ return false;
+ }
+ item = _items[arrayIndex];
+ return true;
+ }
+
+ /// <summary>
+ /// Returns the element at the beginning of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// When this method returns, contains the element at the beginning of the <see cref="Deque{T}" />, if
+ /// <see cref="Deque{T}" /> was not empty; otherwise, the default value for the type of the value parameter. This
+ /// parameter is passed uninitialized.
+ /// </param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="item" /> was successfully from the beginning of the <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if the <see cref="Deque{T}" /> is empty.
+ /// </returns>
+ public bool GetFront(out T item)
+ {
+ return Get(0, out item);
+ }
+
+ /// <summary>
+ /// Returns the element at the end of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// When this method returns, contains the element at the end of the <see cref="Deque{T}" />, if
+ /// <see cref="Deque{T}" /> was not empty; otherwise, the default value for the type of the value parameter. This
+ /// parameter is passed uninitialized.
+ /// </param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="item" /> was successfully from the end of the <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if the <see cref="Deque{T}" /> is empty.
+ /// </returns>
+ public bool GetBack(out T item)
+ {
+ return Get(Count - 1, out item);
+ }
+
+ /// <summary>
+ /// Removes the element at the beginning of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// When this method returns, contains the element removed from the beginning of the
+ /// <see cref="Deque{T}" />, if the <see cref="Deque{T}" /> was not empty; otherwise, the default value for the type of
+ /// the value parameter. This parameter is passed uninitialized.
+ /// </param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="item" /> was successfully removed from the beginning of the <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if the <see cref="Deque{T}" /> is empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// This method is similar to the <see cref="GetFront" /> method, but <see cref="GetFront" /> does not
+ /// modify the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>
+ /// <c>null</c> can be added to the <see cref="Deque{T}" /> as a value. To distinguish between a null value and
+ /// the end of the <see cref="Deque{T}" />, check whether the return value of <see cref="RemoveFromFront(out T)" />
+ /// is
+ /// <c>false</c> or
+ /// <see cref="Count" /> is <c>0</c>.
+ /// </para>
+ /// <para>
+ /// This method is an O(1) operation.
+ /// </para>
+ /// </remarks>
+ public bool RemoveFromFront(out T item)
+ {
+ if (Count == 0)
+ {
+ item = default(T);
+ return false;
+ }
+
+ var index = _frontArrayIndex % _items.Length;
+ item = _items[index];
+ _items[index] = default(T);
+ _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length;
+ Count--;
+ return true;
+ }
+
+ /// <summary>
+ /// Removes the element at the beginning of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <returns>
+ /// <c>true</c> if the element was successfully removed from the beginning of the <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if the <see cref="Deque{T}" /> is empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// This method is similar to the <see cref="GetFront" /> method, but <see cref="GetFront" /> does not
+ /// modify the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>
+ /// <c>null</c> can be added to the <see cref="Deque{T}" /> as a value. To distinguish between a null value and
+ /// the end of the <see cref="Deque{T}" />, check whether the return value of <see cref="RemoveFromFront()" /> is
+ /// <c>false</c> or
+ /// <see cref="Count" /> is <c>0</c>.
+ /// </para>
+ /// <para>
+ /// This method is an O(1) operation.
+ /// </para>
+ /// </remarks>
+ public bool RemoveFromFront()
+ {
+ if (Count == 0)
+ return false;
+
+ var index = _frontArrayIndex % _items.Length;
+ _items[index] = default(T);
+ _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length;
+ Count--;
+ return true;
+ }
+
+ /// <summary>
+ /// Removes the element at the end of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <param name="item">
+ /// When this method returns, contains the element removed from the end of the
+ /// <see cref="Deque{T}" />, if the <see cref="Deque{T}" /> was not empty; otherwise, the default value for the type of
+ /// the value parameter. This parameter is passed uninitialized.
+ /// </param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="item" /> was successfully removed from the end of the <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if the <see cref="Deque{T}" /> is empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// This method is similar to the <see cref="GetBack" /> method, but <see cref="GetBack" /> does not
+ /// modify the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>
+ /// <c>null</c> can be added to the <see cref="Deque{T}" /> as a value. To distinguish between a null value and
+ /// the end of the <see cref="Deque{T}" />, check whether the return value of <see cref="RemoveFromBack(out T)" />
+ /// is
+ /// <c>false</c> or
+ /// <see cref="Count" /> is <c>0</c>.
+ /// </para>
+ /// <para>
+ /// This method is an O(1) operation.
+ /// </para>
+ /// </remarks>
+ public bool RemoveFromBack(out T item)
+ {
+ if (Count == 0)
+ {
+ item = default(T);
+ return false;
+ }
+
+ var circularBackIndex = (_frontArrayIndex + (Count - 1)) % _items.Length;
+ item = _items[circularBackIndex];
+ _items[circularBackIndex] = default(T);
+ Count--;
+ return true;
+ }
+
+ /// <summary>
+ /// Removes the element at the end of the <see cref="Deque{T}" />.
+ /// </summary>
+ /// <returns>
+ /// <c>true</c> if the element was successfully removed from the end of the <see cref="Deque{T}" />;
+ /// otherwise, <c>false</c> if the <see cref="Deque{T}" /> is empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// This method is similar to the <see cref="GetBack" /> method, but <see cref="GetBack" /> does not
+ /// modify the <see cref="Deque{T}" />.
+ /// </para>
+ /// <para>
+ /// <c>null</c> can be added to the <see cref="Deque{T}" /> as a value. To distinguish between a null value and
+ /// the end of the <see cref="Deque{T}" />, check whether the return value of <see cref="RemoveFromBack()" /> is
+ /// <c>false</c> or
+ /// <see cref="Count" /> is <c>0</c>.
+ /// </para>
+ /// <para>
+ /// This method is an O(1) operation.
+ /// </para>
+ /// </remarks>
+ public bool RemoveFromBack()
+ {
+ if (Count == 0)
+ return false;
+
+ var circularBackIndex = (_frontArrayIndex + (Count - 1)) % _items.Length;
+ _items[circularBackIndex] = default(T);
+ Count--;
+ return true;
+ }
+
+ /// <summary>
+ /// Removes and returns the last item.
+ /// </summary>
+ /// <returns>The item that was removed</returns>
+ public T Pop()
+ {
+ if (RemoveFromBack(out var item))
+ return item;
+
+ throw new InvalidOperationException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/DictionaryExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/DictionaryExtensions.cs
new file mode 100644
index 0000000..ee581b7
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/DictionaryExtensions.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace MonoGame.Extended.Collections
+{
+ public static class DictionaryExtensions
+ {
+ public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
+ {
+ return GetValueOrDefault(dictionary, key, default(TValue));
+ }
+
+ public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
+ {
+ TValue value;
+ return dictionary.TryGetValue(key, out value) ? value : defaultValue;
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IObservableCollection.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IObservableCollection.cs
new file mode 100644
index 0000000..bb4fe16
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IObservableCollection.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace MonoGame.Extended.Collections
+{
+ /// <summary>Interface for collections that can be observed</summary>
+ /// <typeparam name="T">Type of items managed in the collection</typeparam>
+ public interface IObservableCollection<T>
+ {
+ /// <summary>Raised when an item has been added to the collection</summary>
+ event EventHandler<ItemEventArgs<T>> ItemAdded;
+
+ /// <summary>Raised when an item is removed from the collection</summary>
+ event EventHandler<ItemEventArgs<T>> ItemRemoved;
+
+ /// <summary>Raised when the collection is about to be cleared</summary>
+ /// <remarks>
+ /// This could be covered by calling ItemRemoved for each item currently
+ /// contained in the collection, but it is often simpler and more efficient
+ /// to process the clearing of the entire collection as a special operation.
+ /// </remarks>
+ event EventHandler Clearing;
+
+ /// <summary>Raised when the collection has been cleared of its items</summary>
+ event EventHandler Cleared;
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IPoolable.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IPoolable.cs
new file mode 100644
index 0000000..cba24b8
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/IPoolable.cs
@@ -0,0 +1,12 @@
+namespace MonoGame.Extended.Collections
+{
+ public delegate void ReturnToPoolDelegate(IPoolable poolable);
+
+ public interface IPoolable
+ {
+ IPoolable NextNode { get; set; }
+ IPoolable PreviousNode { get; set; }
+ void Initialize(ReturnToPoolDelegate returnDelegate);
+ void Return();
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ItemEventArgs.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ItemEventArgs.cs
new file mode 100644
index 0000000..0da853b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ItemEventArgs.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace MonoGame.Extended.Collections
+{
+ /// <summary>
+ /// Arguments class for collections wanting to hand over an item in an event
+ /// </summary>
+ public class ItemEventArgs<T> : EventArgs
+ {
+ /// <summary>Initializes a new event arguments supplier</summary>
+ /// <param name="item">Item to be supplied to the event handler</param>
+ public ItemEventArgs(T item)
+ {
+ Item = item;
+ }
+
+ /// <summary>Obtains the collection item the event arguments are carrying</summary>
+ public T Item { get; }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/KeyedCollection.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/KeyedCollection.cs
new file mode 100644
index 0000000..e4ebeff
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/KeyedCollection.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace MonoGame.Extended.Collections
+{
+ public class KeyedCollection<TKey, TValue> : ICollection<TValue>
+ {
+ private readonly Func<TValue, TKey> _getKey;
+ private readonly Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
+
+ public KeyedCollection(Func<TValue, TKey> getKey)
+ {
+ _getKey = getKey;
+ }
+
+ public TValue this[TKey key] => _dictionary[key];
+ public ICollection<TKey> Keys => _dictionary.Keys;
+ public ICollection<TValue> Values => _dictionary.Values;
+ public int Count => _dictionary.Count;
+ public bool IsReadOnly => false;
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<TValue> GetEnumerator()
+ {
+ return _dictionary.Values.GetEnumerator();
+ }
+
+ public void Add(TValue item)
+ {
+ _dictionary.Add(_getKey(item), item);
+ }
+
+ public void Clear()
+ {
+ _dictionary.Clear();
+ }
+
+ public bool Contains(TValue item)
+ {
+ return _dictionary.ContainsKey(_getKey(item));
+ }
+
+ public void CopyTo(TValue[] array, int arrayIndex)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool Remove(TValue item)
+ {
+ return _dictionary.Remove(_getKey(item));
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return _dictionary.ContainsKey(key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return _dictionary.TryGetValue(key, out value);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ListExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ListExtensions.cs
new file mode 100644
index 0000000..9452a8e
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ListExtensions.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+
+namespace MonoGame.Extended.Collections
+{
+ public static class ListExtensions
+ {
+ public static IList<T> Shuffle<T>(this IList<T> list, Random random)
+ {
+ var n = list.Count;
+ while (n > 1)
+ {
+ n--;
+ var k = random.Next(n + 1);
+ var value = list[k];
+ list[k] = list[n];
+ list[n] = value;
+ }
+ return list;
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObjectPool.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObjectPool.cs
new file mode 100644
index 0000000..066fa96
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObjectPool.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace MonoGame.Extended.Collections
+{
+ public enum ObjectPoolIsFullPolicy
+ {
+ ReturnNull,
+ IncreaseSize,
+ KillExisting,
+ }
+
+ public class ObjectPool<T> : IEnumerable<T>
+ where T : class, IPoolable
+ {
+ private readonly ReturnToPoolDelegate _returnToPoolDelegate;
+
+ private readonly Deque<T> _freeItems; // circular buffer for O(1) operations
+ private T _headNode; // linked list for iteration
+ private T _tailNode;
+
+ private readonly Func<T> _instantiationFunction;
+
+ public ObjectPoolIsFullPolicy IsFullPolicy { get; }
+ public int Capacity { get; private set; }
+ public int TotalCount { get; private set; }
+ public int AvailableCount => _freeItems.Count;
+ public int InUseCount => TotalCount - AvailableCount;
+
+ public event Action<T> ItemUsed;
+ public event Action<T> ItemReturned;
+
+ public ObjectPool(Func<T> instantiationFunc, int capacity = 16, ObjectPoolIsFullPolicy isFullPolicy = ObjectPoolIsFullPolicy.ReturnNull)
+ {
+ if (instantiationFunc == null)
+ throw new ArgumentNullException(nameof(instantiationFunc));
+
+ _returnToPoolDelegate = Return;
+
+ _instantiationFunction = instantiationFunc;
+ _freeItems = new Deque<T>(capacity);
+ IsFullPolicy = isFullPolicy;
+
+ Capacity = capacity;
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ var node = _headNode;
+ while (node != null)
+ {
+ yield return node;
+ node = (T)node.NextNode;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public T New()
+ {
+ if (!_freeItems.RemoveFromFront(out var poolable))
+ {
+ if (TotalCount <= Capacity)
+ {
+ poolable = CreateObject();
+ }
+ else
+ {
+ switch (IsFullPolicy)
+ {
+ case ObjectPoolIsFullPolicy.ReturnNull:
+ return null;
+ case ObjectPoolIsFullPolicy.IncreaseSize:
+ Capacity++;
+ poolable = CreateObject();
+ break;
+ case ObjectPoolIsFullPolicy.KillExisting:
+ if (_headNode == null)
+ return null;
+ var newHeadNode = (T)_headNode.NextNode;
+ _headNode.Return();
+ _freeItems.RemoveFromBack(out poolable);
+ _headNode = newHeadNode;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+
+ Use(poolable);
+ return poolable;
+ }
+
+ private T CreateObject()
+ {
+ TotalCount++;
+ var item = _instantiationFunction();
+ if (item == null)
+ throw new NullReferenceException($"The created pooled object of type '{typeof(T).Name}' is null.");
+ item.PreviousNode = _tailNode;
+ item.NextNode = null;
+ if (_headNode == null)
+ _headNode = item;
+ if (_tailNode != null)
+ _tailNode.NextNode = item;
+ _tailNode = item;
+ return item;
+ }
+
+ private void Return(IPoolable item)
+ {
+ Debug.Assert(item != null);
+
+ var poolable1 = (T) item;
+
+ var previousNode = (T)item.PreviousNode;
+ var nextNode = (T)item.NextNode;
+
+ if (previousNode != null)
+ previousNode.NextNode = nextNode;
+ if (nextNode != null)
+ nextNode.PreviousNode = previousNode;
+
+ if (item == _headNode)
+ _headNode = nextNode;
+ if (item == _tailNode)
+ _tailNode = previousNode;
+
+ if (_tailNode != null)
+ _tailNode.NextNode = null;
+
+ _freeItems.AddToBack(poolable1);
+
+ ItemReturned?.Invoke((T)item);
+ }
+
+ private void Use(T item)
+ {
+ item.Initialize(_returnToPoolDelegate);
+ item.NextNode = null;
+ if (item != _tailNode)
+ {
+ item.PreviousNode = _tailNode;
+ _tailNode.NextNode = item;
+ _tailNode = item;
+ }
+
+ ItemUsed?.Invoke(item);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObservableCollection.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObservableCollection.cs
new file mode 100644
index 0000000..de827df
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/ObservableCollection.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace MonoGame.Extended.Collections
+{
+ public class ObservableCollection<T> : Collection<T>, IObservableCollection<T>
+ {
+ /// <summary>
+ /// Initializes a new instance of the ObservableCollection class that is empty.
+ /// </summary>
+ public ObservableCollection()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the ObservableCollection class as a wrapper
+ /// for the specified list.
+ /// </summary>
+ /// <param name="list">The list that is wrapped by the new collection.</param>
+ /// <exception cref="System.ArgumentNullException">
+ /// List is null.
+ /// </exception>
+ public ObservableCollection(IList<T> list) : base(list)
+ {
+ }
+
+ /// <summary>Raised when an item has been added to the collection</summary>
+ public event EventHandler<ItemEventArgs<T>> ItemAdded;
+
+ /// <summary>Raised when an item is removed from the collection</summary>
+ public event EventHandler<ItemEventArgs<T>> ItemRemoved;
+
+ /// <summary>Raised when the collection is about to be cleared</summary>
+ /// <remarks>
+ /// This could be covered by calling ItemRemoved for each item currently
+ /// contained in the collection, but it is often simpler and more efficient
+ /// to process the clearing of the entire collection as a special operation.
+ /// </remarks>
+ public event EventHandler Clearing;
+
+ /// <summary>Raised when the collection has been cleared</summary>
+ public event EventHandler Cleared;
+
+ /// <summary>Removes all elements from the Collection</summary>
+ protected override void ClearItems()
+ {
+ OnClearing();
+ base.ClearItems();
+ OnCleared();
+ }
+
+ /// <summary>
+ /// Inserts an element into the ObservableCollection at the specified index
+ /// </summary>
+ /// <param name="index">
+ /// The object to insert. The value can be null for reference types.
+ /// </param>
+ /// <param name="item">The zero-based index at which item should be inserted</param>
+ protected override void InsertItem(int index, T item)
+ {
+ base.InsertItem(index, item);
+ OnAdded(item);
+ }
+
+ /// <summary>
+ /// Removes the element at the specified index of the ObservableCollection
+ /// </summary>
+ /// <param name="index">The zero-based index of the element to remove</param>
+ protected override void RemoveItem(int index)
+ {
+ var item = base[index];
+ base.RemoveItem(index);
+ OnRemoved(item);
+ }
+
+ /// <summary>Replaces the element at the specified index</summary>
+ /// <param name="index">
+ /// The new value for the element at the specified index. The value can be null
+ /// for reference types
+ /// </param>
+ /// <param name="item">The zero-based index of the element to replace</param>
+ protected override void SetItem(int index, T item)
+ {
+ var oldItem = base[index];
+ base.SetItem(index, item);
+ OnRemoved(oldItem);
+ OnAdded(item);
+ }
+
+ /// <summary>Fires the 'ItemAdded' event</summary>
+ /// <param name="item">Item that has been added to the collection</param>
+ protected virtual void OnAdded(T item)
+ {
+ ItemAdded?.Invoke(this, new ItemEventArgs<T>(item));
+ }
+
+ /// <summary>Fires the 'ItemRemoved' event</summary>
+ /// <param name="item">Item that has been removed from the collection</param>
+ protected virtual void OnRemoved(T item)
+ {
+ ItemRemoved?.Invoke(this, new ItemEventArgs<T>(item));
+ }
+
+ /// <summary>Fires the 'Clearing' event</summary>
+ protected virtual void OnClearing()
+ {
+ Clearing?.Invoke(this, EventArgs.Empty);
+ }
+
+ /// <summary>Fires the 'Cleared' event</summary>
+ protected virtual void OnCleared()
+ {
+ Cleared?.Invoke(this, EventArgs.Empty);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Pool.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Pool.cs
new file mode 100644
index 0000000..7c1546f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections/Pool.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace MonoGame.Extended.Collections
+{
+ public class Pool<T>
+ where T : class
+ {
+ private readonly Func<T> _createItem;
+ private readonly Action<T> _resetItem;
+ private readonly Deque<T> _freeItems;
+ private readonly int _maximum;
+
+ public Pool(Func<T> createItem, Action<T> resetItem, int capacity = 16, int maximum = int.MaxValue)
+ {
+ _createItem = createItem;
+ _resetItem = resetItem;
+ _maximum = maximum;
+ _freeItems = new Deque<T>(capacity);
+ }
+
+ public Pool(Func<T> createItem, int capacity = 16, int maximum = int.MaxValue)
+ : this(createItem, _ => { }, capacity, maximum)
+ {
+ }
+
+ public int AvailableCount => _freeItems.Count;
+
+ public T Obtain()
+ {
+ if (_freeItems.Count > 0)
+ return _freeItems.Pop();
+
+ return _createItem();
+ }
+
+ public void Free(T item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+
+ if (_freeItems.Count < _maximum)
+ _freeItems.AddToBack(item);
+
+ _resetItem(item);
+ }
+
+ public void Clear()
+ {
+ _freeItems.Clear();
+ }
+ }
+} \ No newline at end of file