using System; using UnityEngine; namespace IngameDebugConsole { public class CircularBuffer { private readonly T[] array; private int startIndex; public CircularBuffer(int capacity) { array = new T[capacity]; } public int Count { get; private set; } public T this[int index] => array[(startIndex + index) % array.Length]; // Old elements are overwritten when capacity is reached public void Add(T value) { if (Count < array.Length) { array[Count++] = value; } else { array[startIndex] = value; if (++startIndex >= array.Length) startIndex = 0; } } public T[] ToArray() { var result = new T[Count]; for (var i = 0; i < Count; i++) result[i] = this[i]; return result; } } public class DynamicCircularBuffer { private T[] array; private int startIndex; public DynamicCircularBuffer(int initialCapacity = 2) { array = new T[initialCapacity]; } public int Count { get; private set; } public int Capacity => array.Length; public T this[int index] { get => array[(startIndex + index) % array.Length]; set => array[(startIndex + index) % array.Length] = value; } private void SetCapacity(int capacity) { var newArray = new T[capacity]; if (Count > 0) { var elementsBeforeWrap = Mathf.Min(Count, array.Length - startIndex); Array.Copy(array, startIndex, newArray, 0, elementsBeforeWrap); if (elementsBeforeWrap < Count) Array.Copy(array, 0, newArray, elementsBeforeWrap, Count - elementsBeforeWrap); } array = newArray; startIndex = 0; } /// Inserts the value to the beginning of the collection. public void AddFirst(T value) { if (array.Length == Count) SetCapacity(Mathf.Max(array.Length * 2, 4)); startIndex = startIndex > 0 ? startIndex - 1 : array.Length - 1; array[startIndex] = value; Count++; } /// Adds the value to the end of the collection. public void Add(T value) { if (array.Length == Count) SetCapacity(Mathf.Max(array.Length * 2, 4)); this[Count++] = value; } public void AddRange(DynamicCircularBuffer other) { if (other.Count == 0) return; if (array.Length < Count + other.Count) SetCapacity(Mathf.Max(array.Length * 2, Count + other.Count)); var insertStartIndex = (startIndex + Count) % array.Length; var elementsBeforeWrap = Mathf.Min(other.Count, array.Length - insertStartIndex); var otherElementsBeforeWrap = Mathf.Min(other.Count, other.array.Length - other.startIndex); Array.Copy(other.array, other.startIndex, array, insertStartIndex, Mathf.Min(elementsBeforeWrap, otherElementsBeforeWrap)); if (elementsBeforeWrap < otherElementsBeforeWrap) // This array wrapped before the other array Array.Copy(other.array, other.startIndex + elementsBeforeWrap, array, 0, otherElementsBeforeWrap - elementsBeforeWrap); else if (elementsBeforeWrap > otherElementsBeforeWrap) // The other array wrapped before this array Array.Copy(other.array, 0, array, insertStartIndex + otherElementsBeforeWrap, elementsBeforeWrap - otherElementsBeforeWrap); var copiedElements = Mathf.Max(elementsBeforeWrap, otherElementsBeforeWrap); if (copiedElements < other.Count) // Both arrays wrapped and there's still some elements left to copy Array.Copy(other.array, copiedElements - otherElementsBeforeWrap, array, copiedElements - elementsBeforeWrap, other.Count - copiedElements); Count += other.Count; } public T RemoveFirst() { var element = array[startIndex]; array[startIndex] = default; if (++startIndex == array.Length) startIndex = 0; Count--; return element; } public T RemoveLast() { var index = (startIndex + Count - 1) % array.Length; var element = array[index]; array[index] = default; Count--; return element; } public int RemoveAll(Predicate shouldRemoveElement) { return RemoveAll(shouldRemoveElement, null, null); } public int RemoveAll(Predicate shouldRemoveElement, Action onElementIndexChanged, DynamicCircularBuffer synchronizedBuffer) { var synchronizedArray = synchronizedBuffer != null ? synchronizedBuffer.array : null; var elementsBeforeWrap = Mathf.Min(Count, array.Length - startIndex); var removedElements = 0; int i = startIndex, newIndex = startIndex, endIndex = startIndex + elementsBeforeWrap; for (; i < endIndex; i++) if (shouldRemoveElement(array[i])) { removedElements++; } else { if (removedElements > 0) { var element = array[i]; array[newIndex] = element; if (synchronizedArray != null) synchronizedArray[newIndex] = synchronizedArray[i]; if (onElementIndexChanged != null) onElementIndexChanged(element, newIndex - startIndex); } newIndex++; } i = 0; endIndex = Count - elementsBeforeWrap; if (newIndex < array.Length) for (; i < endIndex; i++) if (shouldRemoveElement(array[i])) { removedElements++; } else { var element = array[i]; array[newIndex] = element; if (synchronizedArray != null) synchronizedArray[newIndex] = synchronizedArray[i]; if (onElementIndexChanged != null) onElementIndexChanged(element, newIndex - startIndex); if (++newIndex == array.Length) { i++; break; } } if (newIndex == array.Length) { newIndex = 0; for (; i < endIndex; i++) if (shouldRemoveElement(array[i])) { removedElements++; } else { if (removedElements > 0) { var element = array[i]; array[newIndex] = element; if (synchronizedArray != null) synchronizedArray[newIndex] = synchronizedArray[i]; if (onElementIndexChanged != null) onElementIndexChanged(element, newIndex + elementsBeforeWrap); } newIndex++; } } TrimEnd(removedElements); if (synchronizedBuffer != null) synchronizedBuffer.TrimEnd(removedElements); return removedElements; } public void TrimStart(int trimCount, Action perElementCallback = null) { TrimInternal(trimCount, startIndex, perElementCallback); startIndex = (startIndex + trimCount) % array.Length; } public void TrimEnd(int trimCount, Action perElementCallback = null) { TrimInternal(trimCount, (startIndex + Count - trimCount) % array.Length, perElementCallback); } private void TrimInternal(int trimCount, int startIndex, Action perElementCallback) { var elementsBeforeWrap = Mathf.Min(trimCount, array.Length - startIndex); if (perElementCallback == null) { Array.Clear(array, startIndex, elementsBeforeWrap); if (elementsBeforeWrap < trimCount) Array.Clear(array, 0, trimCount - elementsBeforeWrap); } else { for (int i = startIndex, endIndex = startIndex + elementsBeforeWrap; i < endIndex; i++) { perElementCallback(array[i]); array[i] = default; } for (int i = 0, endIndex = trimCount - elementsBeforeWrap; i < endIndex; i++) { perElementCallback(array[i]); array[i] = default; } } Count -= trimCount; } public void Clear() { var elementsBeforeWrap = Mathf.Min(Count, array.Length - startIndex); Array.Clear(array, startIndex, elementsBeforeWrap); if (elementsBeforeWrap < Count) Array.Clear(array, 0, Count - elementsBeforeWrap); startIndex = 0; Count = 0; } public int IndexOf(T value) { var elementsBeforeWrap = Mathf.Min(Count, array.Length - startIndex); var index = Array.IndexOf(array, value, startIndex, elementsBeforeWrap); if (index >= 0) return index - startIndex; if (elementsBeforeWrap < Count) { index = Array.IndexOf(array, value, 0, Count - elementsBeforeWrap); if (index >= 0) return index + elementsBeforeWrap; } return -1; } public void ForEach(Action action) { var elementsBeforeWrap = Mathf.Min(Count, array.Length - startIndex); for (int i = startIndex, endIndex = startIndex + elementsBeforeWrap; i < endIndex; i++) action(array[i]); for (int i = 0, endIndex = Count - elementsBeforeWrap; i < endIndex; i++) action(array[i]); } } }