mirror of
https://github.com/Ed94/gencpp.git
synced 2024-12-22 07:44:45 -08:00
434 lines
15 KiB
C++
434 lines
15 KiB
C++
#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 gen_HashTable_##_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(_ctx->Allocator_Temp).Ptr};
|
|
StrBuilder fn = tbl_type.duplicate(_ctx->Allocator_Temp);
|
|
// c_str_to_lower(fn.Data);
|
|
|
|
StrBuilder name_lower = StrBuilder::make( _ctx->Allocator_Temp, hashtable_name );
|
|
// c_str_to_lower( name_lower.Data );
|
|
|
|
StrBuilder hashtable_entry = StrBuilder::fmt_buf( _ctx->Allocator_Temp, "HTE_%.*s", hashtable_name.Len, hashtable_name.Ptr );
|
|
StrBuilder entry_array_name = StrBuilder::fmt_buf( _ctx->Allocator_Temp, "Arr_HTE_%.*s", hashtable_name.Len, hashtable_name.Ptr );
|
|
StrBuilder entry_array_fn_ns = StrBuilder::fmt_buf( _ctx->Allocator_Temp, "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_<type> <tbl_type>;
|
|
typedef struct HTE_<tbl_name> HTE_<tbl_name>;
|
|
struct HTE_<tbl_name> {
|
|
u64 Key;
|
|
ssize Next;
|
|
<type> Value;
|
|
};
|
|
|
|
typedef void (* <tbl_type>_MapProc) ( <tbl_type> self, u64 key, <type> value );
|
|
typedef void (* <tbl_type>_MapMutProc) ( <tbl_type> self, u64 key, <type>* 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_<type> {
|
|
Array_ssize Hashes;
|
|
<array_entry> Entries;
|
|
};
|
|
|
|
<tbl_type> <fn>_init ( AllocatorInfo allocator );
|
|
<tbl_type> <fn>_init_reserve( AllocatorInfo allocator, ssize num );
|
|
void <fn>_clear ( <tbl_type> self );
|
|
void <fn>_destroy ( <tbl_type>* self );
|
|
<type>* <fn>_get ( <tbl_type> self, u64 key );
|
|
void <fn>_map ( <tbl_type> self, <tbl_type>_MapProc map_proc );
|
|
void <fn>_map_mut ( <tbl_type> self, <tbl_type>_MapMutProc map_proc );
|
|
void <fn>_grow ( <tbl_type>* self );
|
|
void <fn>_rehash ( <tbl_type>* self, ssize new_num );
|
|
void <fn>_rehash_fast ( <tbl_type> self );
|
|
void <fn>_remove ( <tbl_type> self, u64 key );
|
|
void <fn>_remove_entry( <tbl_type> self, ssize idx );
|
|
void <fn>_set ( <tbl_type>* self, u64 key, <type> value );
|
|
ssize <fn>_slot ( <tbl_type> self, u64 key );
|
|
|
|
ssize <fn>__add_entry( <tbl_type>* self, u64 key );
|
|
HT_FindResult <fn>__find ( <tbl_type> self, u64 key );
|
|
b32 <fn>__full ( <tbl_type> self );
|
|
|
|
<tbl_type> <fn>_init( AllocatorInfo allocator )
|
|
{
|
|
<tbl_type> result = hashtable_init_reserve(<type>, allocator, 8);
|
|
return result;
|
|
}
|
|
|
|
<tbl_type> <fn>_init_reserve( AllocatorInfo allocator, ssize num )
|
|
{
|
|
<tbl_type> 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(<entry_type>, allocator, num );
|
|
return result;
|
|
}
|
|
|
|
void <fn>_clear( <tbl_type> 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 <fn>_destroy( <tbl_type>* 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 );
|
|
}
|
|
}
|
|
|
|
<type>* <fn>_get( <tbl_type> self, u64 key )
|
|
{
|
|
GEN_ASSERT_NOT_NULL(self.Hashes);
|
|
GEN_ASSERT_NOT_NULL(self.Entries);
|
|
ssize idx = <fn>__find( self, key ).EntryIndex;
|
|
if ( idx > 0 )
|
|
return & self.Entries[idx].Value;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void <fn>_map( <tbl_type> self, <tbl_type>_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 <fn>_map_mut( <tbl_type> self, <tbl_type>_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 <fn>_grow( <tbl_type>* 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 <fn>_rehash( <tbl_type>* 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 );
|
|
|
|
<tbl_type> new_tbl = hashtable_init_reserve( <type>, 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_type>* entry = & self->Entries[idx];
|
|
HT_FindResult find_result;
|
|
|
|
find_result = <fn>__find( new_tbl, entry->Key);
|
|
last_added_index = <fn>__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;
|
|
}
|
|
|
|
<fn>_destroy( self );
|
|
* self = new_tbl;
|
|
}
|
|
|
|
void <fn>_rehash_fast( <tbl_type> 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_type>* entry;
|
|
HT_FindResult find_result;
|
|
|
|
entry = & self.Entries[ idx ];
|
|
find_result = <fn>__find( self, entry->Key );
|
|
|
|
if ( find_result.PrevIndex < 0 )
|
|
self.Hashes[ find_result.HashIndex ] = idx;
|
|
else
|
|
self.Entries[ find_result.PrevIndex ].Next = idx;
|
|
}
|
|
}
|
|
|
|
void <fn>_remove( <tbl_type> self, u64 key )
|
|
{
|
|
GEN_ASSERT_NOT_NULL(self.Hashes);
|
|
GEN_ASSERT_NOT_NULL(self.Entries);
|
|
HT_FindResult find_result = <fn>__find( self, key );
|
|
|
|
if ( find_result.EntryIndex >= 0 ) {
|
|
array_remove_at( self.Entries, find_result.EntryIndex );
|
|
hashtable_rehash_fast( self );
|
|
}
|
|
}
|
|
|
|
void <fn>_remove_entry( <tbl_type> self, ssize idx )
|
|
{
|
|
GEN_ASSERT_NOT_NULL(self.Hashes);
|
|
GEN_ASSERT_NOT_NULL(self.Entries);
|
|
array_remove_at( self.Entries, idx );
|
|
}
|
|
|
|
void <fn>_set( <tbl_type>* self, u64 key, <type> 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 = <fn>__find( * self, key );
|
|
|
|
if ( find_result.EntryIndex >= 0 ) {
|
|
idx = find_result.EntryIndex;
|
|
}
|
|
else
|
|
{
|
|
idx = <fn>__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 ( <fn>__full( * self ) )
|
|
hashtable_grow( self );
|
|
}
|
|
|
|
ssize <fn>_slot( <tbl_type> 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 <fn>__add_entry( <tbl_type>* self, u64 key )
|
|
{
|
|
GEN_ASSERT_NOT_NULL(self);
|
|
GEN_ASSERT_NOT_NULL(self->Hashes);
|
|
GEN_ASSERT_NOT_NULL(self->Entries);
|
|
ssize idx;
|
|
<entry_type> entry = { key, -1 };
|
|
|
|
idx = array_get_header( self->Entries )->Num;
|
|
array_append( self->Entries, entry );
|
|
return idx;
|
|
}
|
|
|
|
HT_FindResult <fn>__find( <tbl_type> 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 <fn>__full( <tbl_type> 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(_ctx->Allocator_Temp, "%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_<slot>__hashtable_init <type>, <tbl_type>_init
|
|
#define GENERIC_SLOT_<slot>__hashtable_init_reserve <type>, <tbl_type>_init_reserve
|
|
#define GENERIC_SLOT_<slot>__hashtable_clear <tbl_type>, <tbl_type>_clear
|
|
#define GENERIC_SLOT_<slot>__hashtable_destroy <tbl_type>, <tbl_type>_destroy
|
|
#define GENERIC_SLOT_<slot>__hashtable_get <tbl_type>, <tbl_type>_get
|
|
#define GENERIC_SLOT_<slot>__hashtable_map <tbl_type>, <tbl_type>_map
|
|
#define GENERIC_SLOT_<slot>__hashtable_map_mut <tbl_type>, <tbl_type>_map_mut
|
|
#define GENERIC_SLOT_<slot>__hashtable_grow <tbl_type>*, <tbl_type>_grow
|
|
#define GENERIC_SLOT_<slot>__hashtable_rehash <tbl_type>*, <tbl_type>_rehash
|
|
#define GENERIC_SLOT_<slot>__hashtable_rehash_fast <tbl_type>, <tbl_type>_rehash_fast
|
|
#define GENERIC_SLOT_<slot>__hashtable_remove_entry <tbl_type>, <tbl_type>_remove_entry
|
|
#define GENERIC_SLOT_<slot>__hashtable_set <tbl_type>, <tbl_type>_set
|
|
#define GENERIC_SLOT_<slot>__hashtable_slot <tbl_type>, <tbl_type>_slot
|
|
|
|
#define GENERIC_SLOT_<slot>__hashtable__add_entry <tbl_type>*, <tbl_type>__add_entry
|
|
#define GENERIC_SLOT_<slot>__hashtable__find <tbl_type>, <tbl_type>__find
|
|
#define GENERIC_SLOT_<slot>__hashtable__full <tbl_type>, <tbl_type>__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( _ctx->Allocator_Temp, "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( _ctx->Allocator_Temp, "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;
|
|
}
|