1+ /*
2+ Copyright (C) 2023 Axel Gneiting
3+ */
4+
5+ #include "quakedef.h"
6+
7+ #define LOAD_FACTOR 0.75f
8+ #define MIN_KEY_VALUE_STORAGE_SIZE 16
9+ #define MIN_HASH_SIZE 32
10+
11+ typedef struct hash_map_s
12+ {
13+ uint32_t num_entries ;
14+ uint32_t hash_size ;
15+ uint32_t key_value_storage_size ;
16+ uint32_t key_size ;
17+ uint32_t value_size ;
18+ uint32_t (* hasher ) (const void * const );
19+ uint32_t * hash_to_index ;
20+ uint32_t * index_chain ;
21+ void * keys ;
22+ void * values ;
23+ } hash_map_t ;
24+
25+ /*
26+ =================
27+ HashMap_GetKeyImpl
28+ =================
29+ */
30+ void * HashMap_GetKeyImpl (hash_map_t * map , uint32_t index )
31+ {
32+ return (byte * )map -> keys + (map -> key_size * index );
33+ }
34+
35+ /*
36+ =================
37+ HashMap_GetValueImpl
38+ =================
39+ */
40+ void * HashMap_GetValueImpl (hash_map_t * map , uint32_t index )
41+ {
42+ return (byte * )map -> values + (map -> value_size * index );
43+ }
44+
45+ /*
46+ =================
47+ HashMap_Rehash
48+ =================
49+ */
50+ static void HashMap_Rehash (hash_map_t * map , const uint32_t new_size )
51+ {
52+ if (map -> hash_size >= new_size )
53+ return ;
54+ map -> hash_size = new_size ;
55+ map -> hash_to_index = Mem_Realloc (map -> hash_to_index , map -> hash_size * sizeof (uint32_t ));
56+ memset (map -> hash_to_index , 0xFF , map -> hash_size * sizeof (uint32_t ));
57+ for (uint32_t i = 0 ; i < map -> num_entries ; ++ i )
58+ {
59+ void * key = HashMap_GetKeyImpl (map , i );
60+ const uint32_t hash = map -> hasher (key );
61+ const uint32_t hash_index = hash & (map -> hash_size - 1 );
62+ map -> index_chain [i ] = map -> hash_to_index [hash_index ];
63+ map -> hash_to_index [hash_index ] = i ;
64+ }
65+ }
66+
67+ /*
68+ =================
69+ HashMap_ExpandKeyValueStorage
70+ =================
71+ */
72+ static void HashMap_ExpandKeyValueStorage (hash_map_t * map , const uint32_t new_size )
73+ {
74+ map -> keys = Mem_Realloc (map -> keys , new_size * map -> key_size );
75+ map -> values = Mem_Realloc (map -> values , new_size * map -> value_size );
76+ map -> index_chain = Mem_Realloc (map -> index_chain , new_size * sizeof (uint32_t ));
77+ map -> key_value_storage_size = new_size ;
78+ }
79+
80+ /*
81+ =================
82+ HashMap_CreateImpl
83+ =================
84+ */
85+ hash_map_t * HashMap_CreateImpl (const uint32_t key_size , const uint32_t value_size , uint32_t (* hasher ) (const void * const ))
86+ {
87+ hash_map_t * map = Mem_Alloc (sizeof (hash_map_t ));
88+ map -> key_size = key_size ;
89+ map -> value_size = value_size ;
90+ map -> hasher = hasher ;
91+ return map ;
92+ }
93+
94+ /*
95+ =================
96+ HashMap_Destroy
97+ =================
98+ */
99+ void HashMap_Destroy (hash_map_t * map )
100+ {
101+ Mem_Free (map -> hash_to_index );
102+ Mem_Free (map -> index_chain );
103+ Mem_Free (map -> keys );
104+ Mem_Free (map -> values );
105+ Mem_Free (map );
106+ }
107+
108+ /*
109+ =================
110+ HashMap_Reserve
111+ =================
112+ */
113+ void HashMap_Reserve (hash_map_t * map , int capacity )
114+ {
115+ const uint32_t new_key_value_storage_size = Q_nextPow2 (capacity );
116+ if (map -> key_value_storage_size < new_key_value_storage_size )
117+ HashMap_ExpandKeyValueStorage (map , Q_nextPow2 (capacity ));
118+ const uint32_t new_hash_size = Q_nextPow2 (ceilf (capacity / LOAD_FACTOR ));
119+ if (map -> hash_size < new_hash_size )
120+ HashMap_Rehash (map , new_hash_size );
121+ }
122+
123+ /*
124+ =================
125+ HashMap_Insert
126+ =================
127+ */
128+ qboolean HashMap_Insert (hash_map_t * map , const void * const key , const void * const value )
129+ {
130+ if (map -> num_entries >= map -> key_value_storage_size )
131+ HashMap_ExpandKeyValueStorage (map , q_max (map -> key_value_storage_size * 2 , MIN_KEY_VALUE_STORAGE_SIZE ));
132+ if (map -> num_entries >= (LOAD_FACTOR * map -> hash_size ))
133+ HashMap_Rehash (map , q_max (map -> hash_size * 2 , MIN_HASH_SIZE ));
134+
135+ const uint32_t hash = map -> hasher (key );
136+ const uint32_t hash_index = hash & (map -> hash_size - 1 );
137+ {
138+ uint32_t storage_index = map -> hash_to_index [hash_index ];
139+ while (storage_index != UINT32_MAX )
140+ {
141+ if (memcmp (key , HashMap_GetKeyImpl (map , storage_index ), map -> key_size ) == 0 )
142+ {
143+ memcpy (HashMap_GetValueImpl (map , storage_index ), value , map -> value_size );
144+ return true;
145+ }
146+ storage_index = map -> index_chain [storage_index ];
147+ }
148+ }
149+
150+ map -> index_chain [map -> num_entries ] = map -> hash_to_index [hash_index ];
151+ map -> hash_to_index [hash_index ] = map -> num_entries ;
152+ memcpy (HashMap_GetKeyImpl (map , map -> num_entries ), key , map -> key_size );
153+ memcpy (HashMap_GetValueImpl (map , map -> num_entries ), value , map -> value_size );
154+ ++ map -> num_entries ;
155+
156+ return false;
157+ }
158+
159+ /*
160+ =================
161+ HashMap_Erase
162+ =================
163+ */
164+ qboolean HashMap_Erase (hash_map_t * map , const void * const key )
165+ {
166+ if (map -> num_entries == 0 )
167+ return false;
168+
169+ const uint32_t hash = map -> hasher (key );
170+ const uint32_t hash_index = hash & (map -> hash_size - 1 );
171+ uint32_t storage_index = map -> hash_to_index [hash_index ];
172+ uint32_t * prev_storage_index_ptr = NULL ;
173+ while (storage_index != UINT32_MAX )
174+ {
175+ if (memcmp (key , HashMap_GetKeyImpl (map , storage_index ), map -> key_size ) == 0 )
176+ {
177+ {
178+ // Remove found key from index
179+ if (prev_storage_index_ptr == NULL )
180+ map -> hash_to_index [hash_index ] = map -> index_chain [storage_index ];
181+ else
182+ * prev_storage_index_ptr = map -> index_chain [storage_index ];
183+ }
184+
185+ const uint32_t last_index = map -> num_entries - 1 ;
186+ const uint32_t last_hash = map -> hasher (HashMap_GetKeyImpl (map , last_index ));
187+ const uint32_t last_hash_index = last_hash & (map -> hash_size - 1 );
188+
189+ if (storage_index == last_index )
190+ {
191+ -- map -> num_entries ;
192+ return true;
193+ }
194+
195+ {
196+ // Remove last key from index
197+ if (map -> hash_to_index [last_hash_index ] == last_index )
198+ map -> hash_to_index [last_hash_index ] = map -> index_chain [last_index ];
199+ else
200+ {
201+ qboolean found = false;
202+ for (uint32_t last_storage_index = map -> hash_to_index [last_hash_index ]; last_storage_index != UINT32_MAX ;
203+ last_storage_index = map -> index_chain [last_storage_index ])
204+ {
205+ if (map -> index_chain [last_storage_index ] == last_index )
206+ {
207+ map -> index_chain [last_storage_index ] = map -> index_chain [last_index ];
208+ found = true;
209+ break ;
210+ }
211+ }
212+ assert (found );
213+ }
214+ }
215+
216+ {
217+ // Copy last key to current key position and add back to index
218+ memcpy (HashMap_GetKeyImpl (map , storage_index ), HashMap_GetKeyImpl (map , last_index ), map -> key_size );
219+ memcpy (HashMap_GetValueImpl (map , storage_index ), HashMap_GetValueImpl (map , last_index ), map -> value_size );
220+ map -> index_chain [storage_index ] = map -> hash_to_index [last_hash_index ];
221+ map -> hash_to_index [last_hash_index ] = storage_index ;
222+ }
223+
224+ -- map -> num_entries ;
225+ return true;
226+ }
227+ prev_storage_index_ptr = & map -> index_chain [storage_index ];
228+ storage_index = map -> index_chain [storage_index ];
229+ }
230+ return false;
231+ }
232+
233+ /*
234+ =================
235+ HashMap_LookupImpl
236+ =================
237+ */
238+ void * HashMap_LookupImpl (hash_map_t * map , const void * const key )
239+ {
240+ if (map -> num_entries == 0 )
241+ return NULL ;
242+
243+ const uint32_t hash = map -> hasher (key );
244+ const uint32_t hash_index = hash & (map -> hash_size - 1 );
245+ uint32_t storage_index = map -> hash_to_index [hash_index ];
246+ while (storage_index != UINT32_MAX )
247+ {
248+ if (memcmp (key , (byte * )map -> keys + (storage_index * map -> key_size ), map -> key_size ) == 0 )
249+ return (byte * )map -> values + (storage_index * map -> value_size );
250+ storage_index = map -> index_chain [storage_index ];
251+ }
252+
253+ return NULL ;
254+ }
255+
256+ /*
257+ =================
258+ HashMap_Size
259+ =================
260+ */
261+ uint32_t HashMap_Size (hash_map_t * map )
262+ {
263+ return map -> num_entries ;
264+ }
265+
266+ #ifdef _DEBUG
267+ /*
268+ =================
269+ HashMap_TestAssert
270+ =================
271+ */
272+ #define HashMap_TestAssert (cond , what ) \
273+ if (!(cond)) \
274+ { \
275+ Con_Printf (what); \
276+ abort (); \
277+ }
278+
279+ /*
280+ =================
281+ HashMap_BasicTest
282+ =================
283+ */
284+ static void HashMap_BasicTest (const qboolean reserve )
285+ {
286+ const int TEST_SIZE = 1000 ;
287+ hash_map_t * map = HashMap_Create (int32_t , int64_t , & HashInt32 );
288+ if (reserve )
289+ HashMap_Reserve (map , TEST_SIZE );
290+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
291+ {
292+ int64_t value = i ;
293+ HashMap_TestAssert (!HashMap_Insert (map , & i , & value ), va ("%d should not be overwritten\n" , i ));
294+ }
295+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
296+ HashMap_TestAssert (* HashMap_Lookup (int64_t , map , & i ) == i , va ("Wrong lookup for %d\n" , i ));
297+ for (int i = 0 ; i < TEST_SIZE ; i += 2 )
298+ HashMap_Erase (map , & i );
299+ for (int i = 1 ; i < TEST_SIZE ; i += 2 )
300+ HashMap_TestAssert (* HashMap_Lookup (int64_t , map , & i ) == i , va ("Wrong lookup for %d\n" , i ));
301+ for (int i = 0 ; i < TEST_SIZE ; i += 2 )
302+ HashMap_TestAssert (HashMap_Lookup (int64_t , map , & i ) == NULL , va ("Wrong lookup for %d\n" , i ));
303+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
304+ HashMap_Erase (map , & i );
305+ HashMap_TestAssert (HashMap_Size (map ) == 0 , "Map is not empty" );
306+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
307+ HashMap_TestAssert (HashMap_Lookup (int64_t , map , & i ) == NULL , va ("Wrong lookup for %d\n" , i ));
308+ HashMap_Destroy (map );
309+ }
310+
311+ /*
312+ =================
313+ HashMap_BasicTest
314+ =================
315+ */
316+ static void HashMap_StressTest (void )
317+ {
318+ srand (0 );
319+ const int TEST_SIZE = 10000 ;
320+ TEMP_ALLOC (int64_t , keys , TEST_SIZE );
321+ hash_map_t * map = HashMap_Create (int64_t , int32_t , & HashInt64 );
322+ for (int j = 0 ; j < 10 ; ++ j )
323+ {
324+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
325+ {
326+ keys [i ] = i ;
327+ }
328+ for (int i = TEST_SIZE - 1 ; i > 0 ; -- i )
329+ {
330+ const int swap_index = rand () % (i + 1 );
331+ const int temp = keys [swap_index ];
332+ keys [swap_index ] = keys [i ];
333+ keys [i ] = temp ;
334+ }
335+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
336+ HashMap_Insert (map , & keys [i ], & i );
337+ for (int i = 0 ; i < TEST_SIZE ; ++ i )
338+ HashMap_TestAssert (* HashMap_Lookup (int32_t , map , & keys [i ]) == i , va ("Wrong lookup for %d\n" , i ));
339+ for (int i = TEST_SIZE - 1 ; i >= 0 ; -- i )
340+ HashMap_Erase (map , & keys [i ]);
341+ HashMap_TestAssert (HashMap_Size (map ) == 0 , "Map is not empty" );
342+ }
343+ HashMap_Destroy (map );
344+ TEMP_FREE (keys );
345+ }
346+
347+ /*
348+ =================
349+ TestHashMap_f
350+ =================
351+ */
352+ void TestHashMap_f (void )
353+ {
354+ HashMap_BasicTest (false);
355+ HashMap_BasicTest (true);
356+ HashMap_StressTest ();
357+ }
358+ #endif
0 commit comments