diff --git a/core_option_manager.c b/core_option_manager.c index af9a670011..924eb07d61 100644 --- a/core_option_manager.c +++ b/core_option_manager.c @@ -591,6 +591,69 @@ static uint32_t core_option_manager_hash_string(const char *str) return (hash ? hash : 1); } +/* Up to this many options are looked for in order, which is quicker + * than probing the slots */ +#define CORE_OPTION_SCAN_MAX 64 + +/* The slots of every keyed option, the first of a key kept */ +static bool core_option_manager_index_keys(core_option_manager_t *opt) +{ + size_t cap = 4, i; + if (opt->size <= CORE_OPTION_SCAN_MAX) + return true; + while (cap < 2 * opt->size) + cap <<= 1; + if (!(opt->key_slots = (uint32_t*)calloc(cap, sizeof(uint32_t)))) + return false; + opt->key_mask = cap - 1; + for (i = 0; i < opt->size; i++) + { + const struct core_option *option = &opt->opts[i]; + size_t at = option->key_hash & opt->key_mask; + if (!option->key) + continue; + for (;;) + { + uint32_t slot = opt->key_slots[at]; + if (!slot) + { + opt->key_slots[at] = (uint32_t)(i + 1); + break; + } + if ( opt->opts[slot - 1].key_hash == option->key_hash + && string_is_equal(opt->opts[slot - 1].key, option->key)) + break; + at = (at + 1) & opt->key_mask; + } + } + return true; +} + +/* The index of the option of @key, or -1 */ +static INLINE size_t core_option_manager_find( + const core_option_manager_t *opt, const char *key) +{ + uint32_t hash = core_option_manager_hash_string(key); + size_t at; + if (!opt->key_slots) + { + for (at = 0; at < opt->size; at++) + if ( opt->opts[at].key_hash == hash + && string_is_equal(key, opt->opts[at].key)) + return at; + return (size_t)-1; + } + for (at = hash & opt->key_mask; ; at = (at + 1) & opt->key_mask) + { + uint32_t slot = opt->key_slots[at]; + if (!slot) + return (size_t)-1; + if ( opt->opts[slot - 1].key_hash == hash + && string_is_equal(key, opt->opts[slot - 1].key)) + return slot - 1; + } +} + /* Sanitises a core option value label, handling the case * where an explicit label is not provided and performing * conversion of various true/false identifiers to @@ -829,6 +892,8 @@ core_option_manager_t *core_option_manager_new_vars( opt->opts = NULL; opt->size = 0; opt->val_hashes = NULL; + opt->key_slots = NULL; + opt->key_mask = 0; opt->option_map = nested_list_init(); opt->updated = false; opt->log = true; @@ -904,6 +969,9 @@ core_option_manager_t *core_option_manager_new_vars( goto error; } + if (!core_option_manager_index_keys(opt)) + goto error; + if (config_src) config_file_free(config_src); @@ -1135,6 +1203,8 @@ core_option_manager_t *core_option_manager_new( opt->opts = NULL; opt->size = 0; opt->val_hashes = NULL; + opt->key_slots = NULL; + opt->key_mask = 0; opt->option_map = nested_list_init(); opt->updated = false; opt->log = true; @@ -1255,6 +1325,9 @@ core_option_manager_t *core_option_manager_new( goto error; } + if (!core_option_manager_index_keys(opt)) + goto error; + if (config_src) config_file_free(config_src); @@ -1337,6 +1410,7 @@ void core_option_manager_free(core_option_manager_t *opt) free(opt->cats); free(opt->opts); free(opt->val_hashes); + free(opt->key_slots); free(opt); } @@ -1490,26 +1564,12 @@ bool core_option_manager_get_category_visible(core_option_manager_t *opt, bool core_option_manager_get_idx(core_option_manager_t *opt, const char *key, size_t *idx) { - uint32_t key_hash; size_t i; - if (!opt || (!key || !*key) || !idx) + if (!opt || (!key || !*key) || !idx + || (i = core_option_manager_find(opt, key)) == (size_t)-1) return false; - key_hash = core_option_manager_hash_string(key); - - for (i = 0; i < opt->size; i++) - { - struct core_option *option = &opt->opts[i]; - - if ( (key_hash == option->key_hash) - && (option->key && *option->key) - && string_is_equal(key, option->key)) - { - *idx = i; - return true; - } - } - - return false; + *idx = i; + return true; } /** @@ -1909,29 +1969,17 @@ void core_option_manager_set_default(core_option_manager_t *opt, void core_option_manager_set_display(core_option_manager_t *opt, const char *key, bool visible) { - uint32_t key_hash; + struct core_option *option; size_t i; - if (!opt || (!key || !*key)) + if (!opt || (!key || !*key) + || (i = core_option_manager_find(opt, key)) == (size_t)-1) return; - key_hash = core_option_manager_hash_string(key); - - for (i = 0; i < opt->size; i++) - { - struct core_option *option = &opt->opts[i]; - - if ( (key_hash == option->key_hash) - && (option->key && *option->key) - && string_is_equal(key, option->key)) - { - if (option->visible != visible && !opt->log) - RARCH_DBG("[Core] Set display: %s = %s\n", - option->key, - visible ? "visible" : "hidden"); - - option->visible = visible; - return; - } - } + option = &opt->opts[i]; + if (option->visible != visible && !opt->log) + RARCH_DBG("[Core] Set display: %s = %s\n", + option->key, + visible ? "visible" : "hidden"); + option->visible = visible; } /**********************/ diff --git a/core_option_manager.h b/core_option_manager.h index 7f16dcac6e..b31ef7ff01 100644 --- a/core_option_manager.h +++ b/core_option_manager.h @@ -76,6 +76,11 @@ struct core_option_manager nested_list_t *option_map; /* Value hashes of every option, one allocation */ uint32_t *val_hashes; + /* Past CORE_OPTION_SCAN_MAX options, each option's index plus one at + * its key hash, probed on; 0 is empty. key_mask + 1 slots, at least + * twice the options */ + uint32_t *key_slots; + size_t key_mask; size_t cats_size; size_t size; diff --git a/samples/core_options/core_options_test.c b/samples/core_options/core_options_test.c index e648d9bacb..1e7ad85244 100644 --- a/samples/core_options/core_options_test.c +++ b/samples/core_options/core_options_test.c @@ -303,6 +303,53 @@ static void t_legacy_append_failures(void) fail_append(0); } +/* Keys found by their index, @n of them: few enough to be looked for in + * order, or six hundred in their slots; every one at its own place, near + * misses not at all, and the display of each set by its key */ +#define LOOKUP_N 600 +static void t_lookup(size_t n) +{ + static struct retro_variable vars[LOOKUP_N + 1]; + static char keys[LOOKUP_N][16]; + static const char *misses[] = { "opt_6000", "opt_00", "opt_", + "opt_0000x", "OPT_0001", "x" }; + core_option_manager_t *opt; + size_t i, idx; + unsigned ok = 0; + + printf(" lookup: %u keys by index, misses, display\n", (unsigned)n); + write_conf(""); + for (i = 0; i < n; i++) + { + snprintf(keys[i], sizeof(keys[i]), "opt_%04u", (unsigned)i); + vars[i].key = keys[i]; + vars[i].value = "Option; on|off"; + } + vars[n].key = NULL; + vars[n].value = NULL; + opt = core_option_manager_new_vars(CONF_PATH, NULL, vars); + CHECK(opt != NULL, "manager not created"); + if (!opt) + return; + for (i = 0; i < n; i++) + if (core_option_manager_get_idx(opt, keys[i], &idx) && idx == i) + ok++; + CHECK(ok == n, "a key not found at its index"); + for (i = 0; i < sizeof(misses) / sizeof(misses[0]); i++) + CHECK(!core_option_manager_get_idx(opt, misses[i], &idx), + "a key no option has found"); + CHECK(!core_option_manager_get_idx(opt, "", &idx), "the empty key found"); + for (i = 0; i < n; i += 7) + core_option_manager_set_display(opt, keys[i], false); + core_option_manager_set_display(opt, "opt_9999", false); + ok = 0; + for (i = 0; i < n; i++) + if (opt->opts[i].visible == (i % 7 != 0)) + ok++; + CHECK(ok == n, "display set on the wrong option"); + core_option_manager_free(opt); +} + int main(void) { printf("core_options\n"); @@ -310,6 +357,8 @@ int main(void) t_v2_append_failures(); t_legacy(); t_legacy_append_failures(); + t_lookup(30); + t_lookup(LOOKUP_N); remove(CONF_PATH); if (failures)