diff --git a/dqn.h b/dqn.h index d1eb0fd..9b41baf 100644 --- a/dqn.h +++ b/dqn.h @@ -152,6 +152,14 @@ typedef float f32; #define DQN_MIN(a, b) ((a) < (b) ? (a) : (b)) #define DQN_SWAP(type, a, b) do { type tmp = a; a = b; b = tmp; } while(0) +// NOTE: Directives don't get replaced if there's a stringify or paste (# or ##) so TOKEN_COMBINE2 is needed +// to let directives get expanded (i.e. __COUNTER__), then we can combine. +#define DQN_TOKEN_COMBINE(x, y) x ## y +#define DQN_TOKEN_COMBINE2(x, y) DQN_TOKEN_COMBINE(x, y) + +// Produce a unique name with prefix and counter. i.e. data => data1 +#define DQN_UNIQUE_NAME(prefix) DQN_TOKEN_COMBINE2(prefix, __COUNTER__) + // #DqnAssert API // ================================================================================================= // DQN_ASSERT() & DQN_ASSERT_MSG() will hard break the program but it can be @@ -188,9 +196,9 @@ DQN_FILE_SCOPE void *DqnMem_Calloc (const size_t size); DQN_FILE_SCOPE void DqnMem_Clear (void *const memory, const u8 clearValue, const size_t size); DQN_FILE_SCOPE void *DqnMem_Realloc(void *memory, const size_t newSize); DQN_FILE_SCOPE void DqnMem_Free (void *memory); -DQN_FILE_SCOPE void DqnMem_Copy (void *const dest, void *const src, const i64 numBytesToCopy); -DQN_FILE_SCOPE void *DqnMem_Set (void *const dest, u8 value, const i64 numBytesToSet); -DQN_FILE_SCOPE void *DqnMem_Set64 (void *const dest, u8 value, const i64 numBytesToSet); +DQN_FILE_SCOPE void DqnMem_Copy (void *const dest, void const *const src, i64 const numBytesToCopy); +DQN_FILE_SCOPE void *DqnMem_Set (void *const dest, u8 const value, i64 const numBytesToSet); +DQN_FILE_SCOPE void *DqnMem_Set64 (void *const dest, u8 const value, i64 const numBytesToSet); // #DqnMemAPI API // ================================================================================================= @@ -288,8 +296,19 @@ public: // BeginTempRegion and EndTempRegion functions. Specifically freeing // individual items is typically not generalisable in this scheme. +// Usage: DqnMemStack example = DQN_MEM_STACK_FIXED_MEM(example, DQN_KILOBYTES(512), 4); +#define DQN_MEM_STACK_FIXED_MEM(memStack, sizeInBytes, align) \ + DQN_MEM_STACK_FIXED_MEM_INTERNAL(memStack, sizeInBytes, align, DQN_UNIQUE_NAME(memory_)) + +#define DQN_MEM_STACK_FIXED_MEM_INTERNAL(memStack, sizeInBytes, align, uniqueName) \ + {}; \ + u8 uniqueName[sizeInBytes]; \ + memStack.InitWithFixedMem(uniqueName, sizeInBytes, align); + struct DqnMemStack { + static const i32 MINIMUM_BLOCK_SIZE = DQN_KILOBYTE(32); + enum Flag { IsNotExpandable = (1 << 0), @@ -390,7 +409,7 @@ struct DqnMemStack TempRegionGuard_ TempRegionGuard (); // RAII Temp Region // Keep allocations that have occurred since Begin(). End() does not need to be called anymore. - void TempRegionKeepChanges(TempRegion region); + void TempRegionKeepChanges(TempRegion region); // Advanced // ============================================================================================= @@ -410,28 +429,25 @@ struct DqnMemStack // 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. -// NOTE: Pasting tokens doesn't do anything with preprocessor directives IF the preprocessor finds -// a stringify or paste operation (# or ##) so we need this level of indirection. -#define DQN_TOKEN_COMBINE(x, y) x ## y -#define DQN_TOKEN_COMBINE2(x, y) DQN_TOKEN_COMBINE(x, y) - // Usage: DqnString example = DQN_STRING_LITERAL(example, "hello world"); #define DQN_STRING_LITERAL(srcVariable, literal) \ - DQN_STRING_LITERAL_INTERNAL1(srcVariable, literal, DQN_TOKEN_COMBINE2(dqnstring_, __COUNTER__)) + DQN_STRING_LITERAL_INTERNAL(srcVariable, literal, DQN_UNIQUE_NAME(dqnstring_)) class DqnString { + char sso[128]; + public: - char *str; - i32 len; // Len of the string in bytes not including null-terminator - i32 max; // The maximum capacity not including space for null-terminator. DqnMemAPI memAPI; + char *str; + i32 len; // Len of the string in bytes not including null-terminator + i32 max; // The maximum capacity not including space for null-terminator. // Initialisation API // ============================================================================================= // return: False if (size < 0) or (memAPI allocation failed). - bool InitSize (const i32 size, DqnMemStack *const stack); - bool InitSize (const i32 size, DqnMemAPI api = DqnMemAPI::HeapAllocator()); + bool InitSize (i32 size, DqnMemStack *const stack); + bool InitSize (i32 size, DqnMemAPI api = DqnMemAPI::HeapAllocator()); // return: False if arguments are invalid. bool InitFixedMem (char *const memory, const i32 sizeInBytes); @@ -465,6 +481,11 @@ public: wchar_t *ToWChar(DqnMemAPI api = DqnMemAPI::HeapAllocator()); }; +struct DqnSmartString : public DqnString +{ + ~DqnSmartString() { this->Free(); } +}; + // TODO(doyle): Remove this? I only want it to return the string if we can guarantee initialisation. // returns: Initialised string, DQN_FILE_SCOPE DqnString DqnString_(i32 const len, DqnMemAPI const api = DqnMemAPI::HeapAllocator()); @@ -475,10 +496,7 @@ DQN_FILE_SCOPE DqnString DqnString_(DqnMemStack *const stack); // NOTE: First level of indirection needs to turn the combined dqnstring_(guid) into a name. Otherwise // each use of literalVarName will increment __COUNTER__ -#define DQN_STRING_LITERAL_INTERNAL1(srcVariable, literal, literalVarName) \ - DQN_STRING_LITERAL_INTERNAL2(srcVariable, literal, literalVarName) - -#define DQN_STRING_LITERAL_INTERNAL2(srcVariable, literal, literalVarName) \ +#define DQN_STRING_LITERAL_INTERNAL(srcVariable, literal, literalVarName) \ {}; \ char literalVarName[] = literal; \ srcVariable.InitLiteralNoAlloc(literalVarName, DQN_CHAR_COUNT(literalVarName)) @@ -1661,9 +1679,27 @@ public: DQN_FILE_SCOPE DqnRndPCG DqnRndPCG_(); // Uses rdtsc to create a seed DQN_FILE_SCOPE DqnRndPCG DqnRndPCG_(u32 seed); -// #Dqn API +// #Dqn_ API // ================================================================================================= +inline bool Dqn_BitIsSet(u32 const bits, u32 const flag) +{ + bool result = ((bits & flag) == flag); + return result; +} + +inline u32 Dqn_BitSet(u32 const bits, u32 const flag) +{ + u32 result = (bits | flag); + return result; +} + +inline u32 Dqn_BitUnset(u32 const bits, u32 const flag) +{ + u32 result = (bits & ~flag); + return result; +} + template using Dqn_QuickSortLessThanCallback = bool (*)(const T *const , const T *const); @@ -2780,7 +2816,7 @@ DQN_FILE_SCOPE void DqnMem_Free(void *memory) if (memory) free(memory); } -DQN_FILE_SCOPE void DqnMem_Copy(void *const dest, void *const src, const i64 numBytesToCopy) +DQN_FILE_SCOPE void DqnMem_Copy(void *const dest, void const *const src, i64 const numBytesToCopy) { auto *to = (u8 *)dest; auto *from = (u8 *)src; @@ -2788,7 +2824,7 @@ DQN_FILE_SCOPE void DqnMem_Copy(void *const dest, void *const src, const i64 num to[i] = from[i]; } -DQN_FILE_SCOPE void *DqnMem_Set(void *const dest, u8 value, const i64 numBytesToSet) +DQN_FILE_SCOPE void *DqnMem_Set(void *const dest, u8 const value, i64 const numBytesToSet) { auto *ptr = (u8 *)dest; for (auto i = 0; i < numBytesToSet; i++) @@ -2797,7 +2833,7 @@ DQN_FILE_SCOPE void *DqnMem_Set(void *const dest, u8 value, const i64 numBytesTo return dest; } -DQN_FILE_SCOPE void *DqnMem_Set64(void *const dest, u8 value, const i64 numBytesToCopy) +DQN_FILE_SCOPE void *DqnMem_Set64(void *const dest, u8 const value, i64 const numBytesToCopy) { #if defined(DQN_WIN32_PLATFORM) u64 valueU64 = value; @@ -3111,6 +3147,7 @@ bool DqnMemStack::InitWithFixedSize(size_t size, bool const zeroClear, u32 const bool DqnMemStack::Init(size_t size, bool const zeroClear, u32 const byteAlign_, DqnMemAPI memAPI_) { if (!this || size < 0) return false; + if (!DQN_ASSERT_MSG(!this->block, "MemStack has pre-existing block already attached")) return false; @@ -3139,7 +3176,8 @@ void *DqnMemStack::Push(size_t size) if (size == 0) return nullptr; size_t alignedSize = DQN_ALIGN_POW_N(size, this->byteAlign); - if (!this->block || (this->block->used + alignedSize) > this->block->size) + size_t newUsage = this->block->used + alignedSize; + if (!this->block || newUsage > this->block->size) { size_t newBlockSize; @@ -3147,9 +3185,17 @@ void *DqnMemStack::Push(size_t size) // a minimum block size? Not allocate based on the current block // size if (this->block) + { +#if 0 newBlockSize = DQN_MAX(alignedSize, this->block->size); +#else + newBlockSize = DQN_MAX(alignedSize, DqnMemStack::MINIMUM_BLOCK_SIZE); +#endif + } else + { newBlockSize = alignedSize; + } DqnMemStack::Block *newBlock = this->AllocateCompatibleBlock(newBlockSize, true); if (newBlock) @@ -3188,32 +3234,50 @@ void *DqnMemStack::Push(size_t size) return result; } -bool DqnMemStack::Pop(void *const ptr, size_t size) +FILE_SCOPE bool DqnMemStackInternal_TryPopPtrInBlock(DqnMemStack::Block *block, u32 const byteAlign, + void *const ptr, size_t size) { - if (!this->block) return false; u8 *currPtr = block->memory + block->used; - if (DQN_ASSERT_MSG((u8 *)ptr >= block->memory && ptr < currPtr, - "'ptr' to pop does not belong to current memStack attached block")) + if ((size_t)ptr >= (size_t)block->memory && (size_t)ptr < (size_t)currPtr) { - size_t calcSize = (size_t)currPtr - (size_t)ptr; - size_t sizeAligned = DQN_ALIGN_POW_N(size, this->byteAlign); - + auto calcSize = (size_t)currPtr - (size_t)ptr; + size_t sizeAligned = DQN_ALIGN_POW_N(size, byteAlign); if (calcSize == sizeAligned) { - this->block->used -= sizeAligned; - if (this->block->used == 0 && block->prevBlock) - { - bool result = this->FreeLastBlock(); - return result; - } - + block->used -= sizeAligned; return true; } + else + { + // printf("The ptr to free and its size aligned to byteAlign does not match. + // This + // ptr is not a valid address to free. We got: %d, expected: %d", calcSize, + // alignedSized); + DQN_ASSERT(DQN_INVALID_CODE_PATH); + } } return false; } +bool DqnMemStack::Pop(void *const ptr, size_t size) +{ + if (!this->block) return false; + + if (DqnMemStackInternal_TryPopPtrInBlock(this->block, this->byteAlign, ptr, size)) + { + if (this->block->used == 0 && this->block->prevBlock) + { + bool result = this->FreeLastBlock(); + return result; + } + return true; + } + + // printf("Ptr (%x) does not belong to the newest memory blocks", ptr); + return false; +} + void DqnMemStack::Free() { // NOTE(doyle): User is in charge of freeing this memory, so all we need to @@ -3247,7 +3311,7 @@ bool DqnMemStack::FreeMemBlock(DqnMemStack::Block *memBlock) { DqnMemStack::Block *blockToFree = *blockPtr; (*blockPtr) = blockToFree->prevBlock; - DqnMem_Free(blockToFree); + this->memAPI.Free(blockToFree, blockToFree->size); // No more blocks, then last block has been freed if (!this->block) DQN_ASSERT_HARD(this->tempRegionCount == 0); @@ -5257,31 +5321,22 @@ bool DqnString::InitSize(const i32 size, DqnMemStack *const stack) return result; } -bool DqnString::InitSize(const i32 size, DqnMemAPI api) +bool DqnString::InitSize(i32 size, DqnMemAPI api) { + // NOTE: CHAR_COUNT is (ARRAY_COUNT - 1) to leave the last spot as the implicit null-terminator. DQN_ASSERT(size >= 0); - if (size < 0) + if (size < DQN_CHAR_COUNT(this->sso)) { - DqnString nullString = {}; - *this = nullString; - return false; + this->str = &(this->sso[0]); } - - if (size > 0) + else { size_t allocSize = sizeof(*(this->str)) * (size + 1); this->str = (char *)api.Alloc(allocSize, /*zeroClear*/false); - - if (!this->str) - { - return false; - } - } - else // size == 0 - { - this->str = nullptr; + if (!this->str) return false; } + this->str[0] = 0; this->max = size; this->len = 0; this->memAPI = api; @@ -5314,25 +5369,15 @@ bool DqnString::InitLiteral(char const *const cstr, i32 const lenInBytes, DqnMem bool DqnString::InitLiteral(char const *const cstr, i32 const lenInBytes, DqnMemAPI api) { - if (lenInBytes < 0) return false; - - if (lenInBytes > 0) + if (!this->InitSize(lenInBytes, api)) { - size_t allocSize = sizeof(*(this->str)) * (lenInBytes + 1); - this->str = (char *)api.Alloc(allocSize, /*zeroClear*/ false); - if (!this->str) return false; - - this->str[lenInBytes] = 0; + return false; } + this->str[lenInBytes] = 0; this->len = lenInBytes; - this->max = lenInBytes; - this->memAPI = api; - - for (i32 i = 0; i < this->len; i++) - { - this->str[i] = cstr[i]; - } + this->max = this->len; + DqnMem_Copy(this->str, cstr, lenInBytes); return true; } @@ -5354,20 +5399,18 @@ bool DqnString::InitLiteral(wchar_t const *const cstr, DqnMemStack *const stack) bool DqnString::InitLiteral(wchar_t const *const cstr, DqnMemAPI api) { #if defined(DQN_IS_WIN32) && defined(DQN_WIN32_IMPLEMENTATION) - i32 requiredLen = DqnWin32_WCharToUTF8(cstr, nullptr, 0); - this->len = requiredLen - 1; - - size_t allocSize = sizeof(*(this->str)) * (this->len + 1); - this->str = (char *)api.Alloc(allocSize, /*zeroClear*/false); - if (!this->str) return false; - - this->max = this->len; - this->memAPI = api; + 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 @@ -5394,14 +5437,35 @@ bool DqnString::InitLiteralNoAlloc(char *const cstr, i32 cstrLen) } this->max = this->len; - return true; } bool DqnString::Expand(const i32 newMax) { - if (!this->memAPI.IsValid()) return false; - if (newMax < this->max) return true; + if (newMax < this->max) + { + return true; + } + + if (!this->memAPI.IsValid()) + { + return false; + } + + if (newMax < DQN_CHAR_COUNT(this->sso)) + { + DQN_ASSERT(this->memAPI.IsValid()); + DQN_ASSERT(this->sso == this->str); + this->max = newMax; + return true; + } + + bool usingSSO = (this->str == this->sso); + if (usingSSO) + { + DQN_ASSERT(newMax >= DQN_CHAR_COUNT(this->sso)); + this->str = nullptr; + } size_t allocSize = sizeof(*(this->str)) * (newMax + 1); char *result = nullptr; @@ -5411,10 +5475,19 @@ bool DqnString::Expand(const i32 newMax) if (result) { + if (usingSSO) + DqnMem_Copy(result, this->sso, this->max); + this->str = (char *)result; this->max = newMax; return true; } + else + { + // Restore the pointer to the SSO to return to the original state from before this call. + if (usingSSO) + this->str = this->sso; + } return false; } @@ -5435,8 +5508,8 @@ DQN_FILE_SCOPE bool DqnStringInternal_Append(DqnString *const str, char const *c if (!result) return false; } - // Append - for (i32 i = 0; i < bytesToCopy; i++) str->str[str->len + i] = cstr[i]; + char *dest = str->str + str->len; + DqnMem_Copy(dest, cstr, bytesToCopy); str->len = totalLen; str->str[totalLen] = 0; @@ -5512,10 +5585,19 @@ void DqnString::Clear() void DqnString::Free() { - if (this->str && this->memAPI.IsValid()) + if (this->str) { - this->memAPI.Free(this->str, this->len); - this->str = nullptr; + if (this->str == this->sso) + { + this->sso[0] = '\0'; + this->str = nullptr; + } + else if (this->memAPI.IsValid()) + { + this->memAPI.Free(this->str, this->len); + this->str = nullptr; + } + this->len = 0; this->max = 0; } diff --git a/dqn_unit_test.cpp b/dqn_unit_test.cpp index 9825308..93356a4 100644 --- a/dqn_unit_test.cpp +++ b/dqn_unit_test.cpp @@ -1573,11 +1573,10 @@ void DqnMemStack_Test() DqnMemStack::Block *blockA = stack.block; // Alocate B - size_t sizeB = (size_t)(allocSize * 2.0f); + size_t sizeB = DqnMemStack::MINIMUM_BLOCK_SIZE; void *resultB = stack.Push(sizeB); DQN_ASSERT(((intptr_t)resultB % ALIGNMENT) == 0); DQN_ASSERT(stack.block && stack.block->memory); - DQN_ASSERT(stack.block->size == DQN_KILOBYTE(2)); // Since we alignment the pointers we return they can be within 0-3 // bytes of what we expect and since this is in a new block as well used @@ -1598,7 +1597,7 @@ void DqnMemStack_Test() // Check temp regions work DqnMemStack::TempRegion tempBuffer = stack.TempRegionBegin(); - size_t sizeC = 1024 + 1; + size_t sizeC = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE + 1, ALIGNMENT); void *resultC = stack.Push(sizeC); DQN_ASSERT(((intptr_t)resultC % ALIGNMENT) == 0); DQN_ASSERT(stack.block != blockB && stack.block != blockA); @@ -1607,7 +1606,7 @@ void DqnMemStack_Test() DQN_ASSERT(stack.byteAlign == ALIGNMENT); // NOTE: Allocation should be aligned to 4 byte boundary - DQN_ASSERT(tempBuffer.stack->block->size == 2048); + DQN_ASSERT(tempBuffer.stack->block->size == sizeC); u8 *ptrC = (u8 *)resultC; for (u32 i = 0; i < sizeC; i++) ptrC[i] = 3; @@ -1769,19 +1768,19 @@ void DqnMemStack_Test() } // Force it to allocate three new blocks and write out data to each - size_t secondBlockSize = DQN_KILOBYTE(2); + size_t secondBlockSize = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE, stack.byteAlign); u8 *second = (u8 *)stack.Push(secondBlockSize); DqnMemStack::Block *secondBlock = stack.block; for (u32 i = 0; i < secondBlockSize; i++) second[i] = 'd'; - size_t thirdBlockSize = DQN_KILOBYTE(3); + size_t thirdBlockSize = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE, stack.byteAlign); u8 *third = (u8 *)stack.Push(thirdBlockSize); DqnMemStack::Block *thirdBlock = stack.block; for (u32 i = 0; i < thirdBlockSize; i++) third[i] = 'e'; - size_t fourthBlockSize = DQN_KILOBYTE(4); + size_t fourthBlockSize = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE, stack.byteAlign); u8 *fourth = (u8 *)stack.Push(fourthBlockSize); DqnMemStack::Block *fourthBlock = stack.block; for (u32 i = 0; i < fourthBlockSize; i++)