diff --git a/DqnUnitTest.cpp b/DqnUnitTest.cpp index e33073e..2c5f253 100644 --- a/DqnUnitTest.cpp +++ b/DqnUnitTest.cpp @@ -140,6 +140,7 @@ void LogHeader(char const *funcName) #include "DqnFixedString.cpp" #include "DqnOS.cpp" #include "DqnJson.cpp" +#include "DqnVHashTable.cpp" void HandmadeMathVerifyMat4(DqnMat4 dqnMat, hmm_mat4 hmmMat) { @@ -2901,6 +2902,7 @@ int main(void) DqnJson_Test(); #ifdef DQN_PLATFORM_HEADER + DqnVHashTable_Test(); DqnOS_Test(); DqnFile_Test(); DqnTimer_Test(); diff --git a/DqnVHashTable.cpp b/DqnVHashTable.cpp new file mode 100644 index 0000000..72d2314 --- /dev/null +++ b/DqnVHashTable.cpp @@ -0,0 +1,27 @@ +void DqnVHashTable_Test() +{ + LOG_HEADER(); + struct Block + { + int x; + }; + using Height = u32; + Block block = {}; + + DqnVHashTable table = {}; + table.Set(12, block); + + Block *getResult = table.Get(12); + Block *operatorResult = table[12]; + DQN_ASSERT(operatorResult == getResult); + DQN_ASSERT(operatorResult != nullptr); + Log(Status::Ok, "Set and get element using key"); + + table.Erase(12); + getResult = table.Get(12); + operatorResult = table[12]; + + DQN_ASSERT(operatorResult == getResult); + DQN_ASSERT(operatorResult == nullptr); + Log(Status::Ok, "Erase element using key"); +} diff --git a/dqn.h b/dqn.h index dbce9c4..811da71 100644 --- a/dqn.h +++ b/dqn.h @@ -47,13 +47,14 @@ // #DqnHashTable Hash Tables using Templates // #XPlatform (Win32 & Unix) -// #DqnVArray Array backed by virtual memory -// #DqnFile File I/O (Read, Write, Delete) -// #DqnTimer High Resolution Timer -// #DqnLock Mutex Synchronisation -// #DqnJobQueue Multithreaded Job Queue -// #DqnAtomic Interlocks/Atomic Operations -// #DqnOS Common Platform API helpers +// #DqnVArray Array backed by virtual memory +// #DqnVHashTable Hash Table using templates backed by virtual memory +// #DqnFile File I/O (Read, Write, Delete) +// #DqnTimer High Resolution Timer +// #DqnLock Mutex Synchronisation +// #DqnJobQueue Multithreaded Job Queue +// #DqnAtomic Interlocks/Atomic Operations +// #DqnOS Common Platform API helpers // #Platform // - #DqnWin32 Common Win32 API Helpers @@ -1127,6 +1128,7 @@ DQN_FILE_SCOPE void *DqnMem_Realloc(void *memory, usize newSize); DQN_FILE_SCOPE void DqnMem_Free (void *memory); DQN_FILE_SCOPE void DqnMem_Copy (void *dest, void const *src, usize numBytesToCopy); DQN_FILE_SCOPE void *DqnMem_Set (void *dest, u8 value, usize numBytesToSet); +DQN_FILE_SCOPE int DqnMem_Cmp(void const *src, void const *dest, usize numBytes); // #DqnMemAPI API // ================================================================================================= @@ -2327,6 +2329,25 @@ int DqnFixedString::SprintfAppend(char const *fmt, ...) return result; } +template using DqnVHashTableHashingProc = isize(*)(isize count, Key const &data); +template using DqnVHashTableEqualsProc = bool (*)(Key const &a, Key const &b); + +#define DQN_VHASH_TABLE_HASHING_PROC(name) template inline isize name(isize count, Key const &key) +DQN_VHASH_TABLE_HASHING_PROC(DqnVHashTableDefaultHash) +{ + const u64 SEED = 0x9747B28CAB3F8A7B; + u64 index64 = DqnHash_Murmur64Seed(&key, sizeof(key), SEED); + isize result = index64 % count; + return result; +} + +#define DQN_VHASH_TABLE_EQUALS_PROC(name) template inline bool name(Key const &a, Key const &b) +DQN_VHASH_TABLE_EQUALS_PROC(DqnVHashTableDefaultEquals) +{ + bool result = (DqnMem_Cmp(&a, &b, sizeof(a)) == 0); + return result; +} + // TODO(doyle): Load factor // #DqnHashTable API // ================================================================================================= @@ -2938,6 +2959,119 @@ template void DqnVArray::EraseStable(isize index) count--; } +// #XPlatform > #DqnVHashTable API +// ================================================================================================= +#define DQN_VHASH_TABLE_TEMPLATE \ + template Hash = DqnVHashTableDefaultHash, \ + DqnVHashTableEqualsProc Equals = DqnVHashTableDefaultEquals> + +#define DQN_VHASH_TABLE_DECL DqnVHashTable + +DQN_VHASH_TABLE_TEMPLATE struct DqnVHashTable +{ + struct Entry + { + Key key; + Item item; + }; + + struct Bucket + { + Entry entries[4]; + isize entryIndex; + }; + + Bucket *buckets; + isize numBuckets; + isize bucketsUsed; + + DqnVHashTable() = default; + DqnVHashTable(isize size) { LazyInitialise(size); } + ~DqnVHashTable() { if (buckets) DqnOS_VFree(buckets, sizeof(buckets) * numBuckets); } + + void Erase(Key const &key); + Item *Set (Key const &key, Item const &item); + Item *Get (Key const &key); + + Item *operator[](Key const &key) { return Get(key); } + +private: + void LazyInitialise(isize size) { *this = {}; numBuckets = size; buckets = static_cast(DqnOS_VAlloc(numBuckets * sizeof(*buckets))); DQN_ASSERT(buckets); } +}; + +DQN_VHASH_TABLE_TEMPLATE Item *DQN_VHASH_TABLE_DECL::Set(Key const &key, Item const &item) +{ + if (!buckets) LazyInitialise(1024); + + isize index = Hash(this->numBuckets, key); + Bucket *bucket = this->buckets + index; + Entry *entry = nullptr; + + for (isize i = 0; i < bucket->entryIndex && !entry; i++) + { + Entry *check = bucket->entries + i; + entry = Equals(check->key, key) ? check : nullptr; + } + + if (!entry) + { + DQN_ASSERT(bucket->entryIndex < DQN_ARRAY_COUNT(bucket->entries)); + this->bucketsUsed = (bucket->entryIndex == 0) ? this->bucketsUsed + 1 : this->bucketsUsed; + entry = bucket->entries + bucket->entryIndex++; + } + + entry->key = key; + entry->item = item; + return &entry->item; +} + +DQN_VHASH_TABLE_TEMPLATE Item *DQN_VHASH_TABLE_DECL::Get(Key const &key) +{ + Item *result = nullptr; + if (!buckets) + { + return result; + } + + isize index = Hash(this->numBuckets, key); + Bucket *bucket = this->buckets + index; + + for (isize i = 0; i < bucket->entryIndex && !result; i++) + { + Entry *entry = bucket->entries + i; + result = Equals(entry->key, key) ? &entry->item : nullptr; + } + + return result; +} + +DQN_VHASH_TABLE_TEMPLATE void DQN_VHASH_TABLE_DECL::Erase(Key const &key) +{ + if (!buckets) + { + return; + } + + isize index = Hash(this->numBuckets, key); + Bucket *bucket = this->buckets + index; + + for (isize i = 0; i < bucket->entryIndex; ++i) + { + Entry *check = bucket->entries + i; + if (Equals(check->key, key)) + { + for (isize j = i; j < (bucket->entryIndex - 1); ++j) + bucket->entries[j] = bucket->entries[j + 1]; + + --bucket->entryIndex; + DQN_ASSERT(bucket->entryIndex >= 0); + return; + } + } +} + // XPlatform > #DqnFile API // ================================================================================================= struct DqnFile @@ -3356,6 +3490,22 @@ DQN_FILE_SCOPE void *DqnMem_Set(void *dest, u8 value, usize numBytesToSet) return dest; } +DQN_FILE_SCOPE int DqnMem_Cmp(void const *src, void const *dest, usize numBytes) +{ + auto const *srcPtr = static_cast(src); + auto const *destPtr = static_cast(dest); + + usize i; + for (i = 0; i < numBytes; ++i) + { + if (srcPtr[i] != destPtr[i]) + break; + } + + i = DQN_MIN(i, (numBytes - 1)); + return (srcPtr[i] - destPtr[i]); +} + // #DqnMemAPI // ================================================================================================= FILE_SCOPE void DqnMemAPIInternal_ValidateRequest(DqnMemAPI::Request request_)