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;
 | 
						|
}
 | 
						|
 |