Create a default MemoryAPI for MemBuffers

This commit is contained in:
Doyle Thai 2017-05-06 02:44:17 +10:00
parent 1c3c78d738
commit 6fe75928f0
3 changed files with 548 additions and 201 deletions

198
dqn.h
View File

@ -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<T> *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<DqnMemBuffer *>(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;

View File

@ -668,177 +668,262 @@ void VecTest()
printf("VecTest(): Completed successfully\n");
}
void ArrayTest()
void ArrayTestMemAPIInternal(DqnArray<DqnV2> *array, DqnMemAPI memAPI)
{
{
DqnArray<DqnV2> 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<f32> 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<DqnV2> 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<DqnV2> 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<DqnV2> 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)

View File

@ -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