#region License /* The MIT License (MIT) Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft 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. */ #endregion using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace SimSharp { /// /// A copy of StablePriorityQueue which also has generic explicit priority-type /// /// /// Original sources from https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp /// /// The values in the queue. Must extend the GenericPriorityQueue class /// The priority-type. Must extend IComparable<TPriority> public sealed class GenericPriorityQueue : IEnumerable where TItem : GenericPriorityQueueNode where TPriority : IComparable { private int _numNodes; private TItem[] _nodes; private long _numNodesEverEnqueued; private readonly Comparison _comparer; /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) public GenericPriorityQueue(int maxNodes) : this(maxNodes, Comparer.Default) { } /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) /// The comparer used to compare TPriority values. public GenericPriorityQueue(int maxNodes, IComparer comparer) : this(maxNodes, comparer.Compare) { } /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) /// The comparison function to use to compare TPriority values public GenericPriorityQueue(int maxNodes, Comparison comparer) { #if DEBUG if (maxNodes <= 0) { throw new InvalidOperationException("New queue size cannot be smaller than 1"); } #endif _numNodes = 0; _nodes = new TItem[maxNodes + 1]; _numNodesEverEnqueued = 0; _comparer = comparer; } /// /// Returns the number of nodes in the queue. /// O(1) /// public int Count { get { return _numNodes; } } /// /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), /// attempting to enqueue another item will cause undefined behavior. O(1) /// public int MaxSize { get { return _nodes.Length - 1; } } /// /// Removes every node from the queue. /// O(n) (So, don't do this often!) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { Array.Clear(_nodes, 1, _numNodes); _numNodes = 0; } /// /// Returns (in O(1)!) whether the given node is in the queue. O(1) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) { throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?"); } #endif return (_nodes[node.QueueIndex] == node); } /// /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. /// If the queue is full, the result is undefined. /// If the node is already enqueued, the result is undefined. /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Enqueue(TItem node, TPriority priority) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (_numNodes >= _nodes.Length - 1) { throw new InvalidOperationException("Queue is full - node cannot be added: " + node); } if (Contains(node)) { throw new InvalidOperationException("Node is already enqueued: " + node); } #endif node.Priority = priority; _numNodes++; _nodes[_numNodes] = node; node.QueueIndex = _numNodes; node.InsertionIndex = _numNodesEverEnqueued++; CascadeUp(node); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CascadeUp(TItem node) { //aka Heapify-up int parent; if (node.QueueIndex > 1) { parent = node.QueueIndex >> 1; TItem parentNode = _nodes[parent]; if (HasHigherPriority(parentNode, node)) return; //Node has lower priority value, so move parent down the heap to make room _nodes[node.QueueIndex] = parentNode; parentNode.QueueIndex = node.QueueIndex; node.QueueIndex = parent; } else { return; } while (parent > 1) { parent >>= 1; TItem parentNode = _nodes[parent]; if (HasHigherPriority(parentNode, node)) break; //Node has lower priority value, so move parent down the heap to make room _nodes[node.QueueIndex] = parentNode; parentNode.QueueIndex = node.QueueIndex; node.QueueIndex = parent; } _nodes[node.QueueIndex] = node; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CascadeDown(TItem node) { //aka Heapify-down int finalQueueIndex = node.QueueIndex; int childLeftIndex = 2 * finalQueueIndex; // If leaf node, we're done if (childLeftIndex > _numNodes) { return; } // Check if the left-child is higher-priority than the current node int childRightIndex = childLeftIndex + 1; TItem childLeft = _nodes[childLeftIndex]; if (HasHigherPriority(childLeft, node)) { // Check if there is a right child. If not, swap and finish. if (childRightIndex > _numNodes) { node.QueueIndex = childLeftIndex; childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; _nodes[childLeftIndex] = node; return; } // Check if the left-child is higher-priority than the right-child TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childLeft, childRight)) { // left is highest, move it up and continue childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; finalQueueIndex = childLeftIndex; } else { // right is even higher, move it up and continue childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } } // Not swapping with left-child, does right-child exist? else if (childRightIndex > _numNodes) { return; } else { // Check if the right-child is higher-priority than the current node TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childRight, node)) { childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } // Neither child is higher-priority than current, so finish and stop. else { return; } } while (true) { childLeftIndex = 2 * finalQueueIndex; // If leaf node, we're done if (childLeftIndex > _numNodes) { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } // Check if the left-child is higher-priority than the current node childRightIndex = childLeftIndex + 1; childLeft = _nodes[childLeftIndex]; if (HasHigherPriority(childLeft, node)) { // Check if there is a right child. If not, swap and finish. if (childRightIndex > _numNodes) { node.QueueIndex = childLeftIndex; childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; _nodes[childLeftIndex] = node; break; } // Check if the left-child is higher-priority than the right-child TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childLeft, childRight)) { // left is highest, move it up and continue childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; finalQueueIndex = childLeftIndex; } else { // right is even higher, move it up and continue childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } } // Not swapping with left-child, does right-child exist? else if (childRightIndex > _numNodes) { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } else { // Check if the right-child is higher-priority than the current node TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childRight, node)) { childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } // Neither child is higher-priority than current, so finish and stop. else { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } } } } /// /// Returns true if 'higher' has higher priority than 'lower', false otherwise. /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool HasHigherPriority(TItem higher, TItem lower) { var cmp = _comparer(higher.Priority, lower.Priority); return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex)); } /// /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. /// If queue is empty, result is undefined /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TItem Dequeue() { #if DEBUG if (_numNodes <= 0) { throw new InvalidOperationException("Cannot call Dequeue() on an empty queue"); } if (!IsValidQueue()) { throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" + "Or add the same node to two different queues?)"); } #endif TItem returnMe = _nodes[1]; //If the node is already the last node, we can remove it immediately if (_numNodes == 1) { _nodes[1] = null; _numNodes = 0; return returnMe; } //Swap the node with the last node TItem formerLastNode = _nodes[_numNodes]; _nodes[1] = formerLastNode; formerLastNode.QueueIndex = 1; _nodes[_numNodes] = null; _numNodes--; //Now bubble formerLastNode (which is no longer the last node) down CascadeDown(formerLastNode); return returnMe; } /// /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior /// O(n) /// public void Resize(int maxNodes) { #if DEBUG if (maxNodes <= 0) { throw new InvalidOperationException("Queue size cannot be smaller than 1"); } if (maxNodes < _numNodes) { throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes"); } #endif TItem[] newArray = new TItem[maxNodes + 1]; int highestIndexToCopy = Math.Min(maxNodes, _numNodes); Array.Copy(_nodes, newArray, highestIndexToCopy + 1); _nodes = newArray; } /// /// Returns the head of the queue, without removing it (use Dequeue() for that). /// If the queue is empty, behavior is undefined. /// O(1) /// public TItem First { get { #if DEBUG if (_numNodes <= 0) { throw new InvalidOperationException("Cannot call .First on an empty queue"); } #endif return _nodes[1]; } } /// /// This method must be called on a node every time its priority changes while it is in the queue. /// Forgetting to call this method will result in a corrupted queue! /// Calling this method on a node not in the queue results in undefined behavior /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void UpdatePriority(TItem node, TPriority priority) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (!Contains(node)) { throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node); } #endif node.Priority = priority; OnNodeUpdated(node); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OnNodeUpdated(TItem node) { //Bubble the updated node up or down as appropriate int parentIndex = node.QueueIndex >> 1; if (parentIndex > 0 && HasHigherPriority(node, _nodes[parentIndex])) { CascadeUp(node); } else { //Note that CascadeDown will be called if parentNode == node (that is, node is the root) CascadeDown(node); } } /// /// Removes a node from the queue. The node does not need to be the head of the queue. /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (!Contains(node)) { throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node); } #endif //If the node is already the last node, we can remove it immediately if (node.QueueIndex == _numNodes) { _nodes[_numNodes] = null; _numNodes--; return; } //Swap the node with the last node TItem formerLastNode = _nodes[_numNodes]; _nodes[node.QueueIndex] = formerLastNode; formerLastNode.QueueIndex = node.QueueIndex; _nodes[_numNodes] = null; _numNodes--; //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate OnNodeUpdated(formerLastNode); } public IEnumerator GetEnumerator() { IEnumerable e = new ArraySegment(_nodes, 1, _numNodes); return e.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Should not be called in production code. /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue. /// public bool IsValidQueue() { for (int i = 1; i < _nodes.Length; i++) { if (_nodes[i] != null) { int childLeftIndex = 2 * i; if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i])) return false; int childRightIndex = childLeftIndex + 1; if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i])) return false; } } return true; } } /// /// A copy of StablePriorityQueue where the items themselves contain an implicit priority. /// /// /// Either implements IComparable<typeparamref name="TItem"/>> or /// a custom comparer must be provided. /// /// The values in the queue. Must extend the GenericPriorityQueueNode class public sealed class GenericPriorityQueue : IEnumerable where TItem : GenericPriorityQueueNode { private int _numNodes; private TItem[] _nodes; private long _numNodesEverEnqueued; private readonly Comparison _comparer; /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) public GenericPriorityQueue(int maxNodes) : this(maxNodes, Comparer.Default) { } /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) /// The comparer used to compare TPriority values. public GenericPriorityQueue(int maxNodes, IComparer comparer) : this(maxNodes, comparer.Compare) { } /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) /// The comparison function to use to compare TPriority values public GenericPriorityQueue(int maxNodes, Comparison comparer) { #if DEBUG if (maxNodes <= 0) { throw new InvalidOperationException("New queue size cannot be smaller than 1"); } #endif _numNodes = 0; _nodes = new TItem[maxNodes + 1]; _numNodesEverEnqueued = 0; _comparer = comparer; } /// /// Returns the number of nodes in the queue. /// O(1) /// public int Count { get { return _numNodes; } } /// /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), /// attempting to enqueue another item will cause undefined behavior. O(1) /// public int MaxSize { get { return _nodes.Length - 1; } } /// /// Removes every node from the queue. /// O(n) (So, don't do this often!) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { Array.Clear(_nodes, 1, _numNodes); _numNodes = 0; } /// /// Returns (in O(1)!) whether the given node is in the queue. O(1) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) { throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?"); } #endif return (_nodes[node.QueueIndex] == node); } /// /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. /// If the queue is full, the result is undefined. /// If the node is already enqueued, the result is undefined. /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Enqueue(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (_numNodes >= _nodes.Length - 1) { throw new InvalidOperationException("Queue is full - node cannot be added: " + node); } if (Contains(node)) { throw new InvalidOperationException("Node is already enqueued: " + node); } #endif _numNodes++; _nodes[_numNodes] = node; node.QueueIndex = _numNodes; node.InsertionIndex = _numNodesEverEnqueued++; CascadeUp(node); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CascadeUp(TItem node) { //aka Heapify-up int parent; if (node.QueueIndex > 1) { parent = node.QueueIndex >> 1; TItem parentNode = _nodes[parent]; if (HasHigherPriority(parentNode, node)) return; //Node has lower priority value, so move parent down the heap to make room _nodes[node.QueueIndex] = parentNode; parentNode.QueueIndex = node.QueueIndex; node.QueueIndex = parent; } else { return; } while (parent > 1) { parent >>= 1; TItem parentNode = _nodes[parent]; if (HasHigherPriority(parentNode, node)) break; //Node has lower priority value, so move parent down the heap to make room _nodes[node.QueueIndex] = parentNode; parentNode.QueueIndex = node.QueueIndex; node.QueueIndex = parent; } _nodes[node.QueueIndex] = node; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CascadeDown(TItem node) { //aka Heapify-down int finalQueueIndex = node.QueueIndex; int childLeftIndex = 2 * finalQueueIndex; // If leaf node, we're done if (childLeftIndex > _numNodes) { return; } // Check if the left-child is higher-priority than the current node int childRightIndex = childLeftIndex + 1; TItem childLeft = _nodes[childLeftIndex]; if (HasHigherPriority(childLeft, node)) { // Check if there is a right child. If not, swap and finish. if (childRightIndex > _numNodes) { node.QueueIndex = childLeftIndex; childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; _nodes[childLeftIndex] = node; return; } // Check if the left-child is higher-priority than the right-child TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childLeft, childRight)) { // left is highest, move it up and continue childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; finalQueueIndex = childLeftIndex; } else { // right is even higher, move it up and continue childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } } // Not swapping with left-child, does right-child exist? else if (childRightIndex > _numNodes) { return; } else { // Check if the right-child is higher-priority than the current node TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childRight, node)) { childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } // Neither child is higher-priority than current, so finish and stop. else { return; } } while (true) { childLeftIndex = 2 * finalQueueIndex; // If leaf node, we're done if (childLeftIndex > _numNodes) { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } // Check if the left-child is higher-priority than the current node childRightIndex = childLeftIndex + 1; childLeft = _nodes[childLeftIndex]; if (HasHigherPriority(childLeft, node)) { // Check if there is a right child. If not, swap and finish. if (childRightIndex > _numNodes) { node.QueueIndex = childLeftIndex; childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; _nodes[childLeftIndex] = node; break; } // Check if the left-child is higher-priority than the right-child TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childLeft, childRight)) { // left is highest, move it up and continue childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; finalQueueIndex = childLeftIndex; } else { // right is even higher, move it up and continue childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } } // Not swapping with left-child, does right-child exist? else if (childRightIndex > _numNodes) { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } else { // Check if the right-child is higher-priority than the current node TItem childRight = _nodes[childRightIndex]; if (HasHigherPriority(childRight, node)) { childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } // Neither child is higher-priority than current, so finish and stop. else { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } } } } /// /// Returns true if 'higher' has higher priority than 'lower', false otherwise. /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool HasHigherPriority(TItem higher, TItem lower) { var cmp = _comparer(higher, lower); return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex)); } /// /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. /// If queue is empty, result is undefined /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TItem Dequeue() { #if DEBUG if (_numNodes <= 0) { throw new InvalidOperationException("Cannot call Dequeue() on an empty queue"); } if (!IsValidQueue()) { throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" + "Or add the same node to two different queues?)"); } #endif TItem returnMe = _nodes[1]; //If the node is already the last node, we can remove it immediately if (_numNodes == 1) { _nodes[1] = null; _numNodes = 0; return returnMe; } //Swap the node with the last node TItem formerLastNode = _nodes[_numNodes]; _nodes[1] = formerLastNode; formerLastNode.QueueIndex = 1; _nodes[_numNodes] = null; _numNodes--; //Now bubble formerLastNode (which is no longer the last node) down CascadeDown(formerLastNode); return returnMe; } /// /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior /// O(n) /// public void Resize(int maxNodes) { #if DEBUG if (maxNodes <= 0) { throw new InvalidOperationException("Queue size cannot be smaller than 1"); } if (maxNodes < _numNodes) { throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes"); } #endif TItem[] newArray = new TItem[maxNodes + 1]; int highestIndexToCopy = Math.Min(maxNodes, _numNodes); Array.Copy(_nodes, newArray, highestIndexToCopy + 1); _nodes = newArray; } /// /// Returns the head of the queue, without removing it (use Dequeue() for that). /// If the queue is empty, behavior is undefined. /// O(1) /// public TItem First { get { #if DEBUG if (_numNodes <= 0) { throw new InvalidOperationException("Cannot call .First on an empty queue"); } #endif return _nodes[1]; } } /// /// This method must be called on a node every time its priority changes while it is in the queue. /// Forgetting to call this method will result in a corrupted queue! /// Calling this method on a node not in the queue results in undefined behavior /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void UpdatePriority(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (!Contains(node)) { throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node); } #endif OnNodeUpdated(node); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OnNodeUpdated(TItem node) { //Bubble the updated node up or down as appropriate int parentIndex = node.QueueIndex >> 1; if (parentIndex > 0 && HasHigherPriority(node, _nodes[parentIndex])) { CascadeUp(node); } else { //Note that CascadeDown will be called if parentNode == node (that is, node is the root) CascadeDown(node); } } /// /// Removes a node from the queue. The node does not need to be the head of the queue. /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first /// O(log n) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (!Contains(node)) { throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node); } #endif //If the node is already the last node, we can remove it immediately if (node.QueueIndex == _numNodes) { _nodes[_numNodes] = null; _numNodes--; return; } //Swap the node with the last node TItem formerLastNode = _nodes[_numNodes]; _nodes[node.QueueIndex] = formerLastNode; formerLastNode.QueueIndex = node.QueueIndex; _nodes[_numNodes] = null; _numNodes--; //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate OnNodeUpdated(formerLastNode); } public IEnumerator GetEnumerator() { IEnumerable e = new ArraySegment(_nodes, 1, _numNodes); return e.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Should not be called in production code. /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue. /// public bool IsValidQueue() { for (int i = 1; i < _nodes.Length; i++) { if (_nodes[i] != null) { int childLeftIndex = 2 * i; if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i])) return false; int childRightIndex = childLeftIndex + 1; if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i])) return false; } } return true; } } }