using System.Collections;
using System.Collections.Generic;
namespace Svelto.DataStructures
{
///
/// An implementation of a min-Priority Queue using a heap. Has O(1) .Contains()!
/// See https://bitbucket.org/BlueRaja/high-speed-priority-queue-for-c/wiki/Getting%20Started for more information
///
/// The values in the queue. Must implement the PriorityQueueEntityView interface
public sealed class HeapPriorityQueue : IPriorityQueue
where T : PriorityQueueEntityView
{
private int _numEntityViews;
private readonly FasterList _entityViews;
private long _numEntityViewsEverEnqueued;
///
/// Instantiate a new Priority Queue
///
/// The max entityViews ever allowed to be enqueued (going over this will cause an exception)
public HeapPriorityQueue()
{
_numEntityViews = 0;
_entityViews = new FasterList();
_numEntityViewsEverEnqueued = 0;
}
public HeapPriorityQueue(int initialSize)
{
_numEntityViews = 0;
_entityViews = new FasterList(initialSize);
_numEntityViewsEverEnqueued = 0;
}
///
/// Returns the number of entityViews in the queue. O(1)
///
public int Count
{
get
{
return _numEntityViews;
}
}
///
/// 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 _entityViews.Count - 1;
}
}
///
/// Removes every entityView from the queue. O(n) (So, don't do this often!)
///
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public void Clear()
{
_entityViews.FastClear();
_numEntityViews = 0;
}
///
/// Returns (in O(1)!) whether the given entityView is in the queue. O(1)
///
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public bool Contains(T entityView)
{
return (_entityViews[entityView.QueueIndex] == entityView);
}
///
/// Enqueue a entityView - .Priority must be set beforehand! O(log n)
///
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public void Enqueue(T entityView, double priority)
{
entityView.Priority = priority;
_numEntityViews++;
if (_entityViews.Count < _numEntityViews)
_entityViews.Resize(_numEntityViews + 1);
_entityViews[_numEntityViews] = entityView;
entityView.QueueIndex = _numEntityViews;
entityView.InsertionIndex = _numEntityViewsEverEnqueued++;
CascadeUp(_entityViews[_numEntityViews]);
}
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private void Swap(T entityView1, T entityView2)
{
//Swap the entityViews
_entityViews[entityView1.QueueIndex] = entityView2;
_entityViews[entityView2.QueueIndex] = entityView1;
//Swap their indicies
int temp = entityView1.QueueIndex;
entityView1.QueueIndex = entityView2.QueueIndex;
entityView2.QueueIndex = temp;
}
//Performance appears to be slightly better when this is NOT inlined o_O
private void CascadeUp(T entityView)
{
//aka Heapify-up
int parent = entityView.QueueIndex / 2;
while(parent >= 1)
{
T parentEntityView = _entityViews[parent];
if(HasHigherPriority(parentEntityView, entityView))
break;
//EntityView has lower priority value, so move it up the heap
Swap(entityView, parentEntityView); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
parent = entityView.QueueIndex / 2;
}
}
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private void CascadeDown(T entityView)
{
//aka Heapify-down
T newParent;
int finalQueueIndex = entityView.QueueIndex;
while(true)
{
newParent = entityView;
int childLeftIndex = 2 * finalQueueIndex;
//Check if the left-child is higher-priority than the current entityView
if(childLeftIndex > _numEntityViews)
{
//This could be placed outside the loop, but then we'd have to check newParent != entityView twice
entityView.QueueIndex = finalQueueIndex;
_entityViews[finalQueueIndex] = entityView;
break;
}
T childLeft = _entityViews[childLeftIndex];
if(HasHigherPriority(childLeft, newParent))
{
newParent = childLeft;
}
//Check if the right-child is higher-priority than either the current entityView or the left child
int childRightIndex = childLeftIndex + 1;
if(childRightIndex <= _numEntityViews)
{
T childRight = _entityViews[childRightIndex];
if(HasHigherPriority(childRight, newParent))
{
newParent = childRight;
}
}
//If either of the children has higher (smaller) priority, swap and continue cascading
if(newParent != entityView)
{
//Move new parent to its new index. entityView will be moved once, at the end
//Doing it this way is one less assignment operation than calling Swap()
_entityViews[finalQueueIndex] = newParent;
int temp = newParent.QueueIndex;
newParent.QueueIndex = finalQueueIndex;
finalQueueIndex = temp;
}
else
{
//See note above
entityView.QueueIndex = finalQueueIndex;
_entityViews[finalQueueIndex] = entityView;
break;
}
}
}
///
/// Returns true if 'higher' has higher priority than 'lower', false otherwise.
/// Note that calling HasHigherPriority(entityView, entityView) (ie. both arguments the same entityView) will return false
///
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private bool HasHigherPriority(T higher, T lower)
{
return (higher.Priority < lower.Priority ||
(higher.Priority == lower.Priority && higher.InsertionIndex < lower.InsertionIndex));
}
///
/// Removes the head of the queue (entityView with highest priority; ties are broken by order of insertion), and returns it. O(log n)
///
public T Dequeue()
{
T returnMe = _entityViews[1];
Remove(returnMe);
return returnMe;
}
///
/// Returns the head of the queue, without removing it (use Dequeue() for that). O(1)
///
public T First
{
get
{
return _entityViews[1];
}
}
///
/// This method must be called on a entityView 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)
///
#if NET_VERSION_4_5
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public void UpdatePriority(T entityView, double priority)
{
entityView.Priority = priority;
OnEntityViewUpdated(entityView);
}
private void OnEntityViewUpdated(T entityView)
{
//Bubble the updated entityView up or down as appropriate
int parentIndex = entityView.QueueIndex / 2;
T parentEntityView = _entityViews[parentIndex];
if(parentIndex > 0 && HasHigherPriority(entityView, parentEntityView))
{
CascadeUp(entityView);
}
else
{
//Note that CascadeDown will be called if parentEntityView == entityView (that is, entityView is the root)
CascadeDown(entityView);
}
}
///
/// Removes a entityView from the queue. Note that the entityView does not need to be the head of the queue. O(log n)
///
public void Remove(T entityView)
{
if(_numEntityViews <= 1)
{
_entityViews[1] = null;
_numEntityViews = 0;
return;
}
//Make sure the entityView is the last entityView in the queue
bool wasSwapped = false;
T formerLastEntityView = _entityViews[_numEntityViews];
if(entityView.QueueIndex != _numEntityViews)
{
//Swap the entityView with the last entityView
Swap(entityView, formerLastEntityView);
wasSwapped = true;
}
_numEntityViews--;
_entityViews[entityView.QueueIndex] = null;
if(wasSwapped)
{
//Now bubble formerLastEntityView (which is no longer the last entityView) up or down as appropriate
OnEntityViewUpdated(formerLastEntityView);
}
}
public IEnumerator GetEnumerator()
{
for(int i = 1; i <= _numEntityViews; i++)
yield return _entityViews[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 < _entityViews.Count; i++)
{
if(_entityViews[i] != null)
{
int childLeftIndex = 2 * i;
if(childLeftIndex < _entityViews.Count && _entityViews[childLeftIndex] != null && HasHigherPriority(_entityViews[childLeftIndex], _entityViews[i]))
return false;
int childRightIndex = childLeftIndex + 1;
if(childRightIndex < _entityViews.Count && _entityViews[childRightIndex] != null && HasHigherPriority(_entityViews[childRightIndex], _entityViews[i]))
return false;
}
}
return true;
}
}
}