Create a default MemoryAPI for MemBuffers
This commit is contained in:
parent
1c3c78d738
commit
6fe75928f0
188
dqn.h
188
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 - 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
|
typedef struct DqnMemBufferBlock
|
||||||
{
|
{
|
||||||
u8 *memory;
|
u8 *memory;
|
||||||
@ -81,7 +105,7 @@ typedef struct DqnMemBufferBlock
|
|||||||
enum DqnMemBufferFlag
|
enum DqnMemBufferFlag
|
||||||
{
|
{
|
||||||
DqnMemBufferFlag_IsExpandable = (1 << 0),
|
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
|
typedef struct DqnMemBuffer
|
||||||
@ -102,13 +126,14 @@ typedef struct DqnTempBuffer
|
|||||||
} 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_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_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 clearToZero, const u32 byteAlign = 4); // Allocates from platform dynamically as space runs out
|
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_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_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_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 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.
|
// 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
|
// 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
|
// DqnMemAPI - Memory API, For using custom allocators
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// You only need to care about this API if you want to use custom mem-alloc
|
// 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:
|
// How To Use:
|
||||||
// 1. Implement the callback function, where DqnMemApiCallbackInfo will tell you the request.
|
// 1. Implement the callback function, where DqnMemApiCallbackInfo will tell you the request.
|
||||||
@ -218,12 +243,10 @@ bool DqnArray_Init(DqnArray<T> *array, size_t capacity,
|
|||||||
array->data = NULL;
|
array->data = NULL;
|
||||||
}
|
}
|
||||||
array->memAPI = memAPI;
|
array->memAPI = memAPI;
|
||||||
|
|
||||||
size_t allocateSize = (size_t)capacity * sizeof(T);
|
size_t allocateSize = (size_t)capacity * sizeof(T);
|
||||||
|
|
||||||
DqnMemAPICallbackResult memResult = {};
|
DqnMemAPICallbackResult memResult = {};
|
||||||
DqnMemAPICallbackInfo info =
|
DqnMemAPICallbackInfo info = DqnMemAPICallback_InfoAskAllocInternal(array->memAPI, allocateSize);
|
||||||
DqnMemAPICallback_InfoAskAllocInternal(array->memAPI, allocateSize);
|
|
||||||
array->memAPI.callback(info, &memResult);
|
array->memAPI.callback(info, &memResult);
|
||||||
DQN_ASSERT(memResult.type == DqnMemAPICallbackType_Alloc);
|
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
|
// DqnMemBuffer - Memory API, For using custom allocators
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
FILE_SCOPE DqnMemBufferBlock *
|
DQN_FILE_SCOPE DqnMemBufferBlock *
|
||||||
DqnMemBuffer_AllocBlockInternal(u32 byteAlign, size_t size)
|
DqnMemBuffer_AllocateBlockInternal(u32 byteAlign, size_t size)
|
||||||
{
|
{
|
||||||
size_t alignedSize = DQN_ALIGN_POW_N(size, byteAlign);
|
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);
|
DqnMemBufferBlock *result = (DqnMemBufferBlock *)DqnMem_Calloc(totalSize);
|
||||||
if (!result) return NULL;
|
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->size = alignedSize;
|
||||||
result->used = 0;
|
result->used = 0;
|
||||||
return result;
|
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,
|
DQN_FILE_SCOPE bool DqnMemBuffer_Init(DqnMemBuffer *const buffer, size_t size,
|
||||||
const bool clearToZero,
|
const bool zeroClear,
|
||||||
const u32 byteAlign)
|
const u32 byteAlign)
|
||||||
{
|
{
|
||||||
if (!buffer || size <= 0) return false;
|
if (!buffer || size <= 0) return false;
|
||||||
DQN_ASSERT(!buffer->block);
|
DQN_ASSERT(!buffer->block);
|
||||||
|
|
||||||
buffer->block = DqnMemBuffer_AllocBlockInternal(byteAlign, size);
|
buffer->block = DqnMemBuffer_AllocateBlockInternal(byteAlign, size);
|
||||||
if (!buffer->block) return false;
|
if (!buffer->block) return false;
|
||||||
|
|
||||||
buffer->tempBufferCount = 0;
|
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,
|
DQN_FILE_SCOPE bool DqnMemBuffer_InitWithFixedSize(DqnMemBuffer *const buffer,
|
||||||
size_t size,
|
size_t size,
|
||||||
const bool clearToZero,
|
const bool zeroClear,
|
||||||
const u32 byteAlign)
|
const u32 byteAlign)
|
||||||
{
|
{
|
||||||
bool result = DqnMemBuffer_Init(buffer, size, 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;
|
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)
|
if (buffer->flags & DqnMemBufferFlag_IsExpandable)
|
||||||
{
|
{
|
||||||
size_t newBlockSize = DQN_MAX(alignedSize, buffer->block->size);
|
size_t newBlockSize;
|
||||||
DqnMemBufferBlock *newBlock = DqnMemBuffer_AllocBlockInternal(
|
// 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);
|
buffer->byteAlign, newBlockSize);
|
||||||
if (!newBlock) return NULL;
|
if (!newBlock) return NULL;
|
||||||
|
|
||||||
@ -1364,6 +1400,7 @@ DQN_FILE_SCOPE void *DqnMemBuffer_Allocate(DqnMemBuffer *const buffer, size_t si
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO: Better notifying to user, out of space in buffer
|
||||||
return NULL;
|
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);
|
u8 *alignedResult = (u8 *)DQN_ALIGN_POW_N(currPointer, buffer->byteAlign);
|
||||||
size_t alignmentOffset = (size_t)(alignedResult - currPointer);
|
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;
|
void *result = alignedResult;
|
||||||
buffer->block->used += (alignedSize + alignmentOffset);
|
buffer->block->used += (alignedSize + alignmentOffset);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_FILE_SCOPE void
|
DQN_FILE_SCOPE bool DqnMemBuffer_FreeBlock(DqnMemBuffer *const buffer,
|
||||||
DqnMemBuffer_FreeLastBuffer(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;
|
DqnMemBufferBlock **blockPtr = &buffer->block;
|
||||||
DqnMem_Free(buffer->block);
|
|
||||||
buffer->block = prevBlock;
|
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
|
// No more blocks, then last block has been freed
|
||||||
if (!buffer->block) DQN_ASSERT(buffer->tempBufferCount == 0);
|
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)
|
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;
|
if (buffer->flags & DqnMemBufferFlag_IsFixedMemoryFromUser) return;
|
||||||
|
|
||||||
while (buffer->block)
|
while (buffer->block)
|
||||||
{
|
DqnMemBuffer_FreeLastBlock(buffer);
|
||||||
DqnMemBuffer_FreeLastBuffer(buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_FILE_SCOPE void DqnMemBuffer_ClearCurrBlock(DqnMemBuffer *const buffer,
|
DQN_FILE_SCOPE void DqnMemBuffer_ClearCurrBlock(DqnMemBuffer *const buffer,
|
||||||
const bool clearToZero)
|
const bool zeroClear)
|
||||||
{
|
{
|
||||||
if (!buffer) return;
|
if (!buffer) return;
|
||||||
if (buffer->block)
|
if (buffer->block)
|
||||||
{
|
{
|
||||||
buffer->block->used = 0;
|
buffer->block->used = 0;
|
||||||
if (clearToZero)
|
if (zeroClear)
|
||||||
{
|
{
|
||||||
DqnMem_Clear(buffer->block->memory, 0,
|
DqnMem_Clear(buffer->block->memory, 0,
|
||||||
buffer->block->size);
|
buffer->block->size);
|
||||||
@ -1433,7 +1494,7 @@ DQN_FILE_SCOPE void DqnMemBuffer_EndTempRegion(DqnTempBuffer tempBuffer)
|
|||||||
{
|
{
|
||||||
DqnMemBuffer *buffer = tempBuffer.buffer;
|
DqnMemBuffer *buffer = tempBuffer.buffer;
|
||||||
while (buffer->block != tempBuffer.startingBlock)
|
while (buffer->block != tempBuffer.startingBlock)
|
||||||
DqnMemBuffer_FreeLastBuffer(buffer);
|
DqnMemBuffer_FreeLastBlock(buffer);
|
||||||
|
|
||||||
if (buffer->block)
|
if (buffer->block)
|
||||||
{
|
{
|
||||||
@ -1483,7 +1544,6 @@ FILE_SCOPE DqnMemAPICallbackInfo DqnMemAPICallback_InfoAskFreeInternal(
|
|||||||
|
|
||||||
void DqnMemAPI_ValidateCallbackInfo(DqnMemAPICallbackInfo info)
|
void DqnMemAPI_ValidateCallbackInfo(DqnMemAPICallbackInfo info)
|
||||||
{
|
{
|
||||||
DQN_ASSERT(!info.userContext);
|
|
||||||
DQN_ASSERT(info.type != DqnMemAPICallbackType_Invalid);
|
DQN_ASSERT(info.type != DqnMemAPICallbackType_Invalid);
|
||||||
|
|
||||||
switch(info.type)
|
switch(info.type)
|
||||||
@ -1513,6 +1573,7 @@ FILE_SCOPE void
|
|||||||
DqnMemAPI_DefaultUseCallocCallbackInternal(DqnMemAPICallbackInfo info,
|
DqnMemAPI_DefaultUseCallocCallbackInternal(DqnMemAPICallbackInfo info,
|
||||||
DqnMemAPICallbackResult *result)
|
DqnMemAPICallbackResult *result)
|
||||||
{
|
{
|
||||||
|
DQN_ASSERT(!info.userContext);
|
||||||
DqnMemAPI_ValidateCallbackInfo(info);
|
DqnMemAPI_ValidateCallbackInfo(info);
|
||||||
switch(info.type)
|
switch(info.type)
|
||||||
{
|
{
|
||||||
@ -1547,6 +1608,7 @@ FILE_SCOPE void
|
|||||||
DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info,
|
DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info,
|
||||||
DqnMemAPICallbackResult *result)
|
DqnMemAPICallbackResult *result)
|
||||||
{
|
{
|
||||||
|
DQN_ASSERT(info.userContext);
|
||||||
DqnMemAPI_ValidateCallbackInfo(info);
|
DqnMemAPI_ValidateCallbackInfo(info);
|
||||||
DqnMemBuffer *const buffer = static_cast<DqnMemBuffer *>(info.userContext);
|
DqnMemBuffer *const buffer = static_cast<DqnMemBuffer *>(info.userContext);
|
||||||
|
|
||||||
@ -1560,38 +1622,55 @@ DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info,
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case DqnMemAPICallbackType_Realloc:
|
||||||
{
|
{
|
||||||
// NOTE(doyle): Realloc implies that our data must be contiguous.
|
// NOTE(doyle): Realloc implies that we want our data to be
|
||||||
// We also assert that the supplied memory buffer is brand new, i.e.
|
// contiguous. We also enforce (assert) in init that the supplied
|
||||||
// the memory buffer is used exclusively for the MemAPI operations.
|
// memory buffer is brand new, i.e. the memory buffer is used
|
||||||
// So if we don't have enough space, we can free the entire memory
|
// exclusively for the MemAPI operations. So if we don't have enough
|
||||||
// buffer and reallocate.
|
// 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(result);
|
||||||
DQN_ASSERT(!buffer->block->prevBlock);
|
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
|
// TODO(doyle): In regards to above, we have no way of ensuring that
|
||||||
// the user doesn't use this buffer elsewhere, which would
|
// the user doesn't use this buffer elsewhere, which would
|
||||||
// invalidate all our assumptions. We can fix this maybe, by making
|
// invalidate all our assumptions. We can fix this maybe, by making
|
||||||
// the DefaultUseMemBuffer allocate its own DqnMemBuffer privately
|
// the DefaultUseMemBuffer allocate its own DqnMemBuffer privately
|
||||||
// that the user can't see externally to enforce this invariant.
|
// 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);
|
size_t alignedSize = DQN_ALIGN_POW_N(info.newRequestSize, buffer->byteAlign);
|
||||||
if ((buffer->block->used + alignedSize) > buffer->block->size)
|
if (alignedSize > buffer->block->size)
|
||||||
DqnMemBuffer_Free(buffer);
|
|
||||||
|
|
||||||
result->newMemPtr = DqnMemBuffer_Allocate(buffer, alignedSize);
|
|
||||||
if (!result->newMemPtr)
|
|
||||||
{
|
{
|
||||||
// TODO(doyle): This should be big warning. If it failed, system
|
DqnMemBufferBlock *oldBlock = buffer->block;
|
||||||
// must be out of memory. Since we are using a push buffer, and
|
result->newMemPtr = DqnMemBuffer_Allocate(buffer, alignedSize);
|
||||||
// we don't clear to zero on free AND we ensure that there's
|
|
||||||
// only ever a singular block used.. then technically we can
|
if (result->newMemPtr)
|
||||||
// restore data by restoring the block metadata which should
|
{
|
||||||
// preserve the old data living here.
|
DqnMemBufferBlock *newBlock = buffer->block;
|
||||||
*buffer->block = tmpCopy;
|
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);
|
DQN_ASSERT(!buffer->block->prevBlock);
|
||||||
}
|
}
|
||||||
@ -1602,6 +1681,7 @@ DqnMemAPI_DefaultUseMemBufferCallbackInternal(DqnMemAPICallbackInfo info,
|
|||||||
// TODO(doyle): Freeing technically works, if we enforce that the
|
// TODO(doyle): Freeing technically works, if we enforce that the
|
||||||
// MemBuffer exclusively belongs to the MemAPI it's linked to.
|
// MemBuffer exclusively belongs to the MemAPI it's linked to.
|
||||||
if (result) result->type = info.type;
|
if (result) result->type = info.type;
|
||||||
|
DQN_ASSERT(info.ptrToFree == buffer->block->memory);
|
||||||
DqnMemBuffer_Free(buffer);
|
DqnMemBuffer_Free(buffer);
|
||||||
}
|
}
|
||||||
break;
|
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.
|
// TODO(doyle): We assert that the buffer has to be a brand new mem buffer.
|
||||||
// Is this the correct design choice?
|
// Is this the correct design choice?
|
||||||
|
DQN_ASSERT(buffer && buffer->block);
|
||||||
DQN_ASSERT(!buffer->block->prevBlock && buffer->block->used == 0);
|
DQN_ASSERT(!buffer->block->prevBlock && buffer->block->used == 0);
|
||||||
DQN_ASSERT(buffer);
|
|
||||||
|
|
||||||
DqnMemAPI result = {};
|
DqnMemAPI result = {};
|
||||||
result.callback = DqnMemAPI_DefaultUseMemBufferCallbackInternal;
|
result.callback = DqnMemAPI_DefaultUseMemBufferCallbackInternal;
|
||||||
|
@ -668,177 +668,262 @@ void VecTest()
|
|||||||
printf("VecTest(): Completed successfully\n");
|
printf("VecTest(): Completed successfully\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrayTest()
|
void ArrayTestMemAPIInternal(DqnArray<DqnV2> *array, DqnMemAPI memAPI)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
DqnArray<DqnV2> array = {};
|
DQN_ASSERT(DqnArray_Init(array, 1, memAPI));
|
||||||
DQN_ASSERT(DqnArray_Init(&array, 1));
|
DQN_ASSERT(array->capacity == 1);
|
||||||
DQN_ASSERT(array.capacity == 1);
|
DQN_ASSERT(array->count == 0);
|
||||||
DQN_ASSERT(array.count == 0);
|
|
||||||
|
|
||||||
// Test basic insert
|
// Test basic insert
|
||||||
{
|
{
|
||||||
DqnV2 va = DqnV2_2f(5, 10);
|
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(DqnV2_Equals(va, vb));
|
||||||
|
|
||||||
DQN_ASSERT(array.capacity == 1);
|
DQN_ASSERT(array->capacity == 1);
|
||||||
DQN_ASSERT(array.count == 1);
|
DQN_ASSERT(array->count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test array resizing and freeing
|
// Test array resizing and freeing
|
||||||
{
|
{
|
||||||
DqnV2 va = DqnV2_2f(10, 15);
|
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);
|
DQN_ASSERT(DqnV2_Equals(va, vb) == false);
|
||||||
|
|
||||||
vb = array.data[1];
|
vb = array->data[1];
|
||||||
DQN_ASSERT(DqnV2_Equals(va, vb) == true);
|
DQN_ASSERT(DqnV2_Equals(va, vb) == true);
|
||||||
|
|
||||||
DQN_ASSERT(array.capacity == 2);
|
DQN_ASSERT(array->capacity == 2);
|
||||||
DQN_ASSERT(array.count == 2);
|
DQN_ASSERT(array->count == 2);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 3);
|
DQN_ASSERT(array->capacity == 3);
|
||||||
DQN_ASSERT(array.count == 3);
|
DQN_ASSERT(array->count == 3);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 4);
|
DQN_ASSERT(array->capacity == 4);
|
||||||
DQN_ASSERT(array.count == 4);
|
DQN_ASSERT(array->count == 4);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 5);
|
DQN_ASSERT(array->capacity == 5);
|
||||||
DQN_ASSERT(array.count == 5);
|
DQN_ASSERT(array->count == 5);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 6);
|
DQN_ASSERT(array->capacity == 6);
|
||||||
DQN_ASSERT(array.count == 6);
|
DQN_ASSERT(array->count == 6);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 7);
|
DQN_ASSERT(array->capacity == 7);
|
||||||
DQN_ASSERT(array.count == 7);
|
DQN_ASSERT(array->count == 7);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 8);
|
DQN_ASSERT(array->capacity == 8);
|
||||||
DQN_ASSERT(array.count == 8);
|
DQN_ASSERT(array->count == 8);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 9);
|
DQN_ASSERT(array->capacity == 9);
|
||||||
DQN_ASSERT(array.count == 9);
|
DQN_ASSERT(array->count == 9);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 10);
|
DQN_ASSERT(array->capacity == 10);
|
||||||
DQN_ASSERT(array.count == 10);
|
DQN_ASSERT(array->count == 10);
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Push(&array, va));
|
DQN_ASSERT(DqnArray_Push(array, va));
|
||||||
DQN_ASSERT(array.capacity == 12);
|
DQN_ASSERT(array->capacity == 12);
|
||||||
DQN_ASSERT(array.count == 11);
|
DQN_ASSERT(array->count == 11);
|
||||||
|
|
||||||
DqnV2 vc = DqnV2_2f(90, 100);
|
DqnV2 vc = DqnV2_2f(90, 100);
|
||||||
DQN_ASSERT(DqnArray_Push(&array, vc));
|
DQN_ASSERT(DqnArray_Push(array, vc));
|
||||||
DQN_ASSERT(array.capacity == 12);
|
DQN_ASSERT(array->capacity == 12);
|
||||||
DQN_ASSERT(array.count == 12);
|
DQN_ASSERT(array->count == 12);
|
||||||
DQN_ASSERT(DqnV2_Equals(vc, array.data[11]));
|
DQN_ASSERT(DqnV2_Equals(vc, array->data[11]));
|
||||||
|
|
||||||
DQN_ASSERT(DqnArray_Free(&array) == true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DQN_ASSERT(DqnArray_Free(array));
|
||||||
|
|
||||||
{
|
{
|
||||||
DqnArray<f32> array = {};
|
DQN_ASSERT(DqnArray_Init(array, 1, memAPI));
|
||||||
DQN_ASSERT(DqnArray_Init(&array, 1));
|
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.capacity == 1);
|
||||||
DQN_ASSERT(array.count == 0);
|
DQN_ASSERT(array.count == 0);
|
||||||
DqnArray_Free(&array);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// Fill the only slot in the array
|
||||||
DqnV2 a = DqnV2_2f(1, 2);
|
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, 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);
|
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");
|
printf("ArrayTest(): Completed successfully\n");
|
||||||
@ -895,12 +980,12 @@ void FileTest()
|
|||||||
|
|
||||||
void MemBufferTest()
|
void MemBufferTest()
|
||||||
{
|
{
|
||||||
// Expandable memory buffer
|
// Test over allocation, alignments, temp regions
|
||||||
{
|
{
|
||||||
size_t allocSize = DQN_KILOBYTE(1);
|
size_t allocSize = DQN_KILOBYTE(1);
|
||||||
DqnMemBuffer buffer = {};
|
DqnMemBuffer buffer = {};
|
||||||
const u32 ALIGNMENT = 4;
|
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 && buffer.block->memory);
|
||||||
DQN_ASSERT(buffer.block->size == allocSize);
|
DQN_ASSERT(buffer.block->size == allocSize);
|
||||||
DQN_ASSERT(buffer.block->used == 0);
|
DQN_ASSERT(buffer.block->used == 0);
|
||||||
@ -931,10 +1016,8 @@ void MemBufferTest()
|
|||||||
DQN_ASSERT(buffer.block->size == DQN_KILOBYTE(2));
|
DQN_ASSERT(buffer.block->size == DQN_KILOBYTE(2));
|
||||||
|
|
||||||
// Since we alignment the pointers we return they can be within 0-3
|
// Since we alignment the pointers we return they can be within 0-3
|
||||||
// bytes of
|
// bytes of what we expect and since this is in a new block as well used
|
||||||
// what we expect and since this is in a new block as well used will
|
// will reflect just this allocation.
|
||||||
// reflect
|
|
||||||
// just this allocation.
|
|
||||||
DQN_ASSERT(buffer.block->used >= sizeB + 0 &&
|
DQN_ASSERT(buffer.block->used >= sizeB + 0 &&
|
||||||
buffer.block->used <= sizeB + 3);
|
buffer.block->used <= sizeB + 3);
|
||||||
DQN_ASSERT(resultB);
|
DQN_ASSERT(resultB);
|
||||||
@ -994,7 +1077,7 @@ void MemBufferTest()
|
|||||||
DQN_ASSERT(buffer.byteAlign == ALIGNMENT);
|
DQN_ASSERT(buffer.byteAlign == ALIGNMENT);
|
||||||
|
|
||||||
// Release the last linked buffer from the push buffer
|
// Release the last linked buffer from the push buffer
|
||||||
DqnMemBuffer_FreeLastBuffer(&buffer);
|
DqnMemBuffer_FreeLastBlock(&buffer);
|
||||||
|
|
||||||
// Which should return back to the 1st allocation
|
// Which should return back to the 1st allocation
|
||||||
DQN_ASSERT(buffer.block == blockA);
|
DQN_ASSERT(buffer.block == blockA);
|
||||||
@ -1004,7 +1087,7 @@ void MemBufferTest()
|
|||||||
DQN_ASSERT(buffer.byteAlign == ALIGNMENT);
|
DQN_ASSERT(buffer.byteAlign == ALIGNMENT);
|
||||||
|
|
||||||
// Free once more to release buffer A memory
|
// Free once more to release buffer A memory
|
||||||
DqnMemBuffer_FreeLastBuffer(&buffer);
|
DqnMemBuffer_FreeLastBlock(&buffer);
|
||||||
DQN_ASSERT(!buffer.block);
|
DQN_ASSERT(!buffer.block);
|
||||||
DQN_ASSERT(buffer.byteAlign == ALIGNMENT);
|
DQN_ASSERT(buffer.byteAlign == ALIGNMENT);
|
||||||
DQN_ASSERT(buffer.tempBufferCount == 0);
|
DQN_ASSERT(buffer.tempBufferCount == 0);
|
||||||
@ -1028,7 +1111,7 @@ void MemBufferTest()
|
|||||||
|
|
||||||
// Check free does nothing
|
// Check free does nothing
|
||||||
DqnMemBuffer_Free(&buffer);
|
DqnMemBuffer_Free(&buffer);
|
||||||
DqnMemBuffer_FreeLastBuffer(&buffer);
|
DqnMemBuffer_FreeLastBlock(&buffer);
|
||||||
DQN_ASSERT(buffer.block && buffer.block->memory);
|
DQN_ASSERT(buffer.block && buffer.block->memory);
|
||||||
DQN_ASSERT(buffer.block->size ==
|
DQN_ASSERT(buffer.block->size ==
|
||||||
DQN_ARRAY_COUNT(memory) - sizeof(DqnMemBufferBlock));
|
DQN_ARRAY_COUNT(memory) - sizeof(DqnMemBufferBlock));
|
||||||
@ -1042,7 +1125,7 @@ void MemBufferTest()
|
|||||||
size_t allocSize = DQN_KILOBYTE(1);
|
size_t allocSize = DQN_KILOBYTE(1);
|
||||||
DqnMemBuffer buffer = {};
|
DqnMemBuffer buffer = {};
|
||||||
const u32 ALIGNMENT = 4;
|
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 && buffer.block->memory);
|
||||||
DQN_ASSERT(buffer.block->size == allocSize);
|
DQN_ASSERT(buffer.block->size == allocSize);
|
||||||
DQN_ASSERT(buffer.block->used == 0);
|
DQN_ASSERT(buffer.block->used == 0);
|
||||||
@ -1058,6 +1141,190 @@ void MemBufferTest()
|
|||||||
DqnMemBuffer_Free(&buffer);
|
DqnMemBuffer_Free(&buffer);
|
||||||
DQN_ASSERT(!buffer.block);
|
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)
|
int main(void)
|
||||||
|
@ -21,7 +21,7 @@ Global
|
|||||||
Release|x86 = Release|x86
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{87785192-6F49-4F85-AA0D-F0AFA5CCCDDA}.Release|x86.ActiveCfg = Release|x64
|
{87785192-6F49-4F85-AA0D-F0AFA5CCCDDA}.Release|x86.ActiveCfg = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
Loading…
Reference in New Issue
Block a user