gtk/gtk/gtklistlistmodel.c
Benjamin Otte e010cd242c listlistmodel: Add a cache
Cache the last looked up item and use it for looking up the next item if
it's closest. This massively speeds up iteration over the model, because
each call to get_item() will be adjacent to the previous one.

Improves performance of the inspector quite a bit.
2023-04-15 05:27:47 +02:00

417 lines
11 KiB
C

/*
* Copyright © 2018 Benjamin Otte
*
* This library 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 library 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, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/*
* GtkListListModel:
*
* `GtkListListModel` is a `GListModel` implementation that takes a list API
* and provides it as a `GListModel`.
*/
#include "config.h"
#include "gtklistlistmodelprivate.h"
struct _GtkListListModel
{
GObject parent_instance;
guint n_items;
gpointer (* get_first) (gpointer);
gpointer (* get_next) (gpointer, gpointer);
gpointer (* get_previous) (gpointer, gpointer);
gpointer (* get_last) (gpointer);
gpointer (* get_item) (gpointer, gpointer);
gpointer data;
GDestroyNotify notify;
guint cache_pos;
gpointer cache_item;
};
struct _GtkListListModelClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_ITEM_TYPE,
PROP_N_ITEMS,
N_PROPS
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static GType
gtk_list_list_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_list_list_model_get_n_items (GListModel *list)
{
GtkListListModel *self = GTK_LIST_LIST_MODEL (list);
return self->n_items;
}
static gboolean
gtk_list_list_model_cache_is_valid (GtkListListModel *self)
{
return self->cache_item != NULL;
}
static void
gtk_list_list_model_invalidate_cache (GtkListListModel *self)
{
self->cache_item = NULL;
}
static gpointer
gtk_list_list_model_get_item (GListModel *list,
guint position)
{
GtkListListModel *self = GTK_LIST_LIST_MODEL (list);
gpointer result;
guint i;
guint start, end;
if (position >= self->n_items)
return NULL;
start = 0;
end = self->n_items;
if (gtk_list_list_model_cache_is_valid (self))
{
if (self->cache_pos <= position)
start = self->cache_pos;
else
end = self->cache_pos;
}
if (self->get_last &&
position > (start + end) / 2)
{
if (end == self->cache_pos && gtk_list_list_model_cache_is_valid (self))
result = self->get_previous (self->cache_item, self->data);
else
result = self->get_last (self->data);
for (i = end - 1; i > position; i--)
{
result = self->get_previous (result, self->data);
}
}
else
{
if (start == self->cache_pos && gtk_list_list_model_cache_is_valid (self))
result = self->cache_item;
else
result = self->get_first (self->data);
for (i = start; i < position; i++)
{
result = self->get_next (result, self->data);
}
}
self->cache_item = result;
self->cache_pos = position;
return self->get_item (result, self->data);
}
static void
gtk_list_list_model_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_list_list_model_get_item_type;
iface->get_n_items = gtk_list_list_model_get_n_items;
iface->get_item = gtk_list_list_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkListListModel, gtk_list_list_model,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_list_list_model_list_model_init))
static void
gtk_list_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkListListModel *self = GTK_LIST_LIST_MODEL (object);
switch (prop_id)
{
case PROP_ITEM_TYPE:
g_value_set_gtype (value, G_TYPE_OBJECT);
break;
case PROP_N_ITEMS:
g_value_set_uint (value, self->n_items);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_list_list_model_dispose (GObject *object)
{
GtkListListModel *self = GTK_LIST_LIST_MODEL (object);
if (self->notify)
self->notify (self->data);
self->n_items = 0;
self->notify = NULL;
G_OBJECT_CLASS (gtk_list_list_model_parent_class)->dispose (object);
}
static void
gtk_list_list_model_class_init (GtkListListModelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = gtk_list_list_model_get_property;
object_class->dispose = gtk_list_list_model_dispose;
properties[PROP_ITEM_TYPE] =
g_param_spec_gtype ("item-type", NULL, NULL,
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
properties[PROP_N_ITEMS] =
g_param_spec_uint ("n-items", NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
gtk_list_list_model_init (GtkListListModel *self)
{
}
GtkListListModel *
gtk_list_list_model_new (gpointer (* get_first) (gpointer),
gpointer (* get_next) (gpointer, gpointer),
gpointer (* get_previous) (gpointer, gpointer),
gpointer (* get_last) (gpointer),
gpointer (* get_item) (gpointer, gpointer),
gpointer data,
GDestroyNotify notify)
{
guint n_items;
gpointer item;
n_items = 0;
for (item = get_first (data);
item != NULL;
item = get_next (item, data))
n_items++;
return gtk_list_list_model_new_with_size (n_items,
get_first,
get_next,
get_previous,
get_last,
get_item,
data,
notify);
}
GtkListListModel *
gtk_list_list_model_new_with_size (guint n_items,
gpointer (* get_first) (gpointer),
gpointer (* get_next) (gpointer, gpointer),
gpointer (* get_previous) (gpointer, gpointer),
gpointer (* get_last) (gpointer),
gpointer (* get_item) (gpointer, gpointer),
gpointer data,
GDestroyNotify notify)
{
GtkListListModel *result;
g_return_val_if_fail (get_first != NULL, NULL);
g_return_val_if_fail (get_next != NULL, NULL);
g_return_val_if_fail (get_previous != NULL, NULL);
g_return_val_if_fail (get_item != NULL, NULL);
result = g_object_new (GTK_TYPE_LIST_LIST_MODEL, NULL);
result->n_items = n_items;
result->get_first = get_first;
result->get_next = get_next;
result->get_previous = get_previous;
result->get_last = get_last;
result->get_item = get_item;
result->data = data;
result->notify = notify;
return result;
}
static guint
gtk_list_list_model_find (GtkListListModel *self,
gpointer item)
{
guint position;
gpointer x;
position = 0;
for (x = self->get_first (self->data);
x != item;
x = self->get_next (x, self->data))
position++;
return position;
}
void
gtk_list_list_model_item_added (GtkListListModel *self,
gpointer item)
{
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != NULL);
gtk_list_list_model_item_added_at (self, gtk_list_list_model_find (self, item));
}
void
gtk_list_list_model_item_added_at (GtkListListModel *self,
guint position)
{
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (position <= self->n_items);
self->n_items++;
if (position <= self->cache_pos)
self->cache_pos++;
g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
}
void
gtk_list_list_model_item_removed (GtkListListModel *self,
gpointer previous)
{
guint position;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
if (previous == NULL)
position = 0;
else
position = 1 + gtk_list_list_model_find (self, previous);
gtk_list_list_model_item_removed_at (self, position);
}
void
gtk_list_list_model_item_moved (GtkListListModel *self,
gpointer item,
gpointer previous_previous)
{
guint position, previous_position;
guint min, max;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != previous_previous);
position = gtk_list_list_model_find (self, item);
if (previous_previous == NULL)
{
previous_position = 0;
}
else
{
previous_position = gtk_list_list_model_find (self, previous_previous);
if (position > previous_position)
previous_position++;
}
/* item didn't move */
if (position == previous_position)
return;
min = MIN (position, previous_position);
max = MAX (position, previous_position) + 1;
if (self->cache_item == item)
self->cache_pos = position;
else if (self->cache_pos >= min && self->cache_pos < max)
self->cache_pos += (self->cache_pos > position ? 1 : -1);
g_list_model_items_changed (G_LIST_MODEL (self), min, max - min, max - min);
}
void
gtk_list_list_model_item_removed_at (GtkListListModel *self,
guint position)
{
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (position < self->n_items);
self->n_items -= 1;
if (position == self->cache_pos)
gtk_list_list_model_invalidate_cache (self);
else if (position < self->cache_pos)
self->cache_pos--;
g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
}
void
gtk_list_list_model_clear (GtkListListModel *self)
{
guint n_items;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
n_items = self->n_items;
if (self->notify)
self->notify (self->data);
self->n_items = 0;
self->notify = NULL;
gtk_list_list_model_invalidate_cache (self);
if (n_items > 0)
{
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
}
}