diff options
author | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
commit | acea7b2e728787a0d83bbf83c8c1f042d2c32e7e (patch) | |
tree | 0bfec05c1ca2d71be2c337bcd110a0421f19318b /Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections | |
parent | 88febcb02bf127d961c6471d9e846c0e1315f5c3 (diff) |
+ plugins project
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended/Collections')
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 |