1 /* GStreamer
2 * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com>
3 * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
4 *
5 * gstqueuearray.c:
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 /**
24 * SECTION:gstqueuearray
25 * @title: GstQueueArray
26 * @short_description: Array based queue object
27 *
28 * #GstQueueArray is an object that provides standard queue functionality
29 * based on an array instead of linked lists. This reduces the overhead
30 * caused by memory management by a large factor.
31 */
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35
36 #include <string.h>
37 #include <gst/gst.h>
38 #include "gstqueuearray.h"
39
40 struct _GstQueueArray
41 {
42 /* < private > */
43 guint8 *array;
44 guint size;
45 guint head;
46 guint tail;
47 guint length;
48 guint elt_size;
49 gboolean struct_array;
50 GDestroyNotify clear_func;
51 };
52
53 /**
54 * gst_queue_array_new_for_struct: (skip)
55 * @struct_size: Size of each element (e.g. structure) in the array
56 * @initial_size: Initial size of the new queue
57 *
58 * Allocates a new #GstQueueArray object for elements (e.g. structures)
59 * of size @struct_size, with an initial queue size of @initial_size.
60 *
61 * Returns: a new #GstQueueArray object
62 *
63 * Since: 1.6
64 */
65 GstQueueArray *
gst_queue_array_new_for_struct(gsize struct_size,guint initial_size)66 gst_queue_array_new_for_struct (gsize struct_size, guint initial_size)
67 {
68 GstQueueArray *array;
69
70 g_return_val_if_fail (struct_size > 0, NULL);
71
72 array = g_slice_new (GstQueueArray);
73 array->elt_size = struct_size;
74 array->size = initial_size;
75 array->array = g_malloc0 (struct_size * initial_size);
76 array->head = 0;
77 array->tail = 0;
78 array->length = 0;
79 array->struct_array = TRUE;
80 array->clear_func = NULL;
81 return array;
82 }
83
84 /**
85 * gst_queue_array_new: (skip)
86 * @initial_size: Initial size of the new queue
87 *
88 * Allocates a new #GstQueueArray object with an initial
89 * queue size of @initial_size.
90 *
91 * Returns: a new #GstQueueArray object
92 *
93 * Since: 1.2
94 */
95 GstQueueArray *
gst_queue_array_new(guint initial_size)96 gst_queue_array_new (guint initial_size)
97 {
98 GstQueueArray *array;
99
100 array = gst_queue_array_new_for_struct (sizeof (gpointer), initial_size);
101 array->struct_array = FALSE;
102 return array;
103 }
104
105 /**
106 * gst_queue_array_free: (skip)
107 * @array: a #GstQueueArray object
108 *
109 * Frees queue @array and all memory associated to it.
110 *
111 * Since: 1.2
112 */
113 void
gst_queue_array_free(GstQueueArray * array)114 gst_queue_array_free (GstQueueArray * array)
115 {
116 g_return_if_fail (array != NULL);
117 gst_queue_array_clear (array);
118 g_free (array->array);
119 g_slice_free (GstQueueArray, array);
120 }
121
122 /**
123 * gst_queue_array_set_clear_func: (skip)
124 * @array: a #GstQueueArray object
125 * @clear_func: a function to clear an element of @array
126 *
127 * Sets a function to clear an element of @array.
128 *
129 * The @clear_func will be called when an element in the array
130 * data segment is removed and when the array is freed and data
131 * segment is deallocated as well. @clear_func will be passed a
132 * pointer to the element to clear, rather than the element itself.
133 *
134 * Note that in contrast with other uses of #GDestroyNotify
135 * functions, @clear_func is expected to clear the contents of
136 * the array element it is given, but not free the element itself.
137 *
138 * Since: 1.16
139 */
140 void
gst_queue_array_set_clear_func(GstQueueArray * array,GDestroyNotify clear_func)141 gst_queue_array_set_clear_func (GstQueueArray * array,
142 GDestroyNotify clear_func)
143 {
144 g_return_if_fail (array != NULL);
145 array->clear_func = clear_func;
146 }
147
148 static void
gst_queue_array_clear_idx(GstQueueArray * array,guint idx)149 gst_queue_array_clear_idx (GstQueueArray * array, guint idx)
150 {
151 guint pos;
152
153 if (!array->clear_func)
154 return;
155
156 pos = (idx + array->head) % array->size;
157 if (array->struct_array)
158 array->clear_func (array->array + pos * array->elt_size);
159 else
160 array->clear_func (*(gpointer *) (array->array + pos * array->elt_size));
161 }
162
163 /**
164 * gst_queue_array_clear: (skip)
165 * @array: a #GstQueueArray object
166 *
167 * Clears queue @array and frees all memory associated to it.
168 *
169 * Since: 1.16
170 */
171 void
gst_queue_array_clear(GstQueueArray * array)172 gst_queue_array_clear (GstQueueArray * array)
173 {
174 g_return_if_fail (array != NULL);
175
176 if (array->clear_func != NULL) {
177 guint i;
178
179 for (i = 0; i < array->length; i++) {
180 gst_queue_array_clear_idx (array, i);
181 }
182 }
183
184 array->head = 0;
185 array->tail = 0;
186 array->length = 0;
187 }
188
189 /**
190 * gst_queue_array_pop_head_struct: (skip)
191 * @array: a #GstQueueArray object
192 *
193 * Returns the head of the queue @array and removes it from the queue.
194 *
195 * Returns: pointer to element or struct, or NULL if @array was empty. The
196 * data pointed to by the returned pointer stays valid only as long as
197 * the queue array is not modified further!
198 *
199 * Since: 1.6
200 */
201 gpointer
gst_queue_array_pop_head_struct(GstQueueArray * array)202 gst_queue_array_pop_head_struct (GstQueueArray * array)
203 {
204 gpointer p_struct;
205 g_return_val_if_fail (array != NULL, NULL);
206 /* empty array */
207 if (G_UNLIKELY (array->length == 0))
208 return NULL;
209
210 p_struct = array->array + (array->elt_size * array->head);
211
212 array->head++;
213 array->head %= array->size;
214 array->length--;
215
216 return p_struct;
217 }
218
219 /**
220 * gst_queue_array_pop_head: (skip)
221 * @array: a #GstQueueArray object
222 *
223 * Returns and head of the queue @array and removes
224 * it from the queue.
225 *
226 * Returns: The head of the queue
227 *
228 * Since: 1.2
229 */
230 gpointer
gst_queue_array_pop_head(GstQueueArray * array)231 gst_queue_array_pop_head (GstQueueArray * array)
232 {
233 gpointer ret;
234 g_return_val_if_fail (array != NULL, NULL);
235
236 /* empty array */
237 if (G_UNLIKELY (array->length == 0))
238 return NULL;
239
240 ret = *(gpointer *) (array->array + (sizeof (gpointer) * array->head));
241 array->head++;
242 array->head %= array->size;
243 array->length--;
244 return ret;
245 }
246
247 /**
248 * gst_queue_array_peek_head_struct: (skip)
249 * @array: a #GstQueueArray object
250 *
251 * Returns the head of the queue @array without removing it from the queue.
252 *
253 * Returns: pointer to element or struct, or NULL if @array was empty. The
254 * data pointed to by the returned pointer stays valid only as long as
255 * the queue array is not modified further!
256 *
257 * Since: 1.6
258 */
259 gpointer
gst_queue_array_peek_head_struct(GstQueueArray * array)260 gst_queue_array_peek_head_struct (GstQueueArray * array)
261 {
262 g_return_val_if_fail (array != NULL, NULL);
263 /* empty array */
264 if (G_UNLIKELY (array->length == 0))
265 return NULL;
266
267 return array->array + (array->elt_size * array->head);
268 }
269
270 /**
271 * gst_queue_array_peek_head: (skip)
272 * @array: a #GstQueueArray object
273 *
274 * Returns the head of the queue @array and does not
275 * remove it from the queue.
276 *
277 * Returns: The head of the queue
278 *
279 * Since: 1.2
280 */
281 gpointer
gst_queue_array_peek_head(GstQueueArray * array)282 gst_queue_array_peek_head (GstQueueArray * array)
283 {
284 g_return_val_if_fail (array != NULL, NULL);
285 /* empty array */
286 if (G_UNLIKELY (array->length == 0))
287 return NULL;
288
289 return *(gpointer *) (array->array + (sizeof (gpointer) * array->head));
290 }
291
292 /**
293 * gst_queue_array_peek_nth: (skip)
294 *
295 * Returns the item at @idx in @array, but does not remove it from the queue.
296 *
297 * Returns: The item, or %NULL if @idx was out of bounds
298 *
299 * Since: 1.16
300 */
301 gpointer
gst_queue_array_peek_nth(GstQueueArray * array,guint idx)302 gst_queue_array_peek_nth (GstQueueArray * array, guint idx)
303 {
304 g_return_val_if_fail (array != NULL, NULL);
305 g_return_val_if_fail (idx < array->length, NULL);
306
307 idx = (array->head + idx) % array->size;
308
309 return *(gpointer *) (array->array + (sizeof (gpointer) * idx));
310 }
311
312 /**
313 * gst_queue_array_peek_nth_struct: (skip)
314 *
315 * Returns the item at @idx in @array, but does not remove it from the queue.
316 *
317 * Returns: The item, or %NULL if @idx was out of bounds
318 *
319 * Since: 1.16
320 */
321 gpointer
gst_queue_array_peek_nth_struct(GstQueueArray * array,guint idx)322 gst_queue_array_peek_nth_struct (GstQueueArray * array, guint idx)
323 {
324 g_return_val_if_fail (array != NULL, NULL);
325 g_return_val_if_fail (idx < array->length, NULL);
326
327 idx = (array->head + idx) % array->size;
328
329 return array->array + (array->elt_size * idx);
330 }
331
332 static void
gst_queue_array_do_expand(GstQueueArray * array)333 gst_queue_array_do_expand (GstQueueArray * array)
334 {
335 gsize elt_size = array->elt_size;
336 /* newsize is 50% bigger */
337 gsize oldsize = array->size;
338 guint64 newsize;
339
340 newsize = MAX ((3 * (guint64) oldsize) / 2, (guint64) oldsize + 1);
341 if (newsize > G_MAXUINT)
342 g_error ("growing the queue array would overflow");
343
344 /* copy over data */
345 if (array->tail != 0) {
346 guint8 *array2 = NULL;
347 gsize t1 = 0;
348 gsize t2 = 0;
349
350 array2 = g_malloc0_n (newsize, elt_size);
351 t1 = array->head;
352 t2 = oldsize - array->head;
353
354 /* [0-----TAIL][HEAD------SIZE]
355 *
356 * We want to end up with
357 * [HEAD------------------TAIL][----FREEDATA------NEWSIZE]
358 *
359 * 1) move [HEAD-----SIZE] part to beginning of new array
360 * 2) move [0-------TAIL] part new array, after previous part
361 */
362
363 memcpy (array2, array->array + (elt_size * (gsize) array->head),
364 t2 * elt_size);
365 memcpy (array2 + t2 * elt_size, array->array, t1 * elt_size);
366
367 g_free (array->array);
368 array->array = array2;
369 array->head = 0;
370 } else {
371 /* Fast path, we just need to grow the array */
372 array->array = g_realloc_n (array->array, newsize, elt_size);
373 memset (array->array + elt_size * oldsize, 0,
374 elt_size * (newsize - oldsize));
375 }
376 array->tail = oldsize;
377 array->size = newsize;
378 }
379
380 /**
381 * gst_queue_array_push_element_tail: (skip)
382 * @array: a #GstQueueArray object
383 * @p_struct: address of element or structure to push to the tail of the queue
384 *
385 * Pushes the element at address @p_struct to the tail of the queue @array
386 * (Copies the contents of a structure of the struct_size specified when
387 * creating the queue into the array).
388 *
389 * Since: 1.6
390 */
391 void
gst_queue_array_push_tail_struct(GstQueueArray * array,gpointer p_struct)392 gst_queue_array_push_tail_struct (GstQueueArray * array, gpointer p_struct)
393 {
394 guint elt_size;
395
396 g_return_if_fail (p_struct != NULL);
397 g_return_if_fail (array != NULL);
398 elt_size = array->elt_size;
399
400 /* Check if we need to make room */
401 if (G_UNLIKELY (array->length == array->size))
402 gst_queue_array_do_expand (array);
403
404 memcpy (array->array + elt_size * array->tail, p_struct, elt_size);
405 array->tail++;
406 array->tail %= array->size;
407 array->length++;
408 }
409
410 /**
411 * gst_queue_array_push_tail: (skip)
412 * @array: a #GstQueueArray object
413 * @data: object to push
414 *
415 * Pushes @data to the tail of the queue @array.
416 *
417 * Since: 1.2
418 */
419 void
gst_queue_array_push_tail(GstQueueArray * array,gpointer data)420 gst_queue_array_push_tail (GstQueueArray * array, gpointer data)
421 {
422 g_return_if_fail (array != NULL);
423
424 /* Check if we need to make room */
425 if (G_UNLIKELY (array->length == array->size))
426 gst_queue_array_do_expand (array);
427
428 *(gpointer *) (array->array + sizeof (gpointer) * array->tail) = data;
429 array->tail++;
430 array->tail %= array->size;
431 array->length++;
432 }
433
434 /**
435 * gst_queue_array_peek_tail: (skip)
436 * @array: a #GstQueueArray object
437 *
438 * Returns the tail of the queue @array, but does not remove it from the queue.
439 *
440 * Returns: The tail of the queue
441 *
442 * Since: 1.14
443 */
444 gpointer
gst_queue_array_peek_tail(GstQueueArray * array)445 gst_queue_array_peek_tail (GstQueueArray * array)
446 {
447 guint len, idx;
448
449 g_return_val_if_fail (array != NULL, NULL);
450
451 len = array->length;
452
453 /* empty array */
454 if (len == 0)
455 return NULL;
456
457 idx = (array->head + (len - 1)) % array->size;
458
459 return *(gpointer *) (array->array + (sizeof (gpointer) * idx));
460 }
461
462 /**
463 * gst_queue_array_peek_tail_struct: (skip)
464 * @array: a #GstQueueArray object
465 *
466 * Returns the tail of the queue @array, but does not remove it from the queue.
467 *
468 * Returns: The tail of the queue
469 *
470 * Since: 1.14
471 */
472 gpointer
gst_queue_array_peek_tail_struct(GstQueueArray * array)473 gst_queue_array_peek_tail_struct (GstQueueArray * array)
474 {
475 guint len, idx;
476
477 g_return_val_if_fail (array != NULL, NULL);
478
479 len = array->length;
480
481 /* empty array */
482 if (len == 0)
483 return NULL;
484
485 idx = (array->head + (len - 1)) % array->size;
486
487 return array->array + (array->elt_size * idx);
488 }
489
490 /**
491 * gst_queue_array_pop_tail: (skip)
492 * @array: a #GstQueueArray object
493 *
494 * Returns the tail of the queue @array and removes
495 * it from the queue.
496 *
497 * Returns: The tail of the queue
498 *
499 * Since: 1.14
500 */
501 gpointer
gst_queue_array_pop_tail(GstQueueArray * array)502 gst_queue_array_pop_tail (GstQueueArray * array)
503 {
504 gpointer ret;
505 guint len, idx;
506
507 g_return_val_if_fail (array != NULL, NULL);
508
509 len = array->length;
510
511 /* empty array */
512 if (len == 0)
513 return NULL;
514
515 idx = (array->head + (len - 1)) % array->size;
516
517 ret = *(gpointer *) (array->array + (sizeof (gpointer) * idx));
518
519 array->tail = idx;
520 array->length--;
521
522 return ret;
523 }
524
525 /**
526 * gst_queue_array_pop_tail_struct: (skip)
527 * @array: a #GstQueueArray object
528 *
529 * Returns the tail of the queue @array and removes
530 * it from the queue.
531 *
532 * Returns: The tail of the queue
533 *
534 * Since: 1.14
535 */
536 gpointer
gst_queue_array_pop_tail_struct(GstQueueArray * array)537 gst_queue_array_pop_tail_struct (GstQueueArray * array)
538 {
539 gpointer ret;
540 guint len, idx;
541
542 g_return_val_if_fail (array != NULL, NULL);
543
544 len = array->length;
545
546 /* empty array */
547 if (len == 0)
548 return NULL;
549
550 idx = (array->head + (len - 1)) % array->size;
551
552 ret = array->array + (array->elt_size * idx);
553
554 array->tail = idx;
555 array->length--;
556
557 return ret;
558 }
559
560 /**
561 * gst_queue_array_is_empty: (skip)
562 * @array: a #GstQueueArray object
563 *
564 * Checks if the queue @array is empty.
565 *
566 * Returns: %TRUE if the queue @array is empty
567 *
568 * Since: 1.2
569 */
570 gboolean
gst_queue_array_is_empty(GstQueueArray * array)571 gst_queue_array_is_empty (GstQueueArray * array)
572 {
573 g_return_val_if_fail (array != NULL, FALSE);
574 return (array->length == 0);
575 }
576
577
578 /**
579 * gst_queue_array_drop_struct: (skip)
580 * @array: a #GstQueueArray object
581 * @idx: index to drop
582 * @p_struct: address into which to store the data of the dropped structure, or NULL
583 *
584 * Drops the queue element at position @idx from queue @array and copies the
585 * data of the element or structure that was removed into @p_struct if
586 * @p_struct is set (not NULL).
587 *
588 * Returns: TRUE on success, or FALSE on error
589 *
590 * Since: 1.6
591 */
592 gboolean
gst_queue_array_drop_struct(GstQueueArray * array,guint idx,gpointer p_struct)593 gst_queue_array_drop_struct (GstQueueArray * array, guint idx,
594 gpointer p_struct)
595 {
596 int first_item_index, last_item_index;
597 guint actual_idx;
598 guint elt_size;
599
600 g_return_val_if_fail (array != NULL, FALSE);
601 actual_idx = (array->head + idx) % array->size;
602
603 g_return_val_if_fail (array->length > 0, FALSE);
604 g_return_val_if_fail (actual_idx < array->size, FALSE);
605
606 elt_size = array->elt_size;
607
608 first_item_index = array->head;
609
610 /* tail points to the first free spot */
611 last_item_index = (array->tail - 1 + array->size) % array->size;
612
613 if (p_struct != NULL)
614 memcpy (p_struct, array->array + elt_size * actual_idx, elt_size);
615
616 /* simple case actual_idx == first item */
617 if (actual_idx == first_item_index) {
618 /* clear current head position if needed */
619 if (p_struct == NULL)
620 gst_queue_array_clear_idx (array, idx);
621
622 /* move the head plus one */
623 array->head++;
624 array->head %= array->size;
625 array->length--;
626 return TRUE;
627 }
628
629 /* simple case idx == last item */
630 if (actual_idx == last_item_index) {
631 /* clear current tail position if needed */
632 if (p_struct == NULL)
633 gst_queue_array_clear_idx (array, idx);
634
635 /* move tail minus one, potentially wrapping */
636 array->tail = (array->tail - 1 + array->size) % array->size;
637 array->length--;
638 return TRUE;
639 }
640
641 /* non-wrapped case */
642 if (first_item_index < last_item_index) {
643 /* clear idx if needed */
644 if (p_struct == NULL)
645 gst_queue_array_clear_idx (array, idx);
646
647 g_assert (first_item_index < actual_idx && actual_idx < last_item_index);
648 /* move everything beyond actual_idx one step towards zero in array */
649 memmove (array->array + elt_size * actual_idx,
650 array->array + elt_size * (actual_idx + 1),
651 (last_item_index - actual_idx) * elt_size);
652 /* tail might wrap, ie if tail == 0 (and last_item_index == size) */
653 array->tail = (array->tail - 1 + array->size) % array->size;
654 array->length--;
655 return TRUE;
656 }
657
658 /* only wrapped cases left */
659 g_assert (first_item_index > last_item_index);
660
661 if (actual_idx < last_item_index) {
662 /* clear idx if needed */
663 if (p_struct == NULL)
664 gst_queue_array_clear_idx (array, idx);
665
666 /* actual_idx is before last_item_index, move data towards zero */
667 memmove (array->array + elt_size * actual_idx,
668 array->array + elt_size * (actual_idx + 1),
669 (last_item_index - actual_idx) * elt_size);
670 /* tail should not wrap in this case! */
671 g_assert (array->tail > 0);
672 array->tail--;
673 array->length--;
674 return TRUE;
675 }
676
677 if (actual_idx > first_item_index) {
678 /* clear idx if needed */
679 if (p_struct == NULL)
680 gst_queue_array_clear_idx (array, idx);
681
682 /* actual_idx is after first_item_index, move data to higher indices */
683 memmove (array->array + elt_size * (first_item_index + 1),
684 array->array + elt_size * first_item_index,
685 (actual_idx - first_item_index) * elt_size);
686 array->head++;
687 /* head should not wrap in this case! */
688 g_assert (array->head < array->size);
689 array->length--;
690 return TRUE;
691 }
692
693 g_return_val_if_reached (FALSE);
694 }
695
696 /**
697 * gst_queue_array_drop_element: (skip)
698 * @array: a #GstQueueArray object
699 * @idx: index to drop
700 *
701 * Drops the queue element at position @idx from queue @array.
702 *
703 * Returns: the dropped element
704 *
705 * Since: 1.2
706 */
707 gpointer
gst_queue_array_drop_element(GstQueueArray * array,guint idx)708 gst_queue_array_drop_element (GstQueueArray * array, guint idx)
709 {
710 gpointer ptr;
711
712 if (!gst_queue_array_drop_struct (array, idx, &ptr))
713 return NULL;
714
715 return ptr;
716 }
717
718 /**
719 * gst_queue_array_find: (skip)
720 * @array: a #GstQueueArray object
721 * @func: (allow-none): comparison function, or %NULL to find @data by value
722 * @data: data for comparison function
723 *
724 * Finds an element in the queue @array, either by comparing every element
725 * with @func or by looking up @data if no compare function @func is provided,
726 * and returning the index of the found element.
727 *
728 * Returns: Index of the found element or -1 if nothing was found.
729 *
730 * Since: 1.2
731 */
732 guint
gst_queue_array_find(GstQueueArray * array,GCompareFunc func,gpointer data)733 gst_queue_array_find (GstQueueArray * array, GCompareFunc func, gpointer data)
734 {
735 gpointer p_element;
736 guint elt_size;
737 guint i;
738
739 /* For struct arrays we need to implement this differently so that
740 * the user gets a pointer to the element data not the dereferenced
741 * pointer itself */
742
743 g_return_val_if_fail (array != NULL, -1);
744 g_return_val_if_fail (array->struct_array == FALSE, -1);
745
746 elt_size = array->elt_size;
747
748 if (func != NULL) {
749 /* Scan from head to tail */
750 for (i = 0; i < array->length; i++) {
751 p_element = array->array + ((i + array->head) % array->size) * elt_size;
752 if (func (*(gpointer *) p_element, data) == 0)
753 return i;
754 }
755 } else {
756 for (i = 0; i < array->length; i++) {
757 p_element = array->array + ((i + array->head) % array->size) * elt_size;
758 if (*(gpointer *) p_element == data)
759 return i;
760 }
761 }
762
763 return -1;
764 }
765
766 /**
767 * gst_queue_array_get_length: (skip)
768 * @array: a #GstQueueArray object
769 *
770 * Returns the length of the queue @array
771 *
772 * Returns: the length of the queue @array.
773 *
774 * Since: 1.2
775 */
776 guint
gst_queue_array_get_length(GstQueueArray * array)777 gst_queue_array_get_length (GstQueueArray * array)
778 {
779 g_return_val_if_fail (array != NULL, 0);
780 return array->length;
781 }
782