[gtk+] Cache GtkIconInfo



commit 92e904a25710a00b8f1afb0e33a69680712d0c69
Author: Alexander Larsson <alexl redhat com>
Date:   Mon Nov 26 13:49:49 2012 +0100

    Cache GtkIconInfo
    
    In order to avoid loading and keeping around the same icon multiple times
    we keep a cache of all outstanding GtkIconInfo objects for a given theme.
    
    Additionally we return to the app not the normal pixbuf from the info,
    but rather a proxy copy of it sharing the same data, but no extra
    reference. This allows us to track when the app is no longer using
    the pixbuf, and we can thus ensure that the GtkIconInfo in the cache
    stays around for at least as long as the pixbuf is alive.
    
    When the app unrefs the pixbuf we put the Info on a short LRU list
    to keep it alive a bit longer, in case the app needs it in a short
    while.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=689081

 gtk/gtkicontheme.c |  260 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 255 insertions(+), 5 deletions(-)
---
diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c
index 90806ee..6a20548 100644
--- a/gtk/gtkicontheme.c
+++ b/gtk/gtkicontheme.c
@@ -160,9 +160,18 @@ typedef enum
   HAS_ICON_FILE = 1 << 3
 } IconSuffix;
 
+#define INFO_CACHE_LRU_SIZE 32
+#if 0
+#define DEBUG_CACHE(args) g_print args
+#else
+#define DEBUG_CACHE(args)
+#endif
 
 struct _GtkIconThemePrivate
 {
+  GHashTable *info_cache;
+  GList *info_cache_lru;
+
   gchar *current_theme;
   gchar *fallback_theme;
   gchar **search_path;
@@ -197,10 +206,19 @@ struct _GtkIconThemePrivate
   gulong reset_styles_idle;
 };
 
+typedef struct {
+  gchar **icon_names;
+  gint size;
+  GtkIconLookupFlags flags;
+} IconInfoKey;
+
 struct _GtkIconInfo
 {
   /* Information about the source
    */
+  IconInfoKey key;
+  GtkIconTheme *in_cache;
+
   gchar *filename;
   GFile *icon_file;
   GLoadableIcon *loadable;
@@ -231,6 +249,7 @@ struct _GtkIconInfo
    * the icon.
    */
   GdkPixbuf *pixbuf;
+  GdkPixbuf *proxy_pixbuf;
   GError *load_error;
   gdouble scale;
 
@@ -330,6 +349,8 @@ static BuiltinIcon *find_builtin_icon (const gchar *icon_name,
 				       gint        size,
 				       gint        *min_difference_p,
 				       gboolean    *has_larger_p);
+static void remove_from_lru_cache (GtkIconTheme *icon_theme,
+				   GtkIconInfo *icon_info);
 
 static guint signal_changed = 0;
 
@@ -339,6 +360,46 @@ static GHashTable *icon_theme_builtin_icons;
 GtkIconCache *_builtin_cache = NULL;
 static GList *builtin_dirs = NULL;
 
+static guint
+icon_info_key_hash (gconstpointer _key)
+{
+  const IconInfoKey *key = _key;
+  guint h = 0;
+  int i;
+  for (i = 0; key->icon_names[i] != NULL; i++)
+    h ^= g_str_hash (key->icon_names[i]);
+
+  h ^= key->size * 0x10001;
+  h ^= key->flags * 0x1000010;
+
+  return h;
+}
+
+static gboolean
+icon_info_key_equal (gconstpointer  _a,
+		     gconstpointer  _b)
+{
+  const IconInfoKey *a = _a;
+  const IconInfoKey *b = _b;
+  int i;
+
+  if (a->size != b->size)
+    return FALSE;
+
+  if (a->flags != b->flags)
+    return FALSE;
+
+  for (i = 0;
+       a->icon_names[i] != NULL &&
+       b->icon_names[i] != NULL; i++)
+    {
+      if (strcmp (a->icon_names[i], b->icon_names[i]) != 0)
+	return FALSE;
+    }
+
+  return a->icon_names[i] == NULL && b->icon_names[i] == NULL;
+}
+
 G_DEFINE_TYPE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT)
 
 /**
@@ -641,6 +702,25 @@ pixbuf_supports_svg (void)
   return found_svg;
 }
 
+/* The icon info was removed from the icon_info_hash hash table */
+static void
+icon_info_uncached (GtkIconInfo *icon_info)
+{
+  GtkIconTheme *icon_theme = icon_info->in_cache;
+
+  DEBUG_CACHE (("removing %p (%s %d 0x%x) from cache (icon_them: %p)  (cache size %d)\n",
+		icon_info,
+		g_strjoinv (",", icon_info->key.icon_names),
+		icon_info->key.size, icon_info->key.flags,
+		icon_theme,
+		icon_theme != NULL ? g_hash_table_size (icon_theme->priv->info_cache) : 0));
+
+  icon_info->in_cache = NULL;
+
+  if (icon_theme != NULL)
+    remove_from_lru_cache (icon_theme, icon_info);
+}
+
 static void
 gtk_icon_theme_init (GtkIconTheme *icon_theme)
 {
@@ -653,6 +733,9 @@ gtk_icon_theme_init (GtkIconTheme *icon_theme)
                                       GtkIconThemePrivate);
   icon_theme->priv = priv;
 
+  priv->info_cache = g_hash_table_new_full (icon_info_key_hash, icon_info_key_equal, NULL,
+					    (GDestroyNotify)icon_info_uncached);
+
   priv->custom_theme = FALSE;
 
   xdg_data_dirs = g_get_system_data_dirs ();
@@ -712,6 +795,8 @@ do_theme_change (GtkIconTheme *icon_theme)
 {
   GtkIconThemePrivate *priv = icon_theme->priv;
 
+  g_hash_table_remove_all (priv->info_cache);
+
   if (!priv->themes_valid)
     return;
   
@@ -755,6 +840,9 @@ gtk_icon_theme_finalize (GObject *object)
   icon_theme = GTK_ICON_THEME (object);
   priv = icon_theme->priv;
 
+  g_hash_table_destroy (priv->info_cache);
+  g_assert (priv->info_cache_lru == NULL);
+
   if (priv->reset_styles_idle)
     {
       g_source_remove (priv->reset_styles_idle);
@@ -1306,6 +1394,95 @@ ensure_valid_themes (GtkIconTheme *icon_theme)
   priv->loading_themes = FALSE;
 }
 
+/* The LRU cache is a short list of IconInfos that are kept
+   alive even though their IconInfo would otherwise have
+   been freed, so that we can avoid reloading these
+   constantly.
+   We put infos on the lru list when nothing otherwise
+   references the info. So, when we get a cache hit
+   we remove it from the list, and when the proxy
+   pixmap is released we put it on the list.
+*/
+
+static void
+ensure_lru_cache_space (GtkIconTheme *icon_theme)
+{
+  GtkIconThemePrivate *priv = icon_theme->priv;
+  GList *l;
+
+  /* Remove last item if LRU full */
+  l = g_list_nth (priv->info_cache_lru, INFO_CACHE_LRU_SIZE - 1);
+  if (l)
+    {
+      GtkIconInfo *icon_info = l->data;
+
+      DEBUG_CACHE (("removing (due to out of space) %p (%s %d 0x%x) from LRU cache (cache size %d)\n",
+		    icon_info,
+		    g_strjoinv (",", icon_info->key.icon_names),
+		    icon_info->key.size, icon_info->key.flags,
+		    g_list_length (priv->info_cache_lru)));
+
+      priv->info_cache_lru = g_list_delete_link (priv->info_cache_lru, l);
+      gtk_icon_info_free (icon_info);
+    }
+}
+
+static void
+add_to_lru_cache (GtkIconTheme *icon_theme,
+		  GtkIconInfo *icon_info)
+{
+  GtkIconThemePrivate *priv = icon_theme->priv;
+
+  DEBUG_CACHE (("adding  %p (%s %d 0x%x) to LRU cache (cache size %d)\n",
+		icon_info,
+		g_strjoinv (",", icon_info->key.icon_names),
+		icon_info->key.size, icon_info->key.flags,
+		g_list_length (priv->info_cache_lru)));
+
+  g_assert (g_list_find (priv->info_cache_lru, icon_info) == NULL);
+
+  ensure_lru_cache_space (icon_theme);
+  /* prepend new info to LRU */
+  priv->info_cache_lru = g_list_prepend (priv->info_cache_lru,
+					 gtk_icon_info_copy (icon_info));
+}
+
+static void
+ensure_in_lru_cache (GtkIconTheme *icon_theme,
+		     GtkIconInfo *icon_info)
+{
+  GtkIconThemePrivate *priv = icon_theme->priv;
+  GList *l;
+
+  l = g_list_find (priv->info_cache_lru, icon_info);
+  if (l)
+    {
+      /* Move to front of LRU if already in it */
+      priv->info_cache_lru = g_list_remove_link (priv->info_cache_lru, l);
+      priv->info_cache_lru = g_list_concat (l, priv->info_cache_lru);
+    }
+  else
+    add_to_lru_cache (icon_theme, icon_info);
+}
+
+static void
+remove_from_lru_cache (GtkIconTheme *icon_theme,
+		       GtkIconInfo *icon_info)
+{
+  GtkIconThemePrivate *priv = icon_theme->priv;
+  if (g_list_find (priv->info_cache_lru, icon_info))
+    {
+      DEBUG_CACHE (("removing %p (%s %d 0x%x) from LRU cache (cache size %d)\n",
+		    icon_info,
+		    g_strjoinv (",", icon_info->key.icon_names),
+		    icon_info->key.size, icon_info->key.flags,
+		    g_list_length (priv->info_cache_lru)));
+
+      priv->info_cache_lru = g_list_remove (priv->info_cache_lru, icon_info);
+      gtk_icon_info_free (icon_info);
+    }
+}
+
 static GtkIconInfo *
 choose_icon (GtkIconTheme       *icon_theme,
 	     const gchar        *icon_names[],
@@ -1319,9 +1496,31 @@ choose_icon (GtkIconTheme       *icon_theme,
   gboolean allow_svg;
   gboolean use_builtin;
   gint i;
+  IconInfoKey key;
 
   priv = icon_theme->priv;
 
+  ensure_valid_themes (icon_theme);
+
+  key.icon_names = (char **)icon_names;
+  key.size = size;
+  key.flags = flags;
+
+  icon_info = g_hash_table_lookup (priv->info_cache, &key);
+  if (icon_info != NULL)
+    {
+      DEBUG_CACHE (("cache hit %p (%s %d 0x%x) (cache size %d)\n",
+		    icon_info,
+		    g_strjoinv (",", icon_info->key.icon_names),
+		    icon_info->key.size, icon_info->key.flags,
+		    g_hash_table_size (priv->info_cache)));
+
+      icon_info = gtk_icon_info_copy (icon_info);
+      remove_from_lru_cache (icon_theme, icon_info);
+
+      return icon_info;
+    }
+
   if (flags & GTK_ICON_LOOKUP_NO_SVG)
     allow_svg = FALSE;
   else if (flags & GTK_ICON_LOOKUP_FORCE_SVG)
@@ -1330,8 +1529,6 @@ choose_icon (GtkIconTheme       *icon_theme,
     allow_svg = priv->pixbuf_supports_svg;
 
   use_builtin = flags & GTK_ICON_LOOKUP_USE_BUILTIN;
-  
-  ensure_valid_themes (icon_theme);
 
   /* for symbolic icons, do a search in all registered themes first;
    * a theme that inherits them from a parent theme might provide
@@ -1417,10 +1614,21 @@ choose_icon (GtkIconTheme       *icon_theme,
     }
 
  out:
-  if (icon_info) 
+  if (icon_info)
     {
       icon_info->desired_size = size;
       icon_info->forced_size = (flags & GTK_ICON_LOOKUP_FORCE_SIZE) != 0;
+
+      icon_info->key.icon_names = g_strdupv ((char **)icon_names);
+      icon_info->key.size = size;
+      icon_info->key.flags = flags;
+      icon_info->in_cache = icon_theme;
+      DEBUG_CACHE (("adding %p (%s %d 0x%x) to cache (cache size %d)\n",
+		    icon_info,
+		    g_strjoinv (",", icon_info->key.icon_names),
+		    icon_info->key.size, icon_info->key.flags,
+		    g_hash_table_size (priv->info_cache)));
+     g_hash_table_insert (priv->info_cache, &icon_info->key, icon_info);
     }
   else
     {
@@ -2735,7 +2943,12 @@ gtk_icon_info_free (GtkIconInfo *icon_info)
   icon_info->ref_count--;
   if (icon_info->ref_count > 0)
     return;
- 
+
+  if (icon_info->in_cache)
+    g_hash_table_remove (icon_info->in_cache->priv->info_cache, &icon_info->key);
+
+  g_strfreev (icon_info->key.icon_names);
+
   g_free (icon_info->filename);
   g_clear_object (&icon_info->icon_file);
 
@@ -3095,6 +3308,22 @@ icon_info_ensure_scale_and_pixbuf (GtkIconInfo  *icon_info,
   return TRUE;
 }
 
+static void
+proxy_pixbuf_destroy (guchar *pixels, gpointer data)
+{
+  GtkIconInfo *icon_info = data;
+  GtkIconTheme *icon_theme = icon_info->in_cache;
+
+  g_assert (icon_info->proxy_pixbuf != NULL);
+  icon_info->proxy_pixbuf = NULL;
+
+  /* Keep it alive a bit longer */
+  if (icon_theme != NULL)
+    ensure_in_lru_cache (icon_theme, icon_info);
+
+  gtk_icon_info_free (icon_info);
+}
+
 /**
  * gtk_icon_info_load_icon:
  * @icon_info: a #GtkIconInfo structure from gtk_icon_theme_lookup_icon()
@@ -3140,7 +3369,28 @@ gtk_icon_info_load_icon (GtkIconInfo *icon_info,
       return NULL;
     }
 
-  return g_object_ref (icon_info->pixbuf);
+  /* Instead of returning the pixbuf directly we
+     return a proxy to it that we don't own (but that
+     shares the data with the one we own). This way
+     we can know when it is freed and ensure the
+     IconInfo is alive (and thus cached) while
+     the pixbuf is still alive. */
+
+  if (icon_info->proxy_pixbuf != NULL)
+    return g_object_ref (icon_info->proxy_pixbuf);
+
+  icon_info->proxy_pixbuf =
+    gdk_pixbuf_new_from_data (gdk_pixbuf_get_pixels (icon_info->pixbuf),
+			      gdk_pixbuf_get_colorspace (icon_info->pixbuf),
+			      gdk_pixbuf_get_has_alpha (icon_info->pixbuf),
+			      gdk_pixbuf_get_bits_per_sample (icon_info->pixbuf),
+			      gdk_pixbuf_get_width (icon_info->pixbuf),
+			      gdk_pixbuf_get_height (icon_info->pixbuf),
+			      gdk_pixbuf_get_rowstride (icon_info->pixbuf),
+			      proxy_pixbuf_destroy,
+			      gtk_icon_info_copy (icon_info));
+
+  return icon_info->proxy_pixbuf;
 }
 
 static gchar *



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]