knight/shared/KeHashTable.cpp
author David Anderson <dvander@alliedmods.net>
Thu Jan 29 12:50:32 2009 -0500 (2009-01-29)
changeset 2541 0352326a1f3f
parent 2226 d10b38524be3
permissions -rw-r--r--
Fixed bugs in the Knight shared files (not that these are used in SM).
dvander@2541
     1
#include <string.h>
dvander@2226
     2
#include <KeHashTable.h>
dvander@2226
     3
dvander@2226
     4
using namespace Knight;
dvander@2226
     5
dvander@2226
     6
struct KeHashNode
dvander@2226
     7
{
dvander@2226
     8
	KeHashNode *next;
dvander@2226
     9
	uint32_t key_hash;
dvander@2226
    10
	const void *key;
dvander@2226
    11
	void *value;
dvander@2226
    12
};
dvander@2226
    13
dvander@2226
    14
namespace Knight
dvander@2226
    15
{
dvander@2226
    16
	class KeHashTable
dvander@2226
    17
	{
dvander@2226
    18
	public:
dvander@2226
    19
		KeHashNode **buckets;
dvander@2226
    20
		uint32_t num_buckets;
dvander@2226
    21
		uint32_t shift;
dvander@2226
    22
		uint32_t num_entries;
dvander@2226
    23
		KeHashGenerator key_gen;
dvander@2226
    24
		KeHashMarshal key_m;
dvander@2226
    25
		KeHashMarshal val_m;
dvander@2226
    26
		ke_allocator_t *node_alloc;
dvander@2226
    27
		size_t key_offs;
dvander@2226
    28
		size_t val_offs;
dvander@2226
    29
		size_t node_size;
dvander@2226
    30
		uint32_t grow_limit;
dvander@2226
    31
		KeHashNode *free_list;
dvander@2226
    32
		bool keep_free_list;
dvander@2226
    33
	};
dvander@2226
    34
}
dvander@2226
    35
dvander@2226
    36
void *ke_DefHashMalloc(ke_allocator_t *alloc, size_t amt)
dvander@2226
    37
{
dvander@2226
    38
	return malloc(amt);
dvander@2226
    39
}
dvander@2226
    40
dvander@2226
    41
void ke_DefHashFree(ke_allocator_t *alloc, void *addr)
dvander@2226
    42
{
dvander@2226
    43
	free(addr);
dvander@2226
    44
}
dvander@2226
    45
dvander@2226
    46
ke_allocator_t s_DefHashAllocator = 
dvander@2226
    47
{
dvander@2226
    48
	ke_DefHashMalloc,
dvander@2226
    49
	ke_DefHashFree,
dvander@2226
    50
	NULL
dvander@2226
    51
};
dvander@2226
    52
dvander@2226
    53
KeHashTable *Knight::KE_CreateHashTable(
dvander@2226
    54
	uint32_t bits,
dvander@2226
    55
	KeHashGenerator key_gen,
dvander@2226
    56
	const KeHashMarshal *key_marshal,
dvander@2226
    57
	const KeHashMarshal *val_marshal,
dvander@2226
    58
	ke_allocator_t *nodeAlloc,
dvander@2226
    59
	bool keep_free_list)
dvander@2226
    60
{
dvander@2226
    61
	KeHashTable *table;
dvander@2226
    62
dvander@2226
    63
	if (bits >= 27)
dvander@2226
    64
	{
dvander@2226
    65
		bits = 26;
dvander@2226
    66
	}
dvander@2226
    67
	else if (bits < 4)
dvander@2226
    68
	{
dvander@2226
    69
		bits = 4;
dvander@2226
    70
	}
dvander@2226
    71
dvander@2226
    72
	/* Validate marshals. */
dvander@2226
    73
	if ((key_marshal->bytes != 0
dvander@2226
    74
		 && key_marshal->ctor == NULL)
dvander@2226
    75
		|| (val_marshal->bytes != 0
dvander@2226
    76
			&& val_marshal->ctor == NULL))
dvander@2226
    77
	{
dvander@2226
    78
		return NULL;
dvander@2226
    79
	}
dvander@2226
    80
dvander@2226
    81
	table = new KeHashTable;
dvander@2226
    82
	table->key_gen = key_gen;
dvander@2226
    83
	table->key_m = *key_marshal;
dvander@2226
    84
	table->val_m = *val_marshal;
dvander@2226
    85
	table->num_entries = 0;
dvander@2541
    86
	table->shift = 32 - bits;
dvander@2226
    87
	table->node_alloc = nodeAlloc == NULL ? &s_DefHashAllocator : nodeAlloc;
dvander@2226
    88
	table->num_buckets = (1 << bits);
dvander@2226
    89
	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
dvander@2226
    90
	table->keep_free_list = keep_free_list;
dvander@2226
    91
	table->free_list = NULL;
dvander@2226
    92
	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
    93
	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
    94
dvander@2226
    95
	table->key_offs = sizeof(KeHashNode);
dvander@2226
    96
	if (table->key_m.bytes != 0 && table->key_m.bytes % 8 != 0)
dvander@2226
    97
	{
dvander@2226
    98
		table->key_m.bytes += 8;
dvander@2226
    99
		table->key_m.bytes -= (table->key_m.bytes % 8);
dvander@2226
   100
	}
dvander@2226
   101
	
dvander@2226
   102
	table->val_offs = table->key_offs + table->key_m.bytes;
dvander@2226
   103
	table->node_size = table->val_offs + table->val_m.bytes;
dvander@2226
   104
dvander@2226
   105
	return table;
dvander@2226
   106
}
dvander@2226
   107
dvander@2226
   108
#define KE_GET_BUCKET(tbl, hsh) (&(tbl)->buckets[((hsh) * 0x9E3779B9) >> (tbl)->shift])
dvander@2226
   109
dvander@2226
   110
KeHashNode **ke_HashInternalFind(KeHashTable *table, uint32_t key_hash, const void *key)
dvander@2226
   111
{
dvander@2226
   112
	KeHashNode *node;
dvander@2226
   113
	KeHashNode **bucket;
dvander@2226
   114
dvander@2226
   115
	bucket = KE_GET_BUCKET(table, key_hash);
dvander@2226
   116
dvander@2226
   117
	/* :TODO: move to the front once found? */
dvander@2226
   118
dvander@2226
   119
	while ((node = *bucket) != NULL)
dvander@2226
   120
	{
dvander@2226
   121
		if (node->key_hash == key_hash
dvander@2226
   122
			&& ((table->key_m.cmp != NULL && table->key_m.cmp(node->key, key))
dvander@2226
   123
				 || node->key == key))
dvander@2226
   124
		{
dvander@2226
   125
			return bucket;
dvander@2226
   126
		}
dvander@2226
   127
		bucket = &node->next;
dvander@2226
   128
	}
dvander@2226
   129
dvander@2226
   130
	return bucket;
dvander@2226
   131
}
dvander@2226
   132
dvander@2226
   133
void ke_ResizeHashTable(KeHashTable *table, uint32_t new_shift)
dvander@2226
   134
{
dvander@2226
   135
	uint32_t entries;
dvander@2226
   136
	KeHashNode *next;
dvander@2226
   137
	KeHashNode *node;
dvander@2226
   138
	KeHashNode **rbucket;
dvander@2226
   139
	KeHashNode **old_buckets;
dvander@2226
   140
	uint32_t old_num_buckets;
dvander@2226
   141
dvander@2226
   142
	/* Save old data */
dvander@2226
   143
	old_num_buckets = table->num_buckets;
dvander@2226
   144
	old_buckets = table->buckets;
dvander@2226
   145
	entries = table->num_entries;
dvander@2226
   146
dvander@2226
   147
	/* Save new data */
dvander@2226
   148
	table->num_buckets = (1 << new_shift);
dvander@2541
   149
	table->shift = 32 - new_shift;
dvander@2226
   150
	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
dvander@2226
   151
dvander@2226
   152
	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
   153
	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
   154
dvander@2226
   155
	/* For each old bucket... */
dvander@2226
   156
	for (uint32_t i = 0;
dvander@2226
   157
		 i < old_num_buckets && entries != 0;
dvander@2226
   158
		 i++)
dvander@2226
   159
	{
dvander@2226
   160
		node = old_buckets[i];
dvander@2226
   161
		/* Get each item in its list... */
dvander@2226
   162
		while (node != NULL)
dvander@2226
   163
		{
dvander@2226
   164
			next = node->next;
dvander@2226
   165
			
dvander@2226
   166
			/* Find the new replacement bucket it needs to go in. */
dvander@2226
   167
			rbucket = KE_GET_BUCKET(table, node->key_hash);
dvander@2226
   168
dvander@2226
   169
			/* Link this node to the next node in the new bucket. */
dvander@2226
   170
			if (*rbucket == NULL)
dvander@2226
   171
			{
dvander@2226
   172
				node->next = NULL;
dvander@2226
   173
			}
dvander@2226
   174
			else
dvander@2226
   175
			{
dvander@2226
   176
				node->next = *rbucket;
dvander@2226
   177
			}
dvander@2226
   178
dvander@2226
   179
			/* Add us to the front of that bucket's list. */
dvander@2226
   180
			*rbucket = node;
dvander@2226
   181
dvander@2226
   182
			node = next;
dvander@2226
   183
		}
dvander@2226
   184
	}
dvander@2226
   185
dvander@2226
   186
	free(old_buckets);
dvander@2226
   187
}
dvander@2226
   188
dvander@2226
   189
void Knight::KE_AddToHashTable(KeHashTable *table, const void *key, void *val)
dvander@2226
   190
{
dvander@2226
   191
	KeHashNode *node;
dvander@2226
   192
	uint32_t key_hash;
dvander@2226
   193
	KeHashNode **bucket;
dvander@2226
   194
dvander@2226
   195
	key_hash = table->key_gen(key);
dvander@2226
   196
	bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   197
dvander@2226
   198
	if ((node = *bucket) != NULL)
dvander@2226
   199
	{
dvander@2226
   200
		/* Already in the table */
dvander@2226
   201
		if ((table->val_m.cmp != NULL && table->val_m.cmp(node->value, val))
dvander@2226
   202
			 || node->value == val)
dvander@2226
   203
		{
dvander@2226
   204
			return;
dvander@2226
   205
		}
dvander@2226
   206
dvander@2226
   207
		/* Destroy old value if it's set. */
dvander@2226
   208
		if (node->value != NULL && table->val_m.dtor != NULL)
dvander@2226
   209
		{
dvander@2226
   210
			table->val_m.dtor(node->value);
dvander@2226
   211
		}
dvander@2226
   212
		
dvander@2226
   213
		/* Construct or set the new value. */
dvander@2226
   214
		if (table->val_m.bytes != 0)
dvander@2226
   215
		{
dvander@2226
   216
			table->val_m.ctor(node->value, val);
dvander@2226
   217
		}
dvander@2226
   218
		else
dvander@2226
   219
		{
dvander@2226
   220
			node->value = val;
dvander@2226
   221
		}
dvander@2226
   222
dvander@2226
   223
		return;
dvander@2226
   224
	}
dvander@2226
   225
dvander@2226
   226
	/* If we're overloaded, we may need to resize. 
dvander@2226
   227
	 * Right now, we do this if we hit a .9 entry:buckets ratio.
dvander@2226
   228
	 */
dvander@2226
   229
	if (table->num_entries >= table->grow_limit)
dvander@2226
   230
	{
dvander@2226
   231
		ke_ResizeHashTable(table, table->shift << 1);
dvander@2226
   232
		bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   233
	}
dvander@2226
   234
dvander@2226
   235
	if (table->free_list != NULL)
dvander@2226
   236
	{
dvander@2226
   237
		node = table->free_list;
dvander@2226
   238
		table->free_list = node->next;
dvander@2226
   239
	}
dvander@2226
   240
	else
dvander@2226
   241
	{
dvander@2226
   242
		node = (KeHashNode *)table->node_alloc->alloc(table->node_alloc, table->node_size);
dvander@2226
   243
	}
dvander@2226
   244
	
dvander@2226
   245
	if (table->key_m.bytes == 0)
dvander@2226
   246
	{
dvander@2226
   247
		node->key = key;
dvander@2226
   248
	}
dvander@2226
   249
	else
dvander@2226
   250
	{
dvander@2226
   251
		node->key = (char *)node + table->key_offs;
dvander@2226
   252
		table->key_m.ctor((void *)node->key, key);
dvander@2226
   253
	}
dvander@2226
   254
dvander@2226
   255
	if (table->val_m.bytes == 0)
dvander@2226
   256
	{
dvander@2226
   257
		node->value = val;
dvander@2226
   258
	}
dvander@2226
   259
	else
dvander@2226
   260
	{
dvander@2226
   261
		node->value = (char *)node + table->val_offs;
dvander@2226
   262
		table->val_m.ctor(node->value, val);
dvander@2226
   263
	}
dvander@2226
   264
dvander@2226
   265
	node->next = *bucket;
dvander@2226
   266
	node->key_hash = key_hash;
dvander@2226
   267
	*bucket = node;
dvander@2226
   268
}
dvander@2226
   269
dvander@2226
   270
inline void ke_CleanUpHashNode(KeHashTable *table, KeHashNode *node)
dvander@2226
   271
{
dvander@2226
   272
	/* Destroy old value if it's set. */
dvander@2226
   273
	if (node->value != NULL && table->val_m.dtor != NULL)
dvander@2226
   274
	{
dvander@2226
   275
		table->val_m.dtor(node->value);
dvander@2226
   276
	}
dvander@2226
   277
dvander@2226
   278
	/* Destroy the key. */
dvander@2226
   279
	if (table->key_m.dtor != NULL)
dvander@2226
   280
	{
dvander@2226
   281
		table->key_m.dtor(node->key);
dvander@2226
   282
	}
dvander@2226
   283
dvander@2226
   284
	/* Deallocate us as appropriate. */
dvander@2226
   285
	if (table->keep_free_list)
dvander@2226
   286
	{
dvander@2226
   287
		node->next = table->free_list;
dvander@2226
   288
		table->free_list = node;
dvander@2226
   289
	}
dvander@2226
   290
	else
dvander@2226
   291
	{
dvander@2226
   292
		table->node_alloc->dealloc(table->node_alloc, node);
dvander@2226
   293
	}
dvander@2226
   294
}
dvander@2226
   295
dvander@2226
   296
void Knight::KE_RemoveFromHashTable(KeHashTable *table, const void *key)
dvander@2226
   297
{
dvander@2226
   298
	KeHashNode *node;
dvander@2226
   299
	uint32_t key_hash;
dvander@2226
   300
	KeHashNode **bucket;
dvander@2226
   301
dvander@2226
   302
	key_hash = table->key_gen(key);
dvander@2226
   303
	bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   304
dvander@2226
   305
	if ((node = *bucket) == NULL)
dvander@2226
   306
	{
dvander@2226
   307
		return;
dvander@2226
   308
	}
dvander@2226
   309
dvander@2226
   310
	/* Link the bucket to its next (removing us). */
dvander@2226
   311
	*bucket = node->next;
dvander@2226
   312
dvander@2226
   313
	ke_CleanUpHashNode(table, node);
dvander@2226
   314
}
dvander@2226
   315
dvander@2226
   316
bool Knight::KE_FindInHashTable(KeHashTable *table, const void *key, void **value)
dvander@2226
   317
{
dvander@2226
   318
	KeHashNode *node;
dvander@2226
   319
	uint32_t key_hash;
dvander@2226
   320
	KeHashNode **bucket;
dvander@2226
   321
dvander@2226
   322
	key_hash = table->key_gen(key);
dvander@2226
   323
	bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   324
dvander@2226
   325
	if ((node = *bucket) == NULL)
dvander@2226
   326
	{
dvander@2226
   327
		return false;
dvander@2226
   328
	}
dvander@2226
   329
dvander@2226
   330
	if (value != NULL)
dvander@2226
   331
	{
dvander@2226
   332
		*value = node->value;
dvander@2226
   333
	}
dvander@2226
   334
dvander@2226
   335
	return true;
dvander@2226
   336
}
dvander@2226
   337
dvander@2226
   338
void Knight::KE_DestroyHashTable(KeHashTable *table)
dvander@2226
   339
{
dvander@2226
   340
	KeHashNode *node, *next;
dvander@2226
   341
dvander@2226
   342
	/* Turn off this caching! */
dvander@2226
   343
	table->keep_free_list = false;
dvander@2226
   344
dvander@2226
   345
	/* Find entries in buckets that need to be freed. */
dvander@2226
   346
	for (uint32_t i = 0; i < table->num_buckets; i++)
dvander@2226
   347
	{
dvander@2226
   348
		node = table->buckets[i];
dvander@2226
   349
dvander@2226
   350
		while (node != NULL)
dvander@2226
   351
		{
dvander@2226
   352
			next = node->next;
dvander@2226
   353
			ke_CleanUpHashNode(table, node);
dvander@2226
   354
			node = next;
dvander@2226
   355
		}
dvander@2226
   356
	}
dvander@2226
   357
dvander@2226
   358
	/* Free the free list */
dvander@2226
   359
	while (table->free_list != NULL)
dvander@2226
   360
	{
dvander@2226
   361
		next = table->free_list->next;
dvander@2226
   362
		ke_CleanUpHashNode(table, table->free_list);
dvander@2226
   363
		table->free_list = next;
dvander@2226
   364
	}
dvander@2226
   365
dvander@2226
   366
	/* Destroy everything now. */
dvander@2226
   367
	free(table->buckets);
dvander@2226
   368
	delete table;
dvander@2226
   369
}
dvander@2226
   370
dvander@2226
   371
void Knight::KE_ClearHashTable(KeHashTable *table)
dvander@2226
   372
{
dvander@2226
   373
	KeHashNode *node, *next;
dvander@2226
   374
dvander@2226
   375
	/* Free every entry in the table. */
dvander@2226
   376
	for (uint32_t i = 0; i < table->num_buckets; i++)
dvander@2226
   377
	{
dvander@2226
   378
		node = table->buckets[i];
dvander@2226
   379
dvander@2226
   380
		while (node != NULL)
dvander@2226
   381
		{
dvander@2226
   382
			next = node->next;
dvander@2226
   383
			ke_CleanUpHashNode(table, node);
dvander@2226
   384
			node = next;
dvander@2226
   385
		}
dvander@2226
   386
	}
dvander@2226
   387
}
dvander@2226
   388
dvander@2226
   389
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
dvander@2226
   390
#pragma intrinsic(_rotl)
dvander@2226
   391
#endif
dvander@2226
   392
dvander@2226
   393
uint32_t Knight::KE_HashString(const void *str)
dvander@2226
   394
{
dvander@2226
   395
	uint32_t h;
dvander@2226
   396
	const unsigned char *us;
dvander@2226
   397
dvander@2226
   398
	h = 0;
dvander@2226
   399
dvander@2226
   400
	for (us = (const unsigned char *)str; *us != 0; us++)
dvander@2226
   401
	{
dvander@2226
   402
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
dvander@2226
   403
		h = _rotl(h, 4) ^ *us;
dvander@2226
   404
#else
dvander@2226
   405
		h = ((h << 4) | (h >> 28)) ^ *us;
dvander@2226
   406
#endif
dvander@2226
   407
	}
dvander@2226
   408
dvander@2226
   409
	return h;
dvander@2226
   410
}
dvander@2541
   411
dvander@2541
   412
bool Knight::KE_AreStringsEqual(const void* str1, const void* str2)
dvander@2541
   413
{
dvander@2541
   414
    return (strcmp((const char*)str1, (const char*)str2) == 0) ? true : false;
dvander@2541
   415
}
dvander@2541
   416