2727
2828/**
2929 * A specialized priority queue for future event of {@link FlowNode}s sorted on time.
30+ * The queue is based on a min heap binary tree (https://www.digitalocean.com/community/tutorials/min-heap-binary-tree)
31+ * The nodes keep a timerIndex which indicates their placement in the tree.
32+ * The timerIndex is -1 when a node is not in the tree.
33+ *
3034 * <p>
3135 * By using a specialized priority queue, we reduce the overhead caused by the default priority queue implementation
3236 * being generic.
@@ -52,20 +56,24 @@ public FlowEventQueue(int initialCapacity) {
5256 }
5357
5458 /**
55- * Enqueue a timer for the specified context or update the existing timer.
59+ * Enqueue a timer for the specified node or update the existing timer.
60+ *
61+ * When Long.MAX_VALUE is given as a deadline, the node is removed from the queue
62+ * @param node node to queue
5663 */
5764 public void enqueue (FlowNode node ) {
58- FlowNode [] es = queue ;
59- int k = node .getTimerIndex ();
65+ // The timerIndex indicates whether a node is already in the queue
66+ int timerIndex = node .getTimerIndex ();
6067
6168 if (node .getDeadline () != Long .MAX_VALUE ) {
62- if (k >= 0 ) {
63- update (es , node , k );
69+ if (timerIndex >= 0 ) {
70+ update (this . queue , node , timerIndex );
6471 } else {
65- add (es , node );
72+ add (this . queue , node );
6673 }
67- } else if (k >= 0 ) {
68- delete (es , k );
74+ } else if (timerIndex >= 0 ) {
75+ delete (this .queue , timerIndex );
76+ node .setTimerIndex (-1 );
6977 }
7078 }
7179
@@ -80,22 +88,23 @@ public FlowNode poll(long now) {
8088 return null ;
8189 }
8290
83- final FlowNode [] es = queue ;
84- final FlowNode head = es [0 ];
91+ final FlowNode head = this .queue [0 ];
8592
8693 if (now < head .getDeadline ()) {
8794 return null ;
8895 }
8996
90- int n = size - 1 ;
91- this .size = n ;
92- final FlowNode next = es [ n ];
93- es [ n ] = null ; // Clear the last element of the queue
97+ // Move the last element of the queue to the front
98+ this .size -- ;
99+ final FlowNode next = this . queue [ this . size ];
100+ this . queue [ this . size ] = null ; // Clear the last element of the queue
94101
95- if (n > 0 ) {
96- siftDown (0 , next , es , n );
102+ // Sift down the new head.
103+ if (this .size > 0 ) {
104+ siftDown (0 , next , this .queue , this .size );
97105 }
98106
107+ // Set the index of the head to -1 indicating it is not scheduled anymore
99108 head .setTimerIndex (-1 );
100109 return head ;
101110 }
@@ -115,46 +124,58 @@ public long peekDeadline() {
115124 * Add a new entry to the queue.
116125 */
117126 private void add (FlowNode [] es , FlowNode node ) {
118- if (this .size >= es .length ) {
127+ if (this .size >= this . queue .length ) {
119128 // Re-fetch the resized array
120- es = grow ();
129+ this . grow ();
121130 }
122131
123- siftUp (this .size , node , es );
132+ siftUp (this .size , node , this . queue );
124133
125134 this .size ++;
126135 }
127136
128137 /**
129138 * Update the deadline of an existing entry in the queue.
130139 */
131- private void update (FlowNode [] es , FlowNode node , int k ) {
132- if (k > 0 ) {
133- int parent = (k - 1 ) >>> 1 ;
134- if (es [ parent ].getDeadline () > node .getDeadline ()) {
135- siftUp (k , node , es );
140+ private void update (FlowNode [] eventList , FlowNode node , int timerIndex ) {
141+ if (timerIndex > 0 ) {
142+ int parentIndex = (timerIndex - 1 ) >>> 1 ;
143+ if (eventList [ parentIndex ].getDeadline () > node .getDeadline ()) {
144+ siftUp (timerIndex , node , eventList );
136145 return ;
137146 }
138147 }
139148
140- siftDown (k , node , es , this .size );
149+ siftDown (timerIndex , node , eventList , this .size );
141150 }
142151
143152 /**
144- * Deadline an entry from the queue.
153+ * The move a node from the queue
154+ *
155+ * @param eventList all scheduled events
156+ * @param timerIndex the index of the node to remove
145157 */
146- private void delete (FlowNode [] es , int k ) {
147- int s = --this .size ;
148- if (s == k ) {
149- es [k ] = null ; // Element is last in the queue
150- } else {
151- FlowNode moved = es [s ];
152- es [s ] = null ;
153-
154- siftDown (k , moved , es , s );
155-
156- if (es [k ] == moved ) {
157- siftUp (k , moved , es );
158+ private void delete (FlowNode [] eventList , int timerIndex ) {
159+ this .size --;
160+
161+ // If the element is the last element, simply remove it
162+ if (timerIndex == this .size ) {
163+ eventList [timerIndex ] = null ;
164+ }
165+
166+ // Else, swap the node to remove with the last node and sift it up or down to get the moved node in the correct
167+ // position.
168+ else {
169+
170+ // swap the node with the last element
171+ FlowNode moved = eventList [this .size ];
172+ eventList [this .size ] = null ;
173+
174+ siftDown (timerIndex , moved , eventList , this .size );
175+
176+ // SiftUp, if siftDown did not move the node
177+ if (eventList [timerIndex ] == moved ) {
178+ siftUp (timerIndex , moved , eventList );
158179 }
159180 }
160181 }
@@ -172,35 +193,92 @@ private FlowNode[] grow() {
172193 return queue ;
173194 }
174195
175- private static void siftUp (int k , FlowNode key , FlowNode [] es ) {
176- while (k > 0 ) {
177- int parent = (k - 1 ) >>> 1 ;
178- FlowNode e = es [parent ];
179- if (key .getDeadline () >= e .getDeadline ()) break ;
180- es [k ] = e ;
181- e .setTimerIndex (k );
182- k = parent ;
196+ /**
197+ * Iteratively swap the node at the given timerIndex with its parents until the node is at the correct
198+ * position in the binary tree (ie, both children of the node are bigger than the node itself).
199+ *
200+ * @param timerIndex the index of the node that needs to be sifted up
201+ * @param node the node that needs to be sifted up
202+ * @param eventList the list of nodes that are scheduled
203+ */
204+ private static void siftUp (int timerIndex , FlowNode node , FlowNode [] eventList ) {
205+ while (timerIndex > 0 ) {
206+ // Find parent Node
207+ int parentIndex = (timerIndex - 1 ) >>> 1 ;
208+ FlowNode parentNode = eventList [parentIndex ];
209+
210+ // Break if the deadline of the node is bigger than the parent node
211+ if (node .getDeadline () >= parentNode .getDeadline ()) break ;
212+
213+ // Otherwise, swap node with the parentNode
214+ eventList [timerIndex ] = parentNode ;
215+ parentNode .setTimerIndex (timerIndex );
216+ timerIndex = parentIndex ;
183217 }
184- es [k ] = key ;
185- key .setTimerIndex (k );
218+
219+ eventList [timerIndex ] = node ;
220+ node .setTimerIndex (timerIndex );
186221 }
187222
188- private static void siftDown (int k , FlowNode key , FlowNode [] es , int n ) {
189- int half = n >>> 1 ; // loop while a non-leaf
190- while (k < half ) {
191- int child = (k << 1 ) + 1 ; // assume left child is least
192- FlowNode c = es [child ];
193- int right = child + 1 ;
194- if (right < n && c .getDeadline () > es [right ].getDeadline ()) c = es [child = right ];
223+ /**
224+ * Iteratively swap the node at the given timerIndex with its smallest child until the node is at the correct
225+ * position in the binary tree (ie, both children of the node are bigger than the node itself).
226+ *
227+ * @param timerIndex the index of the node that needs to be sifted down
228+ * @param node the node that needs to be sifted down
229+ * @param eventList the list of nodes that are scheduled
230+ * @param queueSize the current size of the queue
231+ */
232+ private static void siftDown (int timerIndex , FlowNode node , FlowNode [] eventList , int queueSize ) {
233+ int half = queueSize >>> 1 ; // loop while a non-leaf
234+ while (timerIndex < half ) {
235+
236+ // Get the index of the smallest child
237+ int smallestChildIndex = getSmallestChildIndex (timerIndex , eventList , queueSize );
195238
196- if (key .getDeadline () <= c .getDeadline ()) break ;
239+ // Get the smallest child
240+ FlowNode smallestChildNode = eventList [smallestChildIndex ];
197241
198- es [k ] = c ;
199- c .setTimerIndex (k );
200- k = child ;
242+ // If the node is smaller than the smallest child, break
243+ if (node .getDeadline () <= smallestChildNode .getDeadline ()) break ;
244+
245+ // Otherwise, swap the node with its smallest child
246+ eventList [timerIndex ] = smallestChildNode ;
247+ smallestChildNode .setTimerIndex (timerIndex );
248+ timerIndex = smallestChildIndex ;
201249 }
202250
203- es [k ] = key ;
204- key .setTimerIndex (k );
251+ eventList [timerIndex ] = node ;
252+ node .setTimerIndex (timerIndex );
253+ }
254+
255+ /**
256+ * Return the index of the child with the smallest deadline time of the node at the given timerIndex
257+ *
258+ * @param timerIndex the index of the parent node
259+ * @param eventList the list of all scheduled events
260+ * @param queueSize the current size of the queue
261+ * @return the timerIndex of the smallest child
262+ */
263+ private static int getSmallestChildIndex (int timerIndex , FlowNode [] eventList , int queueSize ) {
264+ // Calculate the index of the left child
265+ int leftChildIndex = (timerIndex << 1 ) + 1 ;
266+
267+ // If the left child is at the end of the queue, there is no right child.
268+ // Thus, the left child is always the smallest
269+ if (leftChildIndex + 1 >= queueSize ) return leftChildIndex ;
270+
271+ FlowNode leftChildNode = eventList [leftChildIndex ];
272+
273+ // Get right child
274+ int rightChildIndex = leftChildIndex + 1 ;
275+ FlowNode rightChildNode = eventList [rightChildIndex ];
276+
277+ // If the rightChild is smaller, return its index
278+ // otherwise, return the index of the left child
279+ if (rightChildNode .getDeadline () < leftChildNode .getDeadline ()) {
280+ return rightChildIndex ;
281+ }
282+ return leftChildIndex ;
205283 }
206284}
0 commit comments