213 lines
5.5 KiB
C#
213 lines
5.5 KiB
C#
using System.Buffers;
|
|
using System.Collections;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Recyclable.Collections
|
|
{
|
|
public class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
|
|
{
|
|
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
|
|
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
|
|
|
private bool _disposedValue;
|
|
private RecyclableArrayList<T[]> Memory { get; }
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static T Dequeue(RecyclableQueue<T> queue)
|
|
{
|
|
if (queue.LongCount <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(queue), "There are no items in the queue");
|
|
}
|
|
|
|
var toRemove = queue[0];
|
|
queue.RemovedCount++;
|
|
queue.LongCount--;
|
|
if (queue.Capacity - queue.LongCount == queue.BlockSize)
|
|
{
|
|
queue.RemoveBlock(0);
|
|
queue.RemovedCount -= queue.BlockSize;
|
|
}
|
|
|
|
return toRemove;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool TryDequeue(RecyclableQueue<T> queue, out T? item)
|
|
{
|
|
if (queue.LongCount > 0)
|
|
{
|
|
item = queue.Dequeue();
|
|
return true;
|
|
}
|
|
|
|
item = default;
|
|
return false;
|
|
}
|
|
|
|
protected static readonly bool NeedsClearing = !typeof(T).IsValueType;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected long GetRelativeIndex(long index) => index - RemovedCount;
|
|
|
|
protected long RemovedCount { get; set; }
|
|
|
|
public RecyclableQueue(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
|
|
{
|
|
BlockSize = blockSize;
|
|
Memory = new(1);
|
|
foreach (var item in source)
|
|
{
|
|
Enqueue(item);
|
|
}
|
|
}
|
|
|
|
public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize)
|
|
{
|
|
BlockSize = blockSize;
|
|
Memory = new(1);
|
|
}
|
|
|
|
public int BlockSize { get; protected set; }
|
|
public long Capacity { get; set; }
|
|
|
|
public int Count
|
|
{
|
|
get => (int)LongCount;
|
|
set => LongCount = value;
|
|
}
|
|
|
|
public long LongCount { get; set; }
|
|
public bool IsReadOnly { get; } = false;
|
|
|
|
public T this[int index]
|
|
{
|
|
get => GetItem(index);
|
|
set => SetItem(index, value);
|
|
}
|
|
|
|
public T this[long index]
|
|
{
|
|
get => GetItem(index);
|
|
set => SetItem(index, value);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static int ToArrayIndex(long index, int blockSize) => (int)(index / blockSize);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static long ToItemIndex(long index, int blockSize) => index % blockSize;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void SetItem(long index, T value)
|
|
{
|
|
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount);
|
|
int arrayIndex = ToArrayIndex(absoluteIndex, BlockSize);
|
|
long itemIndex = ToItemIndex(absoluteIndex, BlockSize);
|
|
Memory[arrayIndex][itemIndex] = value;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private T GetItem(long index)
|
|
{
|
|
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount);
|
|
int arrayIndex = ToArrayIndex(absoluteIndex, BlockSize);
|
|
long itemIndex = ToItemIndex(absoluteIndex, BlockSize);
|
|
return Memory[arrayIndex][itemIndex];
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Enqueue(T item)
|
|
{
|
|
if (LongCount == Capacity)
|
|
{
|
|
AddBlock();
|
|
}
|
|
|
|
SetItem(LongCount, item);
|
|
LongCount++;
|
|
}
|
|
|
|
public void AddBlock()
|
|
{
|
|
T[] newArray = BlockSize.RentArrayFromPool<T>(_arrayPool);
|
|
Memory.Add(newArray);
|
|
Capacity += BlockSize;
|
|
}
|
|
|
|
public T Dequeue() => Dequeue(this);
|
|
|
|
public void Add(T item) => Enqueue(item);
|
|
|
|
public void Clear()
|
|
{
|
|
try
|
|
{
|
|
// Remove in reversed order for performance savings
|
|
while (Memory.Count > 0)
|
|
{
|
|
try
|
|
{
|
|
RemoveBlock(Memory.Count - 1);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// We want to try returning as many arrays, as possible, before
|
|
// the list is cleared.
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
LongCount = 0;
|
|
RemovedCount = 0;
|
|
Memory.Clear();
|
|
}
|
|
}
|
|
|
|
public bool Contains(T item) => Memory.Contains(item);
|
|
public void CopyTo(T[] array, int arrayIndex) => Memory.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex);
|
|
public IEnumerable<T> Enumerate() => Memory.Enumerate(BlockSize, LongCount);
|
|
public IEnumerator<T> GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator();
|
|
IEnumerator IEnumerable.GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator();
|
|
public int IndexOf(T item) => (int)LongIndexOf(item);
|
|
public long LongIndexOf(T item) => GetRelativeIndex(Memory.LongIndexOf(BlockSize, item, _equalityComparer));
|
|
public void Insert(int index, T item) => throw new NotSupportedException();
|
|
public void RemoveAt(int index) => throw new NotSupportedException();
|
|
public bool TryDequeue(out T? item) => TryDequeue(this, out item);
|
|
public bool Remove(T item) => TryDequeue(out var _);
|
|
public void RemoveBlock(int index)
|
|
{
|
|
try
|
|
{
|
|
Memory[index].ReturnToPool(_arrayPool, NeedsClearing);
|
|
Memory.RemoveAt(index);
|
|
}
|
|
finally
|
|
{
|
|
Capacity -= BlockSize;
|
|
//LongCount -= BlockSize;
|
|
}
|
|
}
|
|
|
|
protected void Dispose(bool disposing)
|
|
{
|
|
if (!_disposedValue)
|
|
{
|
|
Clear();
|
|
_disposedValue = true;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
Dispose(disposing: true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
}
|