diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index d5fa3fe99..f1939fddb 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -2524,6 +2524,20 @@ g_ptr_array_foreach +
+List Arrays +arraylist +GArrayList +g_array_list_new +g_array_list_init +g_array_list_peek +g_array_list_index +g_array_list_add +g_array_list_remove +g_array_list_remove_index +g_array_list_destroy +
+
Byte Arrays arrays_byte diff --git a/glib/Makefile.am b/glib/Makefile.am index 2783b51da..ba7003acb 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -103,6 +103,7 @@ libglib_2_0_la_SOURCES = \ $(deprecated_sources) \ glib_probes.d \ garray.c \ + garraylist.c \ gasyncqueue.c \ gasyncqueueprivate.h \ gatomic.c \ @@ -245,6 +246,7 @@ glibsubinclude_HEADERS = \ glib-autocleanups.h \ galloca.h \ garray.h \ + garraylist.h \ gasyncqueue.h \ gatomic.h \ gbacktrace.h \ diff --git a/glib/garraylist.c b/glib/garraylist.c new file mode 100644 index 000000000..51086ff4a --- /dev/null +++ b/glib/garraylist.c @@ -0,0 +1,428 @@ +/* garraylist.c + * + * Copyright (C) 2015 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "garraylist.h" +#include "gmessages.h" +#include "gslice.h" + +/** + * SECTION:garraylist + * @title: GArrayList + * @short_description: Linked lists implemented as arrays. + * + * Sometimes, when building APIs we make mistakes about the undelrying + * data structure that should have been used. GArrayList is a data structure + * that allows read-only compatability with #GList but is implemented as an + * array underneath. This means fast forward and backward iteration using + * typical array style index access, while providing read-only #GList access + * for compatability APIs. + * + * There is a cost associated with doing this, but the trade-off can be worth + * it under certain scenarios. + * + * Mutating the array list is potentially more expensive than a #GList. + * However, given no array growth is required, appending to the array list + * can be performed in O(1). + * + * The ideal use case for this data structure is a read-heavy data set + * where reverse iteration may be necessary and compatability with #GList + * must be maintained. + * + * It is unlikely that you should be writing new APIs that use this + * data structure. + * + * Since: 2.48 + */ + +#if 0 +# define DEBUG_ASSERT(x) g_assert(x) +#else +# define DEBUG_ASSERT(x) +#endif + +#define G_ARRAY_LIST_DEFAULT_ALLOC 8 + +/* + * The following structures give us a data-structure that can be dynamic, + * by changing it's implementation based on the number of items in the + * #GArrayList. When we have 2 or fewer items, we simply embed the #GList + * items in the toplevel structure. + * + * When this grows, we allocate an array to contain the #GList items and + * update prev/next pionters as necessary. + * + * This structure allows for fast iteration forwards and backwards from any + * arbitrary point in the structure, just like an array. It also allows for + * returning a read-only #GList for when API compatability is needed. + */ + +typedef struct +{ + guint len; + guint flags; + GDestroyNotify destroy; + GList items[2]; +} GArrayListEmbed; + +typedef struct +{ + guint len; + guint flags; + GDestroyNotify destroy; + + /* + * This is an array of GList items, which we keep in sync by updating + * the next/prev pointers when necessary. + */ + GList *items; + gsize items_len; + + gpointer padding[4]; +} GArrayListAlloc; + +typedef struct +{ + guint len; + guint on_heap : 1; + guint mode : 31; + GDestroyNotify destroy; + gpointer padding[6]; +} GArrayListAny; + +G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayList)); +G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayListAlloc)); +G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayListEmbed)); + +enum { + MODE_EMBED, + MODE_ALLOC, +}; + +GArrayList * +g_array_list_new (GDestroyNotify destroy) +{ + GArrayListAny *any; + + any = g_slice_new0 (GArrayListAny); + + any->mode = MODE_EMBED; + any->len = 0; + any->destroy = destroy; + any->on_heap = TRUE; + + return (GArrayList *)any; +} + +void +g_array_list_init (GArrayList *self, + GDestroyNotify destroy) +{ + GArrayListAny *any = (GArrayListAny *)self; + + g_return_if_fail (self != NULL); + + memset (any, 0, sizeof *any); + + any->mode = MODE_EMBED; + any->len = 0; + any->destroy = destroy; + any->on_heap = FALSE; +} + +const GList * +g_array_list_peek (GArrayList *self) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + + g_return_val_if_fail (self != NULL, NULL); + + if (self->len == 0) + return NULL; + + return (any->mode == MODE_EMBED) ? embed->items : (GList *)alloc->items; +} + +gpointer +g_array_list_index (GArrayList *self, + guint index) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + GList *items; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (index < self->len, NULL); + + DEBUG_ASSERT ((any->len < G_N_ELEMENTS (embed->items)) || + (any->mode == MODE_ALLOC)); + + items = (any->mode == MODE_EMBED) ? embed->items : (GList *)alloc->items; + + return items [index].data; +} + +static void +_g_array_list_grow (GArrayList *self) +{ + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + gpointer old_items; + gsize i; + + DEBUG_ASSERT (self != NULL); + DEBUG_ASSERT (alloc->mode == MODE_ALLOC); + DEBUG_ASSERT (alloc->len < G_MAXINT); + DEBUG_ASSERT (alloc->len > 0); + + old_items = alloc->items; + + alloc->items_len <<= 1; + alloc->items = g_realloc_n (alloc->items, alloc->items_len, sizeof (GList)); + + if (G_LIKELY (old_items == alloc->items)) + return; + + if (alloc->len > 0) + alloc->items [0].next = &alloc->items [1]; + + for (i = 1; i < (alloc->len - 1); i++) + { + alloc->items [i].prev = &alloc->items [i-1]; + alloc->items [i].next = &alloc->items [i+1]; + } + + if (alloc->len > 1) + alloc->items [alloc->len - 1].prev = &alloc->items [alloc->len - 1 - 1]; +} + +static void +_g_array_list_transition (GArrayList *self) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + GList *items; + gsize i; + + DEBUG_ASSERT (self != NULL); + DEBUG_ASSERT (embed->mode == MODE_EMBED); + DEBUG_ASSERT (embed->len == G_N_ELEMENTS (embed->items)); + + items = g_malloc_n (G_ARRAY_LIST_DEFAULT_ALLOC, sizeof (GList)); + + items [0].prev = NULL; + items [0].data = embed->items [0].data; + items [0].next = &items [1]; + + for (i = 1; i < (G_N_ELEMENTS (embed->items) - 1); i++) + { + items [i].prev = &items [i - 1]; + items [i].next = &items [i + 1]; + items [i].data = &embed->items [i].data; + } + + items [G_N_ELEMENTS (embed->items) - 1].prev = &items [G_N_ELEMENTS (embed->items) - 1 - 1]; + items [G_N_ELEMENTS (embed->items) - 1].next = NULL; + items [G_N_ELEMENTS (embed->items) - 1].data = embed->items [G_N_ELEMENTS (embed->items) - 1].data; + + any->mode = MODE_ALLOC; + + alloc->items_len = G_ARRAY_LIST_DEFAULT_ALLOC; + alloc->items = items; +} + +void +g_array_list_add (GArrayList *self, + gpointer data) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + GList *item; + GList *prev; + + g_return_if_fail (self != NULL); + + if (G_LIKELY (any->mode == MODE_EMBED)) + { + if (G_LIKELY (embed->len < G_N_ELEMENTS (embed->items))) + { + if (G_LIKELY (embed->len == 0)) + { + embed->items [0].prev = NULL; + embed->items [0].next = NULL; + embed->items [0].data = data; + } + else + { + prev = &embed->items [embed->len - 1]; + item = &embed->items [embed->len]; + + item->prev = prev; + item->next = NULL; + item->data = data; + + prev->next = item; + } + + embed->len++; + + DEBUG_ASSERT (embed->len <= G_N_ELEMENTS (embed->items)); + DEBUG_ASSERT (embed->len > 0); + DEBUG_ASSERT (embed->items [embed->len - 1].data == data); + + return; + } + + _g_array_list_transition (self); + } + + DEBUG_ASSERT (any->mode == MODE_ALLOC); + + if (G_UNLIKELY (alloc->len == alloc->items_len)) + _g_array_list_grow (self); + + item = &alloc->items [alloc->len]; + prev = (alloc->len > 0) ? &alloc->items [alloc->len - 1] : NULL; + + item->data = data; + item->prev = NULL; + item->next = NULL; + + if (prev) + prev->next = item; + + alloc->len++; + + DEBUG_ASSERT (alloc->len <= alloc->items_len); + DEBUG_ASSERT (alloc->len > 0); + DEBUG_ASSERT (alloc->items [alloc->len - 1].data == data); +} + +void +g_array_list_remove_index (GArrayList *self, + guint index) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + GList *items; + gpointer data = NULL; + gsize i; + + g_return_if_fail (self != NULL); + g_return_if_fail (index < self->len); + + if (G_LIKELY (any->mode == MODE_EMBED)) + { + DEBUG_ASSERT (index < G_N_ELEMENTS (embed->items)); + + data = embed->items [index].data; + embed->len--; + items = embed->items; + } + else + { + DEBUG_ASSERT (any->mode == MODE_ALLOC); + DEBUG_ASSERT (index < alloc->items_len); + + data = alloc->items [index].data; + alloc->len--; + items = alloc->items; + } + + if (index != alloc->len) + memmove (&items [index], + &items [index + 1], + sizeof (GList) * (any->len - index)); + + for (i = index; i < any->len; i++) + { + if (i > 0) + items [i].prev = &items [i - 1]; + else + items [i].prev = NULL; + + if (i < (any->len - 1)) + items [i].next = &items [i + 1]; + else + items [i].next = NULL; + } + + if (any->destroy) + any->destroy (data); +} + +void +g_array_list_remove (GArrayList *self, + gpointer data) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + GList *items; + gsize i; + + g_return_if_fail (self != NULL); + + items = (any->mode == MODE_EMBED) ? embed->items : alloc->items; + + for (i = 0; i < any->len; i++) + { + if (items [i].data == data) + { + g_array_list_remove_index (self, i); + return; + } + } + + g_warning ("Failed to locate %p in GArrayList", data); +} + +void +g_array_list_destroy (GArrayList *self) +{ + GArrayListAny *any = (GArrayListAny *)self; + GArrayListEmbed *embed = (GArrayListEmbed *)self; + GArrayListAlloc *alloc = (GArrayListAlloc *)self; + GList *items; + gsize i; + + g_return_if_fail (self != NULL); + + if (any->mode == MODE_EMBED) + items = embed->items; + else + items = alloc->items; + + if (any->destroy) + for (i = 0; i < any->len; i++) + any->destroy (items [i].data); + + if (any->mode == MODE_ALLOC) + g_free (alloc->items); + + if (any->on_heap) + g_slice_free (GArrayListAny, any); +} diff --git a/glib/garraylist.h b/glib/garraylist.h new file mode 100644 index 000000000..b8859086f --- /dev/null +++ b/glib/garraylist.h @@ -0,0 +1,76 @@ +/* garraylist.h + * + * Copyright (C) 2015 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __G_ARRAY_LIST_H__ +#define __G_ARRAY_LIST_H__ + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +typedef struct +{ + guint len; + + guint padding; + gpointer padding1; + gpointer padding2; + gpointer padding3; + gpointer padding4; + gpointer padding5; + gpointer padding6; + gpointer padding7; +} GArrayList; + +GLIB_AVAILABLE_IN_2_46 +GArrayList *g_array_list_new (GDestroyNotify notify); + +GLIB_AVAILABLE_IN_2_46 +void g_array_list_init (GArrayList *list, + GDestroyNotify notify); + +GLIB_AVAILABLE_IN_2_46 +const GList *g_array_list_peek (GArrayList *list); + +GLIB_AVAILABLE_IN_2_46 +gpointer g_array_list_index (GArrayList *list, + guint index); + +GLIB_AVAILABLE_IN_2_46 +void g_array_list_add (GArrayList *list, + gpointer data); + +GLIB_AVAILABLE_IN_2_46 +void g_array_list_remove (GArrayList *list, + gpointer data); + +GLIB_AVAILABLE_IN_2_46 +void g_array_list_remove_index (GArrayList *list, + guint index); + +GLIB_AVAILABLE_IN_2_46 +void g_array_list_destroy (GArrayList *list); + +G_END_DECLS + +#endif /* __G_ARRAY_LIST_H__ */ diff --git a/glib/glib.h b/glib/glib.h index 1212d139f..7b959f053 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include diff --git a/glib/tests/.gitignore b/glib/tests/.gitignore index c421004ee..1a4babdce 100644 --- a/glib/tests/.gitignore +++ b/glib/tests/.gitignore @@ -3,6 +3,7 @@ 642026 642026-ec array-test +arraylist asyncqueue atomic autoptr diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am index 23f29401e..3da632a67 100644 --- a/glib/tests/Makefile.am +++ b/glib/tests/Makefile.am @@ -31,6 +31,7 @@ test_extra_programs = \ test_programs = \ array-test \ + arraylist \ asyncqueue \ base64 \ bitlock \ diff --git a/glib/tests/arraylist.c b/glib/tests/arraylist.c new file mode 100644 index 000000000..8d92acb3d --- /dev/null +++ b/glib/tests/arraylist.c @@ -0,0 +1,100 @@ +/* arraylist.c + * + * Copyright (C) 2015 Christian Hergert + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include + +static int test_basic_counter; + +static void +test_basic_destroy (gpointer data) +{ + test_basic_counter++; +} + +static void +test_basic (GArrayList *al) +{ + const GList *iter, *list; + gsize i; + gsize counter = 0; + + g_assert (al != NULL); + g_assert_cmpint (al->len, ==, 0); + + for (i = 1; i <= 1000; i++) + { + g_array_list_add (al, GSIZE_TO_POINTER (i)); + g_assert_cmpint (al->len, ==, i); + } + + list = g_array_list_peek (al); + + for (iter = list; iter; iter = iter->next) + { + counter++; + g_assert_cmpint (counter, ==, GPOINTER_TO_SIZE (iter->data)); + } + + g_assert_cmpint (counter, ==, 1000); + + for (i = 1; i <= 500; i++) + { + gpointer item = GSIZE_TO_POINTER (i); + gpointer val = g_array_list_index (al, 0); + g_assert_cmpint (GPOINTER_TO_SIZE (val), ==, i); + g_array_list_remove (al, item); + } + + g_assert_cmpint (al->len, ==, 500); + g_assert_cmpint (test_basic_counter, ==, 500); + g_array_list_destroy (al); + g_assert_cmpint (test_basic_counter, ==, 1000); + + test_basic_counter = 0; +} + +static void +test_basic_alloc (void) +{ + test_basic (g_array_list_new (test_basic_destroy)); +} + +static void +test_basic_stack (void) +{ + GArrayList al; + + g_array_list_init (&al, test_basic_destroy); + test_basic (&al); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_bug_base ("http://bugzilla.gnome.org/"); + + g_test_add_func ("/GArrayList/heap", test_basic_alloc); + g_test_add_func ("/GArrayList/stack", test_basic_stack); + + return g_test_run (); +}