filterlistmodel: Rewrite with bitset data structure

Bitsets are more powerful, less memory intensive and faster than the old
GtkRbTree version.
This commit is contained in:
Benjamin Otte 2020-06-29 19:04:46 +02:00
parent b54f6710a7
commit d4b868d9bc

View File

@ -21,7 +21,7 @@
#include "gtkfilterlistmodel.h" #include "gtkfilterlistmodel.h"
#include "gtkrbtreeprivate.h" #include "gtkbitset.h"
#include "gtkintl.h" #include "gtkintl.h"
#include "gtkprivate.h" #include "gtkprivate.h"
@ -44,20 +44,6 @@ enum {
NUM_PROPERTIES NUM_PROPERTIES
}; };
typedef struct _FilterNode FilterNode;
typedef struct _FilterAugment FilterAugment;
struct _FilterNode
{
guint visible : 1;
};
struct _FilterAugment
{
guint n_items;
guint n_visible;
};
struct _GtkFilterListModel struct _GtkFilterListModel
{ {
GObject parent_instance; GObject parent_instance;
@ -66,7 +52,7 @@ struct _GtkFilterListModel
GtkFilter *filter; GtkFilter *filter;
GtkFilterMatch strictness; GtkFilterMatch strictness;
GtkRbTree *items; /* NULL if strictness != GTK_FILTER_MATCH_SOME */ GtkBitset *matches; /* NULL if strictness != GTK_FILTER_MATCH_SOME */
}; };
struct _GtkFilterListModelClass struct _GtkFilterListModelClass
@ -76,119 +62,6 @@ struct _GtkFilterListModelClass
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static void
gtk_filter_list_model_augment (GtkRbTree *filter,
gpointer _aug,
gpointer _node,
gpointer left,
gpointer right)
{
FilterNode *node = _node;
FilterAugment *aug = _aug;
aug->n_items = 1;
aug->n_visible = node->visible ? 1 : 0;
if (left)
{
FilterAugment *left_aug = gtk_rb_tree_get_augment (filter, left);
aug->n_items += left_aug->n_items;
aug->n_visible += left_aug->n_visible;
}
if (right)
{
FilterAugment *right_aug = gtk_rb_tree_get_augment (filter, right);
aug->n_items += right_aug->n_items;
aug->n_visible += right_aug->n_visible;
}
}
static FilterNode *
gtk_filter_list_model_get_nth_filtered (GtkRbTree *tree,
guint position,
guint *out_unfiltered)
{
FilterNode *node, *tmp;
guint unfiltered;
node = gtk_rb_tree_get_root (tree);
unfiltered = 0;
while (node)
{
tmp = gtk_rb_tree_node_get_left (node);
if (tmp)
{
FilterAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
if (position < aug->n_visible)
{
node = tmp;
continue;
}
position -= aug->n_visible;
unfiltered += aug->n_items;
}
if (node->visible)
{
if (position == 0)
break;
position--;
}
unfiltered++;
node = gtk_rb_tree_node_get_right (node);
}
if (out_unfiltered)
*out_unfiltered = unfiltered;
return node;
}
static FilterNode *
gtk_filter_list_model_get_nth (GtkRbTree *tree,
guint position,
guint *out_filtered)
{
FilterNode *node, *tmp;
guint filtered;
node = gtk_rb_tree_get_root (tree);
filtered = 0;
while (node)
{
tmp = gtk_rb_tree_node_get_left (node);
if (tmp)
{
FilterAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
if (position < aug->n_items)
{
node = tmp;
continue;
}
position -= aug->n_items;
filtered += aug->n_visible;
}
if (position == 0)
break;
position--;
if (node->visible)
filtered++;
node = gtk_rb_tree_node_get_right (node);
}
if (out_filtered)
*out_filtered = filtered;
return node;
}
static GType static GType
gtk_filter_list_model_get_item_type (GListModel *list) gtk_filter_list_model_get_item_type (GListModel *list)
{ {
@ -199,8 +72,6 @@ static guint
gtk_filter_list_model_get_n_items (GListModel *list) gtk_filter_list_model_get_n_items (GListModel *list)
{ {
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list); GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list);
FilterAugment *aug;
FilterNode *node;
switch (self->strictness) switch (self->strictness)
{ {
@ -211,18 +82,12 @@ gtk_filter_list_model_get_n_items (GListModel *list)
return g_list_model_get_n_items (self->model); return g_list_model_get_n_items (self->model);
case GTK_FILTER_MATCH_SOME: case GTK_FILTER_MATCH_SOME:
break; return gtk_bitset_get_size (self->matches);
default: default:
g_assert_not_reached (); g_assert_not_reached ();
}
node = gtk_rb_tree_get_root (self->items);
if (node == NULL)
return 0; return 0;
}
aug = gtk_rb_tree_get_augment (self->items, node);
return aug->n_visible;
} }
static gpointer static gpointer
@ -242,7 +107,9 @@ gtk_filter_list_model_get_item (GListModel *list,
break; break;
case GTK_FILTER_MATCH_SOME: case GTK_FILTER_MATCH_SOME:
gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered); unfiltered = gtk_bitset_get_nth (self->matches, position);
if (unfiltered == 0 && position >= gtk_bitset_get_size (self->matches))
return NULL;
break; break;
default: default:
@ -264,7 +131,7 @@ G_DEFINE_TYPE_WITH_CODE (GtkFilterListModel, gtk_filter_list_model, G_TYPE_OBJEC
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init)) G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init))
static gboolean static gboolean
gtk_filter_list_model_run_filter (GtkFilterListModel *self, gtk_filter_list_model_run_filter_on_item (GtkFilterListModel *self,
guint position) guint position)
{ {
gpointer item; gpointer item;
@ -280,24 +147,63 @@ gtk_filter_list_model_run_filter (GtkFilterListModel *self,
return visible; return visible;
} }
static void
gtk_filter_list_model_run_filter (GtkFilterListModel *self)
{
guint i, n_items;
GtkBitset *other;
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
if (self->matches == NULL || self->model == NULL)
return;
n_items = g_list_model_get_n_items (self->model);
other = self->matches;
self->matches = gtk_bitset_new_empty ();
for (i = 0; i < n_items; i++)
{
if (gtk_filter_list_model_run_filter_on_item (self, i))
gtk_bitset_add (self->matches, i);
}
gtk_bitset_difference (other, self->matches);
if (!gtk_bitset_is_empty (other))
{
guint min, max, changes, additions, existing;
min = gtk_bitset_get_minimum (other);
max = gtk_bitset_get_maximum (other);
existing = gtk_bitset_get_size_in_range (self->matches, min, max);
changes = gtk_bitset_get_size (other);
gtk_bitset_intersect (other, self->matches);
additions = gtk_bitset_get_size (other);
g_list_model_items_changed (G_LIST_MODEL (self),
min > 0 ? gtk_bitset_get_size_in_range (self->matches, 0, min - 1) : 0,
existing - additions + (changes - additions),
existing);
}
gtk_bitset_unref (other);
}
static guint static guint
gtk_filter_list_model_add_items (GtkFilterListModel *self, gtk_filter_list_model_add_items (GtkFilterListModel *self,
FilterNode *after,
guint position, guint position,
guint n_items) guint n_items)
{ {
FilterNode *node;
guint i, n_visible; guint i, n_visible;
n_visible = 0; n_visible = 0;
for (i = 0; i < n_items; i++) for (i = 0; i < n_items; i++)
{ {
node = gtk_rb_tree_insert_before (self->items, after); if (gtk_filter_list_model_run_filter_on_item (self, position + i))
node->visible = gtk_filter_list_model_run_filter (self, position + i); {
if (node->visible) gtk_bitset_add (self->matches, position + i);
n_visible++; n_visible++;
} }
}
return n_visible; return n_visible;
} }
@ -309,8 +215,7 @@ gtk_filter_list_model_items_changed_cb (GListModel *model,
guint added, guint added,
GtkFilterListModel *self) GtkFilterListModel *self)
{ {
FilterNode *node; guint filter_position, filter_removed, filter_added;
guint i, filter_position, filter_removed, filter_added;
switch (self->strictness) switch (self->strictness)
{ {
@ -328,19 +233,15 @@ gtk_filter_list_model_items_changed_cb (GListModel *model,
g_assert_not_reached (); g_assert_not_reached ();
} }
node = gtk_filter_list_model_get_nth (self->items, position, &filter_position); if (removed > 0)
filter_removed = gtk_bitset_get_size_in_range (self->matches, position, position + removed - 1);
else
filter_removed = 0; filter_removed = 0;
for (i = 0; i < removed; i++)
{
FilterNode *next = gtk_rb_tree_node_get_next (node);
if (node->visible)
filter_removed++;
gtk_rb_tree_remove (self->items, node);
node = next;
}
filter_added = gtk_filter_list_model_add_items (self, node, position, added); gtk_bitset_slice (self->matches, position, removed, added);
filter_position = gtk_bitset_get_size_in_range (self->matches, 0, position);
filter_added = gtk_filter_list_model_add_items (self, position, added);
if (filter_removed > 0 || filter_added > 0) if (filter_removed > 0 || filter_added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), filter_position, filter_removed, filter_added); g_list_model_items_changed (G_LIST_MODEL (self), filter_position, filter_removed, filter_added);
@ -402,107 +303,13 @@ gtk_filter_list_model_clear_model (GtkFilterListModel *self)
g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self); g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self);
g_clear_object (&self->model); g_clear_object (&self->model);
if (self->items) if (self->matches)
gtk_rb_tree_remove_all (self->items); gtk_bitset_remove_all (self->matches);
}
/*<private>
* gtk_filter_list_model_find_filtered:
* @self: a #GtkFilterListModel
* @start: (out) (caller-allocates): number of unfiltered items
* at start of list
* @end: (out) (caller-allocates): number of unfiltered items
* at end of list
* @n_items: (out) (caller-allocates): number of unfiltered items in
* list
*
* Checks if elements in self->items are filtered out and returns
* the range that they occupy.
* This function is intended to be used for GListModel::items-changed
* emissions, so it is called in an intermediate state for @self.
*
* Returns: %TRUE if elements are filtered out, %FALSE if none are
**/
static gboolean
gtk_filter_list_model_find_filtered (GtkFilterListModel *self,
guint *start,
guint *end,
guint *n_items)
{
FilterNode *root, *node, *tmp;
FilterAugment *aug;
if (self->items == NULL || self->model == NULL)
return FALSE;
root = gtk_rb_tree_get_root (self->items);
if (root == NULL)
return FALSE; /* empty parent model */
aug = gtk_rb_tree_get_augment (self->items, root);
if (aug->n_items == aug->n_visible)
return FALSE; /* all items visible */
/* find first filtered */
*start = 0;
*end = 0;
*n_items = aug->n_visible;
node = root;
while (node)
{
tmp = gtk_rb_tree_node_get_left (node);
if (tmp)
{
aug = gtk_rb_tree_get_augment (self->items, tmp);
if (aug->n_visible < aug->n_items)
{
node = tmp;
continue;
}
*start += aug->n_items;
}
if (!node->visible)
break;
(*start)++;
node = gtk_rb_tree_node_get_right (node);
}
/* find last filtered by doing everything the opposite way */
node = root;
while (node)
{
tmp = gtk_rb_tree_node_get_right (node);
if (tmp)
{
aug = gtk_rb_tree_get_augment (self->items, tmp);
if (aug->n_visible < aug->n_items)
{
node = tmp;
continue;
}
*end += aug->n_items;
}
if (!node->visible)
break;
(*end)++;
node = gtk_rb_tree_node_get_left (node);
}
return TRUE;
} }
static void static void
gtk_filter_list_model_refilter (GtkFilterListModel *self); gtk_filter_list_model_refilter (GtkFilterListModel *self,
GtkFilterChange change)
static void
gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self)
{ {
GtkFilterMatch new_strictness; GtkFilterMatch new_strictness;
@ -520,7 +327,7 @@ gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self)
case GTK_FILTER_MATCH_NONE: case GTK_FILTER_MATCH_NONE:
{ {
guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self)); guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self));
g_clear_pointer (&self->items, gtk_rb_tree_unref); g_clear_pointer (&self->matches, gtk_bitset_unref);
self->strictness = new_strictness; self->strictness = new_strictness;
if (n_before > 0) if (n_before > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0); g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0);
@ -541,16 +348,34 @@ gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self)
case GTK_FILTER_MATCH_SOME: case GTK_FILTER_MATCH_SOME:
{ {
guint start, end, n_before, n_after; guint start, end, n_before, n_after;
self->strictness = new_strictness; self->strictness = new_strictness;
if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_before))
{
n_after = g_list_model_get_n_items (G_LIST_MODEL (self)); n_after = g_list_model_get_n_items (G_LIST_MODEL (self));
g_clear_pointer (&self->items, gtk_rb_tree_unref); start = gtk_bitset_get_minimum (self->matches);
g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start); end = gtk_bitset_get_maximum (self->matches);
n_before = gtk_bitset_get_size (self->matches);
if (n_before == n_after)
{
g_clear_pointer (&self->matches, gtk_bitset_unref);
} }
else else
{ {
g_clear_pointer (&self->items, gtk_rb_tree_unref); GtkBitset *inverse = gtk_bitset_new_empty ();
gtk_bitset_add_range (inverse, 0, n_after);
gtk_bitset_subtract (inverse, self->matches);
/* otherwise all items would be visible */
g_assert (!gtk_bitset_is_empty (inverse));
/* find first filtered */
start = gtk_bitset_get_minimum (inverse);
end = n_after - gtk_bitset_get_maximum (inverse) - 1;
gtk_bitset_unref (inverse);
g_clear_pointer (&self->matches, gtk_bitset_unref);
g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start);
} }
} }
break; break;
@ -562,40 +387,15 @@ gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self)
break; break;
case GTK_FILTER_MATCH_SOME: case GTK_FILTER_MATCH_SOME:
switch (self->strictness) if (self->matches == NULL)
{ {
case GTK_FILTER_MATCH_NONE: self->matches = gtk_bitset_new_empty ();
{ if (self->strictness == GTK_FILTER_MATCH_ALL)
guint n_after; gtk_bitset_add_range (self->matches, 0, g_list_model_get_n_items (self->model));
}
self->strictness = new_strictness; self->strictness = new_strictness;
self->items = gtk_rb_tree_new (FilterNode, gtk_filter_list_model_run_filter (self);
FilterAugment,
gtk_filter_list_model_augment,
NULL, NULL);
n_after = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (self->model));
if (n_after > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, n_after);
}
break;
case GTK_FILTER_MATCH_ALL:
{
guint start, end, n_before, n_after;
self->strictness = new_strictness;
self->items = gtk_rb_tree_new (FilterNode,
FilterAugment,
gtk_filter_list_model_augment,
NULL, NULL);
n_before = g_list_model_get_n_items (self->model);
gtk_filter_list_model_add_items (self, NULL, 0, n_before);
if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_after))
g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start);
}
break;
default:
case GTK_FILTER_MATCH_SOME:
gtk_filter_list_model_refilter (self);
break;
}
} }
} }
@ -604,7 +404,7 @@ gtk_filter_list_model_filter_changed_cb (GtkFilter *filter,
GtkFilterChange change, GtkFilterChange change,
GtkFilterListModel *self) GtkFilterListModel *self)
{ {
gtk_filter_list_model_update_strictness_and_refilter (self); gtk_filter_list_model_refilter (self, change);
} }
static void static void
@ -624,7 +424,7 @@ gtk_filter_list_model_dispose (GObject *object)
gtk_filter_list_model_clear_model (self); gtk_filter_list_model_clear_model (self);
gtk_filter_list_model_clear_filter (self); gtk_filter_list_model_clear_filter (self);
g_clear_pointer (&self->items, gtk_rb_tree_unref); g_clear_pointer (&self->matches, gtk_bitset_unref);
G_OBJECT_CLASS (gtk_filter_list_model_parent_class)->dispose (object); G_OBJECT_CLASS (gtk_filter_list_model_parent_class)->dispose (object);
} }
@ -725,7 +525,7 @@ gtk_filter_list_model_set_filter (GtkFilterListModel *self,
} }
else else
{ {
gtk_filter_list_model_update_strictness_and_refilter (self); gtk_filter_list_model_refilter (self, GTK_FILTER_CHANGE_LESS_STRICT);
} }
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILTER]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILTER]);
@ -784,11 +584,11 @@ gtk_filter_list_model_set_model (GtkFilterListModel *self,
if (removed == 0) if (removed == 0)
{ {
self->strictness = GTK_FILTER_MATCH_NONE; self->strictness = GTK_FILTER_MATCH_NONE;
gtk_filter_list_model_update_strictness_and_refilter (self); gtk_filter_list_model_refilter (self, GTK_FILTER_CHANGE_LESS_STRICT);
added = 0; added = 0;
} }
else if (self->items) else if (self->matches)
added = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (model)); added = gtk_filter_list_model_add_items (self, 0, g_list_model_get_n_items (model));
else else
added = g_list_model_get_n_items (model); added = g_list_model_get_n_items (model);
} }
@ -820,54 +620,3 @@ gtk_filter_list_model_get_model (GtkFilterListModel *self)
return self->model; return self->model;
} }
static void
gtk_filter_list_model_refilter (GtkFilterListModel *self)
{
FilterNode *node;
guint i, first_change, last_change;
guint n_is_visible, n_was_visible;
gboolean visible;
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
if (self->items == NULL || self->model == NULL)
return;
first_change = G_MAXUINT;
last_change = 0;
n_is_visible = 0;
n_was_visible = 0;
for (i = 0, node = gtk_rb_tree_get_first (self->items);
node != NULL;
i++, node = gtk_rb_tree_node_get_next (node))
{
visible = gtk_filter_list_model_run_filter (self, i);
if (visible == node->visible)
{
if (visible)
{
n_is_visible++;
n_was_visible++;
}
continue;
}
node->visible = visible;
gtk_rb_tree_node_mark_dirty (node);
first_change = MIN (n_is_visible, first_change);
if (visible)
n_is_visible++;
else
n_was_visible++;
last_change = MAX (n_is_visible, last_change);
}
if (first_change <= last_change)
{
g_list_model_items_changed (G_LIST_MODEL (self),
first_change,
last_change - first_change + n_was_visible - n_is_visible,
last_change - first_change);
}
}