// This file is part of YamlDotNet - A .NET library for YAML. // Copyright (c) Antoine Aubry and contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using YamlDotNet.Helpers; namespace YamlDotNet.Core { /// /// Generic queue on which items may be inserted /// public sealed class InsertionQueue : IEnumerable { private const int DefaultInitialCapacity = 1 << 7; // Must be a power of 2 // Circular buffer private T[] items; private int readPtr; private int writePtr; private int mask; private int count = 0; public InsertionQueue(int initialCapacity = DefaultInitialCapacity) { if (initialCapacity <= 0) { throw new ArgumentOutOfRangeException(nameof(initialCapacity), "The initial capacity must be a positive number."); } if (!initialCapacity.IsPowerOfTwo()) { throw new ArgumentException("The initial capacity must be a power of 2.", nameof(initialCapacity)); } items = new T[initialCapacity]; readPtr = initialCapacity / 2; writePtr = initialCapacity / 2; mask = initialCapacity - 1; } /// /// Gets the number of items that are contained by the queue. /// public int Count => count; public int Capacity => items.Length; /// /// Enqueues the specified item. /// /// The item to be enqueued. public void Enqueue(T item) { ResizeIfNeeded(); items[writePtr] = item; writePtr = (writePtr - 1) & mask; ++count; } /// /// Dequeues an item. /// /// Returns the item that been dequeued. public T Dequeue() { if (count == 0) { throw new InvalidOperationException("The queue is empty"); } var item = items[readPtr]; readPtr = (readPtr - 1) & mask; --count; return item; } /// /// Inserts an item at the specified index. /// /// The index where to insert the item. /// The item to be inserted. public void Insert(int index, T item) { if (index > count) { throw new InvalidOperationException("Cannot insert outside of the bounds of the queue"); } ResizeIfNeeded(); CalculateInsertionParameters( mask, count, index, ref readPtr, ref writePtr, out var insertPtr, out var copyIndex, out var copyOffset, out var copyLength ); if (copyLength != 0) { Array.Copy(items, copyIndex, items, copyIndex + copyOffset, copyLength); } items[insertPtr] = item; ++count; } private void ResizeIfNeeded() { var capacity = items.Length; if (count == capacity) { Debug.Assert(readPtr == writePtr); var newItems = new T[capacity * 2]; var beginCount = readPtr + 1; if (beginCount > 0) { Array.Copy(items, 0, newItems, 0, beginCount); } writePtr += capacity; var endCount = capacity - beginCount; if (endCount > 0) { Array.Copy(items, readPtr + 1, newItems, writePtr + 1, endCount); } items = newItems; mask = mask * 2 + 1; } } internal static void CalculateInsertionParameters(int mask, int count, int index, ref int readPtr, ref int writePtr, out int insertPtr, out int copyIndex, out int copyOffset, out int copyLength) { var indexOfLastElement = (readPtr + 1) & mask; if (index == 0) { insertPtr = readPtr = indexOfLastElement; // No copy is needed copyIndex = 0; copyOffset = 0; copyLength = 0; return; } insertPtr = (readPtr - index) & mask; if (index == count) { writePtr = (writePtr - 1) & mask; // No copy is needed copyIndex = 0; copyOffset = 0; copyLength = 0; return; } var canMoveRight = indexOfLastElement >= insertPtr; var moveRightCost = canMoveRight ? readPtr - insertPtr : int.MaxValue; var canMoveLeft = writePtr <= insertPtr; var moveLeftCost = canMoveLeft ? insertPtr - writePtr : int.MaxValue; if (moveRightCost <= moveLeftCost) { ++insertPtr; ++readPtr; copyIndex = insertPtr; copyOffset = 1; copyLength = moveRightCost; } else { copyIndex = writePtr + 1; copyOffset = -1; copyLength = moveLeftCost; writePtr = (writePtr - 1) & mask; } } public IEnumerator GetEnumerator() { var ptr = readPtr; for (var i = 0; i < Count; i++) { yield return items[ptr]; ptr = (ptr - 1) & mask; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }