#pragma once #include "../project/gen.hpp" #include "containers.array.hpp" using namespace gen; CodeBody gen_hashtable_base() { return parse_global_body( code( typedef struct HT_FindResult HT_FindResult; struct HT_FindResult { ssize HashIndex; ssize PrevIndex; ssize EntryIndex; }; )); } CodeBody gen_hashtable( StrC type, StrC hashtable_name ) { String fn = String::make_reserve( GlobalAllocator, hashtable_name.Len + sizeof("gen") ); fn.append_fmt( "%.*s", hashtable_name.Len, hashtable_name.Ptr ); str_to_lower(fn.Data); String tbl_type = String::make_reserve( GlobalAllocator, hashtable_name.Len + sizeof("gen") ); tbl_type.append_fmt( "%.*s", hashtable_name.Len, hashtable_name.Ptr ); String name_lower = String::make( GlobalAllocator, hashtable_name ); str_to_lower( name_lower.Data ); String hashtable_entry = String::fmt_buf( GlobalAllocator, "HTE_%.*s", hashtable_name.Len, hashtable_name.Ptr ); String entry_array_name = String::fmt_buf( GlobalAllocator, "Arr_HTE_%.*s", hashtable_name.Len, hashtable_name.Ptr ); String entry_array_fn_ns = String::fmt_buf( GlobalAllocator, "arr_hte_%.*s", name_lower.length(), name_lower.Data ); CodeBody hashtable_types = parse_global_body( token_fmt( "type", (StrC) type, "tbl_name", (StrC) hashtable_name, "tbl_type", (StrC) tbl_type, stringize( 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" ) #undef GEN_ASSERT #undef GEN_ASSERT_NOT_NULL CodeBody hashtable_def = parse_global_body( token_fmt( "type", (StrC) type, "tbl_name", (StrC) hashtable_name, "tbl_type", (StrC) tbl_type, "fn", (StrC) fn, "entry_type", (StrC) hashtable_entry, "array_entry", (StrC) entry_array_name, "fn_array", (StrC) entry_array_fn_ns, stringize( typedef struct ; struct { Array_ssize Hashes; Entries; }; _make ( AllocatorInfo allocator ); _make_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 ); _make( AllocatorInfo allocator ) { result = { NULL, NULL }; result.Hashes = array_ssize_make( allocator ); result.Entries = _make( allocator ); return result; } _make_reserve( AllocatorInfo allocator, ssize num ) { result = { NULL, NULL }; result.Hashes = array_ssize_make_reserve( allocator, num ); result.Entries = _make_reserve( allocator, num ); return result; } void _clear( self ) { for ( ssize idx = 0; idx < array_header( self.Hashes )->Num; idx++ ) self.Hashes[idx] = -1; array_ssize_clear( self.Hashes ); _clear( self.Entries ); } void _destroy( self ) { if ( self.Hashes && self.Entries ) { array_ssize_free( self.Hashes ); _free( self.Entries ); } } * _get( self, u64 key ) { ssize idx = __find( self, key ).EntryIndex; if ( idx > 0 ) return & self.Entries[idx].Value; return NULL; } void _map( self, _MapProc map_proc ) { GEN_ASSERT_NOT_NULL( map_proc ); for ( ssize idx = 0; idx < array_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( map_proc ); for ( ssize idx = 0; idx < array_header( self.Entries )->Num; idx++ ) { map_proc( self, self.Entries[idx].Key, & self.Entries[idx].Value ); } } void _grow( * self ) { ssize new_num = array_grow_formula( array_header( self->Entries )->Num ); _rehash( self, new_num ); } void _rehash( * self, ssize new_num ) { ssize idx; ssize last_added_index; ArrayHeader* old_hash_header = array_header( self->Hashes ); ArrayHeader* old_entries_header = array_header( self->Entries ); new_tbl = _make_reserve( old_hash_header->Allocator, old_hash_header->Num ); ArrayHeader* new_hash_header = array_header( new_tbl.Hashes ); for ( idx = 0; idx < new_hash_header->Num; idx++ ) new_tbl.Hashes[idx] = -1; for ( idx = 0; idx < old_entries_header->Num; idx++ ) { * entry; HT_FindResult find_result; if ( new_hash_header->Num == 0 ) _grow( & new_tbl ); entry = & self->Entries[ idx ]; 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 ) { ssize idx; for ( idx = 0; idx < array_header( self.Entries )->Num; idx++ ) self.Entries[ idx ].Next = -1; for ( idx = 0; idx < array_header( self.Hashes )->Num; idx++ ) self.Hashes[ idx ] = -1; for ( idx = 0; idx < array_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 ) { HT_FindResult find_result = __find( self, key ); if ( find_result.EntryIndex >= 0 ) { _remove_at( self.Entries, find_result.EntryIndex ); _rehash_fast( self ); } } void _remove_entry( self, ssize idx ) { _remove_at( self.Entries, idx ); } void _set( * self, u64 key, value ) { ssize idx; HT_FindResult find_result; if ( array_header( self->Hashes )->Num == 0 ) _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 ) ) _grow( self ); } ssize _slot( self, u64 key ) { for ( ssize idx = 0; idx < array_header( self.Hashes )->Num; ++idx ) if ( self.Hashes[ idx ] == key ) return idx; return -1; } ssize __add_entry( self, u64 key ) { ssize idx; entry = { key, -1 }; idx = array_header( self.Entries )->Num; _append( & self.Entries, entry ); return idx; } HT_FindResult __find( self, u64 key ) { HT_FindResult result = { -1, -1, -1 }; ArrayHeader* hash_header = array_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 ) { ArrayHeader* hash_header = array_header( self.Hashes ); ArrayHeader* entries_header = array_header( self.Entries ); return 0.75f * hash_header->Num < entries_header->Num; } ))); #pragma pop_macro( "GEN_ASSERT" ) #pragma pop_macro( "GEN_ASSERT_NOT_NULL" ) char const* cmt_str = str_fmt_buf( "Name: %.*s Type: %.*s" , tbl_type.length(), tbl_type.Data , type.Len, type.Ptr ); return def_global_body(args( def_pragma( to_str( str_fmt_buf( "region %S", tbl_type ))), fmt_newline, hashtable_types, fmt_newline, entry_array, hashtable_def, fmt_newline, def_pragma( to_str( str_fmt_buf( "endregion %S", tbl_type ))), fmt_newline )); }