417 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <string.h>
 | |
| #include <KeHashTable.h>
 | |
| 
 | |
| using namespace Knight;
 | |
| 
 | |
| struct KeHashNode
 | |
| {
 | |
| 	KeHashNode *next;
 | |
| 	uint32_t key_hash;
 | |
| 	const void *key;
 | |
| 	void *value;
 | |
| };
 | |
| 
 | |
| namespace Knight
 | |
| {
 | |
| 	class KeHashTable
 | |
| 	{
 | |
| 	public:
 | |
| 		KeHashNode **buckets;
 | |
| 		uint32_t num_buckets;
 | |
| 		uint32_t shift;
 | |
| 		uint32_t num_entries;
 | |
| 		KeHashGenerator key_gen;
 | |
| 		KeHashMarshal key_m;
 | |
| 		KeHashMarshal val_m;
 | |
| 		ke_allocator_t *node_alloc;
 | |
| 		size_t key_offs;
 | |
| 		size_t val_offs;
 | |
| 		size_t node_size;
 | |
| 		uint32_t grow_limit;
 | |
| 		KeHashNode *free_list;
 | |
| 		bool keep_free_list;
 | |
| 	};
 | |
| }
 | |
| 
 | |
| void *ke_DefHashMalloc(ke_allocator_t *alloc, size_t amt)
 | |
| {
 | |
| 	return malloc(amt);
 | |
| }
 | |
| 
 | |
| void ke_DefHashFree(ke_allocator_t *alloc, void *addr)
 | |
| {
 | |
| 	free(addr);
 | |
| }
 | |
| 
 | |
| ke_allocator_t s_DefHashAllocator = 
 | |
| {
 | |
| 	ke_DefHashMalloc,
 | |
| 	ke_DefHashFree,
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| KeHashTable *Knight::KE_CreateHashTable(
 | |
| 	uint32_t bits,
 | |
| 	KeHashGenerator key_gen,
 | |
| 	const KeHashMarshal *key_marshal,
 | |
| 	const KeHashMarshal *val_marshal,
 | |
| 	ke_allocator_t *nodeAlloc,
 | |
| 	bool keep_free_list)
 | |
| {
 | |
| 	KeHashTable *table;
 | |
| 
 | |
| 	if (bits >= 27)
 | |
| 	{
 | |
| 		bits = 26;
 | |
| 	}
 | |
| 	else if (bits < 4)
 | |
| 	{
 | |
| 		bits = 4;
 | |
| 	}
 | |
| 
 | |
| 	/* Validate marshals. */
 | |
| 	if ((key_marshal->bytes != 0
 | |
| 		 && key_marshal->ctor == NULL)
 | |
| 		|| (val_marshal->bytes != 0
 | |
| 			&& val_marshal->ctor == NULL))
 | |
| 	{
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	table = new KeHashTable;
 | |
| 	table->key_gen = key_gen;
 | |
| 	table->key_m = *key_marshal;
 | |
| 	table->val_m = *val_marshal;
 | |
| 	table->num_entries = 0;
 | |
| 	table->shift = 32 - bits;
 | |
| 	table->node_alloc = nodeAlloc == NULL ? &s_DefHashAllocator : nodeAlloc;
 | |
| 	table->num_buckets = (1 << bits);
 | |
| 	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
 | |
| 	table->keep_free_list = keep_free_list;
 | |
| 	table->free_list = NULL;
 | |
| 	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
 | |
| 	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
 | |
| 
 | |
| 	table->key_offs = sizeof(KeHashNode);
 | |
| 	if (table->key_m.bytes != 0 && table->key_m.bytes % 8 != 0)
 | |
| 	{
 | |
| 		table->key_m.bytes += 8;
 | |
| 		table->key_m.bytes -= (table->key_m.bytes % 8);
 | |
| 	}
 | |
| 	
 | |
| 	table->val_offs = table->key_offs + table->key_m.bytes;
 | |
| 	table->node_size = table->val_offs + table->val_m.bytes;
 | |
| 
 | |
| 	return table;
 | |
| }
 | |
| 
 | |
| #define KE_GET_BUCKET(tbl, hsh) (&(tbl)->buckets[((hsh) * 0x9E3779B9) >> (tbl)->shift])
 | |
| 
 | |
| KeHashNode **ke_HashInternalFind(KeHashTable *table, uint32_t key_hash, const void *key)
 | |
| {
 | |
| 	KeHashNode *node;
 | |
| 	KeHashNode **bucket;
 | |
| 
 | |
| 	bucket = KE_GET_BUCKET(table, key_hash);
 | |
| 
 | |
| 	/* :TODO: move to the front once found? */
 | |
| 
 | |
| 	while ((node = *bucket) != NULL)
 | |
| 	{
 | |
| 		if (node->key_hash == key_hash
 | |
| 			&& ((table->key_m.cmp != NULL && table->key_m.cmp(node->key, key))
 | |
| 				 || node->key == key))
 | |
| 		{
 | |
| 			return bucket;
 | |
| 		}
 | |
| 		bucket = &node->next;
 | |
| 	}
 | |
| 
 | |
| 	return bucket;
 | |
| }
 | |
| 
 | |
| void ke_ResizeHashTable(KeHashTable *table, uint32_t new_shift)
 | |
| {
 | |
| 	uint32_t entries;
 | |
| 	KeHashNode *next;
 | |
| 	KeHashNode *node;
 | |
| 	KeHashNode **rbucket;
 | |
| 	KeHashNode **old_buckets;
 | |
| 	uint32_t old_num_buckets;
 | |
| 
 | |
| 	/* Save old data */
 | |
| 	old_num_buckets = table->num_buckets;
 | |
| 	old_buckets = table->buckets;
 | |
| 	entries = table->num_entries;
 | |
| 
 | |
| 	/* Save new data */
 | |
| 	table->num_buckets = (1 << new_shift);
 | |
| 	table->shift = 32 - new_shift;
 | |
| 	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
 | |
| 
 | |
| 	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
 | |
| 	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
 | |
| 
 | |
| 	/* For each old bucket... */
 | |
| 	for (uint32_t i = 0;
 | |
| 		 i < old_num_buckets && entries != 0;
 | |
| 		 i++)
 | |
| 	{
 | |
| 		node = old_buckets[i];
 | |
| 		/* Get each item in its list... */
 | |
| 		while (node != NULL)
 | |
| 		{
 | |
| 			next = node->next;
 | |
| 			
 | |
| 			/* Find the new replacement bucket it needs to go in. */
 | |
| 			rbucket = KE_GET_BUCKET(table, node->key_hash);
 | |
| 
 | |
| 			/* Link this node to the next node in the new bucket. */
 | |
| 			if (*rbucket == NULL)
 | |
| 			{
 | |
| 				node->next = NULL;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				node->next = *rbucket;
 | |
| 			}
 | |
| 
 | |
| 			/* Add us to the front of that bucket's list. */
 | |
| 			*rbucket = node;
 | |
| 
 | |
| 			node = next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	free(old_buckets);
 | |
| }
 | |
| 
 | |
| void Knight::KE_AddToHashTable(KeHashTable *table, const void *key, void *val)
 | |
| {
 | |
| 	KeHashNode *node;
 | |
| 	uint32_t key_hash;
 | |
| 	KeHashNode **bucket;
 | |
| 
 | |
| 	key_hash = table->key_gen(key);
 | |
| 	bucket = ke_HashInternalFind(table, key_hash, key);
 | |
| 
 | |
| 	if ((node = *bucket) != NULL)
 | |
| 	{
 | |
| 		/* Already in the table */
 | |
| 		if ((table->val_m.cmp != NULL && table->val_m.cmp(node->value, val))
 | |
| 			 || node->value == val)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		/* Destroy old value if it's set. */
 | |
| 		if (node->value != NULL && table->val_m.dtor != NULL)
 | |
| 		{
 | |
| 			table->val_m.dtor(node->value);
 | |
| 		}
 | |
| 		
 | |
| 		/* Construct or set the new value. */
 | |
| 		if (table->val_m.bytes != 0)
 | |
| 		{
 | |
| 			table->val_m.ctor(node->value, val);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			node->value = val;
 | |
| 		}
 | |
| 
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* If we're overloaded, we may need to resize. 
 | |
| 	 * Right now, we do this if we hit a .9 entry:buckets ratio.
 | |
| 	 */
 | |
| 	if (table->num_entries >= table->grow_limit)
 | |
| 	{
 | |
| 		ke_ResizeHashTable(table, table->shift << 1);
 | |
| 		bucket = ke_HashInternalFind(table, key_hash, key);
 | |
| 	}
 | |
| 
 | |
| 	if (table->free_list != NULL)
 | |
| 	{
 | |
| 		node = table->free_list;
 | |
| 		table->free_list = node->next;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		node = (KeHashNode *)table->node_alloc->alloc(table->node_alloc, table->node_size);
 | |
| 	}
 | |
| 	
 | |
| 	if (table->key_m.bytes == 0)
 | |
| 	{
 | |
| 		node->key = key;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		node->key = (char *)node + table->key_offs;
 | |
| 		table->key_m.ctor((void *)node->key, key);
 | |
| 	}
 | |
| 
 | |
| 	if (table->val_m.bytes == 0)
 | |
| 	{
 | |
| 		node->value = val;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		node->value = (char *)node + table->val_offs;
 | |
| 		table->val_m.ctor(node->value, val);
 | |
| 	}
 | |
| 
 | |
| 	node->next = *bucket;
 | |
| 	node->key_hash = key_hash;
 | |
| 	*bucket = node;
 | |
| }
 | |
| 
 | |
| inline void ke_CleanUpHashNode(KeHashTable *table, KeHashNode *node)
 | |
| {
 | |
| 	/* Destroy old value if it's set. */
 | |
| 	if (node->value != NULL && table->val_m.dtor != NULL)
 | |
| 	{
 | |
| 		table->val_m.dtor(node->value);
 | |
| 	}
 | |
| 
 | |
| 	/* Destroy the key. */
 | |
| 	if (table->key_m.dtor != NULL)
 | |
| 	{
 | |
| 		table->key_m.dtor(node->key);
 | |
| 	}
 | |
| 
 | |
| 	/* Deallocate us as appropriate. */
 | |
| 	if (table->keep_free_list)
 | |
| 	{
 | |
| 		node->next = table->free_list;
 | |
| 		table->free_list = node;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		table->node_alloc->dealloc(table->node_alloc, node);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Knight::KE_RemoveFromHashTable(KeHashTable *table, const void *key)
 | |
| {
 | |
| 	KeHashNode *node;
 | |
| 	uint32_t key_hash;
 | |
| 	KeHashNode **bucket;
 | |
| 
 | |
| 	key_hash = table->key_gen(key);
 | |
| 	bucket = ke_HashInternalFind(table, key_hash, key);
 | |
| 
 | |
| 	if ((node = *bucket) == NULL)
 | |
| 	{
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Link the bucket to its next (removing us). */
 | |
| 	*bucket = node->next;
 | |
| 
 | |
| 	ke_CleanUpHashNode(table, node);
 | |
| }
 | |
| 
 | |
| bool Knight::KE_FindInHashTable(KeHashTable *table, const void *key, void **value)
 | |
| {
 | |
| 	KeHashNode *node;
 | |
| 	uint32_t key_hash;
 | |
| 	KeHashNode **bucket;
 | |
| 
 | |
| 	key_hash = table->key_gen(key);
 | |
| 	bucket = ke_HashInternalFind(table, key_hash, key);
 | |
| 
 | |
| 	if ((node = *bucket) == NULL)
 | |
| 	{
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	if (value != NULL)
 | |
| 	{
 | |
| 		*value = node->value;
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void Knight::KE_DestroyHashTable(KeHashTable *table)
 | |
| {
 | |
| 	KeHashNode *node, *next;
 | |
| 
 | |
| 	/* Turn off this caching! */
 | |
| 	table->keep_free_list = false;
 | |
| 
 | |
| 	/* Find entries in buckets that need to be freed. */
 | |
| 	for (uint32_t i = 0; i < table->num_buckets; i++)
 | |
| 	{
 | |
| 		node = table->buckets[i];
 | |
| 
 | |
| 		while (node != NULL)
 | |
| 		{
 | |
| 			next = node->next;
 | |
| 			ke_CleanUpHashNode(table, node);
 | |
| 			node = next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Free the free list */
 | |
| 	while (table->free_list != NULL)
 | |
| 	{
 | |
| 		next = table->free_list->next;
 | |
| 		ke_CleanUpHashNode(table, table->free_list);
 | |
| 		table->free_list = next;
 | |
| 	}
 | |
| 
 | |
| 	/* Destroy everything now. */
 | |
| 	free(table->buckets);
 | |
| 	delete table;
 | |
| }
 | |
| 
 | |
| void Knight::KE_ClearHashTable(KeHashTable *table)
 | |
| {
 | |
| 	KeHashNode *node, *next;
 | |
| 
 | |
| 	/* Free every entry in the table. */
 | |
| 	for (uint32_t i = 0; i < table->num_buckets; i++)
 | |
| 	{
 | |
| 		node = table->buckets[i];
 | |
| 
 | |
| 		while (node != NULL)
 | |
| 		{
 | |
| 			next = node->next;
 | |
| 			ke_CleanUpHashNode(table, node);
 | |
| 			node = next;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
 | |
| #pragma intrinsic(_rotl)
 | |
| #endif
 | |
| 
 | |
| uint32_t Knight::KE_HashString(const void *str)
 | |
| {
 | |
| 	uint32_t h;
 | |
| 	const unsigned char *us;
 | |
| 
 | |
| 	h = 0;
 | |
| 
 | |
| 	for (us = (const unsigned char *)str; *us != 0; us++)
 | |
| 	{
 | |
| #if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
 | |
| 		h = _rotl(h, 4) ^ *us;
 | |
| #else
 | |
| 		h = ((h << 4) | (h >> 28)) ^ *us;
 | |
| #endif
 | |
| 	}
 | |
| 
 | |
| 	return h;
 | |
| }
 | |
| 
 | |
| bool Knight::KE_AreStringsEqual(const void* str1, const void* str2)
 | |
| {
 | |
|     return (strcmp((const char*)str1, (const char*)str2) == 0) ? true : false;
 | |
| }
 | |
| 
 |