Skip to content

Commit f471d06

Browse files
Updated the FlowEventQueue to be easier to understand (#299)
1 parent 7066dbc commit f471d06

File tree

1 file changed

+139
-61
lines changed
  • opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/engine/engine

1 file changed

+139
-61
lines changed

opendc-simulator/opendc-simulator-flow/src/main/java/org/opendc/simulator/engine/engine/FlowEventQueue.java

Lines changed: 139 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
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

Comments
 (0)