From 6fe75928f0f6ac0e45daec20eaf0d871b79f55d6 Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Sat, 6 May 2017 02:44:17 +1000 Subject: [PATCH] Create a default MemoryAPI for MemBuffers --- dqn.h | 198 ++++++++++----- dqn_unit_test.cpp | 549 ++++++++++++++++++++++++++++++----------- misc/dqn_unit_test.sln | 2 +- 3 files changed, 548 insertions(+), 201 deletions(-) diff --git a/dqn.h b/dqn.h index b34e39f..8b6518e 100644 --- a/dqn.h +++ b/dqn.h @@ -69,6 +69,30 @@ DQN_FILE_SCOPE void DqnMem_Free (void *memory); //////////////////////////////////////////////////////////////////////////////// // DqnMemBuffer - Memory Buffer, For push buffer/ptr memory style management //////////////////////////////////////////////////////////////////////////////// +// DqnMemBuffer is a data structure to dynamically allocate memory in a stack +// like style. It pre-allocates a block of memory in init and sub-allocates from +// this block to take advantage of memory locality. + +// When an allocation requires a larger amount of memory than available in the +// block then the MemBuffer will allocate a new block of sufficient size for +// you in DqnMemBuffer_Allocate(..). This _DOES_ mean that there will be wasted +// space at the end of each block and is a tradeoff for memory locality against +// optimal space usage. + +// How To Use: +// 1. Create a DqnMemBuffer struct and pass it into an initialisation function +// - InitWithFixedMem() allows you to pass in your own memory which is +// converted to a memory block. This disables dynamic allocation. +// NOTE: Space is reserved in the given memory for MemBufferBlock metadata. + +// - InitWithFixedSize() allows you to to disable dynamic allocations and +// sub-allocate from the initial MemBuffer allocation size only. + +// 2. Use DqnMemBuffer_Allocate(..) to allocate memory for use. +// - "Freeing" memory is dealt by creating temporary MemBuffers or using the +// BeginTempRegion and EndTempRegion functions. Specifically freeing +// individual items is typically not generalisable in this scheme. + typedef struct DqnMemBufferBlock { u8 *memory; @@ -81,7 +105,7 @@ typedef struct DqnMemBufferBlock enum DqnMemBufferFlag { DqnMemBufferFlag_IsExpandable = (1 << 0), - DqnMemBufferFlag_IsFixedMemoryFromUser = (1 << 1), + DqnMemBufferFlag_IsFixedMemoryFromUser = (1 << 1), // NOTE(doyle): Required to indicate we CAN'T free this memory when free is called. }; typedef struct DqnMemBuffer @@ -102,13 +126,14 @@ typedef struct DqnTempBuffer } DqnTempBuffer; DQN_FILE_SCOPE bool DqnMemBuffer_InitWithFixedMem (DqnMemBuffer *const buffer, u8 *const mem, const size_t memSize, const u32 byteAlign = 4); // Use preallocated memory, no further allocations, returns NULL on allocate if out of space -DQN_FILE_SCOPE bool DqnMemBuffer_InitWithFixedSize(DqnMemBuffer *const buffer, size_t size, const bool clearToZero, const u32 byteAlign = 4); // Single allocation from platform, no further allocations, returns NULL of allocate if out of space -DQN_FILE_SCOPE bool DqnMemBuffer_Init (DqnMemBuffer *const buffer, size_t size, const bool clearToZero, const u32 byteAlign = 4); // Allocates from platform dynamically as space runs out +DQN_FILE_SCOPE bool DqnMemBuffer_InitWithFixedSize(DqnMemBuffer *const buffer, size_t size, const bool zeroClear, const u32 byteAlign = 4); // Single allocation from platform, no further allocations, returns NULL of allocate if out of space +DQN_FILE_SCOPE bool DqnMemBuffer_Init (DqnMemBuffer *const buffer, size_t size, const bool zeroClear, const u32 byteAlign = 4); // Allocates from platform dynamically as space runs out -DQN_FILE_SCOPE void *DqnMemBuffer_Allocate (DqnMemBuffer *const buffer, size_t size); // Returns NULL if out of space, or platform allocation fails, or buffer is using fixed memory/size -DQN_FILE_SCOPE void DqnMemBuffer_FreeLastBuffer(DqnMemBuffer *const buffer); // Frees the last-most block to the buffer, if it's the only block, the buffer will free that block then, next allocate with attach a block. -DQN_FILE_SCOPE void DqnMemBuffer_Free (DqnMemBuffer *const buffer); // Frees all blocks belonging to this buffer -DQN_FILE_SCOPE void DqnMemBuffer_ClearCurrBlock(DqnMemBuffer *const buffer, const bool clearToZero); // Reset the current blocks usage ptr to 0 +DQN_FILE_SCOPE void *DqnMemBuffer_Allocate (DqnMemBuffer *const buffer, size_t size); // Returns NULL if out of space and buffer is using fixed memory/size, or platform allocation fails +DQN_FILE_SCOPE void DqnMemBuffer_Free (DqnMemBuffer *const buffer); // Frees all blocks belonging to this buffer +DQN_FILE_SCOPE bool DqnMemBuffer_FreeBlock (DqnMemBuffer *const buffer, DqnMemBufferBlock *block); // Frees the specified block, returns false if block doesn't belong +DQN_FILE_SCOPE bool DqnMemBuffer_FreeLastBlock (DqnMemBuffer *const buffer); // Frees the last-most memory block. If last block, free that block, next allocate will attach a block. +DQN_FILE_SCOPE void DqnMemBuffer_ClearCurrBlock(DqnMemBuffer *const buffer, const bool zeroClear); // Reset the current memory block usage to 0 // TempBuffer is only required for the function. Once BeginTempRegion() is called, subsequent allocation calls can be made using the original buffer. // Upon EndTempRegion() the original buffer will free any additional blocks it allocated during the temp region and revert to the original @@ -122,7 +147,7 @@ DQN_FILE_SCOPE void DqnMemBuffer_EndTempRegion (DqnTempBuffer tempBuff // DqnMemAPI - Memory API, For using custom allocators //////////////////////////////////////////////////////////////////////////////// // You only need to care about this API if you want to use custom mem-alloc -// routines in the data structures! Otherwise it reverts to the default one. +// routines in the data structures! Otherwise it has a default one to use. // How To Use: // 1. Implement the callback function, where DqnMemApiCallbackInfo will tell you the request. @@ -217,13 +242,11 @@ bool DqnArray_Init(DqnArray *array, size_t capacity, array->memAPI.callback(info, NULL); array->data = NULL; } - array->memAPI = memAPI; - + array->memAPI = memAPI; size_t allocateSize = (size_t)capacity * sizeof(T); DqnMemAPICallbackResult memResult = {}; - DqnMemAPICallbackInfo info = - DqnMemAPICallback_InfoAskAllocInternal(array->memAPI, allocateSize); + DqnMemAPICallbackInfo info = DqnMemAPICallback_InfoAskAllocInternal(array->memAPI, allocateSize); array->memAPI.callback(info, &memResult); DQN_ASSERT(memResult.type == DqnMemAPICallbackType_Alloc); @@ -1271,16 +1294,18 @@ DQN_FILE_SCOPE void DqnMem_Free(void *memory) //////////////////////////////////////////////////////////////////////////////// // DqnMemBuffer - Memory API, For using custom allocators //////////////////////////////////////////////////////////////////////////////// -FILE_SCOPE DqnMemBufferBlock * -DqnMemBuffer_AllocBlockInternal(u32 byteAlign, size_t size) +DQN_FILE_SCOPE DqnMemBufferBlock * +DqnMemBuffer_AllocateBlockInternal(u32 byteAlign, size_t size) { size_t alignedSize = DQN_ALIGN_POW_N(size, byteAlign); - size_t totalSize = alignedSize + sizeof(DqnMemBufferBlock); + size_t totalSize = alignedSize + sizeof(DqnMemBufferBlock) + (byteAlign -1); + // NOTE(doyle): Total size includes another (byteAlign-1) since we also want + // to align the base pointer to memory that we receive. DqnMemBufferBlock *result = (DqnMemBufferBlock *)DqnMem_Calloc(totalSize); if (!result) return NULL; - result->memory = (u8 *)result + sizeof(*result); + result->memory = (u8 *)DQN_ALIGN_POW_N((u8 *)result + sizeof(*result), byteAlign); result->size = alignedSize; result->used = 0; return result; @@ -1311,13 +1336,13 @@ DQN_FILE_SCOPE bool DqnMemBuffer_InitWithFixedMem(DqnMemBuffer *const buffer, } DQN_FILE_SCOPE bool DqnMemBuffer_Init(DqnMemBuffer *const buffer, size_t size, - const bool clearToZero, + const bool zeroClear, const u32 byteAlign) { if (!buffer || size <= 0) return false; DQN_ASSERT(!buffer->block); - buffer->block = DqnMemBuffer_AllocBlockInternal(byteAlign, size); + buffer->block = DqnMemBuffer_AllocateBlockInternal(byteAlign, size); if (!buffer->block) return false; buffer->tempBufferCount = 0; @@ -1328,7 +1353,7 @@ DQN_FILE_SCOPE bool DqnMemBuffer_Init(DqnMemBuffer *const buffer, size_t size, DQN_FILE_SCOPE bool DqnMemBuffer_InitWithFixedSize(DqnMemBuffer *const buffer, size_t size, - const bool clearToZero, + const bool zeroClear, const u32 byteAlign) { bool result = DqnMemBuffer_Init(buffer, size, byteAlign); @@ -1351,11 +1376,22 @@ DQN_FILE_SCOPE void *DqnMemBuffer_Allocate(DqnMemBuffer *const buffer, size_t si { if (buffer->flags & DqnMemBufferFlag_IsFixedMemoryFromUser) return NULL; - // TODO: Better notifying to user, out of space in buffer + // TODO(doyle): If we make InitWithFixedSize buffer the Is_Expandable + // flag is not set. But if you free the buffery and try to allocate to + // it again since the flag is not set, the buffer becomes useless as we + // can't allocate to it anymore. How should we solve this? if (buffer->flags & DqnMemBufferFlag_IsExpandable) { - size_t newBlockSize = DQN_MAX(alignedSize, buffer->block->size); - DqnMemBufferBlock *newBlock = DqnMemBuffer_AllocBlockInternal( + size_t newBlockSize; + // TODO(doyle): Allocate block size based on the aligned size or + // a minimum block size? Not allocate based on the current block + // size + if (buffer->block) + newBlockSize = DQN_MAX(alignedSize, buffer->block->size); + else + newBlockSize = alignedSize; + + DqnMemBufferBlock *newBlock = DqnMemBuffer_AllocateBlockInternal( buffer->byteAlign, newBlockSize); if (!newBlock) return NULL; @@ -1364,6 +1400,7 @@ DQN_FILE_SCOPE void *DqnMemBuffer_Allocate(DqnMemBuffer *const buffer, size_t si } else { + // TODO: Better notifying to user, out of space in buffer return NULL; } } @@ -1372,23 +1409,49 @@ DQN_FILE_SCOPE void *DqnMemBuffer_Allocate(DqnMemBuffer *const buffer, size_t si u8 *alignedResult = (u8 *)DQN_ALIGN_POW_N(currPointer, buffer->byteAlign); size_t alignmentOffset = (size_t)(alignedResult - currPointer); + // NOTE(doyle): Since all buffers can't change alignment once they've been + // initialised and that the base memory ptr is already aligned, then all + // subsequent allocations should also be aligned automatically. + // TODO(doyle): In the future, do we want to allow arbitrary alignment PER + // allocation, not per MemBuffer? + DQN_ASSERT(alignmentOffset == 0); + void *result = alignedResult; buffer->block->used += (alignedSize + alignmentOffset); return result; } -DQN_FILE_SCOPE void -DqnMemBuffer_FreeLastBuffer(DqnMemBuffer *const buffer) +DQN_FILE_SCOPE bool DqnMemBuffer_FreeBlock(DqnMemBuffer *const buffer, + DqnMemBufferBlock *block) { - if (buffer->flags & DqnMemBufferFlag_IsFixedMemoryFromUser) return; + if (!buffer || !block || !buffer->block) return false; + if (buffer->flags & DqnMemBufferFlag_IsFixedMemoryFromUser) return false; - DqnMemBufferBlock *prevBlock = buffer->block->prevBlock; - DqnMem_Free(buffer->block); - buffer->block = prevBlock; + DqnMemBufferBlock **blockPtr = &buffer->block; - // No more blocks, then last block has been freed - if (!buffer->block) DQN_ASSERT(buffer->tempBufferCount == 0); + while (*blockPtr && (*blockPtr) != block) + blockPtr = &((*blockPtr)->prevBlock); + + if (*blockPtr) + { + DqnMemBufferBlock *blockToFree = *blockPtr; + (*blockPtr) = blockToFree->prevBlock; + DqnMem_Free(blockToFree); + + // No more blocks, then last block has been freed + if (!buffer->block) DQN_ASSERT(buffer->tempBufferCount == 0); + return true; + } + + return false; +} + +DQN_FILE_SCOPE bool +DqnMemBuffer_FreeLastBlock(DqnMemBuffer *const buffer) +{ + bool result = DqnMemBuffer_FreeBlock(buffer, buffer->block); + return result; } DQN_FILE_SCOPE void DqnMemBuffer_Free(DqnMemBuffer *buffer) @@ -1397,19 +1460,17 @@ DQN_FILE_SCOPE void DqnMemBuffer_Free(DqnMemBuffer *buffer) if (buffer->flags & DqnMemBufferFlag_IsFixedMemoryFromUser) return; while (buffer->block) - { - DqnMemBuffer_FreeLastBuffer(buffer); - } + DqnMemBuffer_FreeLastBlock(buffer); } DQN_FILE_SCOPE void DqnMemBuffer_ClearCurrBlock(DqnMemBuffer *const buffer, - const bool clearToZero) + const bool zeroClear) { if (!buffer) return; if (buffer->block) { buffer->block->used = 0; - if (clearToZero) + if (zeroClear) { DqnMem_Clear(buffer->block->memory, 0, buffer->block->size); @@ -1433,7 +1494,7 @@ DQN_FILE_SCOPE void DqnMemBuffer_EndTempRegion(DqnTempBuffer tempBuffer) { DqnMemBuffer *buffer = tempBuffer.buffer; while (buffer->block != tempBuffer.startingBlock) - DqnMemBuffer_FreeLastBuffer(buffer); + DqnMemBuffer_FreeLastBlock(buffer); if (buffer->block) { @@ -1483,7 +1544,6 @@ FILE_SCOPE DqnMemAPICallbackInfo DqnMemAPICallback_InfoAskFreeInternal( void DqnMemAPI_ValidateCallbackInfo(DqnMemAPICallbackInfo info) { - DQN_ASSERT(!info.userContext); DQN_ASSERT(info.type != DqnMemAPICallbackType_Invalid); switch(info.type) @@ -1513,6 +1573,7 @@ FILE_SCOPE void DqnMemAPI_DefaultUseCallocCallbackInternal(DqnMemAPICallbackInfo info, DqnMemAPICallbackResult *result) { + DQN_ASSERT(!info.userContext); DqnMemAPI_ValidateCallbackInfo(info); switch(info.type) { @@ -1547,6 +1608,7 @@ FILE_SCOPE void DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info, DqnMemAPICallbackResult *result) { + DQN_ASSERT(info.userContext); DqnMemAPI_ValidateCallbackInfo(info); DqnMemBuffer *const buffer = static_cast(info.userContext); @@ -1560,38 +1622,55 @@ DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info, } break; + // TODO(doyle): This is so badly handled. Currently MemBuffers are not + // designed to handle realloc in anyway elegantly due to it's memory + // block nature. case DqnMemAPICallbackType_Realloc: { - // NOTE(doyle): Realloc implies that our data must be contiguous. - // We also assert that the supplied memory buffer is brand new, i.e. - // the memory buffer is used exclusively for the MemAPI operations. - // So if we don't have enough space, we can free the entire memory - // buffer and reallocate. + // NOTE(doyle): Realloc implies that we want our data to be + // contiguous. We also enforce (assert) in init that the supplied + // memory buffer is brand new, i.e. the memory buffer is used + // exclusively for the MemAPI operations. So if we don't have enough + // space, we can free the entire memory buffer and reallocate + // without having to worry about part of blocks holding data + // belonging to other objects. DQN_ASSERT(result); DQN_ASSERT(!buffer->block->prevBlock); - + DQN_ASSERT(info.oldMemPtr == buffer->block->memory); + result->type = info.type; + // TODO(doyle): In regards to above, we have no way of ensuring that // the user doesn't use this buffer elsewhere, which would // invalidate all our assumptions. We can fix this maybe, by making // the DefaultUseMemBuffer allocate its own DqnMemBuffer privately // that the user can't see externally to enforce this invariant. - DqnMemBufferBlock tmpCopy = *buffer->block; - - result->type = info.type; size_t alignedSize = DQN_ALIGN_POW_N(info.newRequestSize, buffer->byteAlign); - if ((buffer->block->used + alignedSize) > buffer->block->size) - DqnMemBuffer_Free(buffer); - - result->newMemPtr = DqnMemBuffer_Allocate(buffer, alignedSize); - if (!result->newMemPtr) + if (alignedSize > buffer->block->size) { - // TODO(doyle): This should be big warning. If it failed, system - // must be out of memory. Since we are using a push buffer, and - // we don't clear to zero on free AND we ensure that there's - // only ever a singular block used.. then technically we can - // restore data by restoring the block metadata which should - // preserve the old data living here. - *buffer->block = tmpCopy; + DqnMemBufferBlock *oldBlock = buffer->block; + result->newMemPtr = DqnMemBuffer_Allocate(buffer, alignedSize); + + if (result->newMemPtr) + { + DqnMemBufferBlock *newBlock = buffer->block; + DQN_ASSERT(oldBlock != newBlock); + + for (u32 i = 0; i < oldBlock->used; i++) + newBlock->memory[i] = oldBlock->memory[i]; + + newBlock->used = oldBlock->used; + DqnMemBuffer_FreeBlock(buffer, oldBlock); + } + } + else + { + // Otherwise, the current block still has enough space so we can + // realloc in place. + + // TODO(doyle): Somewhat hacky, we clear the curr block pointer, + // and don't zero it out, and just reallocate the aligned size. + DqnMemBuffer_ClearCurrBlock(buffer, false); + result->newMemPtr = DqnMemBuffer_Allocate(buffer, alignedSize); } DQN_ASSERT(!buffer->block->prevBlock); } @@ -1602,6 +1681,7 @@ DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info, // TODO(doyle): Freeing technically works, if we enforce that the // MemBuffer exclusively belongs to the MemAPI it's linked to. if (result) result->type = info.type; + DQN_ASSERT(info.ptrToFree == buffer->block->memory); DqnMemBuffer_Free(buffer); } break; @@ -1620,8 +1700,8 @@ DQN_FILE_SCOPE DqnMemAPI DqnMemAPI_DefaultUseMemBuffer(DqnMemBuffer *const buffe { // TODO(doyle): We assert that the buffer has to be a brand new mem buffer. // Is this the correct design choice? + DQN_ASSERT(buffer && buffer->block); DQN_ASSERT(!buffer->block->prevBlock && buffer->block->used == 0); - DQN_ASSERT(buffer); DqnMemAPI result = {}; result.callback = DqnMemAPI_DefaultUseMemBufferCallbackInternal; diff --git a/dqn_unit_test.cpp b/dqn_unit_test.cpp index e338655..ccf1942 100644 --- a/dqn_unit_test.cpp +++ b/dqn_unit_test.cpp @@ -668,177 +668,262 @@ void VecTest() printf("VecTest(): Completed successfully\n"); } -void ArrayTest() +void ArrayTestMemAPIInternal(DqnArray *array, DqnMemAPI memAPI) { { - DqnArray array = {}; - DQN_ASSERT(DqnArray_Init(&array, 1)); - DQN_ASSERT(array.capacity == 1); - DQN_ASSERT(array.count == 0); + DQN_ASSERT(DqnArray_Init(array, 1, memAPI)); + DQN_ASSERT(array->capacity == 1); + DQN_ASSERT(array->count == 0); // Test basic insert { DqnV2 va = DqnV2_2f(5, 10); - DQN_ASSERT(DqnArray_Push(&array, va)); + DQN_ASSERT(DqnArray_Push(array, va)); - DqnV2 vb = array.data[0]; + DqnV2 vb = array->data[0]; DQN_ASSERT(DqnV2_Equals(va, vb)); - DQN_ASSERT(array.capacity == 1); - DQN_ASSERT(array.count == 1); + DQN_ASSERT(array->capacity == 1); + DQN_ASSERT(array->count == 1); } // Test array resizing and freeing { DqnV2 va = DqnV2_2f(10, 15); - DQN_ASSERT(DqnArray_Push(&array, va)); + DQN_ASSERT(DqnArray_Push(array, va)); - DqnV2 vb = array.data[0]; + DqnV2 vb = array->data[0]; DQN_ASSERT(DqnV2_Equals(va, vb) == false); - vb = array.data[1]; + vb = array->data[1]; DQN_ASSERT(DqnV2_Equals(va, vb) == true); - DQN_ASSERT(array.capacity == 2); - DQN_ASSERT(array.count == 2); + DQN_ASSERT(array->capacity == 2); + DQN_ASSERT(array->count == 2); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 3); - DQN_ASSERT(array.count == 3); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 3); + DQN_ASSERT(array->count == 3); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 4); - DQN_ASSERT(array.count == 4); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 4); + DQN_ASSERT(array->count == 4); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 5); - DQN_ASSERT(array.count == 5); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 5); + DQN_ASSERT(array->count == 5); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 6); - DQN_ASSERT(array.count == 6); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 6); + DQN_ASSERT(array->count == 6); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 7); - DQN_ASSERT(array.count == 7); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 7); + DQN_ASSERT(array->count == 7); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 8); - DQN_ASSERT(array.count == 8); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 8); + DQN_ASSERT(array->count == 8); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 9); - DQN_ASSERT(array.count == 9); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 9); + DQN_ASSERT(array->count == 9); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 10); - DQN_ASSERT(array.count == 10); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 10); + DQN_ASSERT(array->count == 10); - DQN_ASSERT(DqnArray_Push(&array, va)); - DQN_ASSERT(array.capacity == 12); - DQN_ASSERT(array.count == 11); + DQN_ASSERT(DqnArray_Push(array, va)); + DQN_ASSERT(array->capacity == 12); + DQN_ASSERT(array->count == 11); DqnV2 vc = DqnV2_2f(90, 100); - DQN_ASSERT(DqnArray_Push(&array, vc)); - DQN_ASSERT(array.capacity == 12); - DQN_ASSERT(array.count == 12); - DQN_ASSERT(DqnV2_Equals(vc, array.data[11])); - - DQN_ASSERT(DqnArray_Free(&array) == true); + DQN_ASSERT(DqnArray_Push(array, vc)); + DQN_ASSERT(array->capacity == 12); + DQN_ASSERT(array->count == 12); + DQN_ASSERT(DqnV2_Equals(vc, array->data[11])); } } + DQN_ASSERT(DqnArray_Free(array)); { - DqnArray array = {}; - DQN_ASSERT(DqnArray_Init(&array, 1)); + DQN_ASSERT(DqnArray_Init(array, 1, memAPI)); + DQN_ASSERT(array->capacity == 1); + DQN_ASSERT(array->count == 0); + } + DQN_ASSERT(DqnArray_Free(array)); + + { + DqnV2 a = DqnV2_2f(1, 2); + DqnV2 b = DqnV2_2f(3, 4); + DqnV2 c = DqnV2_2f(5, 6); + DqnV2 d = DqnV2_2f(7, 8); + + DQN_ASSERT(DqnArray_Init(array, 16, memAPI)); + DQN_ASSERT(DqnArray_Remove(array, 0) == false); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 0); + + DQN_ASSERT(DqnArray_Clear(array)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 0); + + DQN_ASSERT(DqnArray_Push(array, a)); + DQN_ASSERT(DqnArray_Push(array, b)); + DQN_ASSERT(DqnArray_Push(array, c)); + DQN_ASSERT(DqnArray_Push(array, d)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 4); + + DQN_ASSERT(DqnArray_Remove(array, 0)); + DQN_ASSERT(DqnV2_Equals(array->data[0], d)); + DQN_ASSERT(DqnV2_Equals(array->data[1], b)); + DQN_ASSERT(DqnV2_Equals(array->data[2], c)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 3); + + DQN_ASSERT(DqnArray_Remove(array, 2)); + DQN_ASSERT(DqnV2_Equals(array->data[0], d)); + DQN_ASSERT(DqnV2_Equals(array->data[1], b)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 2); + + DQN_ASSERT(DqnArray_Remove(array, 100) == false); + DQN_ASSERT(DqnV2_Equals(array->data[0], d)); + DQN_ASSERT(DqnV2_Equals(array->data[1], b)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 2); + + DQN_ASSERT(DqnArray_Clear(array)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 0); + + } + DQN_ASSERT(DqnArray_Free(array)); + + { + DqnV2 a = DqnV2_2f(1, 2); + DqnV2 b = DqnV2_2f(3, 4); + DqnV2 c = DqnV2_2f(5, 6); + DqnV2 d = DqnV2_2f(7, 8); + + DQN_ASSERT(DqnArray_Init(array, 16, memAPI)); + + DQN_ASSERT(DqnArray_Push(array, a)); + DQN_ASSERT(DqnArray_Push(array, b)); + DQN_ASSERT(DqnArray_Push(array, c)); + DQN_ASSERT(DqnArray_Push(array, d)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 4); + + DqnArray_RemoveStable(array, 0); + DQN_ASSERT(DqnV2_Equals(array->data[0], b)); + DQN_ASSERT(DqnV2_Equals(array->data[1], c)); + DQN_ASSERT(DqnV2_Equals(array->data[2], d)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 3); + + DqnArray_RemoveStable(array, 1); + DQN_ASSERT(DqnV2_Equals(array->data[0], b)); + DQN_ASSERT(DqnV2_Equals(array->data[1], d)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 2); + + DqnArray_RemoveStable(array, 1); + DQN_ASSERT(DqnV2_Equals(array->data[0], b)); + DQN_ASSERT(array->capacity == 16); + DQN_ASSERT(array->count == 1); + } + DQN_ASSERT(DqnArray_Free(array)); + printf("ArrayTestMemAPIInternal(): Completed successfully\n"); + +} + +void ArrayTest() +{ + DqnArray array = {}; + ArrayTestMemAPIInternal(&array, DqnMemAPI_DefaultUseCalloc()); + + DqnMemBuffer largeEnoughBuffer = {}; + DqnMemBuffer_Init(&largeEnoughBuffer, DQN_MEGABYTE(1), false); + ArrayTestMemAPIInternal(&array, DqnMemAPI_DefaultUseMemBuffer(&largeEnoughBuffer)); + DqnMemBuffer_Free(&largeEnoughBuffer); + + DqnMemBuffer smallBuffer = {}; + DqnMemBuffer_Init(&smallBuffer, 8, false); + ArrayTestMemAPIInternal(&array, DqnMemAPI_DefaultUseMemBuffer(&smallBuffer)); + DqnMemBuffer_Free(&smallBuffer); + + // TODO(doyle): Doesn't work for now since after freeing a fixed size + // buffer, it becomes useless as the not set Is_Expandable flag blocks any + // further allocations. +#if 0 + DqnMemBuffer largeFixedSizeBuffer = {}; + DqnMemBuffer_InitWithFixedSize(&largeFixedSizeBuffer, DQN_MEGABYTE(1), false); + ArrayTestMemAPIInternal(&array, DqnMemAPI_DefaultUseMemBuffer(&largeFixedSizeBuffer)); + DqnMemBuffer_Free(&largeFixedSizeBuffer); +#endif + + { + DqnMemBuffer smallFixedSizeBuffer = {}; + DqnMemBuffer_InitWithFixedSize(&smallFixedSizeBuffer, 8, false); + + DQN_ASSERT(DqnArray_Init(&array, 1, DqnMemAPI_DefaultUseMemBuffer(&smallFixedSizeBuffer))); DQN_ASSERT(array.capacity == 1); DQN_ASSERT(array.count == 0); - DqnArray_Free(&array); - } - { + // Fill the only slot in the array DqnV2 a = DqnV2_2f(1, 2); - DqnV2 b = DqnV2_2f(3, 4); - DqnV2 c = DqnV2_2f(5, 6); - DqnV2 d = DqnV2_2f(7, 8); - - DqnArray array = {}; - DQN_ASSERT(DqnArray_Init(&array, 16)); - DQN_ASSERT(DqnArray_Remove(&array, 0) == false); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 0); - - DQN_ASSERT(DqnArray_Clear(&array)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 0); - DQN_ASSERT(DqnArray_Push(&array, a)); - DQN_ASSERT(DqnArray_Push(&array, b)); - DQN_ASSERT(DqnArray_Push(&array, c)); - DQN_ASSERT(DqnArray_Push(&array, d)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 4); - - DQN_ASSERT(DqnArray_Remove(&array, 0)); - DQN_ASSERT(DqnV2_Equals(array.data[0], d)); - DQN_ASSERT(DqnV2_Equals(array.data[1], b)); - DQN_ASSERT(DqnV2_Equals(array.data[2], c)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 3); - - DQN_ASSERT(DqnArray_Remove(&array, 2)); - DQN_ASSERT(DqnV2_Equals(array.data[0], d)); - DQN_ASSERT(DqnV2_Equals(array.data[1], b)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 2); - - DQN_ASSERT(DqnArray_Remove(&array, 100) == false); - DQN_ASSERT(DqnV2_Equals(array.data[0], d)); - DQN_ASSERT(DqnV2_Equals(array.data[1], b)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 2); - - DQN_ASSERT(DqnArray_Clear(&array)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 0); - - DqnArray_Free(&array); - } - - { - DqnV2 a = DqnV2_2f(1, 2); - DqnV2 b = DqnV2_2f(3, 4); - DqnV2 c = DqnV2_2f(5, 6); - DqnV2 d = DqnV2_2f(7, 8); - - DqnArray array = {}; - DQN_ASSERT(DqnArray_Init(&array, 16)); - - DQN_ASSERT(DqnArray_Push(&array, a)); - DQN_ASSERT(DqnArray_Push(&array, b)); - DQN_ASSERT(DqnArray_Push(&array, c)); - DQN_ASSERT(DqnArray_Push(&array, d)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 4); - - DqnArray_RemoveStable(&array, 0); - DQN_ASSERT(DqnV2_Equals(array.data[0], b)); - DQN_ASSERT(DqnV2_Equals(array.data[1], c)); - DQN_ASSERT(DqnV2_Equals(array.data[2], d)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 3); - - DqnArray_RemoveStable(&array, 1); - DQN_ASSERT(DqnV2_Equals(array.data[0], b)); - DQN_ASSERT(DqnV2_Equals(array.data[1], d)); - DQN_ASSERT(array.capacity == 16); - DQN_ASSERT(array.count == 2); - - DqnArray_RemoveStable(&array, 1); - DQN_ASSERT(DqnV2_Equals(array.data[0], b)); - DQN_ASSERT(array.capacity == 16); DQN_ASSERT(array.count == 1); - DqnArray_Free(&array); + + // Try push another, but it should fail since it's a fixed size buffer. + // The realloc that occurs in push should also fail. + DQN_ASSERT(!DqnArray_Push(&array, a)); + DQN_ASSERT(array.count == 1); + DQN_ASSERT(smallFixedSizeBuffer.block->prevBlock == NULL); + DQN_ASSERT(smallFixedSizeBuffer.block->used == 8); + DQN_ASSERT(smallFixedSizeBuffer.block->size == 8); + + DQN_ASSERT(DqnArray_Free(&array)); + DqnMemBuffer_Free(&smallFixedSizeBuffer); + } + +#if 0 + { + u8 largeFixedMem[DQN_KILOBYTE(1)] = {}; + DqnMemBuffer largeFixedMemBuffer = {}; + DqnMemBuffer_InitWithFixedMem(&largeFixedMemBuffer, largeFixedMem, + DQN_ARRAY_COUNT(largeFixedMem)); + ArrayTestMemAPIInternal( + &array, DqnMemAPI_DefaultUseMemBuffer(&largeFixedMemBuffer)); + } +#endif + + { + u8 smallFixedMem[sizeof(DqnMemBufferBlock) + 8] = {}; + DqnMemBuffer smallFixedMemBuffer = {}; + DqnMemBuffer_InitWithFixedMem(&smallFixedMemBuffer, smallFixedMem, + DQN_ARRAY_COUNT(smallFixedMem)); + + DQN_ASSERT(DqnArray_Init(&array, 1, DqnMemAPI_DefaultUseMemBuffer(&smallFixedMemBuffer))); + DQN_ASSERT(array.capacity == 1); + DQN_ASSERT(array.count == 0); + + // Fill the only slot in the array + DqnV2 a = DqnV2_2f(1, 2); + DQN_ASSERT(DqnArray_Push(&array, a)); + DQN_ASSERT(array.count == 1); + + // Try push another, but it should fail since it's a fixed mem buffer. + // The realloc that occurs in push should also fail. + DQN_ASSERT(!DqnArray_Push(&array, a)); + DQN_ASSERT(array.count == 1); + DQN_ASSERT(smallFixedMemBuffer.block->prevBlock == NULL); + DQN_ASSERT(smallFixedMemBuffer.block->used == 8); + DQN_ASSERT(smallFixedMemBuffer.block->size == 8); + + DQN_ASSERT(DqnArray_Free(&array)); } printf("ArrayTest(): Completed successfully\n"); @@ -895,12 +980,12 @@ void FileTest() void MemBufferTest() { - // Expandable memory buffer + // Test over allocation, alignments, temp regions { size_t allocSize = DQN_KILOBYTE(1); DqnMemBuffer buffer = {}; const u32 ALIGNMENT = 4; - DqnMemBuffer_Init(&buffer, allocSize, ALIGNMENT); + DqnMemBuffer_Init(&buffer, allocSize, false, ALIGNMENT); DQN_ASSERT(buffer.block && buffer.block->memory); DQN_ASSERT(buffer.block->size == allocSize); DQN_ASSERT(buffer.block->used == 0); @@ -931,10 +1016,8 @@ void MemBufferTest() DQN_ASSERT(buffer.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 will - // reflect - // just this allocation. + // bytes of what we expect and since this is in a new block as well used + // will reflect just this allocation. DQN_ASSERT(buffer.block->used >= sizeB + 0 && buffer.block->used <= sizeB + 3); DQN_ASSERT(resultB); @@ -994,7 +1077,7 @@ void MemBufferTest() DQN_ASSERT(buffer.byteAlign == ALIGNMENT); // Release the last linked buffer from the push buffer - DqnMemBuffer_FreeLastBuffer(&buffer); + DqnMemBuffer_FreeLastBlock(&buffer); // Which should return back to the 1st allocation DQN_ASSERT(buffer.block == blockA); @@ -1004,7 +1087,7 @@ void MemBufferTest() DQN_ASSERT(buffer.byteAlign == ALIGNMENT); // Free once more to release buffer A memory - DqnMemBuffer_FreeLastBuffer(&buffer); + DqnMemBuffer_FreeLastBlock(&buffer); DQN_ASSERT(!buffer.block); DQN_ASSERT(buffer.byteAlign == ALIGNMENT); DQN_ASSERT(buffer.tempBufferCount == 0); @@ -1028,7 +1111,7 @@ void MemBufferTest() // Check free does nothing DqnMemBuffer_Free(&buffer); - DqnMemBuffer_FreeLastBuffer(&buffer); + DqnMemBuffer_FreeLastBlock(&buffer); DQN_ASSERT(buffer.block && buffer.block->memory); DQN_ASSERT(buffer.block->size == DQN_ARRAY_COUNT(memory) - sizeof(DqnMemBufferBlock)); @@ -1042,7 +1125,7 @@ void MemBufferTest() size_t allocSize = DQN_KILOBYTE(1); DqnMemBuffer buffer = {}; const u32 ALIGNMENT = 4; - DqnMemBuffer_InitWithFixedSize(&buffer, allocSize, ALIGNMENT); + DqnMemBuffer_InitWithFixedSize(&buffer, allocSize, false, ALIGNMENT); DQN_ASSERT(buffer.block && buffer.block->memory); DQN_ASSERT(buffer.block->size == allocSize); DQN_ASSERT(buffer.block->used == 0); @@ -1058,6 +1141,190 @@ void MemBufferTest() DqnMemBuffer_Free(&buffer); DQN_ASSERT(!buffer.block); } + + // Test freeing/clear block and alignment + { + size_t firstBlockSize = DQN_KILOBYTE(1); + DqnMemBuffer buffer = {}; + const u32 ALIGNMENT = 16; + DqnMemBuffer_Init(&buffer, firstBlockSize, false, ALIGNMENT); + + DqnMemBufferBlock *firstBlock = buffer.block; + u8 *first = NULL; + { + u32 allocate40Bytes = 40; + u8 *data = (u8 *)DqnMemBuffer_Allocate(&buffer, allocate40Bytes); + + // Test that the allocation got aligned to 16 byte boundary + DQN_ASSERT(data); + DQN_ASSERT(buffer.block->size == firstBlockSize); + DQN_ASSERT((size_t)data % ALIGNMENT == 0); + + for (u32 i = 0; i < allocate40Bytes; i++) + data[i] = 'a'; + + // Clear the block, but don't zero it out + DqnMemBuffer_ClearCurrBlock(&buffer, false); + for (u32 i = 0; i < allocate40Bytes; i++) + DQN_ASSERT(data[i] == 'a'); + + // Test clear reverted the use pointer + DQN_ASSERT(buffer.block->used == 0); + DQN_ASSERT(buffer.block->size == firstBlockSize); + + // Reallocate the data + data = (u8 *)DqnMemBuffer_Allocate(&buffer, firstBlockSize); + DQN_ASSERT(buffer.block->size == firstBlockSize); + DQN_ASSERT((size_t)data % ALIGNMENT == 0); + + // Fill with 'b's + for (u32 i = 0; i < firstBlockSize; i++) + data[i] = 'b'; + + // Clear block and zero it out + DqnMemBuffer_ClearCurrBlock(&buffer, true); + for (u32 i = 0; i < firstBlockSize; i++) + DQN_ASSERT(data[i] == 0); + + // General Check buffer struct contains the values we expect from + // initialisation + DQN_ASSERT(buffer.flags & DqnMemBufferFlag_IsExpandable); + DQN_ASSERT(buffer.tempBufferCount == 0); + DQN_ASSERT(buffer.byteAlign == ALIGNMENT); + DQN_ASSERT(buffer.block->size == firstBlockSize); + + // Write out data to current block + data = (u8 *)DqnMemBuffer_Allocate(&buffer, firstBlockSize); + for (u32 i = 0; i < firstBlockSize; i++) + data[i] = 'c'; + + first = data; + } + + // Force it to allocate three new blocks and write out data to each + size_t secondBlockSize = DQN_KILOBYTE(2); + u8 *second = (u8 *)DqnMemBuffer_Allocate(&buffer, secondBlockSize); + DqnMemBufferBlock *secondBlock = buffer.block; + for (u32 i = 0; i < secondBlockSize; i++) + second[i] = 'd'; + + size_t thirdBlockSize = DQN_KILOBYTE(3); + u8 *third = (u8 *)DqnMemBuffer_Allocate(&buffer, thirdBlockSize); + DqnMemBufferBlock *thirdBlock = buffer.block; + for (u32 i = 0; i < thirdBlockSize; i++) + third[i] = 'e'; + + size_t fourthBlockSize = DQN_KILOBYTE(4); + u8 *fourth = (u8 *)DqnMemBuffer_Allocate(&buffer, fourthBlockSize); + DqnMemBufferBlock *fourthBlock = buffer.block; + for (u32 i = 0; i < fourthBlockSize; i++) + fourth[i] = 'f'; + + DQN_ASSERT((firstBlock != secondBlock) && (secondBlock != thirdBlock) && (thirdBlock != fourthBlock)); + DQN_ASSERT(firstBlock->prevBlock == NULL); + DQN_ASSERT(secondBlock->prevBlock == firstBlock); + DQN_ASSERT(thirdBlock->prevBlock == secondBlock); + DQN_ASSERT(fourthBlock->prevBlock == thirdBlock); + + // NOTE: Making blocks manually is not really recommended .. + // Try and free an invalid block by mocking a fake block + u8 fakeBlockMem[DQN_KILOBYTE(3)] = {}; + DqnMemBufferBlock fakeBlock = {}; + fakeBlock.memory = fakeBlockMem; + fakeBlock.size = DQN_ARRAY_COUNT(fakeBlockMem); + fakeBlock.used = 0; + DQN_ASSERT(!DqnMemBuffer_FreeBlock(&buffer, &fakeBlock)); + + //Ensure that the actual blocks are still valid and freeing did nothing + DQN_ASSERT(firstBlock->size == firstBlockSize); + DQN_ASSERT(secondBlock->size == secondBlockSize); + DQN_ASSERT(thirdBlock->size == thirdBlockSize); + DQN_ASSERT(fourthBlock->size == fourthBlockSize); + + DQN_ASSERT(firstBlock->used == firstBlockSize); + DQN_ASSERT(secondBlock->used == secondBlockSize); + DQN_ASSERT(thirdBlock->used == thirdBlockSize); + DQN_ASSERT(fourthBlock->used == fourthBlockSize); + + DQN_ASSERT((firstBlock != secondBlock) && (secondBlock != thirdBlock) && (thirdBlock != fourthBlock)); + DQN_ASSERT(firstBlock->prevBlock == NULL); + DQN_ASSERT(secondBlock->prevBlock == firstBlock); + DQN_ASSERT(thirdBlock->prevBlock == secondBlock); + DQN_ASSERT(fourthBlock->prevBlock == thirdBlock); + + for (u32 i = 0; i < firstBlockSize; i++) + DQN_ASSERT(first[i] == 'c'); + + for (u32 i = 0; i < secondBlockSize; i++) + DQN_ASSERT(second[i] == 'd'); + + for (u32 i = 0; i < thirdBlockSize; i++) + DQN_ASSERT(third[i] == 'e'); + + for (u32 i = 0; i < fourthBlockSize; i++) + DQN_ASSERT(fourth[i] == 'f'); + + // Free the first block + DqnMemBuffer_FreeBlock(&buffer, firstBlock); + + // Revalidate state + DQN_ASSERT(secondBlock->size == secondBlockSize); + DQN_ASSERT(thirdBlock->size == thirdBlockSize); + DQN_ASSERT(fourthBlock->size == fourthBlockSize); + + DQN_ASSERT(secondBlock->used == secondBlockSize); + DQN_ASSERT(thirdBlock->used == thirdBlockSize); + DQN_ASSERT(fourthBlock->used == fourthBlockSize); + + DQN_ASSERT((secondBlock != thirdBlock) && (thirdBlock != fourthBlock)); + DQN_ASSERT(secondBlock->prevBlock == NULL); + DQN_ASSERT(thirdBlock->prevBlock == secondBlock); + DQN_ASSERT(fourthBlock->prevBlock == thirdBlock); + + for (u32 i = 0; i < secondBlockSize; i++) + DQN_ASSERT(second[i] == 'd'); + + for (u32 i = 0; i < thirdBlockSize; i++) + DQN_ASSERT(third[i] == 'e'); + + for (u32 i = 0; i < fourthBlockSize; i++) + DQN_ASSERT(fourth[i] == 'f'); + + // Free the third block + DqnMemBuffer_FreeBlock(&buffer, thirdBlock); + + // Revalidate state + DQN_ASSERT(secondBlock->size == secondBlockSize); + DQN_ASSERT(fourthBlock->size == fourthBlockSize); + + DQN_ASSERT(secondBlock->used == secondBlockSize); + DQN_ASSERT(fourthBlock->used == fourthBlockSize); + + DQN_ASSERT(secondBlock != fourthBlock); + DQN_ASSERT(secondBlock->prevBlock == NULL); + DQN_ASSERT(fourthBlock->prevBlock == secondBlock); + + for (u32 i = 0; i < secondBlockSize; i++) + DQN_ASSERT(second[i] == 'd'); + + for (u32 i = 0; i < fourthBlockSize; i++) + DQN_ASSERT(fourth[i] == 'f'); + + // Free the second block + DqnMemBuffer_FreeBlock(&buffer, secondBlock); + + // Revalidate state + DQN_ASSERT(fourthBlock->size == fourthBlockSize); + DQN_ASSERT(fourthBlock->used == fourthBlockSize); + DQN_ASSERT(fourthBlock->prevBlock == NULL); + + for (u32 i = 0; i < fourthBlockSize; i++) + DQN_ASSERT(fourth[i] == 'f'); + + // Free the buffer + DqnMemBuffer_Free(&buffer); + DQN_ASSERT(!buffer.block); + } } int main(void) diff --git a/misc/dqn_unit_test.sln b/misc/dqn_unit_test.sln index 020d75e..6aa32fc 100644 --- a/misc/dqn_unit_test.sln +++ b/misc/dqn_unit_test.sln @@ -21,7 +21,7 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {87785192-6F49-4F85-AA0D-F0AFA5CCCDDA}.Release|x86.ActiveCfg = Release|x64 + {87785192-6F49-4F85-AA0D-F0AFA5CCCDDA}.Release|x86.ActiveCfg = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE