2009-01-29 18:50:32 +01:00
|
|
|
#include <string.h>
|
2008-08-15 07:22:26 +02:00
|
|
|
#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;
|
2009-01-29 18:50:32 +01:00
|
|
|
table->shift = 32 - bits;
|
2008-08-15 07:22:26 +02:00
|
|
|
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);
|
2009-01-29 18:50:32 +01:00
|
|
|
table->shift = 32 - new_shift;
|
2008-08-15 07:22:26 +02:00
|
|
|
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;
|
|
|
|
}
|
2009-01-29 18:50:32 +01:00
|
|
|
|
|
|
|
bool Knight::KE_AreStringsEqual(const void* str1, const void* str2)
|
|
|
|
{
|
|
|
|
return (strcmp((const char*)str1, (const char*)str2) == 0) ? true : false;
|
|
|
|
}
|
|
|
|
|