diff --git a/girs/GstBase-1.0.gir b/girs/GstBase-1.0.gir index 227002dc89..a9c05a41fb 100644 --- a/girs/GstBase-1.0.gir +++ b/girs/GstBase-1.0.gir @@ -13322,6 +13322,72 @@ it from the queue. + + Pushes @data to the queue @array, finding the correct position +by comparing @data with each array element using @func. + +This has a time complexity of O(n), so depending on the size of the queue +and expected access patterns, a different data structure might be better. + +Assumes that the array is already sorted. If it is not, make sure +to call gst_queue_array_sort() first. + + + + + + + a #GstQueueArray object + + + + object to push + + + + comparison function + + + + data for comparison function + + + + + + Pushes the element at address @p_struct into the queue @array +(copying the contents of a structure of the struct_size specified +when creating the queue into the array), finding the correct position +by comparing the element at @p_struct with each element in the array using @func. + +This has a time complexity of O(n), so depending on the size of the queue +and expected access patterns, a different data structure might be better. + +Assumes that the array is already sorted. If it is not, make sure +to call gst_queue_array_sort() first. + + + + + + + a #GstQueueArray object + + + + address of element or structure to push into the queue + + + + comparison function + + + + data for comparison function + + + + Pushes @data to the tail of the queue @array. @@ -13379,6 +13445,28 @@ the array element it is given, but not free the element itself. + + Sorts the queue @array by comparing elements against each other using +the provided @compare_func. + + + + + + + a #GstQueueArray object + + + + comparison function + + + + data for comparison function + + + + Allocates a new #GstQueueArray object with an initial queue size of @initial_size. diff --git a/subprojects/gstreamer/libs/gst/base/gstqueuearray.c b/subprojects/gstreamer/libs/gst/base/gstqueuearray.c index 88b9f0b7f9..cfd80e4734 100644 --- a/subprojects/gstreamer/libs/gst/base/gstqueuearray.c +++ b/subprojects/gstreamer/libs/gst/base/gstqueuearray.c @@ -37,6 +37,9 @@ #include #include "gstqueuearray.h" +#define gst_queue_array_idx(a, i) \ + ((a)->array + (((a)->head + (i)) % (a)->size) * (a)->elt_size) + struct _GstQueueArray { /* < private > */ @@ -50,6 +53,12 @@ struct _GstQueueArray GDestroyNotify clear_func; }; +typedef struct +{ + GCompareDataFunc func; + gpointer user_data; +} QueueSortData; + /** * gst_queue_array_new_for_struct: (skip) * @struct_size: Size of each element (e.g. structure) in the array @@ -431,6 +440,212 @@ gst_queue_array_push_tail (GstQueueArray * array, gpointer data) array->length++; } +/* Moves all elements in the queue placed after the given position in the internal array */ +static void +gst_queue_array_move_data_after_position (GstQueueArray * array, guint pos) +{ + guint elt_size = array->elt_size; + + /* If the array does not wrap around OR if it does, but we're inserting past that point */ + if (array->head < array->tail || + (array->head >= array->tail && pos < array->head)) { + memmove (array->array + (pos + 1) * elt_size, array->array + pos * elt_size, + (array->tail - pos) * elt_size); + return; + } + + /* Otherwise, array wraps around and we're inserting before the breaking point. + * First, move everything past that point by one place. */ + memmove (array->array + elt_size, array->array, array->tail * elt_size); + + /* Then move the last element from before the wrap-around point to right after it. */ + memcpy (array->array, array->array + (array->size - 1) * elt_size, elt_size); + + /* If we're inserting right before the breaking point, no further action is needed. + * Otherwise, move data between insertion point and the breaking point by one place. */ + if (pos != array->size - 1) { + memmove (array->array + (pos + 1) * elt_size, array->array + pos * elt_size, + (array->size - pos - 1) * elt_size); + } +} + +/** + * gst_queue_array_push_sorted: (skip) + * @array: a #GstQueueArray object + * @data: object to push + * @func: comparison function + * @user_data: (nullable): data for comparison function + * + * Pushes @data to the queue @array, finding the correct position + * by comparing @data with each array element using @func. + * + * This has a time complexity of O(n), so depending on the size of the queue + * and expected access patterns, a different data structure might be better. + * + * Assumes that the array is already sorted. If it is not, make sure + * to call gst_queue_array_sort() first. + * + * Since: 1.24 + */ +void +gst_queue_array_push_sorted (GstQueueArray * array, gpointer data, + GCompareDataFunc func, gpointer user_data) +{ + guint i; + gpointer *p_element; + + g_return_if_fail (array != NULL); + g_return_if_fail (func != NULL); + + /* Check if we need to make room */ + if (G_UNLIKELY (array->length == array->size)) + gst_queue_array_do_expand (array); + + /* Compare against each element, assuming they're already sorted */ + for (i = 0; i < array->length; i++) { + p_element = (gpointer *) gst_queue_array_idx (array, i); + + if (func (*p_element, data, user_data) > 0) { + guint pos = (array->head + i) % array->size; + gst_queue_array_move_data_after_position (array, pos); + + *p_element = data; + goto finish; + } + } + + /* No 'bigger' element found - append to tail */ + *(gpointer *) (array->array + array->elt_size * array->tail) = data; + +finish: + array->tail++; + array->tail %= array->size; + array->length++; +} + +/** + * gst_queue_array_push_sorted_struct: (skip) + * @array: a #GstQueueArray object + * @p_struct: address of element or structure to push into the queue + * @func: comparison function + * @user_data: (nullable): data for comparison function + * + * Pushes the element at address @p_struct into the queue @array + * (copying the contents of a structure of the struct_size specified + * when creating the queue into the array), finding the correct position + * by comparing the element at @p_struct with each element in the array using @func. + * + * This has a time complexity of O(n), so depending on the size of the queue + * and expected access patterns, a different data structure might be better. + * + * Assumes that the array is already sorted. If it is not, make sure + * to call gst_queue_array_sort() first. + * + * Since: 1.24 + */ +void +gst_queue_array_push_sorted_struct (GstQueueArray * array, gpointer p_struct, + GCompareDataFunc func, gpointer user_data) +{ + guint i; + gpointer p_element; + + g_return_if_fail (array != NULL); + g_return_if_fail (p_struct != NULL); + g_return_if_fail (func != NULL); + + /* Check if we need to make room */ + if (G_UNLIKELY (array->length == array->size)) + gst_queue_array_do_expand (array); + + /* Compare against each element, assuming they're already sorted */ + for (i = 0; i < array->length; i++) { + p_element = gst_queue_array_idx (array, i); + + if (func (p_element, p_struct, user_data) > 0) { + guint pos = (array->head + i) % array->size; + gst_queue_array_move_data_after_position (array, pos); + + memcpy (p_element, p_struct, array->elt_size); + goto finish; + } + } + + /* No 'bigger' element found - append to tail */ + memcpy (array->array + array->elt_size * array->tail, p_struct, + array->elt_size); + +finish: + array->tail++; + array->tail %= array->size; + array->length++; +} + +static int +compare_wrapper (gpointer * a, gpointer * b, QueueSortData * sort_data) +{ + return sort_data->func (*a, *b, sort_data->user_data); +} + +/** + * gst_queue_array_sort: (skip) + * @array: a #GstQueueArray object + * @compare_func: comparison function + * @user_data: (nullable): data for comparison function + * + * Sorts the queue @array by comparing elements against each other using + * the provided @compare_func. + * + * Since: 1.24 + */ +void +gst_queue_array_sort (GstQueueArray * array, GCompareDataFunc compare_func, + gpointer user_data) +{ + g_return_if_fail (array != NULL); + g_return_if_fail (compare_func != NULL); + + if (array->length == 0) + return; + + /* To be able to use g_qsort_with_data, we might need to rearrange: + * [0-----TAIL][HEAD-----SIZE] -> [HEAD-------TAIL] */ + if (array->head >= array->tail) { + gsize t1 = array->head; + gsize t2 = array->size - array->head; + gsize elt_size = array->elt_size; + + /* Copy [0-------TAIL] part to a temporary buffer */ + guint8 *tmp = g_malloc0_n (t1, elt_size); + memcpy (tmp, array->array, t1 * elt_size); + + /* Move [HEAD-----SIZE] part to the beginning of the original array */ + memmove (array->array, array->array + (elt_size * array->head), + t2 * elt_size); + + /* Copy the temporary buffer to the end of the original array */ + memmove (array->array + (t2 * elt_size), tmp, t1 * elt_size); + g_free (tmp); + + array->head = 0; + array->tail = array->length % array->size; + } + + if (array->struct_array) { + g_qsort_with_data (array->array + + (array->head % array->size) * array->elt_size, array->length, + array->elt_size, compare_func, user_data); + } else { + /* For non-struct arrays, we need to wrap the provided compare function + * to dereference our pointers before passing them for comparison. + * This matches the behaviour of gst_queue_array_find(). */ + QueueSortData sort_data = { compare_func, user_data }; + g_qsort_with_data (array->array + + (array->head % array->size) * array->elt_size, array->length, + array->elt_size, (GCompareDataFunc) compare_wrapper, &sort_data); + } +} + /** * gst_queue_array_peek_tail: (skip) * @array: a #GstQueueArray object @@ -718,7 +933,7 @@ gst_queue_array_drop_element (GstQueueArray * array, guint idx) /** * gst_queue_array_find: (skip) * @array: a #GstQueueArray object - * @func: (allow-none): comparison function, or %NULL to find @data by value + * @func: (nullable): comparison function, or %NULL to find @data by value * @data: data for comparison function * * Finds an element in the queue @array, either by comparing every element diff --git a/subprojects/gstreamer/libs/gst/base/gstqueuearray.h b/subprojects/gstreamer/libs/gst/base/gstqueuearray.h index 77edec0990..2a63e95cf3 100644 --- a/subprojects/gstreamer/libs/gst/base/gstqueuearray.h +++ b/subprojects/gstreamer/libs/gst/base/gstqueuearray.h @@ -104,6 +104,23 @@ gpointer gst_queue_array_pop_tail_struct (GstQueueArray * array); GST_BASE_API gpointer gst_queue_array_peek_tail_struct (GstQueueArray * array); +GST_BASE_API +void gst_queue_array_push_sorted (GstQueueArray * array, + gpointer data, + GCompareDataFunc func, + gpointer user_data); + +GST_BASE_API +void gst_queue_array_push_sorted_struct (GstQueueArray * array, + gpointer p_struct, + GCompareDataFunc func, + gpointer user_data); + +GST_BASE_API +void gst_queue_array_sort (GstQueueArray *array, + GCompareDataFunc compare_func, + gpointer user_data); + G_END_DECLS #endif diff --git a/subprojects/gstreamer/tests/check/libs/queuearray.c b/subprojects/gstreamer/tests/check/libs/queuearray.c index acd8e5c9a5..ecce1ec366 100644 --- a/subprojects/gstreamer/tests/check/libs/queuearray.c +++ b/subprojects/gstreamer/tests/check/libs/queuearray.c @@ -200,9 +200,9 @@ GST_START_TEST (test_array_grow_end) GST_END_TEST; static int -compare_pointer_value (gconstpointer a, gconstpointer b) +compare_pointer_value (guintptr a, guintptr b) { - return (int) ((guintptr) a - (guintptr) b); + return (int) (a - b); } GST_START_TEST (test_array_drop2) @@ -232,8 +232,8 @@ GST_START_TEST (test_array_drop2) gpointer dropped; if (g_random_boolean () && g_random_boolean () && in_array[i]) { - idx = gst_queue_array_find (array, compare_pointer_value, - GUINT_TO_POINTER (i)); + idx = gst_queue_array_find (array, + (GCompareFunc) compare_pointer_value, GUINT_TO_POINTER (i)); dropped = gst_queue_array_drop_element (array, idx); fail_unless_equals_int (i, GPOINTER_TO_INT (dropped)); in_array[i] = FALSE; @@ -341,6 +341,273 @@ GST_START_TEST (test_array_peek_pop_tail) GST_END_TEST; +GST_START_TEST (test_array_push_sorted) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new (10); + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + + /* Now try to push even values, in reverse order because why not */ + for (i = 8; i >= 0; i -= 2) + gst_queue_array_push_sorted (array, GINT_TO_POINTER (i), + (GCompareDataFunc) compare_pointer_value, NULL); + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + + gst_queue_array_free (array); +} + +GST_END_TEST; + +GST_START_TEST (test_array_push_sorted_wrapped) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new (10); + + /* Push and pull 4 values to offset head/tail. + * Pushing +1's the tail and popping +1's the head, so the push after this will + * store data at [4] internally, and further 10 pushes will cause the array + * to wrap around. */ + for (i = 0; i < 4; i++) { + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + } + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + + /* Now try to push even values, in reverse order because why not */ + for (i = 8; i >= 0; i -= 2) + gst_queue_array_push_sorted (array, GINT_TO_POINTER (i), + (GCompareDataFunc) compare_pointer_value, NULL); + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + + gst_queue_array_free (array); +} + +GST_END_TEST; + +typedef struct +{ + gint value; +} CompareTestStruct; + +static int +compare_struct_value (CompareTestStruct * a, CompareTestStruct * b) +{ + return a->value - b->value; +} + +GST_START_TEST (test_array_push_sorted_struct) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new_for_struct (sizeof (CompareTestStruct), 10); + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) { + CompareTestStruct s = { i }; + gst_queue_array_push_tail_struct (array, &s); + } + + /* Now try to push even values, in reverse order because why not */ + for (i = 8; i >= 0; i -= 2) { + CompareTestStruct s = { i }; + gst_queue_array_push_sorted_struct (array, &s, + (GCompareDataFunc) compare_struct_value, NULL); + } + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) { + CompareTestStruct *s = gst_queue_array_pop_head_struct (array); + fail_unless_equals_int (s->value, i); + } + + gst_queue_array_free (array); +} + +GST_END_TEST; + +GST_START_TEST (test_array_push_sorted_struct_wrapped) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new_for_struct (sizeof (CompareTestStruct), 10); + + /* Push and pull 4 values to offset head/tail. + * Pushing +1's the tail and popping +1's the head, so the push after this will + * store data at [4] internally, and further 10 pushes will cause the array + * to wrap around. */ + for (i = 0; i < 4; i++) { + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + } + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) { + CompareTestStruct s = { i }; + gst_queue_array_push_tail_struct (array, &s); + } + + /* Now try to push even values, in reverse order because why not */ + for (i = 8; i >= 0; i -= 2) { + CompareTestStruct s = { i }; + gst_queue_array_push_sorted_struct (array, &s, + (GCompareDataFunc) compare_struct_value, NULL); + } + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) { + CompareTestStruct *s = gst_queue_array_pop_head_struct (array); + fail_unless_equals_int (s->value, i); + } + + gst_queue_array_free (array); +} + +GST_END_TEST; + +GST_START_TEST (test_array_sort) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new (10); + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + + /* Now try to push even values, in reverse order because why not */ + for (i = 8; i >= 0; i -= 2) + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Sort the array */ + gst_queue_array_sort (array, (GCompareDataFunc) compare_pointer_value, NULL); + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + + gst_queue_array_free (array); +} + +GST_END_TEST; + +GST_START_TEST (test_array_sort_struct) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new_for_struct (sizeof (CompareTestStruct), 10); + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) { + CompareTestStruct s = { i }; + gst_queue_array_push_tail_struct (array, &s); + } + + /* Now try to push even values, in reverse order because why not */ + for (i = 8; i >= 0; i -= 2) { + CompareTestStruct s = { i }; + gst_queue_array_push_tail_struct (array, &s); + } + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Sort the array */ + gst_queue_array_sort (array, (GCompareDataFunc) compare_struct_value, NULL); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) { + CompareTestStruct *s = gst_queue_array_pop_head_struct (array); + fail_unless_equals_int (s->value, i); + } + + gst_queue_array_free (array); +} + +GST_END_TEST; + +GST_START_TEST (test_array_sort_wrapped) +{ + GstQueueArray *array; + gint i; + + /* Create an array of initial size 10 */ + array = gst_queue_array_new (10); + + /* Push and pull 4 values to offset head/tail */ + for (i = 0; i < 4; i++) { + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + } + + fail_unless_equals_int (gst_queue_array_get_length (array), 0); + + /* Fill it with odd values */ + for (i = 1; i < 10; i += 2) + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + + /* Now try to push even values, in reverse order because why not + * At this point the array should've wrapped around (head > tail) */ + for (i = 8; i >= 0; i -= 2) + gst_queue_array_push_tail (array, GINT_TO_POINTER (i)); + + fail_unless_equals_int (gst_queue_array_get_length (array), 10); + + /* Sort the array */ + gst_queue_array_sort (array, (GCompareDataFunc) compare_pointer_value, NULL); + + /* Check that the array is now 0-9 in correct order */ + for (i = 0; i < 10; i++) + fail_unless_equals_int (GPOINTER_TO_INT (gst_queue_array_pop_head (array)), + i); + + gst_queue_array_free (array); +} + +GST_END_TEST; + static Suite * gst_queue_array_suite (void) { @@ -358,6 +625,13 @@ gst_queue_array_suite (void) tcase_add_test (tc_chain, test_array_grow_from_prealloc1); tcase_add_test (tc_chain, test_array_peek_pop_tail); tcase_add_test (tc_chain, test_array_peek_nth); + tcase_add_test (tc_chain, test_array_push_sorted); + tcase_add_test (tc_chain, test_array_push_sorted_wrapped); + tcase_add_test (tc_chain, test_array_push_sorted_struct); + tcase_add_test (tc_chain, test_array_push_sorted_struct_wrapped); + tcase_add_test (tc_chain, test_array_sort); + tcase_add_test (tc_chain, test_array_sort_struct); + tcase_add_test (tc_chain, test_array_sort_wrapped); return s; }