diff --git a/DqnMemStack.cpp b/DqnMemStack.cpp new file mode 100644 index 0000000..660cb63 --- /dev/null +++ b/DqnMemStack.cpp @@ -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(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 + } +} + diff --git a/DqnUnitTest.cpp b/DqnUnitTest.cpp index d49c50b..017765b 100644 --- a/DqnUnitTest.cpp +++ b/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(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 -T *DqnCatalog::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(buf); + data->len = static_cast(bufSize); + return true; } void DqnCatalog_Test() { - struct TxtFile - { - char *buffer; - int len; - }; + LOG_HEADER(); - DqnCatalog textCatalog = {}; - TxtFile *file = textCatalog.Get("makefile"); - (void)file; + DqnCatalog 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(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(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 diff --git a/DqnVHashTable.cpp b/DqnVHashTable.cpp index f3f9156..67d2226 100644 --- a/DqnVHashTable.cpp +++ b/DqnVHashTable.cpp @@ -30,8 +30,7 @@ void DqnVHashTable_Test() } { - Block blocks[] = {{0}, {1}, {2}, {3}, {4}}; - + Block blocks[] = {{0}, {1}, {2}, {3}, {4}}; DqnVHashTable 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; diff --git a/dqn.h b/dqn.h index a69f98f..e648ecf 100644 --- a/dqn.h +++ b/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 -struct DqnCatalog +template 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 LoadAsset> +#define DQN_CATALOG_DECL DqnCatalog + +DQN_CATALOG_TEMPLATE struct DqnCatalog { struct Entry { - u64 timeLastModified; T data; + u64 lastWriteTimeInS; + bool updated; }; DqnVHashTable 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