From 9bea4831d739eba6431907b35ea258edaef13f14 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sun, 3 Jan 2021 00:52:25 +0900 Subject: [PATCH] Add 32-bit key hash map rhmap Based on ex_hashmap32 in menu_explore.c, modify that to use the new rhmap --- libretro-common/include/array/rhmap.h | 246 ++++++++++++++++++++++++++ menu/menu_explore.c | 214 ++++------------------ 2 files changed, 281 insertions(+), 179 deletions(-) create mode 100644 libretro-common/include/array/rhmap.h diff --git a/libretro-common/include/array/rhmap.h b/libretro-common/include/array/rhmap.h new file mode 100644 index 0000000000..7de4f7ff28 --- /dev/null +++ b/libretro-common/include/array/rhmap.h @@ -0,0 +1,246 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rhmap.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBRETRO_SDK_ARRAY_RHMAP_H__ +#define __LIBRETRO_SDK_ARRAY_RHMAP_H__ + +/* + * This file implements a hash map with 32-bit keys. + * Based on the implementation from the public domain Bitwise project + * by Per Vognsen - https://github.com/pervognsen/bitwise + * + * It's a super simple type safe hash map for C with no need + * to predeclare any type or anything. + * Will always allocate memory for twice the amount of max elements + * so larger structs should be stored as pointers or indices to an array. + * Can be used in C++ with POD types (without any constructor/destructor). + * + * Be careful not to supply modifying statements to the macro arguments. + * Something like RHMAP_FIT(map, i++); would have unintended results. + * + * Sample usage: + * + * -- Set 2 elements with string keys and mytype_t values: + * mytype_t* map = NULL; + * RHMAP_SET_STR(map, "foo", foo_element); + * RHMAP_SET_STR(map, "bar", bar_element); + * -- now RHMAP_LEN(map) == 2, RHMAP_GET_STR(map, "foo") == foo_element + * + * -- Check if keys exist: + * bool has_foo = RHMAP_HAS_STR(map, "foo"); + * bool has_baz = RHMAP_HAS_STR(map, "baz"); + * -- now has_foo == true, has_baz == false + * + * -- Removing a key: + * bool removed = RHMAP_DEL_STR(map, "bar"); + * bool removed_again = RHMAP_DEL_STR(map, "bar"); + * -- now RHMAP_LEN(map) == 1, removed == true, removed_again == false + * + * -- Add/modify via pointer: + * mytype_t* p_elem = RHMAP_PTR_STR(map, "qux"); + * p_elem->a = 123; + * -- New keys initially have memory uninitialized + * -- Pointers can get invalidated when a key is added/removed + * + * -- Looking up the index for a given key: + * ptrdiff_t idx_foo = RHMAP_IDX_STR(map, "foo"); + * ptrdiff_t idx_invalid = RHMAP_IDX_STR(map, "invalid"); + * -- now idx_foo >= 0, idx_invalid == -1, map[idx_foo] == foo_element + * -- Indices can change when a key is added/removed + * + * -- Clear all elements (keep memory allocated): + * RHMAP_CLEAR(map); + * -- now RHMAP_LEN(map) == 0, RHMAP_CAP(map) == 16 + * + * -- Reserve memory for at least N elements: + * RHMAP_FIT(map, 30); + * -- now RHMAP_LEN(map) == 0, RHMAP_CAP(map) == 64 + * + * -- Add elements with custom hash keys: + * RHMAP_SET(map, my_uint32_hash(key1), some_element); + * RHMAP_SET(map, my_uint32_hash(key2), other_element); + * -- now RHMAP_LEN(map) == 2, _GET/_HAS/_DEL/_PTR/_IDX also exist + * + * -- Iterate elements (random order, order can change on insert): + * for (size_t i = 0, cap = RHMAP_CAP(map); i != cap, i++) + * if (RHMAP_KEY(map, i)) + * ------ here map[i] is the value of key RHMAP_KEY(map, i) + * + * -- Set a custom null value (is zeroed by default): + * RHMAP_SETNULLVAL(map, map_null); + * -- now RHMAP_GET_STR(map, "invalid") == map_null + * + * -- Free allocated memory: + * RHMAP_FREE(map); + * -- now map == NULL, RHMAP_LEN(map) == 0, RHMAP_CAP(map) == 0 + * + * -- To handle running out of memory: + * bool ran_out_of_memory = !RHMAP_TRYFIT(map, 1000); + * -- before setting an element (with SET, PTR or NULLVAL). + * -- When out of memory, map will stay unmodified. + * + */ + +#include /* for malloc, realloc */ +#include /* for memcpy, memset */ +#include /* for ptrdiff_t, size_t */ +#include /* for uint32_t */ + +#define RHMAP_LEN(b) ((b) ? RHMAP__HDR(b)->len : 0) +#define RHMAP_MAX(b) ((b) ? RHMAP__HDR(b)->maxlen : 0) +#define RHMAP_CAP(b) ((b) ? RHMAP__HDR(b)->maxlen + 1 : 0) +#define RHMAP_KEY(b, idx) (RHMAP__HDR(b)->keys[idx]) +#define RHMAP_SETNULLVAL(b, val) (RHMAP__FIT1(b), b[-1] = (val)) +#define RHMAP_CLEAR(b) ((b) ? (memset(RHMAP__HDR(b)->keys, 0, RHMAP_CAP(b) * sizeof(uint32_t)), RHMAP__HDR(b)->len = 0) : 0) +#define RHMAP_FREE(b) ((b) ? (free(RHMAP__HDR(b)->keys), free(RHMAP__HDR(b)), (b) = NULL) : 0) +#define RHMAP_FIT(b, n) ((!(n) || ((b) && (size_t)(n) * 2 <= RHMAP_MAX(b))) ? 0 : RHMAP__GROW(b, n)) +#define RHMAP_TRYFIT(b, n) (RHMAP_FIT((b), (n)), (!(n) || ((b) && (size_t)(n) * 2 <= RHMAP_MAX(b)))) + +#define RHMAP_SET(b, key, val) (RHMAP__FIT1(b), b[rhmap__idx(RHMAP__HDR(b), (key), 1, 0)] = (val)) +#define RHMAP_GET(b, key) (RHMAP__FIT1(b), b[rhmap__idx(RHMAP__HDR(b), (key), 0, 0)]) +#define RHMAP_HAS(b, key) ((b) ? rhmap__idx(RHMAP__HDR(b), (key), 0, 0) != -1 : 0) +#define RHMAP_DEL(b, key) ((b) ? rhmap__idx(RHMAP__HDR(b), (key), 0, sizeof(*(b))) != -1 : 0) +#define RHMAP_PTR(b, key) (RHMAP__FIT1(b), &b[rhmap__idx(RHMAP__HDR(b), (key), 1, 0)]) +#define RHMAP_IDX(b, key) ((b) ? rhmap__idx(RHMAP__HDR(b), (key), 0, 0) : -1) + +#ifdef __GNUC__ +#define RHMAP__UNUSED __attribute__((__unused__)) +#else +#define RHMAP__UNUSED +#endif + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4505) //unreferenced local function has been removed +#endif + +#define RHMAP_SET_STR(b, string_key, val) RHMAP_SET(b, hash_string(string_key), val) +#define RHMAP_GET_STR(b, string_key) RHMAP_GET(b, hash_string(string_key)) +#define RHMAP_HAS_STR(b, string_key) RHMAP_HAS(b, hash_string(string_key)) +#define RHMAP_DEL_STR(b, string_key) RHMAP_DEL(b, hash_string(string_key)) +#define RHMAP_PTR_STR(b, string_key) RHMAP_PTR(b, hash_string(string_key)) +#define RHMAP_IDX_STR(b, string_key) RHMAP_IDX(b, hash_string(string_key)) + +RHMAP__UNUSED static uint32_t hash_string(const char* str) +{ + unsigned char c; + uint32_t hash = (uint32_t)0x811c9dc5; + while ((c = (unsigned char)*(str++)) != '\0') + hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)c); + return (hash ? hash : 1); +} + +struct rhmap__hdr { size_t len, maxlen; uint32_t *keys; }; +#define RHMAP__HDR(b) (((struct rhmap__hdr *)&(b)[-1])-1) +#define RHMAP__GROW(b, n) (*(void**)(&(b)) = rhmap__grow(RHMAP__HDR(b), (void*)(b), sizeof(*(b)), (size_t)(n))) +#define RHMAP__FIT1(b) ((b) && RHMAP_LEN(b) * 2 <= RHMAP_MAX(b) ? 0 : RHMAP__GROW(b, 0)) + +RHMAP__UNUSED static void* rhmap__grow(struct rhmap__hdr *old_hdr, void* old_ptr, size_t elem_size, size_t reserve) +{ + struct rhmap__hdr *new_hdr; + char *new_vals; + size_t new_max = (old_ptr ? old_hdr->maxlen * 2 + 1 : 15); + while (new_max && new_max / 2 <= reserve) + if (!(new_max = new_max * 2 + 1)) + return old_ptr; /* overflow */ + + new_hdr = (struct rhmap__hdr *)malloc(sizeof(struct rhmap__hdr) + (new_max + 2) * elem_size); + if (!new_hdr) + return old_ptr; /* out of memory */ + + new_hdr->maxlen = new_max; + new_hdr->keys = (uint32_t *)calloc(new_max + 1, sizeof(uint32_t)); + if (!new_hdr->keys) + return (free(new_hdr), old_ptr); /* out of memory */ + + new_vals = ((char*)(new_hdr + 1)) + elem_size; + if (old_ptr) + { + size_t i; + char* old_vals = ((char*)(old_hdr + 1)) + elem_size; + for (i = 0; i <= old_hdr->maxlen; i++) + { + uint32_t key, j; + if (!old_hdr->keys[i]) + continue; + for (key = old_hdr->keys[i], j = key;; j++) + { + if (!new_hdr->keys[j &= new_hdr->maxlen]) + { + new_hdr->keys[j] = key; + memcpy(new_vals + j * elem_size, old_vals + i * elem_size, elem_size); + break; + } + } + } + memcpy(new_vals - elem_size, old_vals - elem_size, elem_size); + new_hdr->len = old_hdr->len; + free(old_hdr->keys); + free(old_hdr); + } + else + { + memset(new_vals - elem_size, 0, elem_size); + new_hdr->len = 0; + } + return new_vals; +} + +RHMAP__UNUSED static ptrdiff_t rhmap__idx(struct rhmap__hdr* hdr, uint32_t key, int add, size_t del) +{ + uint32_t i; + + if (!key) + return (ptrdiff_t)-1; + + for (i = key;; i++) + { + if (hdr->keys[i &= hdr->maxlen] == key) + { + if (del) + { + hdr->len--; + hdr->keys[i] = 0; + while ((key = hdr->keys[i = (i + 1) & hdr->maxlen]) != 0) + { + if ((key = (uint32_t)rhmap__idx(hdr, key, 1, 0)) == i) continue; + hdr->len--; + hdr->keys[i] = 0; + memcpy(((char*)(hdr + 1)) + (key + 1) * del, + ((char*)(hdr + 1)) + (i + 1) * del, del); + } + } + return (ptrdiff_t)i; + } + if (!hdr->keys[i]) + { + if (add) { hdr->len++; hdr->keys[i] = key; return (ptrdiff_t)i; } + return (ptrdiff_t)-1; + } + } +} + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 8497cc8968..43747f890a 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -24,6 +24,7 @@ #include #include #include +#include #define EX_ARENA_ALIGNMENT 8 #define EX_ARENA_BLOCK_SIZE (64 * 1024) @@ -64,14 +65,6 @@ typedef struct ex_arena char **blocks; } ex_arena; -typedef struct ex_hashmap32 -{ - uint32_t *keys; - uintptr_t *vals; - uint32_t len; - uint32_t cap; -} ex_hashmap32; - typedef struct { uint32_t idx; @@ -163,17 +156,6 @@ static void ex_arena_free(ex_arena *arena) } /* Hash function */ -static uint32_t ex_hash32(const char* str) -{ - unsigned char c; - uint32_t hash = (uint32_t)0x811c9dc5; - for (; (c = *(str++)) != '\0';) - hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)c); - if (hash) - return hash; - return 1; -} - static uint32_t ex_hash32_nocase_filtered( const unsigned char* str, size_t len, unsigned char f_first, unsigned char f_last) @@ -193,127 +175,6 @@ static uint32_t ex_hash32_nocase_filtered( return 1; } -/* Hashmap */ -static void ex_hashmap32__grow(ex_hashmap32* map, uint32_t new_cap) -{ - size_t i, j; - uint32_t old_cap = map->cap; - uint32_t *old_keys = map->keys; - uintptr_t *old_vals = map->vals; - - map->cap = (new_cap < 16) ? 16 : new_cap; - map->keys = (uint32_t *)calloc(map->cap, sizeof(uint32_t)); - map->vals = (uintptr_t *)malloc(map->cap * sizeof(uintptr_t)); - - for (i = 0; i < old_cap; i++) - { - uint32_t key; - if (!old_keys[i]) - continue; - - for (key = old_keys[i], j = key;; j++) - { - if (!map->keys[j &= map->cap - 1]) - { - map->keys[j] = key; - map->vals[j] = old_vals[i]; - break; - } - } - } - - free(old_keys); - free(old_vals); -} - -static INLINE void ex_hashmap32_init(ex_hashmap32* map) -{ - map->len = map->cap = 0; - map->keys = NULL; - map->vals = NULL; -} - -static void ex_hashmap32_free(ex_hashmap32* map) -{ - if (!map) - return; - free(map->keys); - free(map->vals); -} - -static uintptr_t ex_hashmap32_getnum(ex_hashmap32* map, uint32_t key) -{ - uint32_t i; - if (!map || map->len == 0 || !key) - return 0; - for (i = key;; i++) - { - if (map->keys[i &= map->cap - 1] == key) - return map->vals[i]; - if (!map->keys[i]) - break; - } - return 0; -} - -static void ex_hashmap32_setnum( - ex_hashmap32* map, uint32_t key, uintptr_t val) -{ - uint32_t i; - if (!key) - return; - if (2 * map->len >= map->cap) - ex_hashmap32__grow(map, 2 * map->cap); - - for (i = key;; i++) - { - if (!map->keys[i &= map->cap - 1]) - { - map->len++; - map->keys[i] = key; - map->vals[i] = val; - return; - } - if (map->keys[i] == key) - { - map->vals[i] = val; - return; - } - } -} - -static INLINE void *ex_hashmap32_getptr(ex_hashmap32* map, uint32_t key) -{ - return (void*)ex_hashmap32_getnum(map, key); -} - -static INLINE void ex_hashmap32_setptr(ex_hashmap32* map, - uint32_t key, void* ptr) -{ - ex_hashmap32_setnum(map, key, (uintptr_t)ptr); -} - -static INLINE void *ex_hashmap32_strgetptr(ex_hashmap32* map, const char* str) -{ - return (void*)ex_hashmap32_getnum(map, ex_hash32(str)); -} - -static INLINE void ex_hashmap32_strsetptr(ex_hashmap32* map, - const char* str, void* ptr) -{ - ex_hashmap32_setnum(map, ex_hash32(str), (uintptr_t)ptr); -} -static INLINE uintptr_t ex_hashmap32_strgetnum( - ex_hashmap32* map, const char* str) -{ - return ex_hashmap32_getnum(map, ex_hash32(str)); -} -static INLINE void ex_hashmap32_strsetnum(ex_hashmap32* map, - const char* str, uintptr_t num) -{ - ex_hashmap32_setnum(map, ex_hash32(str), num); -} - static int explore_qsort_func_strings(const void *a_, const void *b_) { const explore_string_t **a = (const explore_string_t**)a_; @@ -368,7 +229,7 @@ static int explore_check_company_suffix(const char* p, bool search_reverse) static void explore_add_unique_string( explore_state_t *explore, - ex_hashmap32 *maps, explore_entry_t *e, + explore_string_t** maps[EXPLORE_CAT_COUNT], explore_entry_t *e, unsigned cat, const char *str, explore_string_t ***split_buf) { @@ -420,8 +281,7 @@ static void explore_add_unique_string( len = p - str; hash = ex_hash32_nocase_filtered( (unsigned char*)str, len, '0', 255); - entry = - (explore_string_t*)ex_hashmap32_getptr(&maps[cat], hash); + entry = RHMAP_GET(maps[cat], hash); if (!entry) { @@ -431,7 +291,7 @@ static void explore_add_unique_string( memcpy(entry->str, str, len); entry->str[len] = '\0'; RBUF_PUSH(explore->by[cat], entry); - ex_hashmap32_setptr(&maps[cat], hash, entry); + RHMAP_SET(maps[cat], hash, entry); } if (!e->by[cat]) @@ -546,21 +406,21 @@ static explore_state_t *explore_build_list(void) struct explore_rdb { libretrodb_t *handle; - ex_hashmap32 playlist_crcs; /* ptr alignment */ - ex_hashmap32 playlist_names; /* ptr alignment */ + const struct playlist_entry **playlist_crcs; + const struct playlist_entry **playlist_names; size_t count; char systemname[256]; } - *rdbs = NULL; - ex_hashmap32 rdb_indices = {0}; - ex_hashmap32 cat_maps[EXPLORE_CAT_COUNT] = {{0}}; - explore_string_t **split_buf = NULL; - settings_t *settings = config_get_ptr(); - const char *directory_playlist = settings->paths.directory_playlist; - const char *directory_database = settings->paths.path_content_database; - libretro_vfs_implementation_dir *dir = NULL; + *rdbs = NULL; + int *rdb_indices = NULL; + explore_string_t **cat_maps[EXPLORE_CAT_COUNT] = {NULL}; + explore_string_t **split_buf = NULL; + settings_t *settings = config_get_ptr(); + const char *directory_playlist = settings->paths.directory_playlist; + const char *directory_database = settings->paths.path_content_database; + libretro_vfs_implementation_dir *dir = NULL; - explore_state_t *explore = (explore_state_t*)calloc( + explore_state_t *explore = (explore_state_t*)calloc( 1, sizeof(*explore)); if (!explore) @@ -610,7 +470,7 @@ static explore_state_t *explore_build_list(void) for (j = 0; j < playlist_size(playlist); j++) { - uintptr_t rdb_num; + int rdb_num; uint32_t entry_crc32; struct explore_rdb* rdb = NULL; const struct playlist_entry *entry = NULL; @@ -636,16 +496,16 @@ static explore_state_t *explore_build_list(void) (unsigned char*)db_name, db_ext - db_name, '0', 255); } - rdb_num = ex_hashmap32_getnum(&rdb_indices, rdb_hash); + rdb_num = RHMAP_GET(rdb_indices, rdb_hash); if (!rdb_num) { struct explore_rdb newrdb; size_t systemname_len; - newrdb.handle = libretrodb_new(); - newrdb.count = 0; - ex_hashmap32_init(&newrdb.playlist_crcs); - ex_hashmap32_init(&newrdb.playlist_names); + newrdb.handle = libretrodb_new(); + newrdb.count = 0; + newrdb.playlist_crcs = NULL; + newrdb.playlist_names = NULL; systemname_len = db_ext - db_name; if (systemname_len >= sizeof(newrdb.systemname)) @@ -661,13 +521,13 @@ static explore_state_t *explore_build_list(void) { /* Invalid RDB file */ libretrodb_free(newrdb.handle); - ex_hashmap32_setnum(&rdb_indices, rdb_hash, (uintptr_t)-1); + RHMAP_SET(rdb_indices, rdb_hash, -1); continue; } RBUF_PUSH(rdbs, newrdb); rdb_num = (uintptr_t)RBUF_LEN(rdbs); - ex_hashmap32_setnum(&rdb_indices, rdb_hash, rdb_num); + RHMAP_SET(rdb_indices, rdb_hash, rdb_num); } if (rdb_num == (uintptr_t)-1) @@ -679,13 +539,11 @@ static explore_state_t *explore_build_list(void) (entry->crc32 ? entry->crc32 : ""), NULL, 16); if (entry_crc32) { - ex_hashmap32_setptr(&rdb->playlist_crcs, - entry_crc32, (void*)entry); + RHMAP_SET(rdb->playlist_crcs, entry_crc32, entry); } else { - ex_hashmap32_strsetptr(&rdb->playlist_names, - entry->label, (void*)entry); + RHMAP_SET_STR(rdb->playlist_names, entry->label, entry); } used_entries++; } @@ -779,13 +637,11 @@ static explore_state_t *explore_build_list(void) if (crc32) { - entry = (const struct playlist_entry *)ex_hashmap32_getptr( - &rdb->playlist_crcs, crc32); + entry = RHMAP_GET(rdb->playlist_crcs, crc32); } if (!entry && name) { - entry = (const struct playlist_entry *)ex_hashmap32_strgetptr( - &rdb->playlist_names, name); + entry = RHMAP_GET_STR(rdb->playlist_names, name); } if (!entry) continue; @@ -843,11 +699,11 @@ static explore_state_t *explore_build_list(void) libretrodb_cursor_free(cur); libretrodb_close(rdb->handle); libretrodb_free(rdb->handle); - ex_hashmap32_free(&rdb->playlist_crcs); - ex_hashmap32_free(&rdb->playlist_names); + RHMAP_FREE(rdb->playlist_crcs); + RHMAP_FREE(rdb->playlist_names); } RBUF_FREE(split_buf); - ex_hashmap32_free(&rdb_indices); + RHMAP_FREE(rdb_indices); RBUF_FREE(rdbs); for (i = 0; i != EXPLORE_CAT_COUNT; i++) @@ -862,7 +718,7 @@ static explore_state_t *explore_build_list(void) for (idx = 0; idx != len; idx++) explore->by[i][idx]->idx = idx; - ex_hashmap32_free(&cat_maps[i]); + RHMAP_FREE(cat_maps[i]); } qsort(explore->entries, RBUF_LEN(explore->entries), @@ -1136,7 +992,7 @@ SKIP_EXPLORE_BY_CATEGORY:; explore_string_t* filter[10]; explore_entry_t *e = NULL; explore_entry_t *e_end = NULL; - ex_hashmap32 map_filtered_category = {0}; + bool* map_filtered_category = NULL; unsigned levels = 0; bool use_find = ( *explore_state->find_string != '\0'); @@ -1235,9 +1091,9 @@ SKIP_EXPLORE_BY_CATEGORY:; filtered_category_have_unknown = true; continue; } - if (ex_hashmap32_getnum(&map_filtered_category, str->idx + 1)) + if (RHMAP_HAS(map_filtered_category, str->idx + 1)) continue; - ex_hashmap32_setnum(&map_filtered_category, str->idx + 1, 1); + RHMAP_SET(map_filtered_category, str->idx + 1, true); explore_menu_entry(list, explore_state, str->str, EXPLORE_TYPE_FIRSTITEM + str->idx); @@ -1270,7 +1126,7 @@ SKIP_ENTRY:; explore_append_title(explore_state, " (%u)", (unsigned) (list->size - (is_filtered_category ? 0 : 1))); - ex_hashmap32_free(&map_filtered_category); + RHMAP_FREE(map_filtered_category); } else {