Mirror of Svelto.ECS because we're a fan of it
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
11KB

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using Svelto.DataStructures;
  5. namespace Svelto.DataStructures
  6. {
  7. /// <summary>
  8. /// An implementation of a min-Priority Queue using a heap. Has O(1) .Contains()!
  9. /// See https://bitbucket.org/BlueRaja/high-speed-priority-queue-for-c/wiki/Getting%20Started for more information
  10. /// </summary>
  11. /// <typeparam name="T">The values in the queue. Must implement the PriorityQueueNode interface</typeparam>
  12. public sealed class HeapPriorityQueue<T> : IPriorityQueue<T>
  13. where T : PriorityQueueNode
  14. {
  15. private int _numNodes;
  16. private readonly FasterList<T> _nodes;
  17. private long _numNodesEverEnqueued;
  18. /// <summary>
  19. /// Instantiate a new Priority Queue
  20. /// </summary>
  21. /// <param name="maxNodes">The max nodes ever allowed to be enqueued (going over this will cause an exception)</param>
  22. public HeapPriorityQueue()
  23. {
  24. _numNodes = 0;
  25. _nodes = new FasterList<T>();
  26. _numNodesEverEnqueued = 0;
  27. }
  28. public HeapPriorityQueue(int initialSize)
  29. {
  30. _numNodes = 0;
  31. _nodes = new FasterList<T>(initialSize);
  32. _numNodesEverEnqueued = 0;
  33. }
  34. /// <summary>
  35. /// Returns the number of nodes in the queue. O(1)
  36. /// </summary>
  37. public int Count
  38. {
  39. get
  40. {
  41. return _numNodes;
  42. }
  43. }
  44. /// <summary>
  45. /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize),
  46. /// attempting to enqueue another item will throw an exception. O(1)
  47. /// </summary>
  48. public int MaxSize
  49. {
  50. get
  51. {
  52. return _nodes.Count - 1;
  53. }
  54. }
  55. /// <summary>
  56. /// Removes every node from the queue. O(n) (So, don't do this often!)
  57. /// </summary>
  58. #if NET_VERSION_4_5
  59. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  60. #endif
  61. public void Clear()
  62. {
  63. _nodes.Clear();
  64. _numNodes = 0;
  65. }
  66. /// <summary>
  67. /// Returns (in O(1)!) whether the given node is in the queue. O(1)
  68. /// </summary>
  69. #if NET_VERSION_4_5
  70. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  71. #endif
  72. public bool Contains(T node)
  73. {
  74. return (_nodes[node.QueueIndex] == node);
  75. }
  76. /// <summary>
  77. /// Enqueue a node - .Priority must be set beforehand! O(log n)
  78. /// </summary>
  79. #if NET_VERSION_4_5
  80. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  81. #endif
  82. public void Enqueue(T node, double priority)
  83. {
  84. node.Priority = priority;
  85. _numNodes++;
  86. if (_nodes.Count < _numNodes)
  87. _nodes.Resize(_numNodes + 1);
  88. _nodes[_numNodes] = node;
  89. node.QueueIndex = _numNodes;
  90. node.InsertionIndex = _numNodesEverEnqueued++;
  91. CascadeUp(_nodes[_numNodes]);
  92. }
  93. #if NET_VERSION_4_5
  94. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  95. #endif
  96. private void Swap(T node1, T node2)
  97. {
  98. //Swap the nodes
  99. _nodes[node1.QueueIndex] = node2;
  100. _nodes[node2.QueueIndex] = node1;
  101. //Swap their indicies
  102. int temp = node1.QueueIndex;
  103. node1.QueueIndex = node2.QueueIndex;
  104. node2.QueueIndex = temp;
  105. }
  106. //Performance appears to be slightly better when this is NOT inlined o_O
  107. private void CascadeUp(T node)
  108. {
  109. //aka Heapify-up
  110. int parent = node.QueueIndex / 2;
  111. while(parent >= 1)
  112. {
  113. T parentNode = _nodes[parent];
  114. if(HasHigherPriority(parentNode, node))
  115. break;
  116. //Node has lower priority value, so move it up the heap
  117. Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
  118. parent = node.QueueIndex / 2;
  119. }
  120. }
  121. #if NET_VERSION_4_5
  122. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  123. #endif
  124. private void CascadeDown(T node)
  125. {
  126. //aka Heapify-down
  127. T newParent;
  128. int finalQueueIndex = node.QueueIndex;
  129. while(true)
  130. {
  131. newParent = node;
  132. int childLeftIndex = 2 * finalQueueIndex;
  133. //Check if the left-child is higher-priority than the current node
  134. if(childLeftIndex > _numNodes)
  135. {
  136. //This could be placed outside the loop, but then we'd have to check newParent != node twice
  137. node.QueueIndex = finalQueueIndex;
  138. _nodes[finalQueueIndex] = node;
  139. break;
  140. }
  141. T childLeft = _nodes[childLeftIndex];
  142. if(HasHigherPriority(childLeft, newParent))
  143. {
  144. newParent = childLeft;
  145. }
  146. //Check if the right-child is higher-priority than either the current node or the left child
  147. int childRightIndex = childLeftIndex + 1;
  148. if(childRightIndex <= _numNodes)
  149. {
  150. T childRight = _nodes[childRightIndex];
  151. if(HasHigherPriority(childRight, newParent))
  152. {
  153. newParent = childRight;
  154. }
  155. }
  156. //If either of the children has higher (smaller) priority, swap and continue cascading
  157. if(newParent != node)
  158. {
  159. //Move new parent to its new index. node will be moved once, at the end
  160. //Doing it this way is one less assignment operation than calling Swap()
  161. _nodes[finalQueueIndex] = newParent;
  162. int temp = newParent.QueueIndex;
  163. newParent.QueueIndex = finalQueueIndex;
  164. finalQueueIndex = temp;
  165. }
  166. else
  167. {
  168. //See note above
  169. node.QueueIndex = finalQueueIndex;
  170. _nodes[finalQueueIndex] = node;
  171. break;
  172. }
  173. }
  174. }
  175. /// <summary>
  176. /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
  177. /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
  178. /// </summary>
  179. #if NET_VERSION_4_5
  180. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  181. #endif
  182. private bool HasHigherPriority(T higher, T lower)
  183. {
  184. return (higher.Priority < lower.Priority ||
  185. (higher.Priority == lower.Priority && higher.InsertionIndex < lower.InsertionIndex));
  186. }
  187. /// <summary>
  188. /// Removes the head of the queue (node with highest priority; ties are broken by order of insertion), and returns it. O(log n)
  189. /// </summary>
  190. public T Dequeue()
  191. {
  192. T returnMe = _nodes[1];
  193. Remove(returnMe);
  194. return returnMe;
  195. }
  196. /// <summary>
  197. /// Returns the head of the queue, without removing it (use Dequeue() for that). O(1)
  198. /// </summary>
  199. public T First
  200. {
  201. get
  202. {
  203. return _nodes[1];
  204. }
  205. }
  206. /// <summary>
  207. /// This method must be called on a node every time its priority changes while it is in the queue.
  208. /// <b>Forgetting to call this method will result in a corrupted queue!</b>
  209. /// O(log n)
  210. /// </summary>
  211. #if NET_VERSION_4_5
  212. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  213. #endif
  214. public void UpdatePriority(T node, double priority)
  215. {
  216. node.Priority = priority;
  217. OnNodeUpdated(node);
  218. }
  219. private void OnNodeUpdated(T node)
  220. {
  221. //Bubble the updated node up or down as appropriate
  222. int parentIndex = node.QueueIndex / 2;
  223. T parentNode = _nodes[parentIndex];
  224. if(parentIndex > 0 && HasHigherPriority(node, parentNode))
  225. {
  226. CascadeUp(node);
  227. }
  228. else
  229. {
  230. //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
  231. CascadeDown(node);
  232. }
  233. }
  234. /// <summary>
  235. /// Removes a node from the queue. Note that the node does not need to be the head of the queue. O(log n)
  236. /// </summary>
  237. public void Remove(T node)
  238. {
  239. if(_numNodes <= 1)
  240. {
  241. _nodes[1] = null;
  242. _numNodes = 0;
  243. return;
  244. }
  245. //Make sure the node is the last node in the queue
  246. bool wasSwapped = false;
  247. T formerLastNode = _nodes[_numNodes];
  248. if(node.QueueIndex != _numNodes)
  249. {
  250. //Swap the node with the last node
  251. Swap(node, formerLastNode);
  252. wasSwapped = true;
  253. }
  254. _numNodes--;
  255. _nodes[node.QueueIndex] = null;
  256. if(wasSwapped)
  257. {
  258. //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
  259. OnNodeUpdated(formerLastNode);
  260. }
  261. }
  262. public IEnumerator<T> GetEnumerator()
  263. {
  264. for(int i = 1; i <= _numNodes; i++)
  265. yield return _nodes[i];
  266. }
  267. IEnumerator IEnumerable.GetEnumerator()
  268. {
  269. return GetEnumerator();
  270. }
  271. /// <summary>
  272. /// <b>Should not be called in production code.</b>
  273. /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue.
  274. /// </summary>
  275. public bool IsValidQueue()
  276. {
  277. for(int i = 1; i < _nodes.Count; i++)
  278. {
  279. if(_nodes[i] != null)
  280. {
  281. int childLeftIndex = 2 * i;
  282. if(childLeftIndex < _nodes.Count && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
  283. return false;
  284. int childRightIndex = childLeftIndex + 1;
  285. if(childRightIndex < _nodes.Count && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
  286. return false;
  287. }
  288. }
  289. return true;
  290. }
  291. }
  292. }