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.

325 lines
11KB

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