summaryrefslogtreecommitdiff
path: root/src/cairo-cache.c
diff options
context:
space:
mode:
authorKeith Packard <keithp@keithp.com>2005-08-31 15:08:02 +0000
committerKeith Packard <keithp@keithp.com>2005-08-31 15:08:02 +0000
commitb0c58593b30c1fa085b1e7c8e4897da623b8686d (patch)
tree41201033b243864d4dea6becc75d12bf4acb2f37 /src/cairo-cache.c
parent568ce860264e63f86ae45258eb106fb7a74a33a3 (diff)
Split out scaled font code to cairo-scaled-font.c
Replace cairo cache implementation (this code from cworth) No more global glyph cache to clean up Store glyphs in new per-scaled font caches which hold user-space metrics and device space bounding boxes Refactor glyph drawing APIs so that the surface API is invoked directly from the gstate code. Add path creation/destruction routines (to hold glyph paths) New implementation of scaled fonts which uses per-scaled_font caches for glyphs and keeps user-space metrics, device-space bboxes along with glyph images and/or glyph paths. Adapt to new scaled font API changes. New cache and scaled_font APIs Repond to bug fix in metrics computation for glyphs where y values were rounded up instead of down because of a sign difference between cairo and FreeType. Reviewed by: otaylor, cworth
Diffstat (limited to 'src/cairo-cache.c')
-rw-r--r--src/cairo-cache.c660
1 files changed, 239 insertions, 421 deletions
diff --git a/src/cairo-cache.c b/src/cairo-cache.c
index b43bccab..25a35bf8 100644
--- a/src/cairo-cache.c
+++ b/src/cairo-cache.c
@@ -1,6 +1,7 @@
/* cairo - a vector graphics library with display and print output
*
- * This file is Copyright © 2004 Red Hat, Inc.
+ * Copyright © 2004 Red Hat, Inc.
+ * Copyright © 2005 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
@@ -30,481 +31,298 @@
* The Initial Developer of the Original Code is Red Hat, Inc.
*
* Contributor(s):
- * Keith Packard
+ * Keith Packard <keithp@keithp.com>
* Graydon Hoare <graydon@redhat.com>
+ * Carl Worth <cworth@cworth.org>
*/
#include "cairoint.h"
-/*
- * This structure, and accompanying table, is borrowed/modified from the
- * file xserver/render/glyph.c in the freedesktop.org x server, with
- * permission (and suggested modification of doubling sizes) by Keith
- * Packard.
- */
+struct _cairo_cache {
+ cairo_hash_table_t *hash_table;
-static const cairo_cache_arrangement_t cache_arrangements [] = {
- { 16, 43, 41 },
- { 32, 73, 71 },
- { 64, 151, 149 },
- { 128, 283, 281 },
- { 256, 571, 569 },
- { 512, 1153, 1151 },
- { 1024, 2269, 2267 },
- { 2048, 4519, 4517 },
- { 4096, 9013, 9011 },
- { 8192, 18043, 18041 },
- { 16384, 36109, 36107 },
- { 32768, 72091, 72089 },
- { 65536, 144409, 144407 },
- { 131072, 288361, 288359 },
- { 262144, 576883, 576881 },
- { 524288, 1153459, 1153457 },
- { 1048576, 2307163, 2307161 },
- { 2097152, 4613893, 4613891 },
- { 4194304, 9227641, 9227639 },
- { 8388608, 18455029, 18455027 },
- { 16777216, 36911011, 36911009 },
- { 33554432, 73819861, 73819859 },
- { 67108864, 147639589, 147639587 },
- { 134217728, 295279081, 295279079 },
- { 268435456, 590559793, 590559791 }
-};
+ cairo_destroy_func_t entry_destroy;
-#define N_CACHE_SIZES (sizeof(cache_arrangements)/sizeof(cache_arrangements[0]))
+ unsigned long max_size;
+ unsigned long size;
-/*
- * Entries 'e' are poiners, in one of 3 states:
- *
- * e == NULL: The entry has never had anything put in it
- * e != DEAD_ENTRY: The entry has an active value in it currently
- * e == DEAD_ENTRY: The entry *had* a value in it at some point, but the
- * entry has been killed. Lookups requesting free space can
- * reuse these entries; lookups requesting a precise match
- * should neither return these entries nor stop searching when
- * seeing these entries.
- *
- * We expect keys will not be destroyed frequently, so our table does not
- * contain any explicit shrinking code nor any chain-coalescing code for
- * entries randomly deleted by memory pressure (except during rehashing, of
- * course). These assumptions are potentially bad, but they make the
- * implementation straightforward.
- *
- * Revisit later if evidence appears that we're using excessive memory from
- * a mostly-dead table.
- *
- * Generally you do not need to worry about freeing cache entries; the
- * cache will expire entries randomly as it experiences memory pressure.
- * If max_memory is set, entries are not expired, and must be explicitely
- * removed.
- *
- * This table is open-addressed with double hashing. Each table size is a
- * prime chosen to be a little more than double the high water mark for a
- * given arrangement, so the tables should remain < 50% full. The table
- * size makes for the "first" hash modulus; a second prime (2 less than the
- * first prime) serves as the "second" hash modulus, which is co-prime and
- * thus guarantees a complete permutation of table indices.
- *
- */
+ cairo_bool_t preserve_entries;
+};
-#define DEAD_ENTRY ((cairo_cache_entry_base_t *) 1)
-#define NULL_ENTRY_P(cache, i) ((cache)->entries[i] == NULL)
-#define DEAD_ENTRY_P(cache, i) ((cache)->entries[i] == DEAD_ENTRY)
-#define LIVE_ENTRY_P(cache, i) \
- (!((NULL_ENTRY_P((cache),(i))) || (DEAD_ENTRY_P((cache),(i)))))
-
-#ifdef NDEBUG
-#define _cache_sane_state(c)
-#else
-static void
-_cache_sane_state (cairo_cache_t *cache)
+static cairo_status_t
+_cairo_cache_init (cairo_cache_t *cache,
+ cairo_cache_keys_equal_func_t keys_equal,
+ cairo_destroy_func_t entry_destroy,
+ unsigned long max_size)
{
- assert (cache != NULL);
- assert (cache->entries != NULL);
- assert (cache->backend != NULL);
- assert (cache->arrangement != NULL);
- /* Cannot check this, a single object may larger */
- /* assert (cache->used_memory <= cache->max_memory); */
- assert (cache->live_entries <= cache->arrangement->size);
+ cache->hash_table = _cairo_hash_table_create (keys_equal);
+ if (cache->hash_table == NULL)
+ return CAIRO_STATUS_NO_MEMORY;
+
+ cache->entry_destroy = entry_destroy;
+
+ cache->max_size = max_size;
+ cache->size = 0;
+
+ cache->preserve_entries = FALSE;
+
+ return CAIRO_STATUS_SUCCESS;
}
-#endif
static void
-_entry_destroy (cairo_cache_t *cache, unsigned long i)
+_cairo_cache_fini (cairo_cache_t *cache)
{
- _cache_sane_state (cache);
-
- if (LIVE_ENTRY_P(cache, i))
- {
- cairo_cache_entry_base_t *entry = cache->entries[i];
- assert(cache->live_entries > 0);
- assert(cache->used_memory >= entry->memory);
-
- cache->live_entries--;
- cache->used_memory -= entry->memory;
- cache->backend->destroy_entry (cache, entry);
- cache->entries[i] = DEAD_ENTRY;
+ cairo_cache_entry_t *entry;
+
+ /* We have to manually remove all entries from the cache ourselves
+ * rather than relying on _cairo_hash_table_destroy to do that
+ * since otherwise the cache->entry_destroy callback would not get
+ * called on each entry. */
+
+ while (1) {
+ entry = _cairo_hash_table_random_entry (cache->hash_table, NULL);
+ if (entry == NULL)
+ break;
+ _cairo_cache_remove (cache, entry);
}
+
+ _cairo_hash_table_destroy (cache->hash_table);
+ cache->size = 0;
}
-static cairo_cache_entry_base_t **
-_cache_lookup (cairo_cache_t *cache,
- void *key,
- int (*predicate)(void*,void*,void*))
-{
-
- cairo_cache_entry_base_t **probe;
- unsigned long hash;
- unsigned long table_size, i, idx, step;
-
- _cache_sane_state (cache);
- assert (key != NULL);
-
- table_size = cache->arrangement->size;
- hash = cache->backend->hash (cache, key);
- idx = hash % table_size;
- step = 0;
-
- for (i = 0; i < table_size; ++i)
- {
-#ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
- cache->probes++;
-#endif
- assert(idx < table_size);
- probe = cache->entries + idx;
-
- /*
- * There are two lookup modes: searching for a free slot and searching
- * for an exact entry.
- */
-
- if (predicate != NULL)
- {
- /* We are looking up an exact entry. */
- if (*probe == NULL)
- {
- /* Found an empty spot, there can't be a match */
- break;
- }
- else if (*probe != DEAD_ENTRY
- && (*probe)->hashcode == hash
- && predicate (cache, key, *probe))
- {
- return probe;
- }
- }
- else
- {
- /* We are just looking for a free slot. */
- if (*probe == NULL
- || *probe == DEAD_ENTRY)
- {
- return probe;
- }
- }
+/**
+ * _cairo_cache_create:
+ * @keys_equal: a function to return TRUE if two keys are equal
+ * @entry_destroy: destroy notifier for cache entries
+ * @max_size: the maximum size for this cache
+ *
+ * Creates a new cache using the keys_equal() function to determine
+ * the equality of entries.
+ *
+ * Data is provided to the cache in the form of user-derived version
+ * of cairo_cache_entry_t. A cache entry must be able to hold hash
+ * code, a size, and the key/value pair being stored in the
+ * cache. Sometimes only the key will be necessary, (as in
+ * _cairo_cache_remove()), and in these cases the value portion of the
+ * entry need not be initialized.
+ *
+ * The units for max_size can be chosen by the caller, but should be
+ * consistent with the units of the size field of cache entries. When
+ * adding an entry with _cairo_cache_insert if the total size of
+ * entries in the cache would exceed max_size then entries will be
+ * removed at random until the new entry would fit or the cache is
+ * empty. Then the new entry is inserted.
+ *
+ * There are cases in which the automatic removal of entries is
+ * undesired. If the cache entries have reference counts, then it is a
+ * simple matter to use the reference counts to ensure that entries
+ * continue to live even after being ejected from the cache. However,
+ * in some cases the memory overhead of adding a reference count to
+ * the entry would be objectionable. In such cases, the
+ * _cairo_cache_preserve() and _cairo_cache_release() calls can be
+ * used to establish a window during which no automatic removal of
+ * entries will occur.
+ *
+ * Return value:
+ **/
+cairo_cache_t *
+_cairo_cache_create (cairo_cache_keys_equal_func_t keys_equal,
+ cairo_destroy_func_t entry_destroy,
+ unsigned long max_size)
+{
+ cairo_status_t status;
+ cairo_cache_t *cache;
- if (step == 0) {
- step = hash % cache->arrangement->rehash;
- if (step == 0)
- step = 1;
- }
+ cache = malloc (sizeof (cairo_cache_t));
+ if (cache == NULL)
+ return NULL;
- idx += step;
- if (idx >= table_size)
- idx -= table_size;
+ status = _cairo_cache_init (cache, keys_equal, entry_destroy, max_size);
+ if (status) {
+ free (cache);
+ return NULL;
}
- /*
- * The table should not have permitted you to get here if you were just
- * looking for a free slot: there should have been room.
- */
- assert(predicate != NULL);
- return NULL;
+ return cache;
}
-static cairo_cache_entry_base_t **
-_find_available_entry_for (cairo_cache_t *cache,
- void *key)
+/**
+ * _cairo_cache_destroy:
+ * @cache: a cache to destroy
+ *
+ * Immediately destroys the given cache, freeing all resources
+ * associated with it. As part of this process, the entry_destroy()
+ * function, (as passed to _cairo_cache_create), will be called for
+ * each entry in the cache.
+ **/
+void
+_cairo_cache_destroy (cairo_cache_t *cache)
{
- return _cache_lookup (cache, key, NULL);
-}
+ _cairo_cache_fini (cache);
-static cairo_cache_entry_base_t **
-_find_exact_live_entry_for (cairo_cache_t *cache,
- void *key)
-{
- return _cache_lookup (cache, key, cache->backend->keys_equal);
+ free (cache);
}
-static const cairo_cache_arrangement_t *
-_find_cache_arrangement (unsigned long proposed_size)
+/**
+ * _cairo_cache_preserve:
+ * @cache: a cache with some precious entries in it (or about to be
+ * added)
+ *
+ * Disable the automatic ejection of entries from the cache. Future
+ * calls to _cairo_cache_insert will add new entries to the cache
+ * regardless of how large the cache grows. See
+ * _cairo_cache_release().
+ **/
+void
+_cairo_cache_preserve (cairo_cache_t *cache)
{
- unsigned long idx;
-
- for (idx = 0; idx < N_CACHE_SIZES; ++idx)
- if (cache_arrangements[idx].high_water_mark >= proposed_size)
- return &cache_arrangements[idx];
- return NULL;
+ cache->preserve_entries = TRUE;
}
-static cairo_status_t
-_resize_cache (cairo_cache_t *cache, unsigned long proposed_size)
+/**
+ * _cairo_cache_release:
+ * @cache: a cache, just after the entries in it have become less
+ * previous
+ *
+ * Cancel the effects of _cairo_cache_preserve(). That is, allow the
+ * cache to resume ejecting entries when it is larger than max_size as
+ * passed to cairo_cache_create. If the cache is already larger than
+ * max_size, no entries will be immediately removed, but the cache
+ * will be brought down to size at the time of the next call to
+ * _cairo_cache_insert.
+ **/
+void
+_cairo_cache_release (cairo_cache_t *cache)
{
- cairo_cache_t tmp;
- cairo_cache_entry_base_t **e;
- unsigned long new_size, i;
-
- tmp = *cache;
- tmp.arrangement = _find_cache_arrangement (proposed_size);
- assert(tmp.arrangement != NULL);
- if (tmp.arrangement == cache->arrangement)
- return CAIRO_STATUS_SUCCESS;
-
- new_size = tmp.arrangement->size;
- tmp.entries = calloc (new_size, sizeof (cairo_cache_entry_base_t *));
- if (tmp.entries == NULL)
- return CAIRO_STATUS_NO_MEMORY;
-
- for (i = 0; i < cache->arrangement->size; ++i) {
- if (LIVE_ENTRY_P(cache, i)) {
- e = _find_available_entry_for (&tmp, cache->entries[i]);
- assert (e != NULL);
- *e = cache->entries[i];
- }
- }
- free (cache->entries);
- cache->entries = tmp.entries;
- cache->arrangement = tmp.arrangement;
- return CAIRO_STATUS_SUCCESS;
+ cache->preserve_entries = FALSE;
}
-
-#ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
-static double
-_load_factor (cairo_cache_t *cache)
+/**
+ * _cairo_cache_lookup:
+ * @cache: a cache
+ * @key: the key of interest
+ * @entry_return: pointer for return value
+ *
+ * Performs a lookup in @cache looking for an entry which has a key
+ * that matches @key, (as determined by the keys_equal() function
+ * passed to _cairo_cache_create).
+ *
+ * Return value: TRUE if there is an entry in the cache that matches
+ * @key, (which will now be in *entry_return). FALSE otherwise, (in
+ * which case *entry_return will be NULL).
+ **/
+cairo_private cairo_bool_t
+_cairo_cache_lookup (cairo_cache_t *cache,
+ cairo_cache_entry_t *key,
+ cairo_cache_entry_t **entry_return)
{
- return ((double) cache->live_entries)
- / ((double) cache->arrangement->size);
+ return _cairo_hash_table_lookup (cache->hash_table,
+ (cairo_hash_entry_t *) key,
+ (cairo_hash_entry_t **) entry_return);
}
-#endif
-
-/* Find a random in the cache matching the given predicate. We use the
- * same algorithm as the probing algorithm to walk over the entries in
- * the hash table in a pseudo-random order. Walking linearly would
- * favor entries following gaps in the hash table. We could also
- * call rand() repeatedly, which works well for almost-full tables,
- * but degrades when the table is almost empty, or predicate
- * returns false for most entries.
- */
-static cairo_cache_entry_base_t **
-_random_entry (cairo_cache_t *cache,
- int (*predicate)(void*))
-{
- cairo_cache_entry_base_t **probe;
- unsigned long hash;
- unsigned long table_size, i, idx, step;
-
- _cache_sane_state (cache);
-
- table_size = cache->arrangement->size;
- hash = rand ();
- idx = hash % table_size;
- step = 0;
-
- for (i = 0; i < table_size; ++i)
- {
- assert(idx < table_size);
- probe = cache->entries + idx;
-
- if (LIVE_ENTRY_P(cache, idx)
- && (!predicate || predicate (*probe)))
- return probe;
-
- if (step == 0) {
- step = hash % cache->arrangement->rehash;
- if (step == 0)
- step = 1;
- }
- idx += step;
- if (idx >= table_size)
- idx -= table_size;
- }
-
- return NULL;
-}
+/**
+ * _cairo_cache_remove_random:
+ * @cache: a cache
+ *
+ * Remove a random entry from the cache.
+ *
+ * Return value: CAIRO_STATUS_SUCCESS if an entry was successfully
+ * removed. CAIRO_INT_STATUS_CACHE_EMPTY if there are no entries that
+ * can be removed.
+ **/
+static cairo_int_status_t
+_cairo_cache_remove_random (cairo_cache_t *cache)
+{
+ cairo_cache_entry_t *entry;
-/* public API follows */
-
-cairo_status_t
-_cairo_cache_init (cairo_cache_t *cache,
- const cairo_cache_backend_t *backend,
- unsigned long max_memory)
-{
- assert (backend != NULL);
-
- if (cache != NULL){
- cache->arrangement = &cache_arrangements[0];
- cache->max_memory = max_memory;
- cache->used_memory = 0;
- cache->live_entries = 0;
-
-#ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
- cache->hits = 0;
- cache->misses = 0;
- cache->probes = 0;
-#endif
-
- cache->backend = backend;
- cache->entries = calloc (cache->arrangement->size,
- sizeof(cairo_cache_entry_base_t *));
-
- if (cache->entries == NULL)
- return CAIRO_STATUS_NO_MEMORY;
- }
- _cache_sane_state (cache);
- return CAIRO_STATUS_SUCCESS;
-}
+ entry = _cairo_hash_table_random_entry (cache->hash_table, NULL);
+ if (entry == NULL)
+ return CAIRO_INT_STATUS_CACHE_EMPTY;
-void
-_cairo_cache_destroy (cairo_cache_t *cache)
-{
- unsigned long i;
- if (cache == NULL)
- return;
-
- _cache_sane_state (cache);
-
- for (i = 0; i < cache->arrangement->size; ++i)
- _entry_destroy (cache, i);
-
- free (cache->entries);
- cache->entries = NULL;
- cache->backend->destroy_cache (cache);
-}
+ _cairo_cache_remove (cache, entry);
-void
-_cairo_cache_shrink_to (cairo_cache_t *cache,
- unsigned long max_memory)
-{
- unsigned long idx;
- /* Make some entries die if we're under memory pressure. */
- while (cache->live_entries > 0 && cache->used_memory > max_memory) {
- idx = _random_entry (cache, NULL) - cache->entries;
- assert (idx < cache->arrangement->size);
- _entry_destroy (cache, idx);
- }
+ return CAIRO_STATUS_SUCCESS;
}
-cairo_status_t
-_cairo_cache_lookup (cairo_cache_t *cache,
- void *key,
- void **entry_return,
- int *created_entry)
+/**
+ * _cairo_cache_insert:
+ * @cache: a cache
+ * @entry: an entry to be inserted
+ *
+ * Insert @entry into the cache. If an entry exists in the cache with
+ * a matching key, then the old entry will be removed first, (and the
+ * entry_destroy() callback will be called on it).
+ *
+ * Return value: CAIRO_STATUS_SUCCESS if successful or
+ * CAIRO_STATUS_NO_MEMORY if insufficient memory is available.
+ **/
+cairo_private cairo_status_t
+_cairo_cache_insert (cairo_cache_t *cache,
+ cairo_cache_entry_t *entry)
{
-
- cairo_status_t status = CAIRO_STATUS_SUCCESS;
- cairo_cache_entry_base_t **slot = NULL, *new_entry;
-
- _cache_sane_state (cache);
-
-#ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
- if ((cache->hits + cache->misses) % 0xffff == 0)
- printf("cache %p stats: size %ld, live %ld, load %.2f\n"
- " mem %ld/%ld, hit %ld, miss %ld\n"
- " probe %ld, %.2f probe/access\n",
- cache,
- cache->arrangement->size,
- cache->live_entries,
- _load_factor (cache),
- cache->used_memory,
- cache->max_memory,
- cache->hits,
- cache->misses,
- cache->probes,
- ((double) cache->probes)
- / ((double) (cache->hits +
- cache->misses + 1)));
-#endif
-
- /* See if we have an entry in the table already. */
- slot = _find_exact_live_entry_for (cache, key);
- if (slot != NULL) {
-#ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
- cache->hits++;
-#endif
- *entry_return = *slot;
- if (created_entry)
- *created_entry = 0;
- return status;
+ cairo_status_t status;
+
+ if (! cache->preserve_entries) {
+ while (cache->size + entry->size > cache->max_size) {
+ status = _cairo_cache_remove_random (cache);
+ if (status) {
+ if (status == CAIRO_INT_STATUS_CACHE_EMPTY)
+ break;
+ return status;
+ }
+ }
}
-#ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
- cache->misses++;
-#endif
-
- /* Build the new entry. */
- status = cache->backend->create_entry (cache, key,
- (void **)&new_entry);
- if (status != CAIRO_STATUS_SUCCESS)
+ status = _cairo_hash_table_insert (cache->hash_table,
+ (cairo_hash_entry_t *) entry);
+ if (status)
return status;
- /* Store the hash value in case the backend forgot. */
- new_entry->hashcode = cache->backend->hash (cache, key);
-
- if (cache->live_entries && cache->max_memory)
- _cairo_cache_shrink_to (cache, cache->max_memory);
-
- /* Can't assert this; new_entry->memory may be larger than max_memory */
- /* assert(cache->max_memory >= (cache->used_memory + new_entry->memory)); */
-
- /* Make room in the table for a new slot. */
- status = _resize_cache (cache, cache->live_entries + 1);
- if (status != CAIRO_STATUS_SUCCESS) {
- cache->backend->destroy_entry (cache, new_entry);
- return status;
- }
+ cache->size += entry->size;
- slot = _find_available_entry_for (cache, key);
- assert(slot != NULL);
-
- /* Store entry in slot and increment statistics. */
- *slot = new_entry;
- cache->live_entries++;
- cache->used_memory += new_entry->memory;
-
- _cache_sane_state (cache);
-
- *entry_return = new_entry;
- if (created_entry)
- *created_entry = 1;
-
- return status;
+ return CAIRO_STATUS_SUCCESS;
}
-cairo_status_t
-_cairo_cache_remove (cairo_cache_t *cache,
- void *key)
+/**
+ * _cairo_cache_remove:
+ * @cache: a cache
+ * @entry: key of entry to be removed
+ *
+ * Remove an entry from the cache which has a key that matches @key,
+ * if any (as determined by the keys_equal() function passed to
+ * _cairo_cache_create).
+ **/
+cairo_private void
+_cairo_cache_remove (cairo_cache_t *cache,
+ cairo_cache_entry_t *entry)
{
- cairo_cache_entry_base_t **slot;
+ cache->size -= entry->size;
- _cache_sane_state (cache);
+ _cairo_hash_table_remove (cache->hash_table,
+ (cairo_hash_entry_t *) entry);
- /* See if we have an entry in the table already. */
- slot = _find_exact_live_entry_for (cache, key);
- if (slot != NULL)
- _entry_destroy (cache, slot - cache->entries);
-
- return CAIRO_STATUS_SUCCESS;
+ if (cache->entry_destroy)
+ cache->entry_destroy (entry);
}
-void *
-_cairo_cache_random_entry (cairo_cache_t *cache,
- int (*predicate)(void*))
+/**
+ * _cairo_cache_foreach:
+ * @cache: a cache
+ * @cache_callback: function to be called for each entry
+ * @closure: additional argument to be passed to @cache_callback
+ *
+ * Call @cache_callback for each entry in the cache, in a
+ * non-specified order.
+ **/
+void
+_cairo_cache_foreach (cairo_cache_t *cache,
+ cairo_cache_callback_func_t cache_callback,
+ void *closure)
{
- cairo_cache_entry_base_t **slot = _random_entry (cache, predicate);
-
- return slot ? *slot : NULL;
+ _cairo_hash_table_foreach (cache->hash_table,
+ cache_callback,
+ closure);
}
unsigned long