diff --git a/libnopegl/meson.build b/libnopegl/meson.build index 9aa1854b9..b265e1157 100644 --- a/libnopegl/meson.build +++ b/libnopegl/meson.build @@ -913,6 +913,10 @@ test_progs = { 'exe': 'test_hmap', 'src': files('src/test_hmap.c', 'src/log.c') + utils_src, }, + 'Hash map - custom': { + 'exe': 'test_hmap_custom', + 'src': files('src/test_hmap_custom.c', 'src/log.c') + utils_src, + }, 'Noise': { 'exe': 'test_noise', 'src': files('src/test_noise.c', 'src/noise.c', 'src/log.c') + utils_src, diff --git a/libnopegl/src/test_draw.c b/libnopegl/src/test_draw.c index 8d199114f..e393b3980 100644 --- a/libnopegl/src/test_draw.c +++ b/libnopegl/src/test_draw.c @@ -106,7 +106,7 @@ int main(int ac, char **av) } } - uint32_t crc = ngli_crc32_mem(c.buf, buf_size * 4); + uint32_t crc = ngli_crc32_mem(c.buf, buf_size * 4, NGLI_CRC32_INIT); printf("CRC: 0x%08x\n", crc); ngli_assert(crc == 0x2a07363c); diff --git a/libnopegl/src/test_hmap_custom.c b/libnopegl/src/test_hmap_custom.c new file mode 100644 index 000000000..e649dada1 --- /dev/null +++ b/libnopegl/src/test_hmap_custom.c @@ -0,0 +1,191 @@ +/* + * Copyright 2025 Nope Forge + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include + +#define HMAP_SIZE_NBIT 1 +#include "utils/crc32.h" +#include "utils/hmap.h" +#include "utils/memory.h" +#include "utils/string.h" +#include "utils/utils.h" + +#define PRINT_HMAP(...) do { \ + printf(__VA_ARGS__); \ + const struct hmap_entry *e = NULL; \ + while ((e = ngli_hmap_next(hm, e))) { \ + const struct key *key = e->key.ptr; \ + printf(" %08X %s, %s: %s\n", \ + ngli_crc32_mem(e->key.ptr, sizeof(struct key), NGLI_CRC32_INIT), \ + blend_factor_to_str[key->blend_dst_factor], \ + blend_factor_to_str[key->blend_src_factor], \ + (const char *)e->data); \ + } \ + printf("\n"); \ +} while (0) + +#define RSTR "replaced" + +static void free_func(void *arg, void *data) +{ + ngli_free(data); +} + +enum blend_factor { + BLEND_FACTOR_ZERO, + BLEND_FACTOR_ONE, + BLEND_FACTOR_SRC_COLOR, + BLEND_FACTOR_ONE_MINUS_SRC_COLOR, + BLEND_FACTOR_DST_COLOR, + BLEND_FACTOR_ONE_MINUS_DST_COLOR, + BLEND_FACTOR_SRC_ALPHA, + BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + BLEND_FACTOR_DST_ALPHA, + BLEND_FACTOR_ONE_MINUS_DST_ALPHA, + BLEND_FACTOR_NB, + BLEND_FACTOR_MAX_ENUM = 0x7FFFFFFF +}; + +const char *blend_factor_to_str[] = { + [BLEND_FACTOR_ZERO] = "zero", + [BLEND_FACTOR_ONE] = "one", + [BLEND_FACTOR_SRC_COLOR] = "src_color", + [BLEND_FACTOR_ONE_MINUS_SRC_COLOR] = "one_minus_src_color", + [BLEND_FACTOR_DST_COLOR] = "dst_color", + [BLEND_FACTOR_ONE_MINUS_DST_COLOR] = "one_minus_dst_color", + [BLEND_FACTOR_SRC_ALPHA] = "src_alpha", + [BLEND_FACTOR_ONE_MINUS_SRC_ALPHA] = "one_minus_src_alpha", + [BLEND_FACTOR_DST_ALPHA] = "dst_alpha", + [BLEND_FACTOR_ONE_MINUS_DST_ALPHA] = "one_minus_dst_alpha", +}; + +struct key { + enum blend_factor blend_dst_factor; + enum blend_factor blend_src_factor; +}; + +static const struct { + struct key key; + const char *val; +} kvs[] = { + {{BLEND_FACTOR_ONE, BLEND_FACTOR_ONE_MINUS_SRC_ALPHA}, "src_over"}, + {{BLEND_FACTOR_ONE_MINUS_DST_ALPHA, BLEND_FACTOR_ONE}, "dst_over"}, + {{BLEND_FACTOR_ONE_MINUS_DST_ALPHA, BLEND_FACTOR_ZERO}, "src_out"}, + {{BLEND_FACTOR_ZERO, BLEND_FACTOR_ONE_MINUS_SRC_ALPHA}, "dst_out"}, + {{BLEND_FACTOR_DST_ALPHA, BLEND_FACTOR_ZERO}, "src_in"}, + {{BLEND_FACTOR_ZERO, BLEND_FACTOR_SRC_ALPHA}, "dst_in"}, + {{BLEND_FACTOR_DST_ALPHA, BLEND_FACTOR_ONE_MINUS_SRC_ALPHA}, "src_atop"}, + {{BLEND_FACTOR_ONE_MINUS_DST_ALPHA, BLEND_FACTOR_SRC_ALPHA}, "dst_atop"}, + {{BLEND_FACTOR_ONE_MINUS_DST_ALPHA, BLEND_FACTOR_ONE_MINUS_SRC_ALPHA}, "xor"}, +}; + +static size_t get_key_index(const struct key *key) +{ + for (size_t i = 0; i < NGLI_ARRAY_NB(kvs); i++) + if (!memcmp(&kvs[i].key, key, sizeof(struct key))) + return i; + return SIZE_MAX; +} + +static void check_order(const struct hmap *hm) +{ + size_t last_index = SIZE_MAX; + const struct hmap_entry *e = NULL; + while ((e = ngli_hmap_next(hm, e))) { + const size_t index = get_key_index(e->key.ptr); + ngli_assert(last_index == SIZE_MAX || index > last_index); + last_index = index; + } +} + +static uint32_t key_hash(union hmap_key x) +{ + return ngli_crc32_mem(x.ptr, sizeof(struct key), NGLI_CRC32_INIT); +} + +static int key_cmp(union hmap_key a, union hmap_key b) +{ + return memcmp(a.ptr, b.ptr, sizeof(struct key)); +} + +static union hmap_key key_dup(union hmap_key x) +{ + return (union hmap_key){.ptr=ngli_memdup(x.ptr, sizeof(struct key))}; +} + +static int key_check(union hmap_key x) +{ + return !!x.ptr; +} + +static void key_free(union hmap_key x) +{ + ngli_free(x.ptr); +} + +static const struct hmap_key_funcs key_funcs = { + key_hash, + key_cmp, + key_dup, + key_check, + key_free, +}; + +int main(void) +{ + struct hmap *hm = ngli_hmap_create_ptr(&key_funcs); + ngli_hmap_set_free_func(hm, free_func, NULL); + + /* Test addition */ + for (size_t i = 0; i < NGLI_ARRAY_NB(kvs); i++) { + void *data = ngli_strdup(kvs[i].val); + ngli_assert(ngli_hmap_set_ptr(hm, &kvs[i].key, data) >= 0); + const char *val = ngli_hmap_get_ptr(hm, &kvs[i].key); + ngli_assert(val); + ngli_assert(!strcmp(val, kvs[i].val)); + check_order(hm); + } + + PRINT_HMAP("init [%zu entries]:\n", ngli_hmap_count(hm)); + + for (size_t i = 0; i < NGLI_ARRAY_NB(kvs) - 1; i++) { + /* Test replace */ + if (i & 1) { + void *data = ngli_strdup(RSTR); + ngli_assert(ngli_hmap_set_ptr(hm, &kvs[i].key, data) == 0); + const char *val = ngli_hmap_get_ptr(hm, &kvs[i].key); + ngli_assert(val); + ngli_assert(strcmp(val, RSTR) == 0); + PRINT_HMAP("replace %s:\n", kvs[i].val); + check_order(hm); + } + + /* Test delete */ + ngli_assert(ngli_hmap_set_ptr(hm, &kvs[i].key, NULL) == 1); + ngli_assert(ngli_hmap_set_ptr(hm, &kvs[i].key, NULL) == 0); + PRINT_HMAP("drop %s (%zu remaining):\n", kvs[i].val, ngli_hmap_count(hm)); + check_order(hm); + } + + ngli_hmap_freep(&hm); + + return 0; +} diff --git a/libnopegl/src/utils/crc32.c b/libnopegl/src/utils/crc32.c index e39a3f0cf..d2a6dc532 100644 --- a/libnopegl/src/utils/crc32.c +++ b/libnopegl/src/utils/crc32.c @@ -81,9 +81,9 @@ uint32_t ngli_crc32(const char *s) return ~crc; } -uint32_t ngli_crc32_mem(const uint8_t *buf, size_t size) +uint32_t ngli_crc32_mem(const uint8_t *buf, size_t size, uint32_t state) { - uint32_t crc = ~0U; + uint32_t crc = state; for (size_t i = 0; i < size; i++) crc = (crc >> 8) ^ crc_table[(crc & 0xff) ^ buf[i]]; return ~crc; diff --git a/libnopegl/src/utils/crc32.h b/libnopegl/src/utils/crc32.h index 32f86fab8..d04ae5541 100644 --- a/libnopegl/src/utils/crc32.h +++ b/libnopegl/src/utils/crc32.h @@ -26,7 +26,9 @@ #include #include +#define NGLI_CRC32_INIT (~0U) + uint32_t ngli_crc32(const char *s); -uint32_t ngli_crc32_mem(const uint8_t *s, size_t size); +uint32_t ngli_crc32_mem(const uint8_t *s, size_t size, uint32_t state); #endif /* CRC32_H */ diff --git a/libnopegl/src/utils/hmap.c b/libnopegl/src/utils/hmap.c index e3f1aa0c7..6d555db69 100644 --- a/libnopegl/src/utils/hmap.c +++ b/libnopegl/src/utils/hmap.c @@ -35,14 +35,6 @@ struct bucket { size_t nb_entries; }; -struct key_funcs { - uint32_t (*hash)(union hmap_key x); // mixing/hashing of a key - int (*cmp)(union hmap_key a, union hmap_key b); // compare 2 keys (0 if identical) - union hmap_key (*dup)(union hmap_key x); // create a copy of the key - int (*check)(union hmap_key x); // check whether the key is valid or not - void (*free)(union hmap_key x); // free a key -}; - struct hmap { struct bucket *buckets; size_t size; @@ -53,7 +45,7 @@ struct hmap { struct hmap_ref first; struct hmap_ref last; enum hmap_type type; - struct key_funcs key_funcs; + struct hmap_key_funcs key_funcs; }; void ngli_hmap_set_free_func(struct hmap *hm, ngli_user_free_func_type user_free_func, void *user_arg) @@ -67,7 +59,7 @@ void ngli_hmap_set_free_func(struct hmap *hm, ngli_user_free_func_type user_free #define HAS_REF(ref) ((ref).bucket_id != SIZE_MAX) static uint32_t key_hash_str(union hmap_key x) { return ngli_crc32(x.str); } -static uint32_t key_hash_u64(union hmap_key x) { return ngli_crc32_mem(x.u8_8, sizeof(x.u8_8)); } +static uint32_t key_hash_u64(union hmap_key x) { return ngli_crc32_mem(x.u8_8, sizeof(x.u8_8), NGLI_CRC32_INIT); } static int key_cmp_str(union hmap_key a, union hmap_key b) { return strcmp(a.str, b.str); } static int key_cmp_u64(union hmap_key a, union hmap_key b) { return a.u64 != b.u64; } @@ -81,12 +73,12 @@ static int key_check_u64(union hmap_key x) { return 1; } static void key_free_str(union hmap_key x) { ngli_free(x.str); } static void key_free_u64(union hmap_key x) { } -static const struct key_funcs key_funcs_map[] = { +static const struct hmap_key_funcs key_funcs_map[] = { [NGLI_HMAP_TYPE_STR] = {key_hash_str, key_cmp_str, key_dup_str, key_check_str, key_free_str}, [NGLI_HMAP_TYPE_U64] = {key_hash_u64, key_cmp_u64, key_dup_u64, key_check_u64, key_free_u64}, }; -struct hmap *ngli_hmap_create(enum hmap_type type) +static struct hmap *hmap_create(void) { struct hmap *hm = ngli_calloc(1, sizeof(*hm)); if (!hm) @@ -99,6 +91,20 @@ struct hmap *ngli_hmap_create(enum hmap_type type) return NULL; } hm->first = hm->last = NO_REF; + return hm; +} + +struct hmap *ngli_hmap_create_ptr(const struct hmap_key_funcs *key_funcs) +{ + struct hmap *hm = hmap_create(); + hm->type = NGLI_HMAP_TYPE_PTR; + hm->key_funcs = *key_funcs; + return hm; +} + +struct hmap *ngli_hmap_create(enum hmap_type type) +{ + struct hmap *hm = hmap_create(); hm->type = type; hm->key_funcs = key_funcs_map[type]; return hm; @@ -201,6 +207,45 @@ static int add_entry(struct hmap *hm, struct bucket *b, union hmap_key key, void return 0; } +static int delete_entry(struct hmap *hm, union hmap_key key, struct bucket *b) +{ + for (size_t i = 0; i < b->nb_entries; i++) { + struct hmap_entry *e = &b->entries[i]; + if (!hm->key_funcs.cmp(e->key, key)) { + + /* Link previous and next entries together */ + struct hmap_entry *prev = entry_from_ref(hm, e->prev); + struct hmap_entry *next = entry_from_ref(hm, e->next); + struct hmap_ref *link_from_back = prev ? &prev->next : &hm->first; + struct hmap_ref *link_from_front = next ? &next->prev : &hm->last; + *link_from_back = e->next; + *link_from_front = e->prev; + + /* Keep a reference pre-remove for fixing the refs later */ + const struct hmap_ref removed = ref_from_entry(hm, e); + + hm->key_funcs.free(e->key); + if (hm->user_free_func) + hm->user_free_func(hm->user_arg, e->data); + hm->count--; + b->nb_entries--; + if (!b->nb_entries) { + ngli_freep(&b->entries); + } else { + memmove(e, e + 1, (b->nb_entries - i) * sizeof(*b->entries)); + struct hmap_entry *entries = + ngli_realloc(b->entries, b->nb_entries, sizeof(*b->entries)); + if (!entries) + return 0; // unable to realloc but entry got dropped, so this is OK + b->entries = entries; + fix_refs(hm, b, removed); + } + return 1; + } + } + return 0; +} + static int hmap_set(struct hmap *hm, union hmap_key key, void *data) { if (!hm->key_funcs.check(key)) @@ -212,41 +257,7 @@ static int hmap_set(struct hmap *hm, union hmap_key key, void *data) /* Delete */ if (!data) { - for (size_t i = 0; i < b->nb_entries; i++) { - struct hmap_entry *e = &b->entries[i]; - if (!hm->key_funcs.cmp(e->key, key)) { - - /* Link previous and next entries together */ - struct hmap_entry *prev = entry_from_ref(hm, e->prev); - struct hmap_entry *next = entry_from_ref(hm, e->next); - struct hmap_ref *link_from_back = prev ? &prev->next : &hm->first; - struct hmap_ref *link_from_front = next ? &next->prev : &hm->last; - *link_from_back = e->next; - *link_from_front = e->prev; - - /* Keep a reference pre-remove for fixing the refs later */ - const struct hmap_ref removed = ref_from_entry(hm, e); - - hm->key_funcs.free(e->key); - if (hm->user_free_func) - hm->user_free_func(hm->user_arg, e->data); - hm->count--; - b->nb_entries--; - if (!b->nb_entries) { - ngli_freep(&b->entries); - } else { - memmove(e, e + 1, (b->nb_entries - i) * sizeof(*b->entries)); - struct hmap_entry *entries = - ngli_realloc(b->entries, b->nb_entries, sizeof(*b->entries)); - if (!entries) - return 0; // unable to realloc but entry got dropped, so this is OK - b->entries = entries; - fix_refs(hm, b, removed); - } - return 1; - } - } - return 0; + return delete_entry(hm, key, b); } /* Replace */ @@ -264,16 +275,9 @@ static int hmap_set(struct hmap *hm, union hmap_key key, void *data) if (hm->count * 3 / 4 >= hm->size) { struct hmap old_hm = *hm; -#if HAVE_BUILTIN_OVERFLOW size_t new_size; - if (__builtin_mul_overflow(hm->size, 2, &new_size)) - return NGL_ERROR_LIMIT_EXCEEDED; -#else - /* Also includes the realloc overflow check */ - if (hm->size >= 1ULL << (sizeof(hm->size)*8 - 2)) + if (NGLI_CHK_MUL(&new_size, hm->size, 2)) return NGL_ERROR_LIMIT_EXCEEDED; - size_t new_size = hm->size * 2; -#endif struct bucket *new_buckets = ngli_calloc(new_size, sizeof(*new_buckets)); if (new_buckets) { @@ -332,6 +336,13 @@ static int hmap_set(struct hmap *hm, union hmap_key key, void *data) return 0; } +int ngli_hmap_set_ptr(struct hmap *hm, const void *ptr, void *data) +{ + ngli_assert(hm->type == NGLI_HMAP_TYPE_PTR); + const union hmap_key key = {.ptr=(void *)ptr}; + return hmap_set(hm, key, data); +} + int ngli_hmap_set_str(struct hmap *hm, const char *str, void *data) { ngli_assert(hm->type == NGLI_HMAP_TYPE_STR); @@ -365,6 +376,13 @@ static void *hmap_get(const struct hmap *hm, union hmap_key key) return NULL; } +void *ngli_hmap_get_ptr(const struct hmap *hm, const void *ptr) +{ + ngli_assert(hm->type == NGLI_HMAP_TYPE_PTR); + const union hmap_key key = {.ptr=(void *)ptr}; + return hmap_get(hm, key); +} + void *ngli_hmap_get_str(const struct hmap *hm, const char *str) { ngli_assert(hm->type == NGLI_HMAP_TYPE_STR); diff --git a/libnopegl/src/utils/hmap.h b/libnopegl/src/utils/hmap.h index 51ab52fdb..25c0a2bd1 100644 --- a/libnopegl/src/utils/hmap.h +++ b/libnopegl/src/utils/hmap.h @@ -40,11 +40,20 @@ struct hmap_ref { /* internal entry reference */ }; union hmap_key { + void *ptr; char *str; uint64_t u64; uint8_t u8_8[8]; }; +struct hmap_key_funcs { + uint32_t (*hash)(union hmap_key x); // mixing/hashing of a key + int (*cmp)(union hmap_key a, union hmap_key b); // compare 2 keys (0 if identical) + union hmap_key (*dup)(union hmap_key x); // create a copy of the key + int (*check)(union hmap_key x); // check whether the key is valid or not + void (*free)(union hmap_key x); // free a key +}; + struct hmap_entry { union hmap_key key; void *data; @@ -54,16 +63,20 @@ struct hmap_entry { }; enum hmap_type { + NGLI_HMAP_TYPE_PTR, NGLI_HMAP_TYPE_STR, NGLI_HMAP_TYPE_U64, NGLI_HMAP_TYPE_NB }; struct hmap *ngli_hmap_create(enum hmap_type type); +struct hmap *ngli_hmap_create_ptr(const struct hmap_key_funcs *key_funcs); void ngli_hmap_set_free_func(struct hmap *hm, ngli_user_free_func_type user_free_func, void *user_arg); size_t ngli_hmap_count(const struct hmap *hm); +int ngli_hmap_set_ptr(struct hmap *hm, const void *ptr, void *data); int ngli_hmap_set_str(struct hmap *hm, const char *str, void *data); int ngli_hmap_set_u64(struct hmap *hm, uint64_t u64, void *data); +void *ngli_hmap_get_ptr(const struct hmap *hm, const void *ptr); void *ngli_hmap_get_str(const struct hmap *hm, const char *str); void *ngli_hmap_get_u64(const struct hmap *hm, uint64_t u64); struct hmap_entry *ngli_hmap_next(const struct hmap *hm, const struct hmap_entry *prev);