Add CPP member functions to DqnMemStack
This commit is contained in:
parent
187fc14d02
commit
9bb8efcf9d
105
dqn.h
105
dqn.h
@ -53,7 +53,7 @@
|
|||||||
// #DqnSprintf Cross-platform Sprintf Implementation (Public Domain lib stb_sprintf)
|
// #DqnSprintf Cross-platform Sprintf Implementation (Public Domain lib stb_sprintf)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Platform Checks
|
// Global Preprocessor Checks
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// This needs to be above the portable layer so that, if the user requests
|
// This needs to be above the portable layer so that, if the user requests
|
||||||
// a platform implementation, platform specific implementations in the portable
|
// a platform implementation, platform specific implementations in the portable
|
||||||
@ -66,6 +66,10 @@
|
|||||||
#define DQN_UNIX_PLATFORM
|
#define DQN_UNIX_PLATFORM
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#define DQN_CPP_MODE
|
||||||
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// #Portable Code
|
// #Portable Code
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -77,10 +81,6 @@
|
|||||||
#define DQN_FILE_SCOPE
|
#define DQN_FILE_SCOPE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#define DQN_CPP_MODE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdint.h> // For standard types
|
#include <stdint.h> // For standard types
|
||||||
#include <stddef.h> // For standard types
|
#include <stddef.h> // For standard types
|
||||||
#include <string.h> // memmove
|
#include <string.h> // memmove
|
||||||
@ -210,10 +210,37 @@ enum DqnMemStackFlag
|
|||||||
typedef struct DqnMemStack
|
typedef struct DqnMemStack
|
||||||
{
|
{
|
||||||
struct DqnMemStackBlock *block;
|
struct DqnMemStackBlock *block;
|
||||||
|
|
||||||
u32 flags;
|
u32 flags;
|
||||||
i32 tempRegionCount;
|
i32 tempRegionCount;
|
||||||
u32 byteAlign;
|
u32 byteAlign;
|
||||||
|
|
||||||
|
#if defined(DQN_CPP_MODE)
|
||||||
|
// Initialisation API
|
||||||
|
bool InitWithFixedMem (u8 *const mem, const size_t memSize, const u32 byteAlignment = 4);
|
||||||
|
bool InitWithFixedSize(const size_t size, const bool zeroClear, const u32 byteAlignment = 4);
|
||||||
|
bool Init (const size_t size, const bool zeroClear, const u32 byteAlignment = 4);
|
||||||
|
|
||||||
|
// Memory API
|
||||||
|
void *Push(size_t size);
|
||||||
|
void Pop (void *const ptr, size_t size);
|
||||||
|
void Free();
|
||||||
|
bool FreeMemBlock (DqnMemStackBlock *memBlock);
|
||||||
|
bool FreeLastBlock ();
|
||||||
|
void ClearCurrBlock(const bool zeroClear);
|
||||||
|
|
||||||
|
// Temporary Regions API
|
||||||
|
struct DqnMemStackTempRegion TempRegionBegin();
|
||||||
|
void TempRegionEnd (DqnMemStackTempRegion region);
|
||||||
|
|
||||||
|
// Scoped Temporary Regions API
|
||||||
|
struct DqnMemStackTempRegionScoped TempRegionScoped(bool *const succeeded);
|
||||||
|
|
||||||
|
// Advanced API
|
||||||
|
DqnMemStackBlock *AllocateCompatibleBlock(size_t size);
|
||||||
|
bool AttachBlock (DqnMemStackBlock *const newBlock);
|
||||||
|
bool DetachBlock (DqnMemStackBlock *const detachBlock);
|
||||||
|
void FreeDetachedBlock (DqnMemStackBlock *memBlock);
|
||||||
|
#endif
|
||||||
} DqnMemStack;
|
} DqnMemStack;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -241,6 +268,7 @@ DQN_FILE_SCOPE bool DqnMemStack_Init (DqnMemStack *const stack, size_t size, con
|
|||||||
// DqnMemStack Memory Operations
|
// DqnMemStack Memory Operations
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Allocate memory from the MemStack.
|
// Allocate memory from the MemStack.
|
||||||
|
// size: "size" gets aligned to the byte alignment of the stack.
|
||||||
// return: NULL if out of space OR stack is using fixed memory/size OR stack full and platform malloc fails.
|
// return: NULL if out of space OR stack is using fixed memory/size OR stack full and platform malloc fails.
|
||||||
DQN_FILE_SCOPE void *DqnMemStack_Push (DqnMemStack *const stack, size_t size);
|
DQN_FILE_SCOPE void *DqnMemStack_Push (DqnMemStack *const stack, size_t size);
|
||||||
|
|
||||||
@ -252,7 +280,7 @@ DQN_FILE_SCOPE void DqnMemStack_Free (DqnMemStack *const stack);
|
|||||||
|
|
||||||
// Frees the specified block belonging to the stack.
|
// Frees the specified block belonging to the stack.
|
||||||
// return: FALSE if block doesn't belong this into calls DqnMem_Free() or invalid args.
|
// return: FALSE if block doesn't belong this into calls DqnMem_Free() or invalid args.
|
||||||
DQN_FILE_SCOPE bool DqnMemStack_FreeStackBlock(DqnMemStack *const stack, DqnMemStackBlock *block);
|
DQN_FILE_SCOPE bool DqnMemStack_FreeMemBlock(DqnMemStack *const stack, DqnMemStackBlock *memBlock);
|
||||||
|
|
||||||
// Frees the last-most memory block. If last block, free that block making the MemStack blockless.
|
// Frees the last-most memory block. If last block, free that block making the MemStack blockless.
|
||||||
// Next allocate will attach a block.
|
// Next allocate will attach a block.
|
||||||
@ -287,8 +315,7 @@ DQN_FILE_SCOPE void DqnMemStackTempRegion_End (DqnMemStackTempRegion region);
|
|||||||
#ifdef DQN_CPP_MODE
|
#ifdef DQN_CPP_MODE
|
||||||
struct DqnMemStackTempRegionScoped
|
struct DqnMemStackTempRegionScoped
|
||||||
{
|
{
|
||||||
bool isInit;
|
DqnMemStackTempRegionScoped(DqnMemStack *const stack, bool *const succeeded);
|
||||||
DqnMemStackTempRegionScoped(DqnMemStack *const stack);
|
|
||||||
~DqnMemStackTempRegionScoped();
|
~DqnMemStackTempRegionScoped();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -317,8 +344,8 @@ DQN_FILE_SCOPE bool DqnMemStack_AttachBlock (DqnMemStack
|
|||||||
DQN_FILE_SCOPE bool DqnMemStack_DetachBlock (DqnMemStack *const stack, DqnMemStackBlock *const detachBlock);
|
DQN_FILE_SCOPE bool DqnMemStack_DetachBlock (DqnMemStack *const stack, DqnMemStackBlock *const detachBlock);
|
||||||
|
|
||||||
// (IMPORTANT) Should only be used to free blocks that haven't been attached! Attached blocks should
|
// (IMPORTANT) Should only be used to free blocks that haven't been attached! Attached blocks should
|
||||||
// be freed using DqnMemStack_FreeStackBlock().
|
// be freed using DqnMemStack_FreeMemBlock().
|
||||||
DQN_FILE_SCOPE void DqnMemStack_FreeBlock(DqnMemStackBlock *block);
|
DQN_FILE_SCOPE void DqnMemStack_FreeDetachedBlock(DqnMemStackBlock *memBlock);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// #DqnMemAPI Public API - Custom memory API for Dqn Data Structures
|
// #DqnMemAPI Public API - Custom memory API for Dqn Data Structures
|
||||||
@ -927,9 +954,10 @@ enum DqnFileAction
|
|||||||
|
|
||||||
typedef struct DqnFile
|
typedef struct DqnFile
|
||||||
{
|
{
|
||||||
|
u32 permissionFlags;
|
||||||
void *handle;
|
void *handle;
|
||||||
size_t size;
|
size_t size;
|
||||||
u32 permissionFlags;
|
|
||||||
} DqnFile;
|
} DqnFile;
|
||||||
|
|
||||||
// NOTE: W(ide) versions of functions only work on Win32, since Unix is UTF-8 compatible.
|
// NOTE: W(ide) versions of functions only work on Win32, since Unix is UTF-8 compatible.
|
||||||
@ -1682,12 +1710,45 @@ DqnMemStackInternal_AllocateBlock(u32 byteAlign, size_t size)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// #DqnMemStack CPP Implementation
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#if defined(DQN_CPP_MODE)
|
||||||
|
bool DqnMemStack::InitWithFixedMem (u8 *const mem, const size_t memSize, const u32 byteAlignment) { return DqnMemStack_InitWithFixedMem (this, mem, memSize, byteAlign); }
|
||||||
|
bool DqnMemStack::InitWithFixedSize(const size_t size, const bool zeroClear, const u32 byteAlignment) { return DqnMemStack_InitWithFixedSize(this, size, zeroClear, byteAlign); }
|
||||||
|
bool DqnMemStack::Init (const size_t size, const bool zeroClear, const u32 byteAlignment) { return DqnMemStack_Init (this, size, zeroClear, byteAlign); }
|
||||||
|
|
||||||
|
void *DqnMemStack::Push(size_t size) { return DqnMemStack_Push(this, size); }
|
||||||
|
void DqnMemStack::Pop (void *const ptr, size_t size) { DqnMemStack_Pop (this, ptr, size); }
|
||||||
|
void DqnMemStack::Free() { DqnMemStack_Free(this); }
|
||||||
|
bool DqnMemStack::FreeMemBlock(DqnMemStackBlock *memBlock) { return DqnMemStack_FreeMemBlock(this, block); }
|
||||||
|
bool DqnMemStack::FreeLastBlock() { return DqnMemStack_FreeLastBlock(this); }
|
||||||
|
void DqnMemStack::ClearCurrBlock(const bool zeroClear) { DqnMemStack_ClearCurrBlock(this, zeroClear); }
|
||||||
|
|
||||||
|
DqnMemStackTempRegion DqnMemStack::TempRegionBegin()
|
||||||
|
{
|
||||||
|
// NOTE: Should always succeed since the stack is guaranteed to exist.
|
||||||
|
DqnMemStackTempRegion result = {};
|
||||||
|
bool succeeded = DqnMemStackTempRegion_Begin(&result, this);
|
||||||
|
DQN_ASSERT_HARD(succeeded);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
void DqnMemStack::TempRegionEnd(DqnMemStackTempRegion region) { DqnMemStackTempRegion_End(region); }
|
||||||
|
DqnMemStackTempRegionScoped DqnMemStack::TempRegionScoped(bool *const succeeded) { return DqnMemStackTempRegionScoped(this, succeeded); }
|
||||||
|
|
||||||
|
DqnMemStackBlock *DqnMemStack::AllocateCompatibleBlock(size_t size) { return DqnMemStack_AllocateCompatibleBlock(this, size); }
|
||||||
|
bool DqnMemStack::AttachBlock (DqnMemStackBlock *const newBlock) { return DqnMemStack_AttachBlock (this, newBlock); }
|
||||||
|
bool DqnMemStack::DetachBlock (DqnMemStackBlock *const detachBlock) { return DqnMemStack_DetachBlock (this, detachBlock); }
|
||||||
|
void DqnMemStack::FreeDetachedBlock (DqnMemStackBlock *memBlock) { DqnMemStack_FreeDetachedBlock (memBlock); }
|
||||||
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// #DqnMemStack Initialisation Implementation
|
// #DqnMemStack Initialisation Implementation
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
DQN_FILE_SCOPE bool DqnMemStack_InitWithFixedMem(DqnMemStack *const stack, u8 *const mem,
|
DQN_FILE_SCOPE bool DqnMemStack_InitWithFixedMem(DqnMemStack *const stack, u8 *const mem,
|
||||||
const size_t memSize, const u32 byteAlign)
|
const size_t memSize, const u32 byteAlign)
|
||||||
{
|
{
|
||||||
|
// TODO(doyle): Logging
|
||||||
if (!stack || !mem) return false;
|
if (!stack || !mem) return false;
|
||||||
|
|
||||||
if (!DQN_ASSERT_MSG(
|
if (!DQN_ASSERT_MSG(
|
||||||
@ -1837,14 +1898,14 @@ DQN_FILE_SCOPE void DqnMemStack_Free(DqnMemStack *stack)
|
|||||||
stack->flags &= ~DqnMemStackFlag_IsNotExpandable;
|
stack->flags &= ~DqnMemStackFlag_IsNotExpandable;
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_FILE_SCOPE bool DqnMemStack_FreeStackBlock(DqnMemStack *const stack, DqnMemStackBlock *block)
|
DQN_FILE_SCOPE bool DqnMemStack_FreeMemBlock(DqnMemStack *const stack, DqnMemStackBlock *memBlock)
|
||||||
{
|
{
|
||||||
if (!stack || !block || !stack->block) return false;
|
if (!stack || !memBlock || !stack->block) return false;
|
||||||
if (stack->flags & DqnMemStackFlag_IsFixedMemoryFromUser) return false;
|
if (stack->flags & DqnMemStackFlag_IsFixedMemoryFromUser) return false;
|
||||||
|
|
||||||
DqnMemStackBlock **blockPtr = &stack->block;
|
DqnMemStackBlock **blockPtr = &stack->block;
|
||||||
|
|
||||||
while (*blockPtr && (*blockPtr) != block)
|
while (*blockPtr && (*blockPtr) != memBlock)
|
||||||
blockPtr = &((*blockPtr)->prevBlock);
|
blockPtr = &((*blockPtr)->prevBlock);
|
||||||
|
|
||||||
if (*blockPtr)
|
if (*blockPtr)
|
||||||
@ -1863,7 +1924,7 @@ DQN_FILE_SCOPE bool DqnMemStack_FreeStackBlock(DqnMemStack *const stack, DqnMemS
|
|||||||
|
|
||||||
DQN_FILE_SCOPE bool DqnMemStack_FreeLastBlock(DqnMemStack *const stack)
|
DQN_FILE_SCOPE bool DqnMemStack_FreeLastBlock(DqnMemStack *const stack)
|
||||||
{
|
{
|
||||||
bool result = DqnMemStack_FreeStackBlock(stack, stack->block);
|
bool result = DqnMemStack_FreeMemBlock(stack, stack->block);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1913,10 +1974,10 @@ DQN_FILE_SCOPE void DqnMemStackTempRegion_End(DqnMemStackTempRegion region)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DQN_CPP_MODE
|
#ifdef DQN_CPP_MODE
|
||||||
DqnMemStackTempRegionScoped::DqnMemStackTempRegionScoped(DqnMemStack *const stack)
|
DqnMemStackTempRegionScoped::DqnMemStackTempRegionScoped(DqnMemStack *const stack, bool *const succeeded)
|
||||||
{
|
{
|
||||||
this->isInit = DqnMemStackTempRegion_Begin(&this->tempMemStack, stack);
|
bool result = DqnMemStackTempRegion_Begin(&this->tempMemStack, stack);
|
||||||
DQN_ASSERT(this->isInit);
|
if (succeeded) *succeeded = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DqnMemStackTempRegionScoped::~DqnMemStackTempRegionScoped()
|
DqnMemStackTempRegionScoped::~DqnMemStackTempRegionScoped()
|
||||||
@ -1976,10 +2037,10 @@ DQN_FILE_SCOPE bool DqnMemStack_DetachBlock(DqnMemStack *const stack,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_FILE_SCOPE void DqnMemStack_FreeBlock(DqnMemStackBlock *block)
|
DQN_FILE_SCOPE void DqnMemStack_FreeDetachedBlock(DqnMemStackBlock *memBlock)
|
||||||
{
|
{
|
||||||
if (!block) return;
|
if (!memBlock) return;
|
||||||
DqnMem_Free(block);
|
DqnMem_Free(memBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1399,7 +1399,7 @@ void MemStackTest()
|
|||||||
fakeBlock.memory = fakeBlockMem;
|
fakeBlock.memory = fakeBlockMem;
|
||||||
fakeBlock.size = DQN_ARRAY_COUNT(fakeBlockMem);
|
fakeBlock.size = DQN_ARRAY_COUNT(fakeBlockMem);
|
||||||
fakeBlock.used = 0;
|
fakeBlock.used = 0;
|
||||||
DQN_ASSERT(!DqnMemStack_FreeStackBlock(&stack, &fakeBlock));
|
DQN_ASSERT(!DqnMemStack_FreeMemBlock(&stack, &fakeBlock));
|
||||||
|
|
||||||
// Ensure that the actual blocks are still valid and freeing did nothing
|
// Ensure that the actual blocks are still valid and freeing did nothing
|
||||||
DQN_ASSERT(firstBlock->size == firstBlockSize);
|
DQN_ASSERT(firstBlock->size == firstBlockSize);
|
||||||
@ -1432,7 +1432,7 @@ void MemStackTest()
|
|||||||
DQN_ASSERT(fourth[i] == 'f');
|
DQN_ASSERT(fourth[i] == 'f');
|
||||||
|
|
||||||
// Free the first block
|
// Free the first block
|
||||||
DqnMemStack_FreeStackBlock(&stack, firstBlock);
|
DqnMemStack_FreeMemBlock(&stack, firstBlock);
|
||||||
|
|
||||||
// Revalidate state
|
// Revalidate state
|
||||||
DQN_ASSERT(secondBlock->size == secondBlockSize);
|
DQN_ASSERT(secondBlock->size == secondBlockSize);
|
||||||
@ -1458,7 +1458,7 @@ void MemStackTest()
|
|||||||
DQN_ASSERT(fourth[i] == 'f');
|
DQN_ASSERT(fourth[i] == 'f');
|
||||||
|
|
||||||
// Free the third block
|
// Free the third block
|
||||||
DqnMemStack_FreeStackBlock(&stack, thirdBlock);
|
DqnMemStack_FreeMemBlock(&stack, thirdBlock);
|
||||||
|
|
||||||
// Revalidate state
|
// Revalidate state
|
||||||
DQN_ASSERT(secondBlock->size == secondBlockSize);
|
DQN_ASSERT(secondBlock->size == secondBlockSize);
|
||||||
@ -1478,7 +1478,7 @@ void MemStackTest()
|
|||||||
DQN_ASSERT(fourth[i] == 'f');
|
DQN_ASSERT(fourth[i] == 'f');
|
||||||
|
|
||||||
// Free the second block
|
// Free the second block
|
||||||
DqnMemStack_FreeStackBlock(&stack, secondBlock);
|
DqnMemStack_FreeMemBlock(&stack, secondBlock);
|
||||||
|
|
||||||
// Revalidate state
|
// Revalidate state
|
||||||
DQN_ASSERT(fourthBlock->size == fourthBlockSize);
|
DQN_ASSERT(fourthBlock->size == fourthBlockSize);
|
||||||
@ -1495,6 +1495,9 @@ void MemStackTest()
|
|||||||
|
|
||||||
// Test pop
|
// Test pop
|
||||||
if (1)
|
if (1)
|
||||||
|
{
|
||||||
|
// Test aligned pop
|
||||||
|
if (1)
|
||||||
{
|
{
|
||||||
DqnMemStack stack = {};
|
DqnMemStack stack = {};
|
||||||
DqnMemStack_Init(&stack, DQN_KILOBYTE(1), true);
|
DqnMemStack_Init(&stack, DQN_KILOBYTE(1), true);
|
||||||
@ -1505,6 +1508,24 @@ void MemStackTest()
|
|||||||
|
|
||||||
DQN_ASSERT(DqnMemStack_Pop(&stack, alloc, allocSize));
|
DQN_ASSERT(DqnMemStack_Pop(&stack, alloc, allocSize));
|
||||||
DQN_ASSERT(stack.block->used == 0);
|
DQN_ASSERT(stack.block->used == 0);
|
||||||
|
DqnMemStack_Free(&stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test pop on a non-byte aligned allocation. This checks to see if
|
||||||
|
// Pop() doesn't naiively forget to re-byte align the passed in size.
|
||||||
|
if (1)
|
||||||
|
{
|
||||||
|
DqnMemStack stack = {};
|
||||||
|
DqnMemStack_Init(&stack, DQN_KILOBYTE(1), true);
|
||||||
|
|
||||||
|
size_t allocSize = 1;
|
||||||
|
void *alloc = DqnMemStack_Push(&stack, allocSize);
|
||||||
|
DQN_ASSERT(stack.block->used == DQN_ALIGN_POW_N(allocSize, stack.byteAlign));
|
||||||
|
|
||||||
|
DQN_ASSERT(DqnMemStack_Pop(&stack, alloc, allocSize));
|
||||||
|
DQN_ASSERT(stack.block->used == 0);
|
||||||
|
DqnMemStack_Free(&stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1641,6 +1662,9 @@ void FileTest()
|
|||||||
DqnMemStack_Free(&memStack);
|
DqnMemStack_Free(&memStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test directory listing
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
if (1)
|
if (1)
|
||||||
{
|
{
|
||||||
u32 numFiles;
|
u32 numFiles;
|
||||||
|
@ -21,7 +21,7 @@ Global
|
|||||||
Release|x86 = Release|x86
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{87785192-6F49-4F85-AA0D-F0AFA5CCCDDA}.Release|x86.ActiveCfg = Release|x64
|
{87785192-6F49-4F85-AA0D-F0AFA5CCCDDA}.Release|x86.ActiveCfg = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
Loading…
Reference in New Issue
Block a user