using System;
using System.Collections;
using System.Collections.Generic;
namespace SimSharp {
///
/// An implementation of a min-Priority Queue using a heap. Has O(1) .Contains()!
/// See https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp/wiki/Getting-Started for more information
///
///
/// There are modifications so that the type is not generic anymore and can only hold values of type EventQueueNode
///
public sealed class EventQueue : IEnumerable {
private int _numNodes;
private readonly EventQueueNode[] _nodes;
private long _numNodesEverEnqueued;
///
/// Instantiate a new Priority Queue
///
/// EventQueueNodehe max nodes ever allowed to be enqueued (going over this will cause an exception)
public EventQueue(int maxNodes) {
_numNodes = 0;
_nodes = new EventQueueNode[maxNodes + 1];
_numNodesEverEnqueued = 0;
}
///
/// 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 throw an exception. O(1)
///
public int MaxSize {
get {
return _nodes.Length - 1;
}
}
///
/// Removes every node from the queue. O(n) (So, don't do this often!)
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.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)
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool Contains(EventQueueNode node) {
return (_nodes[node.QueueIndex] == node);
}
///
/// Enqueue a node - .Priority must be set beforehand! O(log n)
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public EventQueueNode Enqueue(DateTime primaryPriority, Event @event, int secondaryPriority = 0) {
var node = new EventQueueNode {
PrimaryPriority = primaryPriority,
SecondaryPriority = secondaryPriority,
Event = @event,
QueueIndex = ++_numNodes,
InsertionIndex = _numNodesEverEnqueued++
};
_nodes[_numNodes] = node;
CascadeUp(_nodes[_numNodes]);
return node;
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private void Swap(EventQueueNode node1, EventQueueNode node2) {
//Swap the nodes
_nodes[node1.QueueIndex] = node2;
_nodes[node2.QueueIndex] = node1;
//Swap their indicies
int temp = node1.QueueIndex;
node1.QueueIndex = node2.QueueIndex;
node2.QueueIndex = temp;
}
//Performance appears to be slightly better when this is NOT inlined o_O
private void CascadeUp(EventQueueNode node) {
//aka Heapify-up
int parent = node.QueueIndex / 2;
while (parent >= 1) {
EventQueueNode parentNode = _nodes[parent];
if (HasHigherPriority(parentNode, node))
break;
//Node has lower priority value, so move it up the heap
Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
parent = node.QueueIndex / 2;
}
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private void CascadeDown(EventQueueNode node) {
//aka Heapify-down
EventQueueNode newParent;
int finalQueueIndex = node.QueueIndex;
while (true) {
newParent = node;
int childLeftIndex = 2 * finalQueueIndex;
//Check if the left-child is higher-priority than the current node
if (childLeftIndex > _numNodes) {
//This could be placed outside the loop, but then we'd have to check newParent != node twice
node.QueueIndex = finalQueueIndex;
_nodes[finalQueueIndex] = node;
break;
}
EventQueueNode childLeft = _nodes[childLeftIndex];
if (HasHigherPriority(childLeft, newParent)) {
newParent = childLeft;
}
//Check if the right-child is higher-priority than either the current node or the left child
int childRightIndex = childLeftIndex + 1;
if (childRightIndex <= _numNodes) {
EventQueueNode childRight = _nodes[childRightIndex];
if (HasHigherPriority(childRight, newParent)) {
newParent = childRight;
}
}
//If either of the children has higher (smaller) priority, swap and continue cascading
if (newParent != node) {
//Move new parent to its new index. node will be moved once, at the end
//Doing it this way is one less assignment operation than calling Swap()
_nodes[finalQueueIndex] = newParent;
int temp = newParent.QueueIndex;
newParent.QueueIndex = finalQueueIndex;
finalQueueIndex = temp;
} else {
//See note above
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
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private bool HasHigherPriority(EventQueueNode higher, EventQueueNode lower) {
return (higher.PrimaryPriority < lower.PrimaryPriority ||
(higher.PrimaryPriority == lower.PrimaryPriority
&& (higher.SecondaryPriority < lower.SecondaryPriority ||
(higher.SecondaryPriority == lower.SecondaryPriority
&& higher.InsertionIndex < lower.InsertionIndex))));
}
///
/// Removes the head of the queue (node with highest priority; ties are broken by order of insertion), and returns it. O(log n)
///
public EventQueueNode Dequeue() {
EventQueueNode returnMe = _nodes[1];
Remove(returnMe);
return returnMe;
}
///
/// Returns the head of the queue, without removing it (use Dequeue() for that). O(1)
///
public EventQueueNode First {
get {
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!
/// O(log n)
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void UpdatePriority(EventQueueNode node, DateTime primaryPriority, int secondaryPriority) {
node.PrimaryPriority = primaryPriority;
node.SecondaryPriority = secondaryPriority;
OnNodeUpdated(node);
}
internal void OnNodeUpdated(EventQueueNode node) {
//Bubble the updated node up or down as appropriate
int parentIndex = node.QueueIndex / 2;
EventQueueNode parentNode = _nodes[parentIndex];
if (parentIndex > 0 && HasHigherPriority(node, parentNode)) {
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. Note that the node does not need to be the head of the queue. O(log n)
///
public void Remove(EventQueueNode node) {
if (!Contains(node)) {
return;
}
if (_numNodes <= 1) {
_nodes[1] = null;
_numNodes = 0;
return;
}
//Make sure the node is the last node in the queue
bool wasSwapped = false;
EventQueueNode formerLastNode = _nodes[_numNodes];
if (node.QueueIndex != _numNodes) {
//Swap the node with the last node
Swap(node, formerLastNode);
wasSwapped = true;
}
_numNodes--;
_nodes[node.QueueIndex] = null;
if (wasSwapped) {
//Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
OnNodeUpdated(formerLastNode);
}
}
public IEnumerator GetEnumerator() {
for (int i = 1; i <= _numNodes; i++)
yield return _nodes[i];
}
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;
}
}
}