using System.Collections;
using System.Collections.Generic;
using Pathfinding.Util;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace Pathfinding.Util {
///
/// Implements an efficient circular buffer that can be appended to in both directions.
///
/// See:
///
public struct CircularBuffer : IReadOnlyList, IReadOnlyCollection {
internal T[] data;
internal int head;
int length;
/// Number of items in the buffer
public readonly int Length => length;
/// Absolute index of the first item in the buffer, may be negative or greater than
public readonly int AbsoluteStartIndex => head;
/// Absolute index of the last item in the buffer, may be negative or greater than
public readonly int AbsoluteEndIndex => head + length - 1;
/// First item in the buffer, throws if the buffer is empty
public readonly ref T First => ref data[head & (data.Length-1)];
/// Last item in the buffer, throws if the buffer is empty
public readonly ref T Last => ref data[(head+length-1) & (data.Length-1)];
readonly int IReadOnlyCollection.Count {
get {
return length;
}
}
/// Create a new buffer with the given capacity
public CircularBuffer(int initialCapacity) {
data = ArrayPool.Claim(initialCapacity);
head = 0;
length = 0;
}
///
/// Create a new buffer using the given array as an internal store.
/// This will take ownership of the given array.
///
public CircularBuffer(T[] backingArray) {
data = backingArray;
head = 0;
length = 0;
}
/// Resets the buffer's length to zero. Does not clear the current allocation
public void Clear () {
length = 0;
head = 0;
}
/// Appends a list of items to the end of the buffer
public void AddRange (List items) {
// TODO: Can be optimized
for (int i = 0; i < items.Count; i++) PushEnd(items[i]);
}
/// Pushes a new item to the start of the buffer
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void PushStart (T item) {
if (data == null || length >= data.Length) Grow();
length += 1;
head -= 1;
this[0] = item;
}
/// Pushes a new item to the end of the buffer
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void PushEnd (T item) {
if (data == null || length >= data.Length) Grow();
length += 1;
this[length-1] = item;
}
/// Pushes a new item to the start or the end of the buffer
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Push (bool toStart, T item) {
if (toStart) PushStart(item);
else PushEnd(item);
}
/// Removes and returns the first element
public T PopStart () {
if (length == 0) throw new System.InvalidOperationException();
var r = this[0];
head++;
length--;
return r;
}
/// Removes and returns the last element
public T PopEnd () {
if (length == 0) throw new System.InvalidOperationException();
var r = this[length-1];
length--;
return r;
}
/// Pops either from the start or from the end of the buffer
public T Pop (bool fromStart) {
if (fromStart) return PopStart();
else return PopEnd();
}
/// Return either the first element or the last element
public readonly T GetBoundaryValue (bool start) {
return GetAbsolute(start ? AbsoluteStartIndex : AbsoluteEndIndex);
}
/// Inserts an item at the given absolute index
public void InsertAbsolute (int index, T item) {
SpliceUninitializedAbsolute(index, 0, 1);
data[index & (data.Length - 1)] = item;
}
/// Removes toRemove items from the buffer, starting at startIndex, and then inserts the toInsert items at startIndex
public void Splice (int startIndex, int toRemove, List toInsert) {
SpliceAbsolute(startIndex + head, toRemove, toInsert);
}
/// Like , but startIndex is an absolute index
public void SpliceAbsolute (int startIndex, int toRemove, List toInsert) {
if (toInsert == null) {
SpliceUninitializedAbsolute(startIndex, toRemove, 0);
} else {
SpliceUninitializedAbsolute(startIndex, toRemove, toInsert.Count);
for (int i = 0; i < toInsert.Count; i++) data[(startIndex + i) & (data.Length - 1)] = toInsert[i];
}
}
/// Like , but the newly inserted items are left in an uninitialized state
public void SpliceUninitialized (int startIndex, int toRemove, int toInsert) {
SpliceUninitializedAbsolute(startIndex + head, toRemove, toInsert);
}
/// Like , but startIndex is an absolute index
public void SpliceUninitializedAbsolute (int startIndex, int toRemove, int toInsert) {
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (startIndex - head < 0 || startIndex + toRemove - head > length) throw new System.ArgumentOutOfRangeException();
#endif
var itemsToAdd = toInsert - toRemove;
while (this.length + itemsToAdd > this.data.Length) Grow();
// move items [startIndex+length .. end] itemsToAdd steps forward in the array
MoveAbsolute(startIndex + toRemove, AbsoluteEndIndex, itemsToAdd);
this.length += itemsToAdd;
}
void MoveAbsolute (int startIndex, int endIndex, int deltaIndex) {
if (deltaIndex > 0) {
for (int i = endIndex; i >= startIndex; i--) data[(i+deltaIndex) & (data.Length-1)] = data[i & (data.Length-1)];
} else if (deltaIndex < 0) {
for (int i = startIndex; i <= endIndex; i++) data[(i+deltaIndex) & (data.Length-1)] = data[i & (data.Length-1)];
}
}
/// Indexes the buffer, with index 0 being the first element
public T this[int index] {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
readonly get {
#if UNITY_EDITOR
if ((uint)index >= length) throw new System.ArgumentOutOfRangeException();
#endif
return data[(index+head) & (data.Length-1)];
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
set {
#if UNITY_EDITOR
if ((uint)index >= length) throw new System.ArgumentOutOfRangeException();
#endif
data[(index+head) & (data.Length-1)] = value;
}
}
///
/// Indexes the buffer using absolute indices.
/// When pushing to and popping from the buffer, the absolute indices do not change.
/// So e.g. after doing PushStart(x) on an empty buffer, GetAbsolute(-1) will get the newly pushed element.
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public readonly T GetAbsolute (int index) {
#if UNITY_EDITOR
if ((uint)(index - head) >= length) throw new System.ArgumentOutOfRangeException();
#endif
return data[index & (data.Length-1)];
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public readonly void SetAbsolute (int index, T value) {
#if UNITY_EDITOR
if ((uint)(index - head) >= length) throw new System.ArgumentOutOfRangeException();
#endif
data[index & (data.Length-1)] = value;
}
void Grow () {
var newData = ArrayPool.Claim(System.Math.Max(4, data != null ? data.Length*2 : 0));
if (data != null) {
var inOrderItems = data.Length - (head & (data.Length-1));
System.Array.Copy(data, head & (data.Length-1), newData, head & (newData.Length - 1), inOrderItems);
var wraparoundItems = length - inOrderItems;
if (wraparoundItems > 0) System.Array.Copy(data, 0, newData, (head + inOrderItems) & (newData.Length - 1), wraparoundItems);
ArrayPool.Release(ref data);
}
data = newData;
}
/// Release the backing array of this buffer back into an array pool
public void Pool () {
ArrayPool.Release(ref data);
length = 0;
head = 0;
}
public IEnumerator GetEnumerator () {
for (int i = 0; i < length; i++) {
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator () {
for (int i = 0; i < length; i++) {
yield return this[i];
}
}
public CircularBuffer Clone () {
return new CircularBuffer {
data = data != null ? (T[])data.Clone() : null,
length = length,
head = head
};
}
}
}