From ed3f11dc7bda065278b2b9c1d9413fba2dba2c06 Mon Sep 17 00:00:00 2001 From: Doyle T Date: Mon, 30 Jul 2018 00:34:00 +1000 Subject: [PATCH] Remove DqnHashTable, add DqnString tests --- DqnUnitTest.cpp | 190 +++++---- dqn.h | 1005 ++--------------------------------------------- 2 files changed, 114 insertions(+), 1081 deletions(-) diff --git a/DqnUnitTest.cpp b/DqnUnitTest.cpp index 47b8c1a..90ae44a 100644 --- a/DqnUnitTest.cpp +++ b/DqnUnitTest.cpp @@ -647,38 +647,104 @@ void DqnString_Test() { LOG_HEADER(); - // Check fixed mem string doesn't allow string to expand and fail if try to append - if (1) - { - char space[4] = {}; - DqnString str = {}; - DQN_ASSERT(str.InitFixedMem(space, DQN_ARRAY_COUNT(space))); - - DQN_ASSERT(str.Append("test_doesnt_fit") == false); - DQN_ASSERT(str.Append("tooo") == false); - DQN_ASSERT(str.Append("fit") == true); - DQN_ASSERT(str.Append("test_doesnt_fit") == false); - DQN_ASSERT(str.Append("1") == false); - - DQN_ASSERT(str.str[str.len] == 0); - DQN_ASSERT(str.len <= str.max); - Log(Status::Ok, "Append: Check fixed mem string doesn't expand and fails"); - } - // Try expanding string if (1) { - DqnString str = {}; - DQN_ASSERT(str.InitLiteral("hello world")); - DQN_ASSERT(str.Append(", hello again")); - DQN_ASSERT(str.Append(", and hello again")); + DqnString str = "hello world"; + DQN_DEFER(str.Free()); + str = "hello world2"; + str.Append(", hello again"); + str.Append(", and hello again"); DQN_ASSERT(str.str[str.len] == 0); DQN_ASSERT(str.len <= str.max); + DQN_ASSERTM(DqnStr_Cmp("hello world2, hello again, and hello again", str.str) == 0, "str: %s", str.str); str.Free(); Log(Status::Ok, "Check expand on append"); } + + { + DqnString str = DQN_SLICE("hello world"); + DQN_DEFER(str.Free()); + DQN_ASSERT(DqnStr_Cmp(str.str, "hello world") == 0); + + Log(Status::Ok, "Copy constructor DqnSlice"); + } + + { + DqnString zero = {}; + DqnString str = DQN_SLICE("hello world"); + str.Free(); + str = zero; + + DqnSlice helloSlice = DQN_SLICE("hello"); + str = helloSlice; + DQN_DEFER(str.Free()); + DQN_ASSERT(DqnStr_Cmp(str.str, "hello") == 0); + + Log(Status::Ok, "Copy constructor (DqnFixedString<>)"); + } + + { + DqnString str = DQN_SLICE("hello world"); + DQN_DEFER(str.Free()); + DQN_ASSERT(str.Sprintf("hello %s", "sailor")); + DQN_ASSERTM(DqnStr_Cmp(str.str, "hello sailor") == 0, "Result: %s", str.str); + + Log(Status::Ok, "Sprintf"); + } + + { + { + DqnString str = DQN_SLICE("hello world"); + DQN_DEFER(str.Free()); + DQN_ASSERT(str.Sprintf("hello %s", "sailor")); + str += DQN_SLICE(".end"); + DQN_ASSERTM(DqnStr_Cmp(str.str, "hello sailor.end") == 0, "Result: %s", str.str); + } + + { + DqnString str = DQN_SLICE("hello world"); + DQN_DEFER(str.Free()); + DQN_ASSERT(str.Sprintf("hello %s", "sailor")); + DQN_ASSERT(str.SprintfAppend(" %d, %d", 100, 200)); + DQN_ASSERT(DqnStr_Cmp(str.str, "hello sailor 100, 200") == 0); + } + + Log(Status::Ok, "Concatenation, operator +=, SprintfAppend"); + } + + { + DqnString str; + str = "hello big world"; + DQN_ASSERT(DqnStr_Cmp(str.str, "hello big world") == 0); + str.Free(); + + str = DqnString("goodbye", DQN_CHAR_COUNT("goodbye")); + DQN_ASSERT(DqnStr_Cmp(str.str, "goodbye") == 0); + Log(Status::Ok, "Copy constructor (char const *str, int len)"); + } + + { + DqnString str = DQN_SLICE("hello world"); + DQN_DEFER(str.Free()); + DQN_ASSERT(str.Sprintf("hello %s", "sailor")); + str = str + " end" + DQN_SLICE(" of"); + DQN_ASSERT(DqnStr_Cmp(str.str, "hello sailor end of") == 0); + + Log(Status::Ok, "Operator +"); + } + + { + DqnString str = "localhost"; + DQN_DEFER(str.Free()); + str.SprintfAppend(":%d", 16832); + str += "/json_rpc"; + DQN_ASSERT(str.len == 24 && DqnStr_Cmp("localhost:16832/json_rpc", str.str) == 0); + + Log(Status::Ok, "Copy constructor, sprintf, operator +="); + } } void DqnRnd_Test() @@ -2023,85 +2089,6 @@ void DqnQuickSort_Test() } } -void DqnHashTable_Test() -{ - LOG_HEADER(); - DqnHashTable hashTable = {}; - hashTable.Init(1); - - { - hashTable.AddNewEntriesToFreeList(+2); - DQN_ASSERT(hashTable.freeList && hashTable.freeList->next); - DQN_ASSERT(hashTable.numFreeEntries == 2); - - hashTable.AddNewEntriesToFreeList(-1); - DQN_ASSERT(hashTable.freeList && !hashTable.freeList->next); - DQN_ASSERT(hashTable.numFreeEntries == 1); - } - - { - DQN_ASSERT(hashTable.Get("hello world") == nullptr); - DQN_ASSERT(hashTable.Get("collide key") == nullptr); - DQN_ASSERT(hashTable.Get("crash again") == nullptr); - - bool entryAlreadyExisted = true; - auto helloEntry = hashTable.Make("hello world", -1, &entryAlreadyExisted); - DQN_ASSERT(entryAlreadyExisted == false); - - entryAlreadyExisted = true; - auto collideEntry = hashTable.Make("collide key", -1, &entryAlreadyExisted); - DQN_ASSERT(entryAlreadyExisted == false); - - entryAlreadyExisted = true; - auto crashEntry = hashTable.Make("crash again", -1, &entryAlreadyExisted); - DQN_ASSERT(entryAlreadyExisted == false); - - helloEntry->data = 5; - collideEntry->data = 10; - crashEntry->data = 15; - - DQN_ASSERT(hashTable.numFreeEntries == 0); - - DqnHashTable::Entry *entry = *hashTable.entries; - DQN_ASSERT(entry->data == 15); - - entry = entry->next; - DQN_ASSERT(entry->data == 10); - - entry = entry->next; - DQN_ASSERT(entry->data == 5); - - DQN_ASSERT(hashTable.usedEntriesIndex == 1); - DQN_ASSERT(hashTable.usedEntries[0] == 0); - DQN_ASSERT(hashTable.numFreeEntries == 0); - } - - hashTable.Remove("hello world"); - DQN_ASSERT(hashTable.ChangeNumEntries(512)); - - { - auto helloEntry = hashTable.Get("hello world"); - DQN_ASSERT(helloEntry == nullptr); - - auto collideEntry = hashTable.Get("collide key"); - DQN_ASSERT(collideEntry->data == 10); - - auto crashEntry = hashTable.Get("crash again"); - DQN_ASSERT(crashEntry->data == 15); - - bool entryAlreadyExisted = false; - collideEntry = hashTable.Make("collide key", -1, &entryAlreadyExisted); - DQN_ASSERT(entryAlreadyExisted == true); - - entryAlreadyExisted = false; - crashEntry = hashTable.Make("crash again", -1, &entryAlreadyExisted); - DQN_ASSERT(entryAlreadyExisted == true); - } - - hashTable.Free(); - Log(Status::Ok, "HashTable"); -} - void DqnBSearch_Test() { LOG_HEADER(); @@ -2407,7 +2394,6 @@ int main(void) DqnRect_Test(); DqnArray_Test(); DqnQuickSort_Test(); - DqnHashTable_Test(); DqnBSearch_Test(); DqnMemSet_Test(); DqnFixedString_Test(); diff --git a/dqn.h b/dqn.h index fae03d0..c235f56 100644 --- a/dqn.h +++ b/dqn.h @@ -1837,106 +1837,30 @@ struct DqnRect // #DqnString Public API - String library // ================================================================================================= -// String allocates +1 extra byte for the null-terminator to be completely compatible with -// C style strings, but this is not reflected in the capacity or len, and is hidden from the user. - -// Usage: DqnString example = DQN_STRING_LITERAL(example, "hello world"); -#define DQN_STRING_LITERAL(srcVariable, literal) \ - DQN_STRING_LITERAL_INTERNAL(srcVariable, literal, DQN_UNIQUE_NAME(dqnstring_)) - struct DqnString -{ - DqnMemAPI *memAPI; - i32 len; // Len of the string in bytes not including null-terminator - i32 max; // The maximum capacity not including space for null-terminator. - char *str; - - // Initialisation API - // ============================================================================================= - void Init (DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR); - void Init (DqnMemStack *const stack); - - // return: False if (size < 0) or (memAPI allocation failed). - bool InitSize (i32 size, DqnMemStack *const stack); - bool InitSize (i32 size, DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR); - - // return: False if arguments are invalid. - bool InitFixedMem (char *const memory, const i32 sizeInBytes); - - bool InitLiteral (char const *const cstr, i32 const lenInBytes, DqnMemStack *const stack); - bool InitLiteral (char const *const cstr, DqnMemStack *const stack); - bool InitLiteral (char const *const cstr, i32 const lenInBytes, DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR); - bool InitLiteral (char const *const cstr, DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR); - - bool InitLiteral (wchar_t const *const cstr, DqnMemStack *const stack); - bool InitLiteral (wchar_t const *const cstr, DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR); - - // return: False if cstr is nullptr. - bool InitLiteralNoAlloc(char *const cstr, i32 cstrLen = -1); - - // API - // ============================================================================================= - // return: These functions return false if allocation failed. String is preserved. - bool Expand (i32 newMax); - bool Sprintf (char const *const fmt, ...); - bool VSprintf(char const *const fmt, va_list argList); - bool Append (DqnString const string); - bool Append (DqnString const *string); - bool Append (char const *const cstr, i32 bytesToCopy = -1); - - void Clear (); - void Free (); - - // return: -1 if invalid, or if bufSize is 0 the required buffer length in wchar_t characters - i32 ToWChar(wchar_t *const buf, i32 const bufSize) const; - - // return: String allocated using api. - wchar_t *ToWChar(DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR) const; - - // Statics - // ============================================================================================= - static bool Cmp(DqnString const *a, DqnString const *b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) - { - bool result = (a->len == b->len) && (DqnStr_Cmp(a->str, b->str, a->len, ignore) == 0); - return result; - } - - static bool Cmp(DqnString const *a, DqnSlice const b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) - { - bool result = (a->len == b.len) && (DqnStr_Cmp(a->str, b.data, b.len, ignore) == 0); - return result; - } - - static bool Cmp(DqnString const *a, DqnSlice const b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) - { - bool result = (a->len == b.len) && (DqnStr_Cmp(a->str, b.data, b.len, ignore) == 0); - return result; - } -}; - -struct DqnString_ { DqnMemAPI *memAPI = DQN_DEFAULT_HEAP_ALLOCATOR; - int len; - int max; - char *str; + int len = 0; + int max = 0; + char *str = nullptr; - DqnString_() = default; - DqnString_(char const *str_) { Append(str_); } - DqnString_(char const *str_, int len_) { Append(str_, len_); } - DqnString_(DqnSlice const &other) { Append(other.data, other.len); } - DqnString_(DqnSlice const &other) { Append(other.data, other.len); } - DqnString_(DqnString_ const &other) { if (this == &other) return; Append(other.str, other.len); } + DqnString() = default; + DqnString(char *buf, int max_) : memAPI(nullptr), len(0), str(buf) { max = max_; NullTerminate(); } + DqnString(char const *str_) { Append(str_); } + DqnString(char const *str_, int len_) { Append(str_, len_); } + DqnString(DqnSlice const &other) { Append(other.data, other.len); } + DqnString(DqnSlice const &other) { Append(other.data, other.len); } + DqnString(DqnString const &other) { if (this == &other) return; *this = other; } // TODO(doyle): I can't decide on copy semantics - DqnString_ &operator+=(char const *other) { Append(other); return *this; } - DqnString_ &operator+=(DqnSlice const &other) { Append(other.data, other.len); return *this; } - DqnString_ &operator+=(DqnSlice const &other) { Append(other.data, other.len); return *this; } - DqnString_ &operator+=(DqnString const &other) { Append(other.str, other.len); return *this; } + DqnString &operator+=(char const *other) { Append(other); return *this; } + DqnString &operator+=(DqnSlice const &other) { Append(other.data, other.len); return *this; } + DqnString &operator+=(DqnSlice const &other) { Append(other.data, other.len); return *this; } + DqnString &operator+=(DqnString const &other) { Append(other.str, other.len); return *this; } - DqnString_ operator+ (char const *other) { auto result = *this; result.Append(other); return result; } - DqnString_ operator+ (DqnSlice const &other) { auto result = *this; result.Append(other.data, other.len); return result; } - DqnString_ operator+ (DqnSlice const &other) { auto result = *this; result.Append(other.data, other.len); return result; } - DqnString_ operator+ (DqnString const &other) { auto result = *this; result.Append(other.str, other.len); return result; } + DqnString operator+ (char const *other) { auto result = *this; result.Append(other); return result; } + DqnString operator+ (DqnSlice const &other) { auto result = *this; result.Append(other.data, other.len); return result; } + DqnString operator+ (DqnSlice const &other) { auto result = *this; result.Append(other.data, other.len); return result; } + DqnString operator+ (DqnString const &other) { auto result = *this; result.Append(other.str, other.len); return result; } // Xprintf functions always modifies buffer and null-terminates even with insufficient buffer size. // return: The number of characters copied to the buffer @@ -1947,18 +1871,18 @@ struct DqnString_ int VSprintfAppend (char const *fmt, va_list va) { return VSprintfAtOffset(fmt, va, len/*offset*/); } void NullTerminate () { str[len] = 0; } // NOTE: If you modify the storage directly, this can be handy. - void Clear (Dqn::ZeroClear clear = Dqn::ZeroClear::No) { if (clear == Dqn::ZeroClear::Yes) DqnMem_Set(str, 0, max); len = max = 0; str[0] = 0; } + void Clear (Dqn::ZeroClear clear = Dqn::ZeroClear::No) { if (clear == Dqn::ZeroClear::Yes) DqnMem_Set(str, 0, max); len = max = 0; NullTerminate(); } void Free () { if (str) memAPI->Free(str); str = nullptr; } void Resize (int newMax) { if (newMax > max) Reserve(newMax); len = DQN_MIN(newMax, len); NullTerminate(); } void Reserve (int newMax); void Append (char const *src, int len_ = -1); - int VSprintfAtOffset(char const *fmt, va_list va, int offset) { Reserve(len + VAListLen(fmt, va)); int result = Dqn_vsnprintf(str + offset, max - len, fmt, va); len = (offset + result); return result; } + int VSprintfAtOffset(char const *fmt, va_list va, int offset) { Reserve(len + VAListLen(fmt, va) + 1); int result = Dqn_vsnprintf(str + offset, max - len, fmt, va); len = (offset + result); return result; } static int VAListLen (char const *fmt, va_list va); - static bool Cmp (DqnString_ const *a, DqnString_ const *b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) { return (a->len == b->len) && (DqnStr_Cmp(a->str, b->str, a->len, ignore) == 0); } - static bool Cmp (DqnString_ const *a, DqnSlice const b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) { return (a->len == b.len) && (DqnStr_Cmp(a->str, b.data, b.len, ignore) == 0); } - static bool Cmp (DqnString_ const *a, DqnSlice const b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) { return (a->len == b.len) && (DqnStr_Cmp(a->str, b.data, b.len, ignore) == 0); } + static bool Cmp (DqnString const *a, DqnString const *b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) { return (a->len == b->len) && (DqnStr_Cmp(a->str, b->str, a->len, ignore) == 0); } + static bool Cmp (DqnString const *a, DqnSlice const b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) { return (a->len == b.len) && (DqnStr_Cmp(a->str, b.data, b.len, ignore) == 0); } + static bool Cmp (DqnString const *a, DqnSlice const b, Dqn::IgnoreCase ignore = Dqn::IgnoreCase::No) { return (a->len == b.len) && (DqnStr_Cmp(a->str, b.data, b.len, ignore) == 0); } // return: -1 if invalid, or if bufSize is 0 the required buffer length in wchar_t characters i32 ToWChar(wchar_t *const buf, i32 const bufSize) const; @@ -2017,499 +1941,6 @@ using DqnFixedString256 = DqnFixedString<256>; using DqnFixedString512 = DqnFixedString<512>; using DqnFixedString1024 = DqnFixedString<1024>; -// TODO(doyle): Load factor -// #DqnHashTable API -// ================================================================================================= -template -struct DqnHashTable -{ - struct Entry - { - DqnString key; - T data; - Entry *next; - }; - - DqnMemAPI *memAPI; - - Entry **entries; - i64 numEntries; - Entry *freeList; // Entries which are allocated and can be reused are stored here. - i64 numFreeEntries; - i64 *usedEntries; // Tracks the indexes used in the entries. - i64 usedEntriesIndex; - - // Initialisation API - // ============================================================================================= - bool Init(i64 const numTableEntries = 1024, DqnMemAPI *const api = DQN_DEFAULT_HEAP_ALLOCATOR); - bool Init(i64 const numTableEntries, DqnMemStack *const stack); - - // API - // ============================================================================================= - // keyLen: String length not incl. null-terminator. If -1, utf8 strlen will be used. - // return: Pre-existing entry if it exists, otherwise a nullptr. - Entry *Get (DqnString const key) const; - Entry *Get (char const *const key, i32 keyLen = -1) const; - - // exists: Pass a bool that gets set true if a new entry was created, false if it already exists. - // return: Pre-existing entry if it exists. Else create entry with key. Nullptr if out of memory. - Entry *Make (DqnString const key, bool *exists = nullptr); - Entry *Make (char const *const key, i32 keyLen = -1, bool *exists = nullptr); - - void Remove (DqnString const key); - void Remove (char const *const key, i32 keyLen = -1); - void Clear (); - void Free (); - - // num: If num > 0, allocate num entries to free list. If num < 0, remove num entries from free list. - // return: False if allocation failed. - bool AddNewEntriesToFreeList(i64 num); - - // newNumEntries: If different from numEntries, reallocate the table and rehash all entries in the table. - // return: False if allocation failed. - bool ChangeNumEntries (i64 newNumEntries); -}; - -template -bool DqnHashTable::Init(i64 const numTableEntries, DqnMemAPI *const api) -{ - usize arrayOfPtrsSize = sizeof(*this->entries) * numTableEntries; - auto *arrayOfPtrs = (u8 *)api->Alloc((isize)arrayOfPtrsSize); - if (!arrayOfPtrs) return false; - - usize usedEntriesSize = sizeof(*this->usedEntries) * numTableEntries; - auto *usedEntriesPtr = (u8 *)api->Alloc(usedEntriesSize); - if (!usedEntriesPtr) - { - api->Free((void *)arrayOfPtrs, arrayOfPtrsSize); - return false; - } - - this->memAPI = api; - this->entries = (Entry **)arrayOfPtrs; - this->numEntries = numTableEntries; - this->freeList = nullptr; - this->numFreeEntries = 0; - this->usedEntries = (i64 *)usedEntriesPtr; - this->usedEntriesIndex = 0; - - return true; -} - -template -bool DqnHashTable::Init(i64 const numTableEntries, DqnMemStack *const stack) -{ - if (!stack) return false; - bool result = Init(numTableEntries, &stack->myHeadAPI); - return result; -} - -template -typename DqnHashTable::Entry *DqnHashTableInternal_AllocateEntry(DqnHashTable *table) -{ - auto *result = (typename DqnHashTable::Entry *)table->memAPI->Alloc(sizeof(typename DqnHashTable::Entry)); - - if (result) - { - if (result->key.InitSize(0, table->memAPI)) - { - return result; - } - - DQN_ASSERTM(DQN_INVALID_CODE_PATH, "Out of memory error"); - table->memAPI->Free(result, sizeof(*result)); - return nullptr; - } - else - { - return nullptr; - } -} - -template -FILE_SCOPE typename DqnHashTable::Entry * -DqnHashTableInternal_GetFreeEntry(DqnHashTable *table) -{ - typename DqnHashTable::Entry *result = {}; - if (table->freeList) - { - result = table->freeList; - table->freeList = table->freeList->next; - table->numFreeEntries--; - } - else - { - DQN_ASSERT(table->numFreeEntries == 0); - result = DqnHashTableInternal_AllocateEntry(table); - } - - return result; -} - -template -FILE_SCOPE inline i64 DqnHashTableInternal_GetHashIndex(DqnHashTable const *table, - char const *const key, i32 keyLen) -{ - u64 hash = DqnHash_Murmur64(key, keyLen); - i64 hashIndex = hash % table->numEntries; - return hashIndex; -} - -FILE_SCOPE inline i64 DqnHashTableInternal_GetHashIndex(i64 numEntries, char const *const key, - i32 keyLen) -{ - u64 hash = DqnHash_Murmur64(key, keyLen); - i64 hashIndex = hash % numEntries; - return hashIndex; -} - -template -typename DqnHashTable::Entry * -DqnHashTableInternal_FindMatchingKey(typename DqnHashTable::Entry *entry, char const *const key, - i32 keyLen, - typename DqnHashTable::Entry **prevEntry = nullptr) -{ - for (;;) - { - if (entry->key.len == keyLen && DqnStr_Cmp(entry->key.str, key) == 0) - { - return entry; - } - - if (entry->next == nullptr) break; - if (prevEntry) *prevEntry = entry; - - entry = entry->next; - } - - return nullptr; -} - -template -DQN_FILE_SCOPE inline typename DqnHashTable::Entry * -DqnHashTableInternal_Get(DqnHashTable const *table, char const *const key, i32 keyLen, i64 hashIndex) -{ - typename DqnHashTable::Entry *entry = table->entries[hashIndex]; - if (entry) - { - typename DqnHashTable::Entry *matchingEntry - = DqnHashTableInternal_FindMatchingKey(entry, key, (i64)keyLen); - if (matchingEntry) - return matchingEntry; - } - - return nullptr; -} - -template -typename DqnHashTable::Entry *DqnHashTable::Get(char const *const key, i32 keyLen) const -{ - if (keyLen == -1) DqnStr_LenUTF8((u32 *)key, &keyLen); - i64 hashIndex = DqnHashTableInternal_GetHashIndex(this, key, keyLen); - Entry *result = DqnHashTableInternal_Get(this, key, keyLen, hashIndex); - return result; -} - -template -typename DqnHashTable::Entry *DqnHashTable::Get(DqnString const key) const -{ - Entry *result = this->Get(key.str, key.len); - return result; -} - -template -typename DqnHashTable::Entry *DqnHashTable::Make(char const *const key, i32 keyLen, - bool *exists) -{ - // NOTE: Internal_Get() function because we want a way to allow re-using the hashIndex - if (keyLen == -1) DqnStr_LenUTF8((u32 *)key, &keyLen); - - i64 hashIndex = DqnHashTableInternal_GetHashIndex(this, key, keyLen); - Entry *existingEntry = DqnHashTableInternal_Get(this, key, keyLen, hashIndex); - - if (exists) *exists = true; - if (existingEntry) return existingEntry; - - Entry *newEntry = DqnHashTableInternal_GetFreeEntry(this); - if (newEntry) - { - if (exists) *exists = false; - - // If entry for hashIndex not used yet, mark it down as a used slot. - if (!this->entries[hashIndex]) - { - i64 index = DqnBSearch(this->usedEntries, this->usedEntriesIndex, hashIndex, - DqnBSearchType::MinusOne); - i64 indexToEndAt = index; - if (index == -1) indexToEndAt = 0; - - this->usedEntriesIndex++; - for (i64 i = this->usedEntriesIndex; i > indexToEndAt; i--) - this->usedEntries[i] = this->usedEntries[i - 1]; - - this->usedEntries[indexToEndAt] = hashIndex; - } - - // TODO(doyle): Is this a robust check? If we retrieve from the freeEntryList, the memAPI - // may already be initialised. - if (!newEntry->key.memAPI) - { - newEntry->key.InitSize(keyLen, this->memAPI); - DQN_ASSERT(newEntry->key.memAPI == this->memAPI); - } - - newEntry->key.Append(key, keyLen); - newEntry->next = this->entries[hashIndex]; - this->entries[hashIndex] = newEntry; - - return newEntry; - } - else - { - DQN_ASSERTM(DQN_INVALID_CODE_PATH, "DqnHashTable_Get() failed: Out of memory."); - return nullptr; - } -} - -template -typename DqnHashTable::Entry *DqnHashTable::Make(DqnString const key, bool *exists) -{ - Entry *result = this->Make(key.str, key.len, exists); - return result; -} - -template -void DqnHashTable::Remove(char const *const key, i32 keyLen) -{ - if (keyLen == -1) DqnStr_LenUTF8((u32 *)key, &keyLen); - - i64 hashIndex = DqnHashTableInternal_GetHashIndex(this, key, keyLen); - Entry *entry = this->entries[hashIndex]; - - if (entry) - { - Entry prevEntry_; - - Entry *prevEntry = &prevEntry_; - Entry *entryToFree = - DqnHashTableInternal_FindMatchingKey(entry, key, (i64)keyLen, &prevEntry); - if (entryToFree) - { - if (entryToFree == entry) - { - // Unique entry, so remove this index from the used list as well. - i64 indexToRemove = DqnBSearch(this->usedEntries, this->usedEntriesIndex, - hashIndex, DqnBSearchType::MinusOne); - - for (i64 i = indexToRemove; i < this->usedEntriesIndex - 1; i++) - this->usedEntries[i] = this->usedEntries[i + 1]; - - this->usedEntriesIndex--; - } - - if (prevEntry) - { - prevEntry->next = entryToFree->next; - } - - entryToFree->key.Clear(); - entryToFree->data = {}; - entryToFree->next = this->freeList; - - this->freeList = entryToFree; - this->numFreeEntries++; - } - } -} - -template -void DqnHashTable::Remove(DqnString key) -{ - Entry result = this->Remove(key.str, key.len); -} - -template -void DqnHashTable::Clear() -{ - for (auto usedIndex = 0; usedIndex < this->usedEntriesIndex; usedIndex++) - { - auto entryIndex = this->usedEntries[usedIndex]; - Entry *baseEntry = this->entries[entryIndex]; - - for (Entry *entry = baseEntry; entry;) - { - Entry *entryToFree = entry; - entry = entryToFree->next; - - entryToFree->key.Clear(); - entryToFree->data = {}; - entryToFree->next = this->freeList; - this->freeList = entryToFree; - this->numFreeEntries++; - } - - this->entries[entryIndex] = nullptr; - } - - this->usedEntriesIndex = 0; -} - -template -void DqnHashTable::Free() -{ - usize const ENTRY_SIZE = sizeof(*this->entries); - for (i64 i = 0; i < usedEntriesIndex; i++) - { - i64 indexToFree = usedEntries[i]; - Entry *entryToFree = *(this->entries + indexToFree); - - entryToFree->key.Free(); - this->memAPI->Free(entryToFree, ENTRY_SIZE); - } - - // Free usedEntries list - { - usize sizeToFree = sizeof(*this->usedEntries) * this->numEntries; - this->memAPI->Free(this->usedEntries, sizeToFree); - } - - // Free freeList - { - Entry *entry = this->freeList; - while (entry) - { - Entry *entryToFree = entry; - entry = entry->next; - - entryToFree->key.Free(); - this->memAPI->Free(entryToFree, ENTRY_SIZE); - } - } - - // Free the array of ptrs - { - usize sizeToFree = ENTRY_SIZE * this->numEntries; - this->memAPI->Free(this->entries, sizeToFree); - } - -} - -template -bool DqnHashTable::AddNewEntriesToFreeList(i64 num) -{ - if (num < 0) - { - num = DQN_ABS(num); - for (i64 i = 0; i < num; i++) - { - Entry *entryToFree = this->freeList; - if (entryToFree) - { - this->freeList = entryToFree->next; - this->memAPI->Free(entryToFree, sizeof(*this->freeList)); - } - } - - this->numFreeEntries -= num; - } - else - { - for (i64 i = 0; i < num; i++) - { - Entry *newEntry = DqnHashTableInternal_AllocateEntry(this); - if (!newEntry) return false; - - newEntry->next = this->freeList; - this->freeList = newEntry; - } - - this->numFreeEntries += num; - } - - DQN_ASSERT(this->numFreeEntries >= 0); - return true; -} - -template -bool DqnHashTable::ChangeNumEntries(i64 newNumEntries) -{ - if (newNumEntries == this->numEntries) return true; - - Entry **newEntries = {}; - usize newEntriesSize = sizeof(*this->entries) * newNumEntries; - - i64 *newUsedEntries = {}; - usize newUsedEntriesSize = sizeof(*this->usedEntries) * newNumEntries; - i64 newUsedEntriesIndex = 0; - - // NOTE: If you change allocation order, be sure to change the free order. - // Allocate newEntries - { - auto *newEntriesPtr = (u8 *)this->memAPI->Alloc(newEntriesSize); - if (!newEntriesPtr) return false; - - newEntries = (Entry **)newEntriesPtr; - } - - // Allocate usedEntries - { - auto *usedEntriesPtr = (u8 *)this->memAPI->Alloc(newUsedEntriesSize); - if (!usedEntriesPtr) - { - this->memAPI->Free(newEntries, newEntriesSize); - return false; - } - - newUsedEntries = (i64 *)usedEntriesPtr; - } - - for (i64 i = 0; i < this->usedEntriesIndex; i++) - { - i64 usedIndex = this->usedEntries[i]; - Entry *oldEntry = this->entries[usedIndex]; - - while (oldEntry) - { - i64 newHashIndex = DqnHashTableInternal_GetHashIndex(newNumEntries, oldEntry->key.str, - oldEntry->key.len); - Entry *entryToAppendTo = newEntries[newHashIndex]; - - if (entryToAppendTo) - { - while (entryToAppendTo->next) - entryToAppendTo = entryToAppendTo->next; - - entryToAppendTo->next = oldEntry; - } - else - { - newEntries[newHashIndex] = oldEntry; - newUsedEntries[newUsedEntriesIndex++] = newHashIndex; - } - - oldEntry = oldEntry->next; - } - } - - // Free the old entry list - { - usize freeSize = sizeof(*this->entries) * this->numEntries; - this->memAPI->Free(this->entries, freeSize); - } - - // Free the old used entry list - { - usize freeSize = sizeof(*this->usedEntries) * this->numEntries; - this->memAPI->Free(this->usedEntries, freeSize); - } - - this->entries = newEntries; - this->numEntries = newNumEntries; - this->usedEntries = newUsedEntries; - this->usedEntriesIndex = newUsedEntriesIndex; - return true; -} - struct DqnJson { enum struct Type @@ -6170,391 +5601,7 @@ DQN_FILE_SCOPE inline u32 Dqn_BitToggle(u32 bits, u32 flag) // #DqnString Impleemntation // ================================================================================================= -// TODO(doyle): SSO requires handling assign/copy op when copying strings, we need to reassign the -// str to point to the new copy's SSO buffer which sort of breaks the way I want to use -// strings. So disabled for now. -// #define DQN_STRING_ENABLE_SSO - -void DqnString::Init(DqnMemAPI *api) -{ - DQN_ASSERT(api); - - this->memAPI = api; -#if defined(DQN_STRING_ENABLE_SSO) - this->sso[0] = 0; -#endif - this->str = nullptr; - this->len = 0; - this->max = 0; -} - -void DqnString::Init(DqnMemStack *stack) -{ - this->Init(&stack->myHeadAPI); -} - -bool DqnString::InitSize(i32 size, DqnMemStack *const stack) -{ - bool result = this->InitSize(size, &stack->myHeadAPI); - return result; -} - -bool DqnString::InitSize(i32 size, DqnMemAPI *const api) -{ - // NOTE: CHAR_COUNT is (ARRAY_COUNT - 1) to leave the last spot as the implicit null-terminator. - DQN_ASSERT(size >= 0); -#if defined(DQN_STRING_ENABLE_SSO) - if (size < DQN_CHAR_COUNT(this->sso)) - { - this->str = &(this->sso[0]); - } - else -#endif - if (size == 0) - { - this->str = nullptr; - } - else - { - usize allocSize = sizeof(*(this->str)) * (size + 1); - this->str = (char *)api->Alloc(allocSize, Dqn::ZeroClear::No); - if (!this->str) return false; - - this->str[0] = 0; - } - - this->max = size; - this->len = 0; - this->memAPI = api; - return true; -} - -bool DqnString::InitFixedMem(char *const memory, i32 sizeInBytes) -{ - if (!memory || sizeInBytes == 0) return false; - - this->str = (char *)memory; - this->len = 0; - this->max = sizeInBytes - 1; - this->memAPI = {}; - - return true; -} - -bool DqnString::InitLiteral(char const *cstr, DqnMemStack *stack) -{ - bool result = this->InitLiteral(cstr, &stack->myHeadAPI); - return result; -} - -bool DqnString::InitLiteral(char const *cstr, i32 lenInBytes, DqnMemStack *stack) -{ - bool result = this->InitLiteral(cstr, lenInBytes, &stack->myHeadAPI); - return result; -} - -bool DqnString::InitLiteral(char const *cstr, i32 lenInBytes, DqnMemAPI *api) -{ - if (!this->InitSize(lenInBytes, api)) - { - return false; - } - - if (lenInBytes > 0) - { - this->str[lenInBytes] = 0; - } - - this->len = lenInBytes; - this->max = this->len; - DqnMem_Copy(this->str, cstr, lenInBytes); - - return true; -} - -bool DqnString::InitLiteral(char const *cstr, DqnMemAPI *api) -{ - i32 utf8LenInBytes = 0; - DqnStr_LenUTF8((u32 *)cstr, &utf8LenInBytes); - - bool result = this->InitLiteral(cstr, utf8LenInBytes, api); - - return result; -} - -bool DqnString::InitLiteral(wchar_t const *cstr, DqnMemStack *stack) -{ - bool result = this->InitLiteral(cstr, &stack->myHeadAPI); - return result; -} - -bool DqnString::InitLiteral(wchar_t const *cstr, DqnMemAPI *api) -{ -#if defined(DQN_IS_WIN32) && defined(DQN_PLATFORM_IMPLEMENTATION) - i32 reqLenInclNullTerminator = DqnWin32_WCharToUTF8(cstr, nullptr, 0); - if (!this->InitSize(reqLenInclNullTerminator - 1, api)) - { - return false; - } - - this->len = reqLenInclNullTerminator - 1; - this->max = this->len; - i32 convertResult = DqnWin32_WCharToUTF8(cstr, this->str, this->len + 1); - DQN_ASSERT(convertResult != -1); - this->str[this->len] = 0; - - return true; - -#else - (void)cstr; (void)api; - DQN_ASSERT(DQN_INVALID_CODE_PATH); - return false; - -#endif -} - -bool DqnString::InitLiteralNoAlloc(char *cstr, i32 cstrLen) -{ - if (!cstr) return false; - - this->str = cstr; - if (cstrLen == -1) - { - i32 utf8LenInBytes = 0; - DqnStr_LenUTF8((u32 *)cstr, &utf8LenInBytes); - this->len = utf8LenInBytes; - } - else - { - this->len = cstrLen; - } - - this->max = this->len; - return true; -} - -bool DqnString::Expand(i32 newMax) -{ - if (newMax < this->max) - { - return true; - } - - if (!this->memAPI || !this->memAPI->IsValid()) - { - return false; - } - -#if defined(DQN_STRING_ENABLE_SSO) - if (newMax < DQN_CHAR_COUNT(this->sso)) - { - DQN_ASSERT(this->memAPI->IsValid()); - DQN_ASSERT(this->sso == this->str || this->str == nullptr); - this->str = this->sso; - this->max = newMax; - return true; - } - - bool usingSSO = (this->str == this->sso); - if (usingSSO) - { - DQN_ASSERT(newMax >= DQN_CHAR_COUNT(this->sso)); - this->str = nullptr; - } -#endif - - usize allocSize = sizeof(*(this->str)) * (newMax + 1); - char *result = nullptr; - - if (this->str) result = (char *)this->memAPI->Realloc(this->str, (this->max + 1), allocSize); - else result = (char *)this->memAPI->Alloc(allocSize, Dqn::ZeroClear::No); - - if (result) - { -#if defined(DQN_STRING_ENABLE_SSO) - if (usingSSO) - DqnMem_Copy(result, this->sso, this->max); -#endif - - this->str = (char *)result; - this->max = newMax; - return true; - } -#if defined(DQN_STRING_ENABLE_SSO) - else - { - // Restore the pointer to the SSO to return to the original state from before this call. - if (usingSSO) - this->str = this->sso; - } -#endif - - return false; -} - -DQN_FILE_SCOPE bool DqnStringInternal_Append(DqnString *str, char const *cstr, - i32 bytesToCopy) -{ - if (bytesToCopy <= 0) - { - return true; - } - - // Check and reserve space if needed - i32 totalLen = str->len + bytesToCopy; - if (totalLen > str->max) - { - bool result = str->Expand(totalLen); - if (!result) return false; - } - - char *dest = str->str + str->len; - DqnMem_Copy(dest, cstr, bytesToCopy); - - str->len = totalLen; - str->str[totalLen] = 0; - return true; -} - -bool DqnString::Sprintf(char const *fmt, ...) -{ - va_list argList; - va_start(argList, fmt); - bool result = this->VSprintf(fmt, argList); - va_end(argList); - - return result; -} - -bool DqnString::VSprintf(char const *fmt, va_list argList) -{ - LOCAL_PERSIST char tmp[STB_SPRINTF_MIN]; - - auto PrintCallback = [](char *buf, void *user, int len) -> char * - { - (void)len; (void)user; - return buf; - }; - - i32 const reqLen = Dqn_vsprintfcb(PrintCallback, nullptr, tmp, fmt, argList); - auto const remainingSpace = this->max - this->len; - if (reqLen > remainingSpace) - { - i32 const newMax = this->max + reqLen; - if (!this->Expand(newMax)) - { - return false; - } - } - - char *bufStart = this->str + this->len; - i32 numCopied = Dqn_vsprintf(bufStart, fmt, argList); - this->len += numCopied; - DQN_ASSERT(this->len <= this->max); - - return true; -} - -bool DqnString::Append(char const *cstr, i32 bytesToCopy) -{ - i32 cstrLen = 0; - if (bytesToCopy == -1) - { - i32 utf8LenInBytes = 0; - DqnStr_LenUTF8((u32 *)cstr, &utf8LenInBytes); - cstrLen = utf8LenInBytes; - } - else - { - cstrLen = bytesToCopy; - } - - bool result = DqnStringInternal_Append(this, cstr, cstrLen); - return result; -} - -bool DqnString::Append(DqnString const string) -{ - bool result = DqnStringInternal_Append(this, string.str, string.len); - return result; -} - -bool DqnString::Append(DqnString const *string) -{ - bool result = DqnStringInternal_Append(this, string->str, string->len); - return result; -} - -void DqnString::Clear() -{ - this->len = 0; - if (this->str) - { - this->str[0] = 0; - } -} - -void DqnString::Free() -{ - if (this->str) - { -#if defined(DQN_STRING_ENABLE_SSO) - if (this->str == this->sso) - { - this->sso[0] = '\0'; - this->str = nullptr; - } - else -#endif - if (this->memAPI && this->memAPI->IsValid()) - { - this->memAPI->Free(this->str, (this->max + 1)); - this->str = nullptr; - } - - this->len = 0; - this->max = 0; - } -} - -i32 DqnString::ToWChar(wchar_t *buf, i32 bufSize) const -{ -#if defined(DQN_IS_WIN32) && defined(DQN_PLATFORM_IMPLEMENTATION) - i32 result = DqnWin32_UTF8ToWChar(this->str, buf, bufSize); - return result; - -#else - (void)buf; (void)bufSize; - DQN_ASSERT(DQN_INVALID_CODE_PATH); - return -1; -#endif -} - -wchar_t *DqnString::ToWChar(DqnMemAPI *api) const -{ - // TODO(doyle): Should the "in" string allow specifyign len? probably - // Otherwise a c-string and a literal initiated string might have different lengths - // to wchar will produce an unintuitive output -#if defined(DQN_IS_WIN32) && defined(DQN_PLATFORM_IMPLEMENTATION) - i32 requiredLenInclNull = DqnWin32_UTF8ToWChar(this->str, nullptr, 0); - - i32 allocSize = sizeof(wchar_t) * requiredLenInclNull; - auto *result = (wchar_t *)api->Alloc(allocSize); - if (!result) return nullptr; - - DqnWin32_UTF8ToWChar(this->str, result, requiredLenInclNull); - result[requiredLenInclNull - 1] = 0; - return result; - -#else - (void)api; - DQN_ASSERT(DQN_INVALID_CODE_PATH); - return nullptr; - -#endif -} - -int DqnString_::VAListLen(char const *fmt, va_list va) +int DqnString::VAListLen(char const *fmt, va_list va) { LOCAL_PERSIST char tmp[STB_SPRINTF_MIN]; auto PrintCallback = [](char *buf, void * /*user*/, int /*len*/) -> char * { return buf; }; @@ -6562,7 +5609,7 @@ int DqnString_::VAListLen(char const *fmt, va_list va) return result; } -void DqnString_::Reserve(int newMax) +void DqnString::Reserve(int newMax) { if (newMax >= this->max) { @@ -6577,7 +5624,7 @@ void DqnString_::Reserve(int newMax) } } -void DqnString_::Append(char const *src, int len_) +void DqnString::Append(char const *src, int len_) { if (len_ == -1) len_ = DqnStr_Len(src);