// #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 struct ArenaTable { using KeyType = T; using ValueType = U; ArenaArray>* 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 bool table_is_valid (ArenaTable* 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; } // table_release template void table_init (ArenaTable* 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> (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 U* table_add (ArenaTable* 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* entry = &(*table->entries)[index]; entry->hash = hash; entry->key = key; entry->value = value; return &entry->value; } template U* table_find_pointer (ArenaTable* 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* 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 U* table_set (ArenaTable* 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 void table_resize (ArenaTable* 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> 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 entry = old_entries[i]; // if entry is valid! if (entry.hash > HASH_TABLE_FIRST_VALID_HASH) { table_add(table, entry.key, entry.value); } } } template void table_reset (ArenaTable* 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 void table_release (ArenaTable* table) { arena_array_free(*table->entries); #if BUILD_DEBUG poison_struct(table); #endif } template bool table_contains (ArenaTable* table, T key) { return (table_find_pointer(table, key) != nullptr); } template bool table_find (ArenaTable* table, T key, U* value) { U* pointer = table_find_pointer(table, key); if (pointer) { (*value) = (*pointer); return true; } return false; } template bool table_remove (ArenaTable* 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* 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) { template ArrayView table_remove (ArenaTable* table, T key, U* value) { Array 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* 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); }