diff options
Diffstat (limited to 'src/cache.c')
-rw-r--r-- | src/cache.c | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..4f8ed4e --- /dev/null +++ b/src/cache.c @@ -0,0 +1,447 @@ +/* + * This file is part of libplacebo. + * + * libplacebo 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. + * + * libplacebo 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 libplacebo. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <locale.h> +#include <limits.h> + +#include "common.h" +#include "cache.h" +#include "log.h" +#include "pl_thread.h" + +const struct pl_cache_params pl_cache_default_params = {0}; + +struct priv { + pl_log log; + pl_mutex lock; + PL_ARRAY(pl_cache_obj) objects; + size_t total_size; +}; + +int pl_cache_objects(pl_cache cache) +{ + if (!cache) + return 0; + + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + int num = p->objects.num; + pl_mutex_unlock(&p->lock); + return num; +} + +size_t pl_cache_size(pl_cache cache) +{ + if (!cache) + return 0; + + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + size_t size = p->total_size; + pl_mutex_unlock(&p->lock); + return size; +} + +pl_cache pl_cache_create(const struct pl_cache_params *params) +{ + struct pl_cache_t *cache = pl_zalloc_obj(NULL, cache, struct priv); + struct priv *p = PL_PRIV(cache); + pl_mutex_init(&p->lock); + if (params) { + cache->params = *params; + p->log = params->log; + } + + // Sanitize size limits + size_t total_size = PL_DEF(cache->params.max_total_size, SIZE_MAX); + size_t object_size = PL_DEF(cache->params.max_object_size, SIZE_MAX); + object_size = PL_MIN(total_size, object_size); + cache->params.max_total_size = total_size; + cache->params.max_object_size = object_size; + + return cache; +} + +static void remove_obj(pl_cache cache, pl_cache_obj obj) +{ + struct priv *p = PL_PRIV(cache); + + p->total_size -= obj.size; + if (obj.free) + obj.free(obj.data); +} + +void pl_cache_destroy(pl_cache *pcache) +{ + pl_cache cache = *pcache; + if (!cache) + return; + + struct priv *p = PL_PRIV(cache); + for (int i = 0; i < p->objects.num; i++) + remove_obj(cache, p->objects.elem[i]); + + pl_assert(p->total_size == 0); + pl_mutex_destroy(&p->lock); + pl_free((void *) cache); + *pcache = NULL; +} + +void pl_cache_reset(pl_cache cache) +{ + if (!cache) + return; + + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + for (int i = 0; i < p->objects.num; i++) + remove_obj(cache, p->objects.elem[i]); + p->objects.num = 0; + pl_assert(p->total_size == 0); + pl_mutex_unlock(&p->lock); +} + +static bool try_set(pl_cache cache, pl_cache_obj obj) +{ + struct priv *p = PL_PRIV(cache); + + // Remove any existing entry with this key + for (int i = p->objects.num - 1; i >= 0; i--) { + pl_cache_obj prev = p->objects.elem[i]; + if (prev.key == obj.key) { + PL_TRACE(p, "Removing out-of-date object 0x%"PRIx64, prev.key); + remove_obj(cache, prev); + PL_ARRAY_REMOVE_AT(p->objects, i); + break; + } + } + + if (!obj.size) { + PL_TRACE(p, "Deleted object 0x%"PRIx64, obj.key); + return true; + } + + if (obj.size > cache->params.max_object_size) { + PL_DEBUG(p, "Object 0x%"PRIx64" (size %zu) exceeds max size %zu, discarding", + obj.key, obj.size, cache->params.max_object_size); + return false; + } + + // Make space by deleting old objects + while (p->total_size + obj.size > cache->params.max_total_size || + p->objects.num == INT_MAX) + { + pl_assert(p->objects.num); + pl_cache_obj old = p->objects.elem[0]; + PL_TRACE(p, "Removing object 0x%"PRIx64" (size %zu) to make room", + old.key, old.size); + remove_obj(cache, old); + PL_ARRAY_REMOVE_AT(p->objects, 0); + } + + if (!obj.free) { + obj.data = pl_memdup(NULL, obj.data, obj.size); + obj.free = pl_free; + } + + PL_TRACE(p, "Inserting new object 0x%"PRIx64" (size %zu)", obj.key, obj.size); + PL_ARRAY_APPEND((void *) cache, p->objects, obj); + p->total_size += obj.size; + return true; +} + +static pl_cache_obj strip_obj(pl_cache_obj obj) +{ + return (pl_cache_obj) { .key = obj.key }; +} + +bool pl_cache_try_set(pl_cache cache, pl_cache_obj *pobj) +{ + if (!cache) + return false; + + pl_cache_obj obj = *pobj; + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + bool ok = try_set(cache, obj); + pl_mutex_unlock(&p->lock); + if (ok) { + *pobj = strip_obj(obj); // ownership transfers, clear ptr + } else { + obj = strip_obj(obj); // ownership remains with caller, clear copy + } + if (cache->params.set) + cache->params.set(cache->params.priv, obj); + return ok; +} + +void pl_cache_set(pl_cache cache, pl_cache_obj *obj) +{ + if (!pl_cache_try_set(cache, obj)) { + if (obj->free) + obj->free(obj->data); + *obj = (pl_cache_obj) { .key = obj->key }; + } +} + +static void noop(void *ignored) +{ + (void) ignored; +} + +bool pl_cache_get(pl_cache cache, pl_cache_obj *out_obj) +{ + const uint64_t key = out_obj->key; + if (!cache) + goto fail; + + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + + // Search backwards to prioritize recently added entries + for (int i = p->objects.num - 1; i >= 0; i--) { + pl_cache_obj obj = p->objects.elem[i]; + if (obj.key == key) { + PL_ARRAY_REMOVE_AT(p->objects, i); + p->total_size -= obj.size; + pl_mutex_unlock(&p->lock); + pl_assert(obj.free); + *out_obj = obj; + return true; + } + } + + pl_mutex_unlock(&p->lock); + if (!cache->params.get) + goto fail; + + pl_cache_obj obj = cache->params.get(cache->params.priv, key); + if (!obj.size) + goto fail; + + // Sanitize object + obj.key = key; + obj.free = PL_DEF(obj.free, noop); + *out_obj = obj; + return true; + +fail: + *out_obj = (pl_cache_obj) { .key = key }; + return false; +} + +void pl_cache_iterate(pl_cache cache, + void (*cb)(void *priv, pl_cache_obj obj), + void *priv) +{ + if (!cache) + return; + + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + for (int i = 0; i < p->objects.num; i++) + cb(priv, p->objects.elem[i]); + pl_mutex_unlock(&p->lock); +} + +// --- Saving/loading + +#define CACHE_MAGIC "pl_cache" +#define CACHE_VERSION 1 +#define PAD_ALIGN(x) PL_ALIGN2(x, sizeof(uint32_t)) + +struct __attribute__((__packed__)) cache_header { + char magic[8]; + uint32_t version; + uint32_t num_entries; +}; + +struct __attribute__((__packed__)) cache_entry { + uint64_t key; + uint64_t size; + uint64_t hash; +}; + +pl_static_assert(sizeof(struct cache_header) % alignof(struct cache_entry) == 0); + +int pl_cache_save_ex(pl_cache cache, + void (*write)(void *priv, size_t size, const void *ptr), + void *priv) +{ + if (!cache) + return 0; + + struct priv *p = PL_PRIV(cache); + pl_mutex_lock(&p->lock); + pl_clock_t start = pl_clock_now(); + + const int num_objects = p->objects.num; + const size_t saved_bytes = p->total_size; + write(priv, sizeof(struct cache_header), &(struct cache_header) { + .magic = CACHE_MAGIC, + .version = CACHE_VERSION, + .num_entries = num_objects, + }); + + for (int i = 0; i < num_objects; i++) { + pl_cache_obj obj = p->objects.elem[i]; + PL_TRACE(p, "Saving object 0x%"PRIx64" (size %zu)", obj.key, obj.size); + write(priv, sizeof(struct cache_entry), &(struct cache_entry) { + .key = obj.key, + .size = obj.size, + .hash = pl_mem_hash(obj.data, obj.size), + }); + static const uint8_t padding[PAD_ALIGN(1)] = {0}; + write(priv, obj.size, obj.data); + write(priv, PAD_ALIGN(obj.size) - obj.size, padding); + } + + pl_mutex_unlock(&p->lock); + pl_log_cpu_time(p->log, start, pl_clock_now(), "saving cache"); + if (num_objects) + PL_DEBUG(p, "Saved %d objects, totalling %zu bytes", num_objects, saved_bytes); + + return num_objects; +} + +int pl_cache_load_ex(pl_cache cache, + bool (*read)(void *priv, size_t size, void *ptr), + void *priv) +{ + if (!cache) + return 0; + + struct priv *p = PL_PRIV(cache); + struct cache_header header; + if (!read(priv, sizeof(header), &header)) { + PL_ERR(p, "Failed loading cache: file seems empty or truncated"); + return -1; + } + if (memcmp(header.magic, CACHE_MAGIC, sizeof(header.magic)) != 0) { + PL_ERR(p, "Failed loading cache: invalid magic bytes"); + return -1; + } + if (header.version != CACHE_VERSION) { + PL_INFO(p, "Failed loading cache: wrong version... skipping"); + return 0; + } + if (header.num_entries > INT_MAX) { + PL_ERR(p, "Failed loading cache: %"PRIu32" entries overflows int", + header.num_entries); + return 0; + } + + int num_loaded = 0; + size_t loaded_bytes = 0; + pl_mutex_lock(&p->lock); + pl_clock_t start = pl_clock_now(); + + for (int i = 0; i < header.num_entries; i++) { + struct cache_entry entry; + if (!read(priv, sizeof(entry), &entry)) { + PL_WARN(p, "Cache seems truncated, missing objects.. ignoring rest"); + goto error; + } + + if (entry.size > SIZE_MAX) { + PL_WARN(p, "Cache object size %"PRIu64" overflows SIZE_MAX.. " + "suspect broken file, ignoring rest", entry.size); + goto error; + } + + void *buf = pl_alloc(NULL, PAD_ALIGN(entry.size)); + if (!read(priv, PAD_ALIGN(entry.size), buf)) { + PL_WARN(p, "Cache seems truncated, missing objects.. ignoring rest"); + pl_free(buf); + goto error; + } + + uint64_t checksum = pl_mem_hash(buf, entry.size); + if (checksum != entry.hash) { + PL_WARN(p, "Cache entry seems corrupt, checksum mismatch.. ignoring rest"); + pl_free(buf); + goto error; + } + + pl_cache_obj obj = { + .key = entry.key, + .size = entry.size, + .data = buf, + .free = pl_free, + }; + + PL_TRACE(p, "Loading object 0x%"PRIx64" (size %zu)", obj.key, obj.size); + if (try_set(cache, obj)) { + num_loaded++; + loaded_bytes += entry.size; + } else { + pl_free(buf); + } + } + + pl_log_cpu_time(p->log, start, pl_clock_now(), "loading cache"); + if (num_loaded) + PL_DEBUG(p, "Loaded %d objects, totalling %zu bytes", num_loaded, loaded_bytes); + + // fall through +error: + pl_mutex_unlock(&p->lock); + return num_loaded; +} + +// Save/load wrappers + +struct ptr_ctx { + uint8_t *data; // base pointer + size_t size; // total size + size_t pos; // read/write index +}; + +static void write_ptr(void *priv, size_t size, const void *ptr) +{ + struct ptr_ctx *ctx = priv; + size_t end = PL_MIN(ctx->pos + size, ctx->size); + if (end > ctx->pos) + memcpy(ctx->data + ctx->pos, ptr, end - ctx->pos); + ctx->pos += size; +} + +static bool read_ptr(void *priv, size_t size, void *ptr) +{ + struct ptr_ctx *ctx = priv; + if (ctx->pos + size > ctx->size) + return false; + memcpy(ptr, ctx->data + ctx->pos, size); + ctx->pos += size; + return true; +} + +size_t pl_cache_save(pl_cache cache, uint8_t *data, size_t size) +{ + struct ptr_ctx ctx = { data, size }; + pl_cache_save_ex(cache, write_ptr, &ctx); + return ctx.pos; +} + +int pl_cache_load(pl_cache cache, const uint8_t *data, size_t size) +{ + return pl_cache_load_ex(cache, read_ptr, &(struct ptr_ctx) { + .data = (uint8_t *) data, + .size = size, + }); +} |