Cleanup DqnVHashTable, FixedString and add DqnCatalog
This commit is contained in:
parent
34907eeb04
commit
6d6565c605
522
DqnMemStack.cpp
Normal file
522
DqnMemStack.cpp
Normal file
@ -0,0 +1,522 @@
|
||||
FILE_SCOPE void DqnMemStack_Test()
|
||||
{
|
||||
LOG_HEADER();
|
||||
|
||||
// Check Alignment
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
i32 const ALIGN64 = 64;
|
||||
i32 const ALIGN16 = 16;
|
||||
i32 const ALIGN4 = 4;
|
||||
DqnMemStack::AllocTo allocTo = DqnMemStack::AllocTo::Head;
|
||||
if (1)
|
||||
{
|
||||
u8 *result1 = (u8 *)stack.Push(2, allocTo, ALIGN4);
|
||||
u8 *result2 = (u8 *)DQN_ALIGN_POW_N(result1, ALIGN4);
|
||||
DQN_ASSERT(result1 == result2);
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
}
|
||||
|
||||
if (1)
|
||||
{
|
||||
u8 *result1 = (u8 *)stack.Push(120, allocTo, ALIGN16);
|
||||
u8 *result2 = (u8 *)DQN_ALIGN_POW_N(result1, ALIGN16);
|
||||
DQN_ASSERT(result1 == result2);
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
}
|
||||
|
||||
if (1)
|
||||
{
|
||||
u8 *result1 = (u8 *)stack.Push(12, allocTo, ALIGN64);
|
||||
u8 *result2 = (u8 *)DQN_ALIGN_POW_N(result1, ALIGN64);
|
||||
DQN_ASSERT(result1 == result2);
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
}
|
||||
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Check allocated alignment to 4, 16, 64");
|
||||
}
|
||||
|
||||
// Check Non-Expandable
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::NonExpandable);
|
||||
auto *result1 = stack.Push(DQN_MEGABYTE(2));
|
||||
DQN_ASSERT(result1 == nullptr);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Check non-expandable flag prevents expansion.");
|
||||
}
|
||||
|
||||
// Check Expansion
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes);
|
||||
DQN_ASSERT(stack.tracker.boundsGuardSize == 0);
|
||||
|
||||
auto *oldBlock = stack.block;
|
||||
DQN_ASSERT(oldBlock);
|
||||
DQN_ASSERT(oldBlock->size == DQN_MEGABYTE(1));
|
||||
DQN_ASSERT(oldBlock->head == oldBlock->head);
|
||||
DQN_ASSERT(oldBlock->tail == oldBlock->tail);
|
||||
DQN_ASSERT(oldBlock->prevBlock == nullptr);
|
||||
|
||||
auto *result1 = stack.Push(DQN_MEGABYTE(2));
|
||||
DQN_ASSERT(result1);
|
||||
DQN_ASSERT(stack.block->prevBlock == oldBlock);
|
||||
DQN_ASSERT(stack.block != oldBlock);
|
||||
|
||||
Log(Status::Ok, "Check memory stack allocates additional memory blocks.");
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Temporary Regions
|
||||
if (1)
|
||||
{
|
||||
// Check temporary regions
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
DqnMemStack::Block *blockToReturnTo = stack.block;
|
||||
auto headBefore = blockToReturnTo->head;
|
||||
auto tailBefore = blockToReturnTo->tail;
|
||||
if (1)
|
||||
{
|
||||
auto memGuard1 = stack.TempRegionGuard();
|
||||
auto *result2 = stack.Push(100);
|
||||
auto *result3 = stack.Push(100);
|
||||
auto *result4 = stack.Push(100);
|
||||
DQN_ASSERT(result2 && result3 && result4);
|
||||
DQN_ASSERT(stack.block->head != headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
DQN_ASSERT(stack.block->memory == blockToReturnTo->memory);
|
||||
|
||||
// Force allocation of new block
|
||||
auto *result5 = stack.Push(DQN_MEGABYTE(5));
|
||||
DQN_ASSERT(result5);
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 1);
|
||||
}
|
||||
|
||||
DQN_ASSERT(stack.block == blockToReturnTo);
|
||||
DQN_ASSERT(stack.block->head == headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Check temporary regions keep state
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
DqnMemStack::Block *blockToReturnTo = stack.block;
|
||||
auto headBefore = blockToReturnTo->head;
|
||||
auto tailBefore = blockToReturnTo->tail;
|
||||
if (1)
|
||||
{
|
||||
auto memGuard1 = stack.TempRegionGuard();
|
||||
auto *result2 = stack.Push(100);
|
||||
auto *result3 = stack.Push(100);
|
||||
auto *result4 = stack.Push(100);
|
||||
DQN_ASSERT(result2 && result3 && result4);
|
||||
DQN_ASSERT(stack.block->head != headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
DQN_ASSERT(stack.block->memory == blockToReturnTo->memory);
|
||||
|
||||
// Force allocation of new block
|
||||
auto *result5 = stack.Push(DQN_MEGABYTE(5));
|
||||
DQN_ASSERT(result5);
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 1);
|
||||
memGuard1.region.keepHeadChanges = true;
|
||||
}
|
||||
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.block->prevBlock == blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 0);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Check temporary regions with tail and head pushes
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
auto *pop1 = stack.Push(222);
|
||||
auto *pop2 = stack.Push(333, DqnMemStack::AllocTo::Tail);
|
||||
|
||||
DqnMemStack::Block *blockToReturnTo = stack.block;
|
||||
auto headBefore = blockToReturnTo->head;
|
||||
auto tailBefore = blockToReturnTo->tail;
|
||||
if (1)
|
||||
{
|
||||
auto memGuard1 = stack.TempRegionGuard();
|
||||
auto *result2 = stack.Push(100);
|
||||
auto *result3 = stack.Push(100, DqnMemStack::AllocTo::Tail);
|
||||
auto *result4 = stack.Push(100);
|
||||
auto *result5 = stack.Push(100, DqnMemStack::AllocTo::Tail);
|
||||
DQN_ASSERT(result2 && result3 && result4 && result5);
|
||||
DQN_ASSERT(result3 > result5);
|
||||
DQN_ASSERT(result2 < result4);
|
||||
DQN_ASSERT(stack.block->head > headBefore && stack.block->head < stack.block->tail);
|
||||
DQN_ASSERT(stack.block->tail >= stack.block->head && stack.block->tail < (stack.block->memory + stack.block->size));
|
||||
DQN_ASSERT(stack.block->memory == blockToReturnTo->memory);
|
||||
|
||||
// Force allocation of new block
|
||||
auto *result6 = stack.Push(DQN_MEGABYTE(5));
|
||||
DQN_ASSERT(result6);
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 1);
|
||||
}
|
||||
|
||||
DQN_ASSERT(stack.block == blockToReturnTo);
|
||||
DQN_ASSERT(stack.block->head == headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
|
||||
stack.Pop(pop1);
|
||||
stack.Pop(pop2);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
Log(Status::Ok, "Temporary regions return state and/or keep changes if requested.");
|
||||
}
|
||||
|
||||
// Check Fixed Mem Init
|
||||
if (1)
|
||||
{
|
||||
// Check success
|
||||
if (1)
|
||||
{
|
||||
isize const bufSize = sizeof(DqnMemStack::Block) * 5;
|
||||
char buf[bufSize] = {};
|
||||
auto stack = DqnMemStack(&buf, bufSize, Dqn::ZeroClear::No);
|
||||
|
||||
DQN_ASSERT(stack.block);
|
||||
DQN_ASSERT(stack.block->prevBlock == false);
|
||||
DQN_ASSERT(stack.tempRegionCount == 0);
|
||||
DQN_ASSERT(stack.flags == DqnMemStack::Flag::NonExpandable);
|
||||
|
||||
auto *result1 = stack.Push(32);
|
||||
DQN_ASSERT(result1);
|
||||
stack.Pop(result1);
|
||||
|
||||
auto *result2 = stack.Push(bufSize * 2);
|
||||
DQN_ASSERT(result2 == nullptr);
|
||||
DQN_ASSERT(stack.block);
|
||||
DQN_ASSERT(stack.block->prevBlock == false);
|
||||
DQN_ASSERT(stack.tempRegionCount == 0);
|
||||
DQN_ASSERT(stack.flags == DqnMemStack::Flag::NonExpandable);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
Log(Status::Ok, "Checked fixed mem initialisation");
|
||||
}
|
||||
|
||||
// Check Freeing Blocks
|
||||
if (1)
|
||||
{
|
||||
usize size = 32;
|
||||
usize additionalSize = DqnMemStack::MINIMUM_BLOCK_SIZE;
|
||||
DqnMemAPI heap = DqnMemAPI::HeapAllocator();
|
||||
|
||||
auto stack = DqnMemStack(size, Dqn::ZeroClear::Yes, 0, &heap);
|
||||
auto *block1 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result1 = stack.Push(size);
|
||||
auto *block2 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result2 = stack.Push(size);
|
||||
auto *block3 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result3 = stack.Push(size);
|
||||
auto *block4 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result4 = stack.Push(size);
|
||||
auto *block5 = stack.block;
|
||||
|
||||
DQN_ASSERT(result1 && result2 && result3 && result4);
|
||||
DQN_ASSERT(block1 && block2 && block3 && block4 && block5);
|
||||
DQN_ASSERT(block5->prevBlock == block4);
|
||||
DQN_ASSERT(block4->prevBlock == block3);
|
||||
DQN_ASSERT(block3->prevBlock == block2);
|
||||
DQN_ASSERT(block2->prevBlock == block1);
|
||||
DQN_ASSERT(block1->prevBlock == nullptr);
|
||||
|
||||
DQN_ASSERT(stack.FreeMemBlock(block4));
|
||||
DQN_ASSERT(stack.block == block5);
|
||||
DQN_ASSERT(block5->prevBlock == block3);
|
||||
DQN_ASSERT(block3->prevBlock == block2);
|
||||
DQN_ASSERT(block2->prevBlock == block1);
|
||||
DQN_ASSERT(block1->prevBlock == nullptr);
|
||||
|
||||
DQN_ASSERT(stack.FreeMemBlock(block5));
|
||||
DQN_ASSERT(stack.block == block3);
|
||||
DQN_ASSERT(block3->prevBlock == block2);
|
||||
DQN_ASSERT(block2->prevBlock == block1);
|
||||
DQN_ASSERT(block1->prevBlock == nullptr);
|
||||
|
||||
stack.Free();
|
||||
DQN_ASSERT(stack.memAPI->bytesAllocated == 0);
|
||||
DQN_ASSERT(stack.block == nullptr);
|
||||
Log(Status::Ok, "Check freeing arbitrary blocks and freeing");
|
||||
}
|
||||
|
||||
// Check bounds guard places magic values
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
char *result = static_cast<char *>(stack.Push(64));
|
||||
|
||||
// TODO(doyle): check head and tail are adjacent to the bounds of the allocation
|
||||
u32 *head = stack.tracker.PtrToHeadGuard(result);
|
||||
u32 *tail = stack.tracker.PtrToTailGuard(result);
|
||||
DQN_ASSERT(*head == DqnMemTracker::HEAD_GUARD_VALUE);
|
||||
DQN_ASSERT(*tail == DqnMemTracker::TAIL_GUARD_VALUE);
|
||||
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Bounds guards are placed adjacent and have magic values.");
|
||||
}
|
||||
|
||||
if (1)
|
||||
{
|
||||
// Push to tail and head
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
auto *result1 = stack.Push(100);
|
||||
auto *result2 = stack.Push(100, DqnMemStack::AllocTo::Tail);
|
||||
auto *headBefore = stack.block->head;
|
||||
auto *tailBefore = stack.block->tail;
|
||||
DQN_ASSERT(result2 && result1);
|
||||
DQN_ASSERT(result2 != result1 && result1 < result2);
|
||||
|
||||
stack.Pop(result2);
|
||||
DQN_ASSERT(headBefore == stack.block->head)
|
||||
DQN_ASSERT(tailBefore != stack.block->tail)
|
||||
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->prevBlock == false);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Push, pop to tail and head.");
|
||||
}
|
||||
|
||||
// Expansion with tail
|
||||
if (1)
|
||||
{
|
||||
// Push too much to tail causes expansion
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
auto *result1 = stack.Push(100);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
DQN_ASSERT(stack.block->head > stack.block->memory && stack.block->head < stack.block->tail);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
auto *blockBefore = stack.block;
|
||||
|
||||
auto *result2 = stack.Push(DQN_MEGABYTE(1), DqnMemStack::AllocTo::Tail);
|
||||
DQN_ASSERT(result2 && result1);
|
||||
DQN_ASSERT(result2 != result1);
|
||||
DQN_ASSERT(stack.block->prevBlock == blockBefore);
|
||||
DQN_ASSERT(stack.block != blockBefore);
|
||||
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail < stack.block->memory + stack.block->size &&
|
||||
stack.block->tail >= stack.block->head);
|
||||
|
||||
stack.Pop(result2);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Push too much to tail fails to expand when non expandable
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::NonExpandable);
|
||||
|
||||
auto *result1 = stack.Push(100);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
DQN_ASSERT(stack.block->head != stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
auto *blockBefore = stack.block;
|
||||
|
||||
auto *result2 = stack.Push(DQN_MEGABYTE(1), DqnMemStack::AllocTo::Tail);
|
||||
DQN_ASSERT(result2 == nullptr);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
DQN_ASSERT(stack.block == blockBefore);
|
||||
DQN_ASSERT(stack.block->head > stack.block->memory && stack.block->head < stack.block->tail);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
|
||||
stack.Pop(result2);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
Log(Status::Ok, "Non-Expanding and expanding stack with tail push.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check stack allocator mem api callbacks
|
||||
if (1)
|
||||
{
|
||||
// Realloc in same block and allow it to grow in place.
|
||||
if (1)
|
||||
{
|
||||
// Using push on head
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *headBefore = stack.block->head;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = 32;
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(headBefore < stack.block->head);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(headBefore == stack.block->head);
|
||||
DQN_ASSERT(headBefore == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Using push on tail
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *tailBefore = stack.block->tail;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize, DqnMemStack::AllocTo::Tail);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = 32;
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(tailBefore > stack.block->tail);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(tailBefore == stack.block->tail);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
Log(Status::Ok, "Allocator MemAPI callback, realloc grow in place");
|
||||
}
|
||||
|
||||
// Realloc in same block and insufficient size and expand
|
||||
if (1)
|
||||
{
|
||||
// Using push on head
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *headBefore = stack.block->head;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = DQN_MEGABYTE(2);
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block->prevBlock);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(headBefore == stack.block->head);
|
||||
DQN_ASSERT(headBefore == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Using push on tail
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *tailBefore = stack.block->tail;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize, DqnMemStack::AllocTo::Tail);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = DQN_MEGABYTE(2);
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++)
|
||||
DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore != stack.block);
|
||||
DQN_ASSERT(blockBefore == stack.block->prevBlock);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(tailBefore == stack.block->tail);
|
||||
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
Log(Status::Ok, "Allocator MemAPI callback, realloc insufficient size so expand");
|
||||
}
|
||||
|
||||
// TODO(doyle): Realloc to smaller size logic
|
||||
}
|
||||
}
|
||||
|
590
DqnUnitTest.cpp
590
DqnUnitTest.cpp
@ -141,6 +141,7 @@ void LogHeader(char const *funcName)
|
||||
#include "DqnOS.cpp"
|
||||
#include "DqnJson.cpp"
|
||||
#include "DqnVHashTable.cpp"
|
||||
#include "DqnMemStack.cpp"
|
||||
|
||||
void HandmadeMathVerifyMat4(DqnMat4 dqnMat, hmm_mat4 hmmMat)
|
||||
{
|
||||
@ -2321,548 +2322,69 @@ void DqnMemSet_Test()
|
||||
Log(Status::Ok, "MemSet");
|
||||
}
|
||||
|
||||
FILE_SCOPE void DqnMemStack_Test()
|
||||
struct RawBuf
|
||||
{
|
||||
LOG_HEADER();
|
||||
char *buffer;
|
||||
int len;
|
||||
};
|
||||
|
||||
// Check Alignment
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
i32 const ALIGN64 = 64;
|
||||
i32 const ALIGN16 = 16;
|
||||
i32 const ALIGN4 = 4;
|
||||
DqnMemStack::AllocTo allocTo = DqnMemStack::AllocTo::Head;
|
||||
if (1)
|
||||
{
|
||||
u8 *result1 = (u8 *)stack.Push(2, allocTo, ALIGN4);
|
||||
u8 *result2 = (u8 *)DQN_ALIGN_POW_N(result1, ALIGN4);
|
||||
DQN_ASSERT(result1 == result2);
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
}
|
||||
|
||||
if (1)
|
||||
{
|
||||
u8 *result1 = (u8 *)stack.Push(120, allocTo, ALIGN16);
|
||||
u8 *result2 = (u8 *)DQN_ALIGN_POW_N(result1, ALIGN16);
|
||||
DQN_ASSERT(result1 == result2);
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
}
|
||||
|
||||
if (1)
|
||||
{
|
||||
u8 *result1 = (u8 *)stack.Push(12, allocTo, ALIGN64);
|
||||
u8 *result2 = (u8 *)DQN_ALIGN_POW_N(result1, ALIGN64);
|
||||
DQN_ASSERT(result1 == result2);
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
}
|
||||
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Check allocated alignment to 4, 16, 64");
|
||||
}
|
||||
|
||||
// Check Non-Expandable
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::NonExpandable);
|
||||
auto *result1 = stack.Push(DQN_MEGABYTE(2));
|
||||
DQN_ASSERT(result1 == nullptr);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Check non-expandable flag prevents expansion.");
|
||||
}
|
||||
|
||||
// Check Expansion
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes);
|
||||
DQN_ASSERT(stack.tracker.boundsGuardSize == 0);
|
||||
|
||||
auto *oldBlock = stack.block;
|
||||
DQN_ASSERT(oldBlock);
|
||||
DQN_ASSERT(oldBlock->size == DQN_MEGABYTE(1));
|
||||
DQN_ASSERT(oldBlock->head == oldBlock->head);
|
||||
DQN_ASSERT(oldBlock->tail == oldBlock->tail);
|
||||
DQN_ASSERT(oldBlock->prevBlock == nullptr);
|
||||
|
||||
auto *result1 = stack.Push(DQN_MEGABYTE(2));
|
||||
DQN_ASSERT(result1);
|
||||
DQN_ASSERT(stack.block->prevBlock == oldBlock);
|
||||
DQN_ASSERT(stack.block != oldBlock);
|
||||
|
||||
Log(Status::Ok, "Check memory stack allocates additional memory blocks.");
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Temporary Regions
|
||||
if (1)
|
||||
{
|
||||
// Check temporary regions
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
DqnMemStack::Block *blockToReturnTo = stack.block;
|
||||
auto headBefore = blockToReturnTo->head;
|
||||
auto tailBefore = blockToReturnTo->tail;
|
||||
if (1)
|
||||
{
|
||||
auto memGuard1 = stack.TempRegionGuard();
|
||||
auto *result2 = stack.Push(100);
|
||||
auto *result3 = stack.Push(100);
|
||||
auto *result4 = stack.Push(100);
|
||||
DQN_ASSERT(result2 && result3 && result4);
|
||||
DQN_ASSERT(stack.block->head != headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
DQN_ASSERT(stack.block->memory == blockToReturnTo->memory);
|
||||
|
||||
// Force allocation of new block
|
||||
auto *result5 = stack.Push(DQN_MEGABYTE(5));
|
||||
DQN_ASSERT(result5);
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 1);
|
||||
}
|
||||
|
||||
DQN_ASSERT(stack.block == blockToReturnTo);
|
||||
DQN_ASSERT(stack.block->head == headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Check temporary regions keep state
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
DqnMemStack::Block *blockToReturnTo = stack.block;
|
||||
auto headBefore = blockToReturnTo->head;
|
||||
auto tailBefore = blockToReturnTo->tail;
|
||||
if (1)
|
||||
{
|
||||
auto memGuard1 = stack.TempRegionGuard();
|
||||
auto *result2 = stack.Push(100);
|
||||
auto *result3 = stack.Push(100);
|
||||
auto *result4 = stack.Push(100);
|
||||
DQN_ASSERT(result2 && result3 && result4);
|
||||
DQN_ASSERT(stack.block->head != headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
DQN_ASSERT(stack.block->memory == blockToReturnTo->memory);
|
||||
|
||||
// Force allocation of new block
|
||||
auto *result5 = stack.Push(DQN_MEGABYTE(5));
|
||||
DQN_ASSERT(result5);
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 1);
|
||||
memGuard1.region.keepHeadChanges = true;
|
||||
}
|
||||
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.block->prevBlock == blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 0);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Check temporary regions with tail and head pushes
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
auto *pop1 = stack.Push(222);
|
||||
auto *pop2 = stack.Push(333, DqnMemStack::AllocTo::Tail);
|
||||
|
||||
DqnMemStack::Block *blockToReturnTo = stack.block;
|
||||
auto headBefore = blockToReturnTo->head;
|
||||
auto tailBefore = blockToReturnTo->tail;
|
||||
if (1)
|
||||
{
|
||||
auto memGuard1 = stack.TempRegionGuard();
|
||||
auto *result2 = stack.Push(100);
|
||||
auto *result3 = stack.Push(100, DqnMemStack::AllocTo::Tail);
|
||||
auto *result4 = stack.Push(100);
|
||||
auto *result5 = stack.Push(100, DqnMemStack::AllocTo::Tail);
|
||||
DQN_ASSERT(result2 && result3 && result4 && result5);
|
||||
DQN_ASSERT(result3 > result5);
|
||||
DQN_ASSERT(result2 < result4);
|
||||
DQN_ASSERT(stack.block->head > headBefore && stack.block->head < stack.block->tail);
|
||||
DQN_ASSERT(stack.block->tail >= stack.block->head && stack.block->tail < (stack.block->memory + stack.block->size));
|
||||
DQN_ASSERT(stack.block->memory == blockToReturnTo->memory);
|
||||
|
||||
// Force allocation of new block
|
||||
auto *result6 = stack.Push(DQN_MEGABYTE(5));
|
||||
DQN_ASSERT(result6);
|
||||
DQN_ASSERT(stack.block != blockToReturnTo);
|
||||
DQN_ASSERT(stack.tempRegionCount == 1);
|
||||
}
|
||||
|
||||
DQN_ASSERT(stack.block == blockToReturnTo);
|
||||
DQN_ASSERT(stack.block->head == headBefore);
|
||||
DQN_ASSERT(stack.block->tail == tailBefore);
|
||||
|
||||
stack.Pop(pop1);
|
||||
stack.Pop(pop2);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
Log(Status::Ok, "Temporary regions return state and/or keep changes if requested.");
|
||||
}
|
||||
|
||||
// Check Fixed Mem Init
|
||||
if (1)
|
||||
{
|
||||
// Check success
|
||||
if (1)
|
||||
{
|
||||
isize const bufSize = sizeof(DqnMemStack::Block) * 5;
|
||||
char buf[bufSize] = {};
|
||||
auto stack = DqnMemStack(&buf, bufSize, Dqn::ZeroClear::No);
|
||||
|
||||
DQN_ASSERT(stack.block);
|
||||
DQN_ASSERT(stack.block->prevBlock == false);
|
||||
DQN_ASSERT(stack.tempRegionCount == 0);
|
||||
DQN_ASSERT(stack.flags == DqnMemStack::Flag::NonExpandable);
|
||||
|
||||
auto *result1 = stack.Push(32);
|
||||
DQN_ASSERT(result1);
|
||||
stack.Pop(result1);
|
||||
|
||||
auto *result2 = stack.Push(bufSize * 2);
|
||||
DQN_ASSERT(result2 == nullptr);
|
||||
DQN_ASSERT(stack.block);
|
||||
DQN_ASSERT(stack.block->prevBlock == false);
|
||||
DQN_ASSERT(stack.tempRegionCount == 0);
|
||||
DQN_ASSERT(stack.flags == DqnMemStack::Flag::NonExpandable);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
Log(Status::Ok, "Checked fixed mem initialisation");
|
||||
}
|
||||
|
||||
// Check Freeing Blocks
|
||||
if (1)
|
||||
{
|
||||
usize size = 32;
|
||||
usize additionalSize = DqnMemStack::MINIMUM_BLOCK_SIZE;
|
||||
DqnMemAPI heap = DqnMemAPI::HeapAllocator();
|
||||
|
||||
auto stack = DqnMemStack(size, Dqn::ZeroClear::Yes, 0, &heap);
|
||||
auto *block1 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result1 = stack.Push(size);
|
||||
auto *block2 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result2 = stack.Push(size);
|
||||
auto *block3 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result3 = stack.Push(size);
|
||||
auto *block4 = stack.block;
|
||||
|
||||
size += additionalSize;
|
||||
auto *result4 = stack.Push(size);
|
||||
auto *block5 = stack.block;
|
||||
|
||||
DQN_ASSERT(result1 && result2 && result3 && result4);
|
||||
DQN_ASSERT(block1 && block2 && block3 && block4 && block5);
|
||||
DQN_ASSERT(block5->prevBlock == block4);
|
||||
DQN_ASSERT(block4->prevBlock == block3);
|
||||
DQN_ASSERT(block3->prevBlock == block2);
|
||||
DQN_ASSERT(block2->prevBlock == block1);
|
||||
DQN_ASSERT(block1->prevBlock == nullptr);
|
||||
|
||||
DQN_ASSERT(stack.FreeMemBlock(block4));
|
||||
DQN_ASSERT(stack.block == block5);
|
||||
DQN_ASSERT(block5->prevBlock == block3);
|
||||
DQN_ASSERT(block3->prevBlock == block2);
|
||||
DQN_ASSERT(block2->prevBlock == block1);
|
||||
DQN_ASSERT(block1->prevBlock == nullptr);
|
||||
|
||||
DQN_ASSERT(stack.FreeMemBlock(block5));
|
||||
DQN_ASSERT(stack.block == block3);
|
||||
DQN_ASSERT(block3->prevBlock == block2);
|
||||
DQN_ASSERT(block2->prevBlock == block1);
|
||||
DQN_ASSERT(block1->prevBlock == nullptr);
|
||||
|
||||
stack.Free();
|
||||
DQN_ASSERT(stack.memAPI->bytesAllocated == 0);
|
||||
DQN_ASSERT(stack.block == nullptr);
|
||||
Log(Status::Ok, "Check freeing arbitrary blocks and freeing");
|
||||
}
|
||||
|
||||
// Check bounds guard places magic values
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
char *result = static_cast<char *>(stack.Push(64));
|
||||
|
||||
// TODO(doyle): check head and tail are adjacent to the bounds of the allocation
|
||||
u32 *head = stack.tracker.PtrToHeadGuard(result);
|
||||
u32 *tail = stack.tracker.PtrToTailGuard(result);
|
||||
DQN_ASSERT(*head == DqnMemTracker::HEAD_GUARD_VALUE);
|
||||
DQN_ASSERT(*tail == DqnMemTracker::TAIL_GUARD_VALUE);
|
||||
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Bounds guards are placed adjacent and have magic values.");
|
||||
}
|
||||
|
||||
if (1)
|
||||
{
|
||||
// Push to tail and head
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
auto *result1 = stack.Push(100);
|
||||
auto *result2 = stack.Push(100, DqnMemStack::AllocTo::Tail);
|
||||
auto *headBefore = stack.block->head;
|
||||
auto *tailBefore = stack.block->tail;
|
||||
DQN_ASSERT(result2 && result1);
|
||||
DQN_ASSERT(result2 != result1 && result1 < result2);
|
||||
|
||||
stack.Pop(result2);
|
||||
DQN_ASSERT(headBefore == stack.block->head)
|
||||
DQN_ASSERT(tailBefore != stack.block->tail)
|
||||
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(stack.block->prevBlock == false);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
stack.Free();
|
||||
Log(Status::Ok, "Push, pop to tail and head.");
|
||||
}
|
||||
|
||||
// Expansion with tail
|
||||
if (1)
|
||||
{
|
||||
// Push too much to tail causes expansion
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
|
||||
auto *result1 = stack.Push(100);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
DQN_ASSERT(stack.block->head > stack.block->memory && stack.block->head < stack.block->tail);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
auto *blockBefore = stack.block;
|
||||
|
||||
auto *result2 = stack.Push(DQN_MEGABYTE(1), DqnMemStack::AllocTo::Tail);
|
||||
DQN_ASSERT(result2 && result1);
|
||||
DQN_ASSERT(result2 != result1);
|
||||
DQN_ASSERT(stack.block->prevBlock == blockBefore);
|
||||
DQN_ASSERT(stack.block != blockBefore);
|
||||
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail < stack.block->memory + stack.block->size &&
|
||||
stack.block->tail >= stack.block->head);
|
||||
|
||||
stack.Pop(result2);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Push too much to tail fails to expand when non expandable
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::NonExpandable);
|
||||
|
||||
auto *result1 = stack.Push(100);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
DQN_ASSERT(stack.block->head != stack.block->memory);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
auto *blockBefore = stack.block;
|
||||
|
||||
auto *result2 = stack.Push(DQN_MEGABYTE(1), DqnMemStack::AllocTo::Tail);
|
||||
DQN_ASSERT(result2 == nullptr);
|
||||
DQN_ASSERT(stack.block->prevBlock == nullptr);
|
||||
DQN_ASSERT(stack.block == blockBefore);
|
||||
DQN_ASSERT(stack.block->head > stack.block->memory && stack.block->head < stack.block->tail);
|
||||
DQN_ASSERT(stack.block->tail == stack.block->memory + stack.block->size);
|
||||
|
||||
stack.Pop(result2);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Pop(result1);
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
Log(Status::Ok, "Non-Expanding and expanding stack with tail push.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check stack allocator mem api callbacks
|
||||
if (1)
|
||||
{
|
||||
// Realloc in same block and allow it to grow in place.
|
||||
if (1)
|
||||
{
|
||||
// Using push on head
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *headBefore = stack.block->head;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = 32;
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(headBefore < stack.block->head);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(headBefore == stack.block->head);
|
||||
DQN_ASSERT(headBefore == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Using push on tail
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *tailBefore = stack.block->tail;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize, DqnMemStack::AllocTo::Tail);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = 32;
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(tailBefore > stack.block->tail);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(tailBefore == stack.block->tail);
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
Log(Status::Ok, "Allocator MemAPI callback, realloc grow in place");
|
||||
}
|
||||
|
||||
// Realloc in same block and insufficient size and expand
|
||||
if (1)
|
||||
{
|
||||
// Using push on head
|
||||
if (1)
|
||||
{
|
||||
auto stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *headBefore = stack.block->head;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = DQN_MEGABYTE(2);
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block->prevBlock);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(headBefore == stack.block->head);
|
||||
DQN_ASSERT(headBefore == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
|
||||
// Using push on tail
|
||||
if (1)
|
||||
{
|
||||
DqnMemStack stack = DqnMemStack(DQN_MEGABYTE(1), Dqn::ZeroClear::Yes, DqnMemStack::Flag::BoundsGuard);
|
||||
auto *api = &stack.myHeadAPI;
|
||||
|
||||
auto *blockBefore = stack.block;
|
||||
auto *tailBefore = stack.block->tail;
|
||||
|
||||
isize bufSize = 16;
|
||||
char *buf = (char *)stack.Push(bufSize, DqnMemStack::AllocTo::Tail);
|
||||
DqnMem_Set(buf, 'X', bufSize);
|
||||
for (auto i = 0; i < bufSize; i++) DQN_ASSERT(buf[i] == 'X');
|
||||
|
||||
isize oldBufSize = bufSize;
|
||||
bufSize = DQN_MEGABYTE(2);
|
||||
buf = (char *)api->Realloc(buf, oldBufSize, bufSize);
|
||||
for (auto i = 0; i < oldBufSize; i++)
|
||||
DQN_ASSERT(buf[i] == 'X');
|
||||
DqnMem_Set(buf, '@', bufSize);
|
||||
|
||||
DQN_ASSERT(blockBefore != stack.block);
|
||||
DQN_ASSERT(blockBefore == stack.block->prevBlock);
|
||||
stack.Pop(buf);
|
||||
|
||||
DQN_ASSERT(blockBefore == stack.block);
|
||||
DQN_ASSERT(tailBefore == stack.block->tail);
|
||||
|
||||
DQN_ASSERT(stack.block->head == stack.block->memory);
|
||||
stack.Free();
|
||||
}
|
||||
Log(Status::Ok, "Allocator MemAPI callback, realloc insufficient size so expand");
|
||||
}
|
||||
|
||||
// TODO(doyle): Realloc to smaller size logic
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *DqnCatalog<T>::Get(DqnFixedString128 file)
|
||||
DQN_CATALOG_LOAD_PROC(CatalogRawLoad, RawBuf)
|
||||
{
|
||||
Entry *result = this->assetTable.GetOrMake(file.str);
|
||||
(void)result;
|
||||
usize bufSize;
|
||||
u8 *buf = DqnFile_ReadAll(file.str, &bufSize);
|
||||
|
||||
return nullptr;
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
data->buffer = reinterpret_cast<char *>(buf);
|
||||
data->len = static_cast<int>(bufSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DqnCatalog_Test()
|
||||
{
|
||||
struct TxtFile
|
||||
{
|
||||
char *buffer;
|
||||
int len;
|
||||
};
|
||||
LOG_HEADER();
|
||||
|
||||
DqnCatalog<TxtFile> textCatalog = {};
|
||||
TxtFile *file = textCatalog.Get("makefile");
|
||||
(void)file;
|
||||
DqnCatalog<RawBuf, CatalogRawLoad> textCatalog = {};
|
||||
textCatalog.PollAssets();
|
||||
|
||||
char const bufA[] = "aaaa";
|
||||
char const bufX[] = "xxxx";
|
||||
|
||||
DqnFixedString128 testFile = "DqnCatalog_TrackFile";
|
||||
|
||||
DqnFile file = {};
|
||||
|
||||
// Write file A and check we are able to open it up in the catalog
|
||||
{
|
||||
DQN_ASSERTM(file.Open(testFile.str,
|
||||
DqnFile::Permission::FileRead | DqnFile::Permission::FileWrite,
|
||||
DqnFile::Action::ForceCreate),
|
||||
"Could not create testing file for DqnCatalog");
|
||||
file.Write(reinterpret_cast<u8 const *>(bufA), DQN_CHAR_COUNT(bufA), 0);
|
||||
file.Close();
|
||||
|
||||
RawBuf *buf = textCatalog.GetIfUpdated(testFile);
|
||||
DQN_ASSERT(DqnMem_Cmp(buf, bufA, DQN_CHAR_COUNT(bufA)) == 0);
|
||||
Log(Status::Ok, "Catalog finds and loads on demand new file");
|
||||
}
|
||||
|
||||
// Write file B check that it has been updated
|
||||
{
|
||||
file = {};
|
||||
DQN_ASSERTM(file.Open(testFile.str,
|
||||
DqnFile::Permission::FileRead | DqnFile::Permission::FileWrite,
|
||||
DqnFile::Action::ForceCreate),
|
||||
"Could not create testing file for DqnCatalog");
|
||||
file.Write(reinterpret_cast<u8 const *>(bufX), DQN_CHAR_COUNT(bufX), 0);
|
||||
file.Close();
|
||||
|
||||
RawBuf *buf = textCatalog.GetIfUpdated(testFile);
|
||||
DQN_ASSERT(DqnMem_Cmp(buf, bufX, DQN_CHAR_COUNT(bufX)) == 0);
|
||||
Log(Status::Ok, "Catalog finds updated file after subsequent write");
|
||||
}
|
||||
|
||||
DqnFile_Delete(testFile.str);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
@ -2885,10 +2407,10 @@ int main(void)
|
||||
DqnJson_Test();
|
||||
|
||||
#ifdef DQN_PLATFORM_HEADER
|
||||
DqnCatalog_Test();
|
||||
DqnVHashTable_Test();
|
||||
DqnOS_Test();
|
||||
DqnFile_Test();
|
||||
DqnCatalog_Test();
|
||||
DqnTimer_Test();
|
||||
DqnJobQueue_Test();
|
||||
#endif
|
||||
|
@ -30,8 +30,7 @@ void DqnVHashTable_Test()
|
||||
}
|
||||
|
||||
{
|
||||
Block blocks[] = {{0}, {1}, {2}, {3}, {4}};
|
||||
|
||||
Block blocks[] = {{0}, {1}, {2}, {3}, {4}};
|
||||
DqnVHashTable<Height, Block> table = {};
|
||||
DQN_DEFER(table.Free());
|
||||
|
||||
@ -44,10 +43,11 @@ void DqnVHashTable_Test()
|
||||
{
|
||||
bool blockSeen[DQN_ARRAY_COUNT(blocks)] = {};
|
||||
isize blocksSeen = 0;
|
||||
for (Block const &block : table)
|
||||
for (auto const &entry : table)
|
||||
{
|
||||
DQN_ASSERT(blockSeen[block.x] == false);
|
||||
blockSeen[block.x] = true;
|
||||
Block const *block = &entry.item;
|
||||
DQN_ASSERT(blockSeen[block->x] == false);
|
||||
blockSeen[block->x] = true;
|
||||
blocksSeen++;
|
||||
}
|
||||
DQN_ASSERT(blocksSeen == DQN_ARRAY_COUNT(blockSeen));
|
||||
@ -59,12 +59,12 @@ void DqnVHashTable_Test()
|
||||
isize blocksSeen = 0;
|
||||
for (auto it = table.begin(); it != table.end();)
|
||||
{
|
||||
it = it + 1;
|
||||
Block *block = it.GetCurrItem();
|
||||
Block *block = &it.entry->item;
|
||||
|
||||
DQN_ASSERT(blockSeen[block->x] == false);
|
||||
blockSeen[block->x] = true;
|
||||
blocksSeen++;
|
||||
it = it + 1;
|
||||
}
|
||||
DQN_ASSERT(blocksSeen == DQN_ARRAY_COUNT(blockSeen));
|
||||
Log(Status::Ok, "Auto iterator using operator+");
|
||||
@ -75,7 +75,7 @@ void DqnVHashTable_Test()
|
||||
isize blocksSeen = 0;
|
||||
for (auto it = table.begin(); it != table.end(); it++)
|
||||
{
|
||||
Block *block = it.GetCurrItem();
|
||||
Block *block = &it.entry->item;
|
||||
|
||||
DQN_ASSERT(blockSeen[block->x] == false);
|
||||
blockSeen[block->x] = true;
|
||||
|
136
dqn.h
136
dqn.h
@ -2632,8 +2632,8 @@ DQN_VHASH_TABLE_TEMPLATE struct DqnVHashTable
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
Key key;
|
||||
Item item;
|
||||
union { Key key; Key first; };
|
||||
union { Item item; Item second; };
|
||||
};
|
||||
|
||||
struct Bucket
|
||||
@ -2653,9 +2653,9 @@ DQN_VHASH_TABLE_TEMPLATE struct DqnVHashTable
|
||||
void LazyInit (isize size = DQN_MAX(DQN_MEGABYTE(1)/sizeof(Bucket), 1024) );
|
||||
void Free () { if (buckets) DqnOS_VFree(buckets, sizeof(buckets) * numBuckets); *this = {}; }
|
||||
|
||||
Entry *GetEntry (Key const &key); // return: The (key, item) entry associated with the key, nullptr if key not in table yet.
|
||||
void Erase (Key const &key); // Delete the element matching key, does nothing if key not found.
|
||||
Item *GetOrMake(Key const &key); // return: Item if found, otherwise make an entry (key, item) and return the ptr to the uninitialised item
|
||||
void Erase (Key const &key); // Delete the element matching key, does nothing if key not found.
|
||||
Entry *GetEntry (Key const &key); // return: The (key, item) entry associated with the key, nullptr if key not in table yet.
|
||||
Item *GetOrMake(Key const &key, bool *existed = nullptr); // return: Item if found, otherwise make an entry (key, item) and return the ptr to the uninitialised item
|
||||
Item *Get (Key const &key) { Entry *entry = GetEntry(key); return (entry) ? &entry->item : nullptr; }
|
||||
Item *Set (Key const &key, Item const &item) { Item *result = GetOrMake(key); *result = item; return result; }
|
||||
|
||||
@ -2663,28 +2663,37 @@ DQN_VHASH_TABLE_TEMPLATE struct DqnVHashTable
|
||||
|
||||
struct Iterator
|
||||
{
|
||||
DqnVHashTable *table;
|
||||
isize indexInIndexesOfUsedBuckets;
|
||||
isize indexInBucket;
|
||||
|
||||
Iterator(DqnVHashTable *table_, isize indexInIndexesOfUsedBuckets_ = 0, isize indexInBucket_ = 0) : table(table_), indexInIndexesOfUsedBuckets(indexInIndexesOfUsedBuckets_), indexInBucket(indexInBucket_) {}
|
||||
Entry *entry;
|
||||
Iterator(DqnVHashTable *table_, isize indexInIndexesOfUsedBuckets_ = 0, isize indexInBucket_ = 0)
|
||||
: table(table_), indexInIndexesOfUsedBuckets(indexInIndexesOfUsedBuckets_), indexInBucket(indexInBucket_), entry(nullptr)
|
||||
{
|
||||
if (indexInIndexesOfUsedBuckets != -1 && indexInBucket != -1)
|
||||
entry = GetCurrEntry();
|
||||
}
|
||||
|
||||
Bucket *GetCurrBucket() const { return (table->buckets + table->indexesOfUsedBuckets[indexInIndexesOfUsedBuckets]); }
|
||||
Entry *GetCurrEntry() const { return GetCurrBucket()->entries + indexInBucket; }
|
||||
Item *GetCurrItem () const { return &(GetCurrEntry()->item); }
|
||||
|
||||
bool operator!=(Iterator const &other) const { return !Equals(GetCurrEntry()->key, other.GetCurrEntry()->key); }
|
||||
Item &operator* () const { return *GetCurrItem(); }
|
||||
Iterator &operator++() { if (++indexInBucket >= GetCurrBucket()->entryIndex) { indexInBucket = 0; indexInIndexesOfUsedBuckets++; } return *this; }
|
||||
Iterator &operator--() { if (--indexInBucket < 0) { indexInBucket = 0; indexInIndexesOfUsedBuckets = DQN_MAX(--indexInIndexesOfUsedBuckets, 0); } return *this; }
|
||||
bool operator!=(Iterator const &other) const { return (indexInIndexesOfUsedBuckets == other.indexInIndexesOfUsedBuckets && indexInBucket == other.indexInBucket); }
|
||||
Entry &operator* () const { return *GetCurrEntry(); }
|
||||
Iterator &operator++() { if (++indexInBucket >= GetCurrBucket()->entryIndex) { indexInBucket = 0; indexInIndexesOfUsedBuckets++; } entry = GetCurrEntry(); return *this; }
|
||||
Iterator &operator--() { if (--indexInBucket < 0) { indexInBucket = 0; indexInIndexesOfUsedBuckets = DQN_MAX(--indexInIndexesOfUsedBuckets, 0); } entry = GetCurrEntry(); return *this; }
|
||||
Iterator operator++(int) { Iterator result = *this; ++(*this); return result; }
|
||||
Iterator operator--(int) { Iterator result = *this; --(*this); return result; }
|
||||
Iterator operator+ (int offset) const { Iterator result = *this; DQN_FOR_EACH(i, offset) { (offset > 0) ? ++result : --result; } return result; } // TODO(doyle): Improve
|
||||
Iterator operator- (int offset) const { Iterator result = *this; DQN_FOR_EACH(i, offset) { (offset > 0) ? --result : ++result; } return result; } // TODO(doyle): Improve
|
||||
Iterator operator+ (int offset) const { Iterator result = *this; DQN_FOR_EACH(i, DQN_ABS(offset)) { (offset > 0) ? ++result : --result; } return result; } // TODO(doyle): Improve
|
||||
Iterator operator- (int offset) const { Iterator result = *this; DQN_FOR_EACH(i, DQN_ABS(offset)) { (offset > 0) ? --result : ++result; } return result; } // TODO(doyle): Improve
|
||||
|
||||
private:
|
||||
DqnVHashTable *table;
|
||||
isize indexInIndexesOfUsedBuckets;
|
||||
isize indexInBucket;
|
||||
};
|
||||
|
||||
Iterator Begin() { return begin(); }
|
||||
Iterator End() { return end(); }
|
||||
Iterator begin() { return Iterator(this); }
|
||||
Iterator end() { isize lastBucketIndex = indexesOfUsedBuckets[indexInIndexesOfUsedBuckets - 1]; isize lastIndexInBucket = (buckets + lastBucketIndex)->entryIndex - 1; return Iterator(this, DQN_MAX(lastBucketIndex, 0), DQN_MAX(lastIndexInBucket, 0)); }
|
||||
Iterator end() { return Iterator(this, -1, -1); }
|
||||
};
|
||||
|
||||
DQN_VHASH_TABLE_TEMPLATE void DQN_VHASH_TABLE_DECL::LazyInit(isize size)
|
||||
@ -2714,7 +2723,7 @@ DQN_VHASH_TABLE_DECL::GetEntry(Key const &key)
|
||||
return result;
|
||||
}
|
||||
|
||||
DQN_VHASH_TABLE_TEMPLATE Item *DQN_VHASH_TABLE_DECL::GetOrMake(Key const &key)
|
||||
DQN_VHASH_TABLE_TEMPLATE Item *DQN_VHASH_TABLE_DECL::GetOrMake(Key const &key, bool *existed)
|
||||
{
|
||||
if (!this->buckets) LazyInit();
|
||||
|
||||
@ -2728,6 +2737,9 @@ DQN_VHASH_TABLE_TEMPLATE Item *DQN_VHASH_TABLE_DECL::GetOrMake(Key const &key)
|
||||
entry = Equals(check->key, key) ? check : nullptr;
|
||||
}
|
||||
|
||||
if (existed)
|
||||
*existed = (entry != nullptr);
|
||||
|
||||
if (!entry)
|
||||
{
|
||||
DQN_ALWAYS_ASSERTM(bucket->entryIndex < DQN_ARRAY_COUNT(bucket->entries),
|
||||
@ -2789,20 +2801,98 @@ DQN_VHASH_TABLE_TEMPLATE void DQN_VHASH_TABLE_DECL::Erase(Key const &key)
|
||||
|
||||
// XPlatform > #DqnCatalog API
|
||||
// =================================================================================================
|
||||
template <typename T>
|
||||
struct DqnCatalog
|
||||
template <typename T> using DqnCatalogLoadProc = bool (*)(DqnFixedString128 const &file, T *data);
|
||||
#define DQN_CATALOG_LOAD_PROC(name, type) bool name(DqnFixedString128 const &file, type *data)
|
||||
|
||||
#define DQN_CATALOG_TEMPLATE template <typename T, DqnCatalogLoadProc<T> LoadAsset>
|
||||
#define DQN_CATALOG_DECL DqnCatalog<T, LoadAsset>
|
||||
|
||||
DQN_CATALOG_TEMPLATE struct DqnCatalog
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
u64 timeLastModified;
|
||||
T data;
|
||||
u64 lastWriteTimeInS;
|
||||
bool updated;
|
||||
};
|
||||
|
||||
DqnVHashTable<DqnFixedString128, Entry> assetTable;
|
||||
// void Update();
|
||||
T *Get (DqnFixedString128 file);
|
||||
|
||||
// Register the asset and load it if it hasn't yet.
|
||||
Entry *GetEntry (DqnFixedString128 const &file); // return: Entry, or nullptr if the asset could not be loaded
|
||||
T *Get (DqnFixedString128 const &file) { Entry *entry = GetEntry(file.str); return (entry) ? &entry->data : nullptr; }
|
||||
T *GetIfUpdated(DqnFixedString128 const &file); // return: Asset if an update has been detected and not consumed yet. Update is consumed after called.
|
||||
|
||||
// Call to iterate all loaded assets for updates.
|
||||
void PollAssets ();
|
||||
void Free () { assetTable.Free(); }
|
||||
};
|
||||
|
||||
DQN_CATALOG_TEMPLATE typename DQN_CATALOG_DECL::Entry *
|
||||
DQN_CATALOG_DECL::GetEntry(DqnFixedString128 const &file)
|
||||
{
|
||||
bool existed = false;
|
||||
Entry *result = this->assetTable.GetOrMake(file, &existed);
|
||||
|
||||
if (!existed)
|
||||
{
|
||||
T newData = {};
|
||||
DqnFileInfo info = {};
|
||||
if (DqnFile_GetInfo(file.str, &info) && LoadAsset(file, &newData))
|
||||
{
|
||||
result->lastWriteTimeInS = info.lastWriteTimeInS;
|
||||
result->data = newData;
|
||||
result->updated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = nullptr;
|
||||
DQN_LOGE("Catalog could not load file: %s\n", file.str);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DQN_CATALOG_TEMPLATE T *DQN_CATALOG_DECL::GetIfUpdated(DqnFixedString128 const &file)
|
||||
{
|
||||
Entry *entry = this->GetEntry(file.str);
|
||||
if (entry)
|
||||
{
|
||||
if (entry->updated) entry->updated = false;
|
||||
else entry = nullptr;
|
||||
}
|
||||
|
||||
return (entry) ? &entry->data : nullptr;
|
||||
}
|
||||
|
||||
DQN_CATALOG_TEMPLATE void DQN_CATALOG_DECL::PollAssets()
|
||||
{
|
||||
for (auto it = this->assetTable.Begin(); it != this->assetTable.End(); ++it)
|
||||
{
|
||||
DqnFixedString128 const *file = &it.entry->key;
|
||||
Entry *entry = &it.entry->item;
|
||||
|
||||
DqnFileInfo info = {};
|
||||
if (!DqnFile_GetInfo(file->str, &info))
|
||||
continue;
|
||||
|
||||
if (entry->lastWriteTimeInS == info.lastWriteTimeInS)
|
||||
continue;
|
||||
|
||||
T newData = {};
|
||||
if (LoadAsset(*file, &newData))
|
||||
{
|
||||
entry->lastWriteTimeInS = info.lastWriteTimeInS;
|
||||
entry->data = newData;
|
||||
}
|
||||
else
|
||||
{
|
||||
DQN_LOGE("Catalog could not load file: %s\n", file->str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XPlatform > #DqnFile API
|
||||
// =================================================================================================
|
||||
struct DqnFile
|
||||
|
Loading…
Reference in New Issue
Block a user