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
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										598
									
								
								DqnUnitTest.cpp
									
									
									
									
									
								
							
							
						
						
									
										598
									
								
								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() | ||||
| { | ||||
|     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
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| T *DqnCatalog<T>::Get(DqnFixedString128 file) | ||||
| { | ||||
|     Entry *result = this->assetTable.GetOrMake(file.str); | ||||
|     (void)result; | ||||
| 
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| void DqnCatalog_Test() | ||||
| { | ||||
|     struct TxtFile | ||||
| struct RawBuf | ||||
| { | ||||
|     char *buffer; | ||||
|     int   len; | ||||
| }; | ||||
| 
 | ||||
|     DqnCatalog<TxtFile> textCatalog = {}; | ||||
|     TxtFile *file = textCatalog.Get("makefile"); | ||||
|     (void)file; | ||||
| DQN_CATALOG_LOAD_PROC(CatalogRawLoad, RawBuf) | ||||
| { | ||||
|     usize bufSize; | ||||
|     u8 *buf = DqnFile_ReadAll(file.str, &bufSize); | ||||
| 
 | ||||
|     if (!buf) | ||||
|         return false; | ||||
| 
 | ||||
|     data->buffer = reinterpret_cast<char *>(buf); | ||||
|     data->len    = static_cast<int>(bufSize); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void DqnCatalog_Test() | ||||
| { | ||||
|     LOG_HEADER(); | ||||
| 
 | ||||
|     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 | ||||
|  | ||||
| @ -31,7 +31,6 @@ void DqnVHashTable_Test() | ||||
| 
 | ||||
|     { | ||||
|         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; | ||||
|  | ||||
							
								
								
									
										134
									
								
								dqn.h
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								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
 | ||||
|     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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user