[libshumate] memory-cache: Add methods to store/retrieve tiles



commit cf35575bc5892c748b3d14307106fd6c001c62ee
Author: James Westman <james jwestman net>
Date:   Tue Mar 23 22:56:17 2021 -0500

    memory-cache: Add methods to store/retrieve tiles
    
    Add methods to directly store and fill tiles from a memory cache,
    rather than going through the ShumateMapSource interface. This will
    allow ShumateMapLayer to use its own memory cache, simplifying the map
    source system.
    
    Also added tests for the new methods.

 shumate/shumate-memory-cache.c | 112 ++++++++++++++++++++++----------
 shumate/shumate-memory-cache.h |   8 +++
 tests/memory-cache.c           | 142 +++++++++++++++++++++++++++++++++++++++++
 tests/meson.build              |   1 +
 4 files changed, 230 insertions(+), 33 deletions(-)
---
diff --git a/shumate/shumate-memory-cache.c b/shumate/shumate-memory-cache.c
index d505348..0afa25c 100644
--- a/shumate/shumate-memory-cache.c
+++ b/shumate/shumate-memory-cache.c
@@ -230,7 +230,8 @@ shumate_memory_cache_set_size_limit (ShumateMemoryCache *memory_cache,
 
 static char *
 generate_queue_key (ShumateMemoryCache *memory_cache,
-    ShumateTile *tile)
+    ShumateTile *tile,
+    const char *source_id)
 {
   g_return_val_if_fail (SHUMATE_IS_MEMORY_CACHE (memory_cache), NULL);
   g_return_val_if_fail (SHUMATE_IS_TILE (tile), NULL);
@@ -238,11 +239,14 @@ generate_queue_key (ShumateMemoryCache *memory_cache,
   ShumateMapSource *map_source = SHUMATE_MAP_SOURCE (memory_cache);
   char *key;
 
+  if (source_id == NULL)
+    source_id = shumate_map_source_get_id (map_source);
+
   key = g_strdup_printf ("%d/%d/%d/%s",
         shumate_tile_get_zoom_level (tile),
         shumate_tile_get_x (tile),
         shumate_tile_get_y (tile),
-        shumate_map_source_get_id (map_source));
+        source_id);
   return key;
 }
 
@@ -284,7 +288,7 @@ fill_tile (ShumateMapSource *map_source,
       ShumateMemoryCache *memory_cache = SHUMATE_MEMORY_CACHE (map_source);
       ShumateMemoryCachePrivate *priv = shumate_memory_cache_get_instance_private (memory_cache);
       GList *link;
-      g_autofree char *key = generate_queue_key (memory_cache, tile);
+      g_autofree char *key = generate_queue_key (memory_cache, tile, NULL);
 
       link = g_hash_table_lookup (priv->hash_table, key);
       if (link)
@@ -350,36 +354,9 @@ store_tile (ShumateTileCache *tile_cache,
 
   ShumateMapSource *map_source = SHUMATE_MAP_SOURCE (tile_cache);
   ShumateMapSource *next_source = shumate_map_source_get_next_source (map_source);
-  ShumateMemoryCache *memory_cache = SHUMATE_MEMORY_CACHE (tile_cache);
-  ShumateMemoryCachePrivate *priv = shumate_memory_cache_get_instance_private (memory_cache);
-  GList *link;
-  char *key;
-
-  key = generate_queue_key (memory_cache, tile);
-  link = g_hash_table_lookup (priv->hash_table, key);
-  if (link)
-    {
-      move_queue_member_to_head (priv->queue, link);
-      g_free (key);
-    }
-  else
-    {
-      QueueMember *member;
-
-      if (priv->queue->length >= priv->size_limit)
-        {
-          member = g_queue_pop_tail (priv->queue);
-          g_hash_table_remove (priv->hash_table, member->key);
-          delete_queue_member (member, NULL);
-        }
+  g_autoptr(GdkTexture) texture = create_texture_from_data (contents, size);
 
-      member = g_new0 (QueueMember, 1);
-      member->key = key;
-      member->texture = create_texture_from_data (contents, size);
-
-      g_queue_push_head (priv->queue, member);
-      g_hash_table_insert (priv->hash_table, g_strdup (key), g_queue_peek_head_link (priv->queue));
-    }
+  shumate_memory_cache_store_texture (SHUMATE_MEMORY_CACHE (tile_cache), tile, texture, NULL);
 
   if (SHUMATE_IS_TILE_CACHE (next_source))
     shumate_tile_cache_store_tile (SHUMATE_TILE_CACHE (next_source), tile, contents, size);
@@ -418,6 +395,75 @@ shumate_memory_cache_clean (ShumateMemoryCache *memory_cache)
 }
 
 
+gboolean
+shumate_memory_cache_try_fill_tile (ShumateMemoryCache *self, ShumateTile *tile, const char *source_id)
+{
+  ShumateMemoryCache *memory_cache = (ShumateMemoryCache *) self;
+  ShumateMemoryCachePrivate *priv = shumate_memory_cache_get_instance_private (memory_cache);
+  GList *link;
+  QueueMember *member;
+  g_autofree char *key = NULL;
+
+  g_return_val_if_fail (SHUMATE_IS_MEMORY_CACHE (self), FALSE);
+  g_return_val_if_fail (SHUMATE_IS_TILE (tile), FALSE);
+
+  key = generate_queue_key (memory_cache, tile, source_id);
+
+  link = g_hash_table_lookup (priv->hash_table, key);
+  if (link == NULL)
+    return FALSE;
+
+  member = link->data;
+
+  move_queue_member_to_head (priv->queue, link);
+
+  if (!member->texture)
+    return FALSE;
+
+  shumate_tile_set_texture (tile, member->texture);
+  shumate_tile_set_fade_in (tile, FALSE);
+  shumate_tile_set_state (tile, SHUMATE_STATE_DONE);
+  return TRUE;
+}
+
+void
+shumate_memory_cache_store_texture (ShumateMemoryCache *self, ShumateTile *tile, GdkTexture *texture, const 
char *source_id)
+{
+  ShumateMemoryCachePrivate *priv = shumate_memory_cache_get_instance_private (self);
+  GList *link;
+  char *key;
+
+  g_return_if_fail (SHUMATE_IS_MEMORY_CACHE (self));
+  g_return_if_fail (SHUMATE_IS_TILE (tile));
+
+  key = generate_queue_key (self, tile, source_id);
+  link = g_hash_table_lookup (priv->hash_table, key);
+  if (link)
+    {
+      move_queue_member_to_head (priv->queue, link);
+      g_free (key);
+    }
+  else
+    {
+      QueueMember *member;
+
+      if (priv->queue->length >= priv->size_limit)
+        {
+          member = g_queue_pop_tail (priv->queue);
+          g_hash_table_remove (priv->hash_table, member->key);
+          delete_queue_member (member, NULL);
+        }
+
+      member = g_new0 (QueueMember, 1);
+      member->key = key;
+      member->texture = g_object_ref (texture);
+
+      g_queue_push_head (priv->queue, member);
+      g_hash_table_insert (priv->hash_table, g_strdup (key), g_queue_peek_head_link (priv->queue));
+    }
+}
+
+
 static void
 on_tile_filled (ShumateTileCache *tile_cache,
     ShumateTile *tile)
@@ -432,7 +478,7 @@ on_tile_filled (ShumateTileCache *tile_cache,
   GList *link;
   char *key;
 
-  key = generate_queue_key (memory_cache, tile);
+  key = generate_queue_key (memory_cache, tile, NULL);
   link = g_hash_table_lookup (priv->hash_table, key);
   g_free (key);
   if (link)
diff --git a/shumate/shumate-memory-cache.h b/shumate/shumate-memory-cache.h
index 9caad99..2b8ad9a 100644
--- a/shumate/shumate-memory-cache.h
+++ b/shumate/shumate-memory-cache.h
@@ -52,6 +52,14 @@ void shumate_memory_cache_set_size_limit (ShumateMemoryCache *memory_cache,
 
 void shumate_memory_cache_clean (ShumateMemoryCache *memory_cache);
 
+gboolean shumate_memory_cache_try_fill_tile (ShumateMemoryCache *self,
+                                             ShumateTile        *tile,
+                                             const char         *source_id);
+void shumate_memory_cache_store_texture (ShumateMemoryCache *self,
+                                         ShumateTile        *tile,
+                                         GdkTexture         *texture,
+                                         const char         *source_id);
+
 G_END_DECLS
 
 #endif /* _SHUMATE_MEMORY_CACHE_H_ */
diff --git a/tests/memory-cache.c b/tests/memory-cache.c
new file mode 100644
index 0000000..95670a7
--- /dev/null
+++ b/tests/memory-cache.c
@@ -0,0 +1,142 @@
+#include <shumate/shumate.h>
+
+
+static GdkTexture *
+create_texture ()
+{
+  g_autoptr(GdkPixbuf) pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 256, 256);
+  return gdk_texture_new_for_pixbuf (pixbuf);
+}
+
+
+/* Test that storing and retrieving a texture from the cache works */
+static void
+test_memory_cache_store_retrieve ()
+{
+  g_autoptr(ShumateMemoryCache) cache = shumate_memory_cache_new_full (100);
+  g_autoptr(ShumateTile) tile = shumate_tile_new_full (0, 0, 256, 0);
+  g_autoptr(GdkTexture) texture = create_texture ();
+
+  g_object_ref_sink (tile);
+
+  /* Store the tile */
+  shumate_memory_cache_store_texture (cache, tile, texture, "A");
+
+  /* Now retrieve it */
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "A"));
+  g_assert_true (texture == shumate_tile_get_texture (tile));
+}
+
+
+/* Test that cache misses work properly */
+static void
+test_memory_cache_miss ()
+{
+  g_autoptr(ShumateMemoryCache) cache = shumate_memory_cache_new_full (100);
+  g_autoptr(ShumateTile) tile1 = shumate_tile_new_full (0, 0, 256, 0);
+  g_autoptr(ShumateTile) tile2 = shumate_tile_new_full (0, 0, 256, 1);
+  g_autoptr(GdkTexture) texture = create_texture ();
+
+  g_object_ref_sink (tile1);
+  g_object_ref_sink (tile2);
+
+  /* Store a tile */
+  shumate_memory_cache_store_texture (cache, tile1, texture, "A");
+
+  /* Now retrieve a different one */
+  g_assert_false (shumate_memory_cache_try_fill_tile (cache, tile2, "A"));
+  g_assert_null (shumate_tile_get_texture (tile2));
+}
+
+
+/* Test that multiple sources can be cached in parallel */
+static void
+test_memory_cache_source_id ()
+{
+  g_autoptr(ShumateMemoryCache) cache = shumate_memory_cache_new_full (100);
+  g_autoptr(ShumateTile) tile = shumate_tile_new_full (0, 0, 256, 0);
+  g_autoptr(GdkTexture) texture1 = create_texture ();
+  g_autoptr(GdkTexture) texture2 = create_texture ();
+
+  g_object_ref_sink (tile);
+
+  /* Store the tiles */
+  shumate_memory_cache_store_texture (cache, tile, texture1, "A");
+  shumate_memory_cache_store_texture (cache, tile, texture2, "B");
+
+  /* Now retrieve them */
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "A"));
+  g_assert_true (texture1 == shumate_tile_get_texture (tile));
+
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "B"));
+  g_assert_true (texture2 == shumate_tile_get_texture (tile));
+}
+
+
+/* Test that the cache is purged properly */
+static void
+test_memory_cache_purge ()
+{
+  g_autoptr(ShumateMemoryCache) cache = shumate_memory_cache_new_full (3);
+  g_autoptr(ShumateTile) tile = shumate_tile_new_full (0, 0, 256, 0);
+  g_autoptr(GdkTexture) texture = create_texture ();
+
+  g_object_ref_sink (tile);
+
+  /* Store a few tiles */
+  shumate_memory_cache_store_texture (cache, tile, texture, "A");
+  shumate_memory_cache_store_texture (cache, tile, texture, "B");
+  shumate_memory_cache_store_texture (cache, tile, texture, "C");
+
+  /* Make sure they're all still cached */
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "B"));
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "A"));
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "C"));
+
+  /* Store another one */
+  shumate_memory_cache_store_texture (cache, tile, texture, "D");
+
+  /* Since B was the least recently accessed, it should be the one that was
+   * dropped */
+  g_assert_false (shumate_memory_cache_try_fill_tile (cache, tile, "B"));
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "A"));
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "C"));
+  g_assert_true (shumate_memory_cache_try_fill_tile (cache, tile, "D"));
+}
+
+
+/* Test that cleaning the cache works */
+static void
+test_memory_cache_clean ()
+{
+  g_autoptr(ShumateMemoryCache) cache = shumate_memory_cache_new_full (100);
+  g_autoptr(ShumateTile) tile = shumate_tile_new_full (0, 0, 256, 0);
+  g_autoptr(GdkTexture) texture = create_texture ();
+
+  g_object_ref_sink (tile);
+
+  /* Store a tile */
+  shumate_memory_cache_store_texture (cache, tile, texture, "A");
+
+  /* Clean the cache */
+  shumate_memory_cache_clean (cache);
+
+  /* Make sure it worked */
+  g_assert_false (shumate_memory_cache_try_fill_tile (cache, tile, "A"));
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  gtk_init ();
+
+  g_test_add_func ("/file-cache/store-retrieve", test_memory_cache_store_retrieve);
+  g_test_add_func ("/file-cache/miss", test_memory_cache_miss);
+  g_test_add_func ("/file-cache/source-id", test_memory_cache_source_id);
+  g_test_add_func ("/file-cache/purge", test_memory_cache_purge);
+  g_test_add_func ("/file-cache/clean", test_memory_cache_clean);
+
+  return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index a9eaea6..52866b2 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -12,6 +12,7 @@ tests = [
   'marker',
   'view',
   'marker-layer',
+  'memory-cache',
   'network-tile-source',
   'viewport',
 ]


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