diff --git a/libretro-common/file/config_file.c b/libretro-common/file/config_file.c index 85dda16e29..235db83006 100644 --- a/libretro-common/file/config_file.c +++ b/libretro-common/file/config_file.c @@ -39,6 +39,7 @@ #include #include #include +#include #define MAX_INCLUDE_DEPTH 16 @@ -48,6 +49,15 @@ struct config_include_list struct config_include_list *next; }; +static uint32_t config_file_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); +} + /* Forward declaration */ static bool config_file_parse_line(config_file_t *conf, struct config_entry_list *list, char *line, config_file_cb_t *cb); @@ -273,6 +283,8 @@ static char *config_file_extract_value(char *line, bool is_value) static void config_file_add_child_list(config_file_t *parent, config_file_t *child) { struct config_entry_list *list = child->entries; + bool merge_hash_map = false; + if (parent->entries) { struct config_entry_list *head = parent->entries; @@ -286,6 +298,8 @@ static void config_file_add_child_list(config_file_t *parent, config_file_t *chi list = list->next; } head->next = child->entries; + + merge_hash_map = true; } else { @@ -298,8 +312,6 @@ static void config_file_add_child_list(config_file_t *parent, config_file_t *chi parent->entries = child->entries; } - child->entries = NULL; - /* Rebase tail. */ if (parent->entries) { @@ -312,6 +324,44 @@ static void config_file_add_child_list(config_file_t *parent, config_file_t *chi } else parent->tail = NULL; + + /* Update hash map */ + if (merge_hash_map) + { + size_t i; + size_t cap; + + /* We are merging two lists - if any child entry + * (key) is not present in the parent list, add it + * to the parent hash map */ + for (i = 0, cap = RHMAP_CAP(child->entries_map); i != cap; i++) + { + uint32_t child_hash = RHMAP_KEY(child->entries_map, i); + + if (!RHMAP_HAS(parent->entries_map, child_hash)) + { + struct config_entry_list *entry = + RHMAP_GET(child->entries_map, child_hash); + + if (entry) + RHMAP_SET(parent->entries_map, child_hash, entry); + } + } + + /* Child entries map is no longer required, + * so free it now */ + RHMAP_FREE(child->entries_map); + } + else + { + /* If parent list was originally empty, + * take map from child list */ + RHMAP_FREE(parent->entries_map); + parent->entries_map = child->entries_map; + child->entries_map = NULL; + } + + child->entries = NULL; } static void config_file_get_realpath(char *s, size_t len, @@ -426,8 +476,21 @@ static int config_file_load_internal( conf->tail = list; - if (cb && list->key && list->value) - cb->config_file_new_entry_cb(list->key, list->value) ; + if (list->key) + { + /* Only add entry to the map if an entry + * with the specified value does not + * already exist */ + uint32_t hash = config_file_hash_string(list->key); + + if (!RHMAP_HAS(conf->entries_map, hash)) + { + RHMAP_SET(conf->entries_map, hash, list); + + if (cb && list->value) + cb->config_file_new_entry_cb(list->key, list->value); + } + } } free(line); @@ -628,6 +691,16 @@ static int config_file_from_string_internal( conf->entries = list; conf->tail = list; + + if (list->key) + { + /* Only add entry to the map if an entry + * with the specified value does not + * already exist */ + uint32_t hash = config_file_hash_string(list->key); + if (!RHMAP_HAS(conf->entries_map, hash)) + RHMAP_SET(conf->entries_map, hash, list); + } } if (list != conf->tail) @@ -705,6 +778,9 @@ bool config_file_deinitialize(config_file_t *conf) if (conf->path) free(conf->path); + + RHMAP_FREE(conf->entries_map); + return true; } @@ -718,9 +794,22 @@ void config_file_free(config_file_t *conf) bool config_append_file(config_file_t *conf, const char *path) { config_file_t *new_conf = config_file_new_from_path_to_string(path); + size_t i; + size_t cap; + if (!new_conf) return false; + /* Update hash map */ + for (i = 0, cap = RHMAP_CAP(new_conf->entries_map); i != cap; i++) + { + uint32_t new_hash = RHMAP_KEY(new_conf->entries_map, i); + struct config_entry_list *entry = RHMAP_GET(new_conf->entries_map, new_hash); + + if (entry) + RHMAP_SET(conf->entries_map, new_hash, entry); + } + if (new_conf->tail) { new_conf->tail->next = conf->entries; @@ -818,6 +907,7 @@ void config_file_initialize(struct config_file *conf) return; conf->path = NULL; + conf->entries_map = NULL; conf->entries = NULL; conf->tail = NULL; conf->last = NULL; @@ -844,16 +934,18 @@ static struct config_entry_list *config_get_entry_internal( struct config_entry_list *entry = NULL; struct config_entry_list *previous = prev ? *prev : NULL; - for (entry = conf->entries; entry; entry = entry->next) - { - if (string_is_equal(key, entry->key)) - return entry; + entry = RHMAP_GET(conf->entries_map, config_file_hash_string(key)); - previous = entry; - } + if (entry) + return entry; if (prev) + { + for (entry = conf->entries; entry; entry = entry->next) + previous = entry; + *prev = previous; + } return NULL; } @@ -861,16 +953,9 @@ static struct config_entry_list *config_get_entry_internal( struct config_entry_list *config_get_entry( const config_file_t *conf, const char *key) { - struct config_entry_list *entry = NULL; - for (entry = conf->entries; entry; entry = entry->next) - { - if (string_is_equal(key, entry->key)) - return entry; - } - return NULL; + return RHMAP_GET(conf->entries_map, config_file_hash_string(key)); } - bool config_get_double(config_file_t *conf, const char *key, double *in) { const struct config_entry_list *entry = config_get_entry(conf, key); @@ -1141,6 +1226,8 @@ void config_set_string(config_file_t *conf, const char *key, const char *val) conf->entries = entry; conf->last = entry; + + RHMAP_SET(conf->entries_map, config_file_hash_string(entry->key), entry); } void config_unset(config_file_t *conf, const char *key) @@ -1157,6 +1244,8 @@ void config_unset(config_file_t *conf, const char *key) if (!entry) return; + (void)RHMAP_DEL(conf->entries_map, config_file_hash_string(entry->key)); + if (entry->key) free(entry->key); @@ -1376,16 +1465,7 @@ void config_file_dump(config_file_t *conf, FILE *file, bool sort) bool config_entry_exists(config_file_t *conf, const char *entry) { - struct config_entry_list *list = conf->entries; - - while (list) - { - if (string_is_equal(entry, list->key)) - return true; - list = list->next; - } - - return false; + return (bool)RHMAP_HAS(conf->entries_map, config_file_hash_string(entry)); } bool config_get_entry_list_head(config_file_t *conf, diff --git a/libretro-common/include/file/config_file.h b/libretro-common/include/file/config_file.h index 1913bf2b9b..87e4b32b42 100644 --- a/libretro-common/include/file/config_file.h +++ b/libretro-common/include/file/config_file.h @@ -55,6 +55,7 @@ struct config_file { char *path; char *reference; + struct config_entry_list **entries_map; struct config_entry_list *entries; struct config_entry_list *tail; struct config_entry_list *last;