Skip to content

Commit 340561b

Browse files
committed
Hash map
1 parent bec08af commit 340561b

10 files changed

Lines changed: 451 additions & 0 deletions

File tree

Quake/common.make

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ OBJS := strlcat.o \
219219
world.o \
220220
mem.o \
221221
tasks.o \
222+
hash_map.o \
222223
embedded_pak.o \
223224
$(SYSOBJ_SYS) $(SYSOBJ_MAIN)
224225

Quake/hash_map.c

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
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

Comments
 (0)