Musa-Cpp-Lib-V2/lib/Base/Arena_Hash_Table.h

287 lines
8.2 KiB
C++

// #ArenaTableConfusion I just realized ArenaTable (a hash-table backed by an Arena allocator) may be confused with Arena_Table (which is a table tracking free Arenas)
// Solution: rename Arena_Table, to Arena_Free_List.
// Should ArenaTable be bootstrapped like String_Builder?
// see: new_string_builder
template <typename T, typename U>
struct ArenaTable {
using KeyType = T; using ValueType = U;
ArenaArray<Table_Entry<T, U>>* entries = {};
s64 count = 0;
s64 slots_filled = 0;
Hash_Function hash_function = nullptr;
Hash_Compare_Function compare_function = nullptr;
s64 add_collisions = 0;
s64 find_collisions = 0;
u32 load_factor_percent = 70;
bool refill_removed = true;
bool count_collisions = false;
};
template <typename T, typename U> bool table_is_valid (ArenaTable<T, U>* table) {
if (table == nullptr) return false;
if (table->entries == nullptr) return false;
if (table->entries->allocated == 0) return false;
if (table->hash_function == nullptr) return false;
if (table->compare_function == nullptr) return false;
return true;
}
template <typename T, typename U> void table_init (ArenaTable<T, U>* table, s64 slots_to_allocate=64, Arena_Reserve new_reserve=Arena_Reserve::Size_64M) {
s64 n = Next_Power_Of_Two(slots_to_allocate);
table->entries = arena_array_new<Table_Entry<T, U>> (n, new_reserve);
array_resize(*table->entries, n, false); // don't init
for (s64 i = 0; i < n; i += 1) {
(*table->entries)[i].hash = 0;
}
// default hash and compare functions:
table->hash_function = table_hash_function_fnv1a;
table->compare_function = u64_keys_match;
}
// Adds given key value pair to the table, returns a pointer to the inserted value.
template <typename T, typename U> U* table_add (ArenaTable<T, U>* table, T key, U value) {
Assert(table_is_valid(table));
Assert(table->load_factor_percent < 100);
if ( ((table->slots_filled + 1) * 100) >= (table->entries->allocated * table->load_factor_percent) ) {
table_resize(table, Next_Power_Of_Two(table->entries->allocated + 64));
}
Assert(table->slots_filled < table->entries->allocated);
// #Walk_Table
u32 mask = (u32)(table->entries->allocated - 1);
u32 hash = table->hash_function(&key, sizeof(T));
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
hash += HASH_TABLE_FIRST_VALID_HASH;
}
u32 index = hash & mask;
u32 probe_increment = 1;
u32 table_while_loop = (*table->entries)[index].hash;
while (table_while_loop) {
if (table->refill_removed) {
if ((*table->entries)[index].hash == HASH_TABLE_REMOVED_HASH) {
table->slots_filled -= 1; // 1 will get re-added below, for total increment 0.
break;
}
}
if (table->count_collisions) {
table->add_collisions += 1;
}
index = (index + probe_increment) & mask;
probe_increment += 1;
table_while_loop = (*table->entries)[index].hash;
}
// Walk_Table walked us to an unused entry, so add our new data into this slot:
table->count += 1;
table->slots_filled += 1;
Table_Entry<T, U>* entry = &(*table->entries)[index];
entry->hash = hash;
entry->key = key;
entry->value = value;
return &entry->value;
}
template <typename T, typename U> U* table_find_pointer (ArenaTable<T, U>* table, T key) {
Assert(table_is_valid(table));
if (!table_is_valid(table)) return nullptr;
// #Walk_Table
u32 mask = (u32)(table->entries->allocated - 1);
u32 hash = table->hash_function(&key, sizeof(T));
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
hash += HASH_TABLE_FIRST_VALID_HASH;
}
u32 index = hash & mask;
u32 probe_increment = 1;
u32 table_while_loop = (*table->entries)[index].hash;
while (table_while_loop) {
Table_Entry<T, U>* entry = &(*table->entries)[index];
if (entry->hash == hash) {
if (table->compare_function(&entry->key, &key)) {
return &entry->value;
}
}
if (table->count_collisions) { table->find_collisions += 1; }
index = (index + probe_increment) & mask;
probe_increment += 1;
table_while_loop = (*table->entries)[index].hash;
}
return nullptr;
}
template <typename T, typename U> U* table_set (ArenaTable<T, U>* table, T key, U value) {
U* value_ptr = table_find_pointer(table, key);
if (value_ptr) {
(*value_ptr) = value;
return value_ptr;
} else {
return table_add(table, key, value);
}
}
template <typename T, typename U> void table_resize (ArenaTable<T, U>* table, s64 slots_to_allocate) {
s64 initial_count = table->entries->count;
Assert(slots_to_allocate > initial_count);
if (slots_to_allocate <= initial_count) {
return;
}
// #TODO: When you resize you need to reinsert all the values, so we
// need a temporary copy of the original data:
ArrayView<Table_Entry<T, U>> old_entries = array_copy(temp(), to_view(*table->entries));
s64 n = Next_Power_Of_Two(slots_to_allocate);
array_resize(*table->entries, n, false);
table->count = 0;
table->slots_filled = 0;
// Initialize new values:
for (s64 i = 0; i < old_entries.count; i += 1) {
Table_Entry<T, U> entry = old_entries[i];
// if entry is valid!
if (entry.hash > HASH_TABLE_FIRST_VALID_HASH) {
table_add(table, entry.key, entry.value);
}
}
}
template <typename T, typename U> void table_reset (ArenaTable<T, U>* table, bool keep_memory=true) {
table->count = 0;
table->slots_filled = 0;
for (s64 i = 0; i < table->entries->count; i += 1) {
(*table->entries)[i].hash = 0;
}
if (!keep_memory) { array_reset(*table->entries); }
}
template <typename T, typename U> void table_release (ArenaTable<T, U>* table) {
arena_array_free(*table->entries);
#if BUILD_DEBUG
poison_struct(table);
#endif
}
template <typename T, typename U> bool table_contains (ArenaTable<T, U>* table, T key) {
return (table_find_pointer(table, key) != nullptr);
}
template <typename T, typename U> bool table_find (ArenaTable<T, U>* table, T key, U* value) {
U* pointer = table_find_pointer(table, key);
if (pointer) {
(*value) = (*pointer);
return true;
}
return false;
}
template <typename T, typename U> bool table_remove (ArenaTable<T, U>* table, T key, U* value) {
Assert(table_is_valid(table));
if (!table_is_valid(table)) return nullptr;
// #Walk_Table
u32 mask = (u32)(table->entries->allocated - 1);
u32 hash = table->hash_function(&key, sizeof(T));
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
hash += HASH_TABLE_FIRST_VALID_HASH;
}
u32 index = hash & mask;
u32 probe_increment = 1;
u32 table_while_loop = (*table->entries)[index].hash;
while (table_while_loop) {
Table_Entry<T, U>* entry = &(*table->entries)[index];
if ((entry->hash == hash) && table->compare_function(&entry->key, &key)) {
entry->hash = HASH_TABLE_REMOVED_HASH;
table->count -= 1;
(*value) = entry->value;
return true;
}
index = (index + probe_increment) & mask;
probe_increment += 1;
table_while_loop = (*table->entries)[index].hash;
}
return false;
}
// #TODO: we need a for expansion iterator?
// table_find_multiple (put results in Temp-backed Array<>, and return it as an ArrayView<T>) {
template <typename T, typename U> ArrayView<U> table_remove (ArenaTable<T, U>* table, T key, U* value) {
Array<U> results;
results.allocator = temp();
// #Walk_Table
u32 mask = (u32)(table->entries->allocated - 1);
u32 hash = table->hash_function(&key, sizeof(T));
if (hash < HASH_TABLE_FIRST_VALID_HASH) {
hash += HASH_TABLE_FIRST_VALID_HASH;
}
u32 index = hash & mask;
u32 probe_increment = 1;
u32 table_while_loop = (*table->entries)[index].hash;
while (table_while_loop) {
Table_Entry<T, U>* entry = &(*table->entries)[index];
if (entry->hash == hash) {
if (table->compare_function(&entry->key, &key)) {
array_add(results, entry->value);
} else {
if (table->count_collisions) { table->find_collisions += 1; }
}
} else {
if (table->count_collisions) { table->find_collisions += 1; }
}
index = (index + probe_increment) & mask;
probe_increment += 1;
table_while_loop = (*table->entries)[index].hash;
}
return to_view(results);
}