diff --git a/include/mgba-util/table.h b/include/mgba-util/table.h index ea2c21748..b950fcb3b 100644 --- a/include/mgba-util/table.h +++ b/include/mgba-util/table.h @@ -29,6 +29,11 @@ struct Table { struct TableFunctions fn; }; +struct TableIterator { + size_t bucket; + size_t entry; +}; + void TableInit(struct Table*, size_t initialSize, void (*deinitializer)(void*)); void TableDeinit(struct Table*); @@ -41,6 +46,12 @@ void TableClear(struct Table*); void TableEnumerate(const struct Table*, void (*handler)(uint32_t key, void* value, void* user), void* user); size_t TableSize(const struct Table*); +bool TableIteratorStart(const struct Table*, struct TableIterator*); +bool TableIteratorNext(const struct Table*, struct TableIterator*); +uint32_t TableIteratorGetKey(const struct Table*, const struct TableIterator*); +void* TableIteratorGetValue(const struct Table*, const struct TableIterator*); +bool TableIteratorLookup(const struct Table*, struct TableIterator*, uint32_t key); + void HashTableInit(struct Table* table, size_t initialSize, void (*deinitializer)(void*)); void HashTableInitCustom(struct Table* table, size_t initialSize, const struct TableFunctions* funcs); void HashTableDeinit(struct Table* table); @@ -66,6 +77,17 @@ const char* HashTableSearchData(const struct Table* table, const void* value, si const char* HashTableSearchString(const struct Table* table, const char* value); size_t HashTableSize(const struct Table*); +bool HashTableIteratorStart(const struct Table*, struct TableIterator*); +bool HashTableIteratorNext(const struct Table*, struct TableIterator*); +const char* HashTableIteratorGetKey(const struct Table*, const struct TableIterator*); +const void* HashTableIteratorGetBinaryKey(const struct Table*, const struct TableIterator*); +size_t HashTableIteratorGetBinaryKeyLen(const struct Table*, const struct TableIterator*); +void* HashTableIteratorGetCustomKey(const struct Table*, const struct TableIterator*); +void* HashTableIteratorGetValue(const struct Table*, const struct TableIterator*); +bool HashTableIteratorLookup(const struct Table*, struct TableIterator*, const char* key); +bool HashTableIteratorLookupBinary(const struct Table*, struct TableIterator*, const void* key, size_t keylen); +bool HashTableIteratorLookupCustom(const struct Table*, struct TableIterator*, void* key); + CXX_GUARD_END #endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index d43f901f7..8d86f7b60 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -30,6 +30,7 @@ set(GUI_FILES set(TEST_FILES test/string-parser.c test/string-utf8.c + test/table.c test/text-codec.c test/vfs.c) diff --git a/src/util/table.c b/src/util/table.c index b23c88de8..ca256db7a 100644 --- a/src/util/table.c +++ b/src/util/table.c @@ -217,6 +217,52 @@ size_t TableSize(const struct Table* table) { return table->size; } +bool TableIteratorStart(const struct Table* table, struct TableIterator* iter) { + iter->entry = 0; + for (iter->bucket = 0; iter->bucket < table->tableSize; ++iter->bucket) { + if (table->table[iter->bucket].nEntries) { + break; + } + } + return iter->bucket < table->tableSize; +} + +bool TableIteratorNext(const struct Table* table, struct TableIterator* iter) { + if (iter->entry + 1 < table->table[iter->bucket].nEntries) { + ++iter->entry; + return true; + } + if (iter->bucket + 1 < table->tableSize) { + iter->entry = 0; + for (++iter->bucket; iter->bucket < table->tableSize; ++iter->bucket) { + if (table->table[iter->bucket].nEntries) { + break; + } + } + return iter->bucket < table->tableSize; + } + return false; +} + +uint32_t TableIteratorGetKey(const struct Table* table, const struct TableIterator* iter) { + return table->table[iter->bucket].list[iter->entry].key; +} + +void* TableIteratorGetValue(const struct Table* table, const struct TableIterator* iter) { + return table->table[iter->bucket].list[iter->entry].value; +} + +bool TableIteratorLookup(const struct Table* table, struct TableIterator* iter, uint32_t key) { + uint32_t bucket = key & (table->tableSize - 1); + const struct TableList* list = &table->table[bucket]; + TABLE_LOOKUP_START(TABLE_COMPARATOR, list) { + iter->bucket = bucket; + iter->entry = i; + return true; + } TABLE_LOOKUP_END; + return false; +} + void HashTableInit(struct Table* table, size_t initialSize, void (*deinitializer)(void*)) { TableInit(table, initialSize, deinitializer); table->seed = 1; @@ -531,3 +577,77 @@ const char* HashTableSearchString(const struct Table* table, const char* value) size_t HashTableSize(const struct Table* table) { return table->size; } + +bool HashTableIteratorStart(const struct Table* table, struct TableIterator* iter) { + return TableIteratorStart(table, iter); +} + +bool HashTableIteratorNext(const struct Table* table, struct TableIterator* iter) { + return TableIteratorNext(table, iter); +} + +const char* HashTableIteratorGetKey(const struct Table* table, const struct TableIterator* iter) { + return table->table[iter->bucket].list[iter->entry].stringKey; +} + +const void* HashTableIteratorGetBinaryKey(const struct Table* table, const struct TableIterator* iter) { + return table->table[iter->bucket].list[iter->entry].stringKey; +} + +size_t HashTableIteratorGetBinaryKeyLen(const struct Table* table, const struct TableIterator* iter) { + return table->table[iter->bucket].list[iter->entry].keylen; +} + +void* HashTableIteratorGetCustomKey(const struct Table* table, const struct TableIterator* iter) { + return (char*) table->table[iter->bucket].list[iter->entry].stringKey; +} + +void* HashTableIteratorGetValue(const struct Table* table, const struct TableIterator* iter) { + return TableIteratorGetValue(table, iter); +} + +bool HashTableIteratorLookup(const struct Table* table, struct TableIterator* iter, const char* key) { + uint32_t hash; + if (table->fn.hash) { + hash = table->fn.hash(key, strlen(key), table->seed); + } else { + hash = hash32(key, strlen(key), table->seed); + } + uint32_t bucket = hash & (table->tableSize - 1); + const struct TableList* list = &table->table[bucket]; + TABLE_LOOKUP_START(HASH_TABLE_STRNCMP_COMPARATOR, list) { + iter->bucket = bucket; + iter->entry = i; + return true; + } TABLE_LOOKUP_END; + return false; +} + +bool HashTableIteratorLookupBinary(const struct Table* table, struct TableIterator* iter, const void* key, size_t keylen) { + uint32_t hash; + if (table->fn.hash) { + hash = table->fn.hash(key, keylen, table->seed); + } else { + hash = hash32(key, keylen, table->seed); + } + uint32_t bucket = hash & (table->tableSize - 1); + const struct TableList* list = &table->table[bucket]; + TABLE_LOOKUP_START(HASH_TABLE_MEMCMP_COMPARATOR, list) { + iter->bucket = bucket; + iter->entry = i; + return true; + } TABLE_LOOKUP_END; + return false; +} + +bool HashTableIteratorLookupCustom(const struct Table* table, struct TableIterator* iter, void* key) { + uint32_t hash = table->fn.hash(key, 0, table->seed); + uint32_t bucket = hash & (table->tableSize - 1); + const struct TableList* list = &table->table[bucket]; + TABLE_LOOKUP_START(HASH_TABLE_CUSTOM_COMPARATOR, list) { + iter->bucket = bucket; + iter->entry = i; + return true; + } TABLE_LOOKUP_END; + return false; +} diff --git a/src/util/test/table.c b/src/util/test/table.c new file mode 100644 index 000000000..31d1b5f0a --- /dev/null +++ b/src/util/test/table.c @@ -0,0 +1,158 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(basic) { + struct Table table; + TableInit(&table, 0, NULL); + + size_t i; + for (i = 0; i < 5000; ++i) { + TableInsert(&table, i, (void*) i); + } + + for (i = 0; i < 5000; ++i) { + assert_int_equal(i, (size_t) TableLookup(&table, i)); + } + + TableDeinit(&table); +} + +M_TEST_DEFINE(iterator) { + struct Table table; + struct TableIterator iter; + + TableInit(&table, 0, NULL); + assert_false(TableIteratorStart(&table, &iter)); + + size_t i; + for (i = 0; i < 32; ++i) { + TableInsert(&table, i, (void*) i); + } + + assert_true(TableIteratorStart(&table, &iter)); + uint32_t mask = 0; + while (true) { + assert_int_equal(TableIteratorGetKey(&table, &iter), (uintptr_t) TableIteratorGetValue(&table, &iter)); + mask ^= 1 << TableIteratorGetKey(&table, &iter); + if (!TableIteratorNext(&table, &iter)) { + break; + } + } + assert_int_equal(mask, 0xFFFFFFFFU); + + TableDeinit(&table); +} + +M_TEST_DEFINE(iteratorLookup) { + struct Table table; + struct TableIterator iter; + + TableInit(&table, 0, NULL); + + size_t i; + for (i = 0; i < 500; ++i) { + TableInsert(&table, (i * 0x5DEECE66D) >> 16, (void*) i); + } + + for (i = 0; i < 500; ++i) { + assert_true(TableIteratorLookup(&table, &iter, (i * 0x5DEECE66D) >> 16)); + assert_int_equal(TableIteratorGetKey(&table, &iter), (i * 0x5DEECE66D) >> 16); + assert_int_equal((uintptr_t) TableIteratorGetValue(&table, &iter), i); + } + for (i = 1000; i < 1200; ++i) { + assert_false(TableIteratorLookup(&table, &iter, (i * 0x5DEECE66D) >> 16)); + } + + TableDeinit(&table); +} + +M_TEST_DEFINE(hash) { + struct Table table; + HashTableInit(&table, 0, NULL); + + size_t i; + for (i = 0; i < 5000; ++i) { + char buffer[16]; + snprintf(buffer, sizeof(buffer), "%"PRIz"i", i); + HashTableInsert(&table, buffer, (void*) i); + } + + for (i = 0; i < 5000; ++i) { + char buffer[16]; + snprintf(buffer, sizeof(buffer), "%"PRIz"i", i); + assert_int_equal(i, (size_t) HashTableLookup(&table, buffer)); + } + + HashTableDeinit(&table); +} + +M_TEST_DEFINE(hashIterator) { + struct Table table; + struct TableIterator iter; + char buf[18]; + + HashTableInit(&table, 0, NULL); + assert_false(HashTableIteratorStart(&table, &iter)); + + size_t i; + for (i = 0; i < 32; ++i) { + snprintf(buf, sizeof(buf), "%zu", i); + HashTableInsert(&table, buf, (void*) i); + } + + assert_true(TableIteratorStart(&table, &iter)); + uint32_t mask = 0; + while (true) { + assert_int_equal(atoi(HashTableIteratorGetKey(&table, &iter)), (uintptr_t) HashTableIteratorGetValue(&table, &iter)); + mask ^= 1 << atoi(HashTableIteratorGetKey(&table, &iter)); + if (!HashTableIteratorNext(&table, &iter)) { + break; + } + } + assert_int_equal(mask, 0xFFFFFFFFU); + + HashTableDeinit(&table); +} + + +M_TEST_DEFINE(hashIteratorLookup) { + struct Table table; + struct TableIterator iter; + char buf[18]; + + HashTableInit(&table, 0, NULL); + + size_t i; + for (i = 0; i < 500; ++i) { + snprintf(buf, sizeof(buf), "%zu", (i * 0x5DEECE66D) >> 4); + HashTableInsert(&table, buf, (void*) i); + } + + for (i = 0; i < 500; ++i) { + snprintf(buf, sizeof(buf), "%zu", (i * 0x5DEECE66D) >> 4); + assert_true(HashTableIteratorLookup(&table, &iter, buf)); + assert_string_equal(HashTableIteratorGetKey(&table, &iter), buf); + assert_int_equal((uintptr_t) HashTableIteratorGetValue(&table, &iter), i); + } + for (i = 1000; i < 1200; ++i) { + snprintf(buf, sizeof(buf), "%zu", (i * 0x5DEECE66D) >> 4); + assert_false(HashTableIteratorLookup(&table, &iter, buf)); + } + + HashTableDeinit(&table); +} + +M_TEST_SUITE_DEFINE(Table, + cmocka_unit_test(basic), + cmocka_unit_test(iterator), + cmocka_unit_test(iteratorLookup), + cmocka_unit_test(hash), + cmocka_unit_test(hashIterator), + cmocka_unit_test(hashIteratorLookup), +)