1 #include "../common.h"
2
3 #include "../php/iterators/php_priority_queue_iterator.h"
4 #include "../php/handlers/php_priority_queue_handlers.h"
5 #include "../php/classes/php_priority_queue_ce.h"
6
7 #include "ds_priority_queue.h"
8
9 #define LEFT(x) (((x) * 2) + 1)
10 #define RIGHT(x) (((x) * 2) + 2)
11 #define PARENT(x) (((x) - 1) / 2)
12
13 // Insertion stamp, for equal priority comparison fallback.
14 #define STAMP(n) (Z_NEXT((n)->value))
15
ds_priority_queue_get_capacity_for_size(uint32_t size)16 static inline uint32_t ds_priority_queue_get_capacity_for_size(uint32_t size)
17 {
18 return ds_next_power_of_2(size, DS_PRIORITY_QUEUE_MIN_CAPACITY);
19 }
20
21 // Priority comparison, with insertion stamp fallback.
ds_priority_queue_node_compare(ds_priority_queue_node_t * a,ds_priority_queue_node_t * b)22 static inline int ds_priority_queue_node_compare(
23 ds_priority_queue_node_t *a,
24 ds_priority_queue_node_t *b
25 ) {
26 zval retval;
27
28 if (compare_function(&retval, &a->priority, &b->priority) == SUCCESS) {
29 int result = (int) zval_get_long(&retval);
30
31 // If the priorities are equal, use the insertion stamp as a tie-break.
32 if (result == 0) {
33 return (STAMP(a) < STAMP(b) ? 1 : -1);
34 }
35
36 return result;
37
38 } else {
39 // Not sure what to do when the compare function fails.
40 return 0;
41 }
42 }
43
reallocate_nodes(ds_priority_queue_node_t * nodes,uint32_t capacity)44 static inline ds_priority_queue_node_t *reallocate_nodes(ds_priority_queue_node_t *nodes, uint32_t capacity)
45 {
46 return erealloc(nodes, capacity * sizeof(ds_priority_queue_node_t));
47 }
48
allocate_nodes(uint32_t capacity)49 static inline ds_priority_queue_node_t *allocate_nodes(uint32_t capacity)
50 {
51 return ecalloc(capacity, sizeof(ds_priority_queue_node_t));
52 }
53
reallocate_to_capacity(ds_priority_queue_t * queue,uint32_t capacity)54 static inline void reallocate_to_capacity(ds_priority_queue_t *queue, uint32_t capacity)
55 {
56 queue->nodes = reallocate_nodes(queue->nodes, capacity);
57 queue->capacity = capacity;
58 }
59
increase_capacity(ds_priority_queue_t * queue)60 static inline void increase_capacity(ds_priority_queue_t *queue)
61 {
62 reallocate_to_capacity(queue, queue->capacity * 2);
63 }
64
ds_priority_queue_allocate(ds_priority_queue_t * queue,uint32_t capacity)65 void ds_priority_queue_allocate(ds_priority_queue_t *queue, uint32_t capacity)
66 {
67 capacity = ds_priority_queue_get_capacity_for_size(capacity);
68
69 if (capacity > queue->capacity) {
70 reallocate_to_capacity(queue, capacity);
71 }
72 }
73
ds_priority_queue()74 ds_priority_queue_t *ds_priority_queue()
75 {
76 ds_priority_queue_t *queue = ecalloc(1, sizeof(ds_priority_queue_t));
77
78 queue->nodes = allocate_nodes(DS_PRIORITY_QUEUE_MIN_CAPACITY);
79 queue->capacity = DS_PRIORITY_QUEUE_MIN_CAPACITY;
80 queue->size = 0;
81 queue->next = 0;
82
83 return queue;
84 }
85
ds_priority_queue_capacity(ds_priority_queue_t * queue)86 uint32_t ds_priority_queue_capacity(ds_priority_queue_t *queue)
87 {
88 return queue->capacity;
89 }
90
ds_priority_queue_push(ds_priority_queue_t * queue,zval * value,zval * priority)91 void ds_priority_queue_push(ds_priority_queue_t *queue, zval *value, zval *priority)
92 {
93 zval comparison;
94
95 uint32_t parent;
96 uint32_t index;
97
98 ds_priority_queue_node_t *nodes;
99 ds_priority_queue_node_t *node;
100
101 if (queue->size == queue->capacity) {
102 increase_capacity(queue);
103 }
104
105 nodes = queue->nodes;
106
107 for (index = queue->size; index > 0; index = parent) {
108
109 // Move up the heap
110 parent = PARENT(index);
111
112 // If the parent node's priority is less than or equal to the inserted,
113 // the heap is valid and we can break.
114 if (compare_function(&comparison, priority, &nodes[parent].priority) == SUCCESS) {
115 if (((int) zval_get_long(&comparison)) < 1) {
116 break;
117 }
118 } else {
119 // Not sure what to do if the compare function fails.
120 return;
121 }
122
123 nodes[index] = nodes[parent];
124 }
125
126 node = &queue->nodes[index];
127
128 // Initialize the new node
129 STAMP(node) = ++queue->next;
130
131 ZVAL_COPY(&node->value, value);
132 ZVAL_COPY(&node->priority, priority);
133
134 queue->size++;
135 }
136
ds_priority_queue_compact(ds_priority_queue_t * queue)137 static inline void ds_priority_queue_compact(ds_priority_queue_t *queue)
138 {
139 if (queue->size <= (queue->capacity / 4) && (queue->capacity / 2) >= DS_PRIORITY_QUEUE_MIN_CAPACITY) {
140 reallocate_to_capacity(queue, queue->capacity / 2);
141 }
142 }
143
ds_priority_queue_pop(ds_priority_queue_t * queue,zval * return_value)144 void ds_priority_queue_pop(ds_priority_queue_t *queue, zval *return_value)
145 {
146 uint32_t index;
147 uint32_t swap;
148
149 ds_priority_queue_node_t bottom;
150 ds_priority_queue_node_t *nodes = queue->nodes;
151
152 const uint32_t size = queue->size;
153 const uint32_t half = (size - 1) / 2;
154
155 // Guard against pop when the queue is empty.
156 if (size == 0) {
157 NOT_ALLOWED_WHEN_EMPTY();
158 ZVAL_NULL(return_value);
159 return;
160 }
161
162 // Return the root if a return value was requested.
163 if (return_value) {
164 ZVAL_COPY(return_value, &(nodes[0].value));
165 }
166
167 // Grab the last node in the queue, which should have the lowest priority.
168 bottom = nodes[size - 1];
169
170 // Destruct the root, because we're removing it from the queue.
171 DTOR_AND_UNDEF(&(nodes[0].value));
172 DTOR_AND_UNDEF(&(nodes[0].priority));
173
174 queue->size--;
175
176 for (index = 0; index < half; index = swap) {
177 swap = LEFT(index);
178
179 // If the right leaf is smaller than the left, swap right instead.
180 if (swap < queue->size && ds_priority_queue_node_compare(&nodes[swap], &nodes[swap + 1]) < 0) {
181 swap++;
182 }
183
184 // Heap constraints are preserved when the
185 if (ds_priority_queue_node_compare(&nodes[swap], &bottom) < 0) {
186 break;
187 }
188
189 nodes[index] = nodes[swap];
190 }
191
192 nodes[index] = bottom;
193
194 // Reduce the size of the buffer if the size has dropped below a threshold.
195 ds_priority_queue_compact(queue);
196 }
197
copy_nodes(ds_priority_queue_t * queue)198 static ds_priority_queue_node_t *copy_nodes(ds_priority_queue_t *queue)
199 {
200 ds_priority_queue_node_t *copies = allocate_nodes(queue->capacity);
201
202 ds_priority_queue_node_t *src = queue->nodes;
203 ds_priority_queue_node_t *end = queue->nodes + queue->size;
204 ds_priority_queue_node_t *dst = copies;
205
206 for (; src < end; ++src, ++dst) {
207 ZVAL_COPY(&dst->value, &src->value); // This also copies insertion stamp.
208 ZVAL_COPY(&dst->priority, &src->priority);
209 }
210
211 return copies;
212 }
213
ds_priority_queue_clone(ds_priority_queue_t * queue)214 ds_priority_queue_t *ds_priority_queue_clone(ds_priority_queue_t * queue)
215 {
216 ds_priority_queue_t *clone = ecalloc(1, sizeof(ds_priority_queue_t));
217
218 clone->nodes = copy_nodes(queue);
219 clone->capacity = queue->capacity;
220 clone->size = queue->size;
221 clone->next = queue->next;
222
223 return clone;
224 }
225
ds_priority_queue_peek(ds_priority_queue_t * queue)226 zval *ds_priority_queue_peek(ds_priority_queue_t *queue)
227 {
228 if (queue->size == 0) {
229 NOT_ALLOWED_WHEN_EMPTY();
230 return NULL;
231 }
232
233 return &queue->nodes[0].value;
234 }
235
priority_sort(const void * a,const void * b)236 static int priority_sort(const void *a, const void *b)
237 {
238 return ds_priority_queue_node_compare(
239 (ds_priority_queue_node_t *) b,
240 (ds_priority_queue_node_t *) a
241 );
242 }
243
ds_priority_queue_create_sorted_buffer(ds_priority_queue_t * queue)244 ds_priority_queue_node_t* ds_priority_queue_create_sorted_buffer(ds_priority_queue_t *queue)
245 {
246 ds_priority_queue_node_t *buffer = allocate_nodes(queue->size);
247
248 memcpy(buffer, queue->nodes, queue->size * sizeof(ds_priority_queue_node_t));
249 qsort(buffer, queue->size, sizeof(ds_priority_queue_node_t), priority_sort);
250
251 return buffer;
252 }
253
ds_priority_queue_to_array(ds_priority_queue_t * queue,zval * array)254 void ds_priority_queue_to_array(ds_priority_queue_t *queue, zval *array)
255 {
256 if (DS_PRIORITY_QUEUE_IS_EMPTY(queue)) {
257 array_init(array);
258
259 } else {
260 ds_priority_queue_node_t *pos, *end, *buf;
261
262 buf = ds_priority_queue_create_sorted_buffer(queue);
263 pos = buf;
264 end = buf + queue->size;
265
266 array_init_size(array, queue->size);
267
268 for (; pos < end; ++pos) {
269 add_next_index_zval(array, &pos->value);
270 Z_TRY_ADDREF_P(&pos->value);
271 }
272
273 efree(buf);
274 }
275 }
276
ds_priority_queue_clear(ds_priority_queue_t * queue)277 void ds_priority_queue_clear(ds_priority_queue_t *queue)
278 {
279 ds_priority_queue_node_t *pos = queue->nodes;
280 ds_priority_queue_node_t *end = queue->nodes + queue->size;
281
282 for (; pos < end; ++pos) {
283 DTOR_AND_UNDEF(&pos->value);
284 DTOR_AND_UNDEF(&pos->priority);
285 }
286
287 queue->size = 0;
288
289 reallocate_to_capacity(queue, DS_PRIORITY_QUEUE_MIN_CAPACITY);
290 }
291
ds_priority_queue_free(ds_priority_queue_t * queue)292 void ds_priority_queue_free(ds_priority_queue_t *queue)
293 {
294 ds_priority_queue_clear(queue);
295 efree(queue->nodes);
296 efree(queue);
297 }
298