#pragma once #include "gen.hpp" #include "containers.array.hpp" using namespace gen; global s32 HashTable_DefinitionCounter = 0; CodeBody gen_hashtable_base() { CodeBody struct_def = parse_global_body( code( typedef struct HT_FindResult_Def HT_FindResult; struct HT_FindResult_Def { ssize HashIndex; ssize PrevIndex; ssize EntryIndex; }; )); Code define_type = untyped_str(txt( R"(#define HashTable(_type) struct _type )" )); Code define_critical_load_scale = untyped_str(txt("#define HashTable_CriticalLoadScale 0.7f\n")); return def_global_body(args(struct_def, define_type, define_critical_load_scale)); } CodeBody gen_hashtable( Str type, Str hashtable_name ) { StrBuilder tbl_type = {(char*) hashtable_name.duplicate(GlobalAllocator).Ptr}; StrBuilder fn = tbl_type.duplicate(GlobalAllocator); // c_str_to_lower(fn.Data); StrBuilder name_lower = StrBuilder::make( GlobalAllocator, hashtable_name ); // c_str_to_lower( name_lower.Data ); StrBuilder hashtable_entry = StrBuilder::fmt_buf( GlobalAllocator, "HTE_%.*s", hashtable_name.Len, hashtable_name.Ptr ); StrBuilder entry_array_name = StrBuilder::fmt_buf( GlobalAllocator, "Arr_HTE_%.*s", hashtable_name.Len, hashtable_name.Ptr ); StrBuilder entry_array_fn_ns = StrBuilder::fmt_buf( GlobalAllocator, "arr_hte_%.*s", name_lower.length(), name_lower.Data ); CodeBody hashtable_types = parse_global_body( token_fmt( "type", (Str) type, "tbl_name", (Str) hashtable_name, "tbl_type", (Str) tbl_type, stringize( typedef struct HashTable_ ; typedef struct HTE_ HTE_; struct HTE_ { u64 Key; ssize Next; Value; }; typedef void (* _MapProc) ( self, u64 key, value ); typedef void (* _MapMutProc) ( self, u64 key, * value ); ))); CodeBody entry_array = gen_array( hashtable_entry, entry_array_name ); #pragma push_macro( "GEN_ASSERT" ) #pragma push_macro( "GEN_ASSERT_NOT_NULL" ) #pragma push_macro( "rcast" ) #pragma push_macro( "cast" ) #pragma push_macro( "typeof" ) #pragma push_macro( "forceinline" ) #undef GEN_ASSERT #undef GEN_ASSERT_NOT_NULL #undef GEN_ASSERT #undef rcast #undef cast #undef typeof #undef forceinline CodeBody hashtable_def = parse_global_body( token_fmt( "type", (Str) type, "tbl_name", (Str) hashtable_name, "tbl_type", (Str) tbl_type, "fn", (Str) fn, "entry_type", (Str) hashtable_entry, "array_entry", (Str) entry_array_name, "fn_array", (Str) entry_array_fn_ns, stringize( struct HashTable_ { Array_ssize Hashes; Entries; }; _init ( AllocatorInfo allocator ); _init_reserve( AllocatorInfo allocator, ssize num ); void _clear ( self ); void _destroy ( * self ); * _get ( self, u64 key ); void _map ( self, _MapProc map_proc ); void _map_mut ( self, _MapMutProc map_proc ); void _grow ( * self ); void _rehash ( * self, ssize new_num ); void _rehash_fast ( self ); void _remove ( self, u64 key ); void _remove_entry( self, ssize idx ); void _set ( * self, u64 key, value ); ssize _slot ( self, u64 key ); ssize __add_entry( * self, u64 key ); HT_FindResult __find ( self, u64 key ); b32 __full ( self ); _init( AllocatorInfo allocator ) { result = hashtable_init_reserve(, allocator, 8); return result; } _init_reserve( AllocatorInfo allocator, ssize num ) { result = { NULL, NULL }; result.Hashes = array_init_reserve(ssize, allocator, num ); array_get_header(result.Hashes)->Num = num; array_resize(result.Hashes, num); array_fill(result.Hashes, 0, num, -1); result.Entries = array_init_reserve(, allocator, num ); return result; } void _clear( self ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); array_clear( self.Entries ); s32 what = array_num(self.Hashes); array_fill( self.Hashes, 0, what, (ssize)-1 ); } void _destroy( * self ) { GEN_ASSERT_NOT_NULL(self); GEN_ASSERT_NOT_NULL(self->Hashes); GEN_ASSERT_NOT_NULL(self->Entries); if ( self->Hashes && array_get_header(self->Hashes)->Capacity) { array_free( self->Hashes ); array_free( self->Entries ); } } * _get( self, u64 key ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); ssize idx = __find( self, key ).EntryIndex; if ( idx > 0 ) return & self.Entries[idx].Value; return nullptr; } void _map( self, _MapProc map_proc ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); GEN_ASSERT_NOT_NULL( map_proc ); for ( ssize idx = 0; idx < array_get_header( self.Entries )->Num; idx++ ) { map_proc( self, self.Entries[idx].Key, self.Entries[idx].Value ); } } void _map_mut( self, _MapMutProc map_proc ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); GEN_ASSERT_NOT_NULL( map_proc ); for ( ssize idx = 0; idx < array_get_header( self.Entries )->Num; idx++ ) { map_proc( self, self.Entries[idx].Key, & self.Entries[idx].Value ); } } void _grow( * self ) { GEN_ASSERT_NOT_NULL(self); GEN_ASSERT_NOT_NULL(self->Hashes); GEN_ASSERT_NOT_NULL(self->Entries); ssize new_num = array_grow_formula( array_get_header( self->Entries )->Num ); hashtable_rehash( self, new_num ); } void _rehash( * self, ssize new_num ) { GEN_ASSERT_NOT_NULL(self); GEN_ASSERT_NOT_NULL(self->Hashes); GEN_ASSERT_NOT_NULL(self->Entries); GEN_ASSERT( new_num > 0 ); ssize idx; ssize last_added_index; ArrayHeader* old_hash_header = array_get_header( self->Hashes ); ArrayHeader* old_entries_header = array_get_header( self->Entries ); new_tbl = hashtable_init_reserve( , old_hash_header->Allocator, old_hash_header->Num ); ArrayHeader* new_hash_header = array_get_header( new_tbl.Hashes ); for (ssize idx = 0; idx < cast(ssize, old_hash_header->Num); ++idx) { * entry = & self->Entries[idx]; HT_FindResult find_result; find_result = __find( new_tbl, entry->Key); last_added_index = __add_entry( & new_tbl, entry->Key); if (find_result.PrevIndex < 0) new_tbl.Hashes[find_result.HashIndex] = last_added_index; else new_tbl.Entries[find_result.PrevIndex].Next = last_added_index; new_tbl.Entries[last_added_index].Next = find_result.EntryIndex; new_tbl.Entries[last_added_index].Value = entry->Value; } _destroy( self ); * self = new_tbl; } void _rehash_fast( self ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); ssize idx; for ( idx = 0; idx < array_get_header( self.Entries )->Num; idx++ ) self.Entries[ idx ].Next = -1; for ( idx = 0; idx < array_get_header( self.Hashes )->Num; idx++ ) self.Hashes[ idx ] = -1; for ( idx = 0; idx < array_get_header( self.Entries )->Num; idx++ ) { * entry; HT_FindResult find_result; entry = & self.Entries[ idx ]; find_result = __find( self, entry->Key ); if ( find_result.PrevIndex < 0 ) self.Hashes[ find_result.HashIndex ] = idx; else self.Entries[ find_result.PrevIndex ].Next = idx; } } void _remove( self, u64 key ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); HT_FindResult find_result = __find( self, key ); if ( find_result.EntryIndex >= 0 ) { array_remove_at( self.Entries, find_result.EntryIndex ); hashtable_rehash_fast( self ); } } void _remove_entry( self, ssize idx ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); array_remove_at( self.Entries, idx ); } void _set( * self, u64 key, value ) { GEN_ASSERT_NOT_NULL(self); GEN_ASSERT_NOT_NULL(self->Hashes); GEN_ASSERT_NOT_NULL(self->Entries); ssize idx; HT_FindResult find_result; if ( array_get_header( self->Hashes )->Num == 0 ) hashtable_grow( self ); find_result = __find( * self, key ); if ( find_result.EntryIndex >= 0 ) { idx = find_result.EntryIndex; } else { idx = __add_entry( self, key ); if ( find_result.PrevIndex >= 0 ) { self->Entries[ find_result.PrevIndex ].Next = idx; } else { self->Hashes[ find_result.HashIndex ] = idx; } } self->Entries[ idx ].Value = value; if ( __full( * self ) ) hashtable_grow( self ); } ssize _slot( self, u64 key ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); for ( ssize idx = 0; idx < array_get_header( self.Hashes )->Num; ++idx ) if ( self.Hashes[ idx ] == key ) return idx; return -1; } ssize __add_entry( * self, u64 key ) { GEN_ASSERT_NOT_NULL(self); GEN_ASSERT_NOT_NULL(self->Hashes); GEN_ASSERT_NOT_NULL(self->Entries); ssize idx; entry = { key, -1 }; idx = array_get_header( self->Entries )->Num; array_append( self->Entries, entry ); return idx; } HT_FindResult __find( self, u64 key ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); HT_FindResult result = { -1, -1, -1 }; ArrayHeader* hash_header = array_get_header( self.Hashes ); if ( hash_header->Num > 0 ) { result.HashIndex = key % hash_header->Num; result.EntryIndex = self.Hashes[ result.HashIndex ]; while ( result.EntryIndex >= 0 ) { if ( self.Entries[ result.EntryIndex ].Key == key ) break; result.PrevIndex = result.EntryIndex; result.EntryIndex = self.Entries[ result.EntryIndex ].Next; } } return result; } b32 __full( self ) { GEN_ASSERT_NOT_NULL(self.Hashes); GEN_ASSERT_NOT_NULL(self.Entries); ArrayHeader* hash_header = array_get_header( self.Hashes ); ArrayHeader* entries_header = array_get_header( self.Entries ); usize critical_load = cast(usize, HashTable_CriticalLoadScale * cast(f32, hash_header->Num)); b32 result = entries_header->Num > critical_load; return result; } ))); #pragma pop_macro( "GEN_ASSERT" ) #pragma pop_macro( "GEN_ASSERT_NOT_NULL" ) #pragma pop_macro( "rcast" ) #pragma pop_macro( "cast" ) #pragma pop_macro( "typeof" ) #pragma pop_macro( "forceinline" ) ++ HashTable_DefinitionCounter; Str slot_str = StrBuilder::fmt_buf(GlobalAllocator, "%d", HashTable_DefinitionCounter).to_str(); Code generic_interface_slot = untyped_str(token_fmt( "type", type, "tbl_type", (Str)tbl_type, "slot", (Str)slot_str, R"(#define GENERIC_SLOT___hashtable_init , _init #define GENERIC_SLOT___hashtable_init_reserve , _init_reserve #define GENERIC_SLOT___hashtable_clear , _clear #define GENERIC_SLOT___hashtable_destroy , _destroy #define GENERIC_SLOT___hashtable_get , _get #define GENERIC_SLOT___hashtable_map , _map #define GENERIC_SLOT___hashtable_map_mut , _map_mut #define GENERIC_SLOT___hashtable_grow *, _grow #define GENERIC_SLOT___hashtable_rehash *, _rehash #define GENERIC_SLOT___hashtable_rehash_fast , _rehash_fast #define GENERIC_SLOT___hashtable_remove_entry , _remove_entry #define GENERIC_SLOT___hashtable_set , _set #define GENERIC_SLOT___hashtable_slot , _slot #define GENERIC_SLOT___hashtable__add_entry *, __add_entry #define GENERIC_SLOT___hashtable__find , __find #define GENERIC_SLOT___hashtable__full , __full )" )); char const* cmt_str = c_str_fmt_buf( "Name: %.*s Type: %.*s" , tbl_type.length(), tbl_type.Data , type.Len, type.Ptr ); return def_global_body(args( def_pragma( strbuilder_to_str( strbuilder_fmt_buf( GlobalAllocator, "region %SB", tbl_type ))), fmt_newline, generic_interface_slot, fmt_newline, hashtable_types, fmt_newline, entry_array, hashtable_def, fmt_newline, def_pragma( strbuilder_to_str( strbuilder_fmt_buf( GlobalAllocator, "endregion %SB", tbl_type ))), fmt_newline )); } CodeBody gen_hashtable_generic_selection_interface() { CodeBody interface_defines = def_body(CT_Global_Body); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_init"), GenericSel_Direct_Type )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_init_reserve"), GenericSel_Direct_Type )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_clear"), GenericSel_Default, GenericSel_One_Arg )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_destroy"), GenericSel_By_Ref, GenericSel_One_Arg ) ); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_get") )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_grow"), GenericSel_Default, GenericSel_One_Arg )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_rehash") )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_rehash_fast"), GenericSel_Default, GenericSel_One_Arg )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_remove") )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_remove_entry") )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_set"), GenericSel_By_Ref )); interface_defines.append( gen_generic_selection_function_macro( HashTable_DefinitionCounter, txt("hashtable_slot") )); return interface_defines; }