More memory arena unit test

Starting to converting the display system to the new memory arena.
This commit is contained in:
2026-02-07 16:21:18 -05:00
parent 4d66261c9f
commit 84f82ba478
9 changed files with 130 additions and 42 deletions

View File

@@ -29,6 +29,8 @@ namespace Juliet
uint64 Committed;
uint64 Reserved;
Arena* FreeLast;
};
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);

View File

@@ -20,13 +20,19 @@ namespace Juliet
}
// Single linked list
void SingleLinkedListPush(auto*& stackTop, auto* node)
void SingleLinkedListPushNext(auto*& stackTop, auto* node)
{
node->Next = stackTop;
stackTop = node;
}
void SingleLinkedListPop(auto*& stackTop)
void SingleLinkedListPushPrevious(auto*& stackTop, auto* node)
{
node->Previous = stackTop;
stackTop = node;
}
void SingleLinkedListPopNext(auto*& stackTop)
{
stackTop = stackTop->Next;
}

View File

@@ -22,13 +22,15 @@ namespace Juliet
{
Assert(!g_CurrentDisplayDevice);
Arena* arena = ArenaAllocate();
DisplayDevice* candidateDevice = nullptr;
DisplayDeviceFactory* candidateFactory = nullptr;
for (DisplayDeviceFactory* factory : Internal::Display::Factories)
{
if (factory)
{
candidateDevice = factory->CreateDevice();
candidateDevice = factory->CreateDevice(arena);
if (candidateDevice)
{
candidateFactory = factory;
@@ -40,8 +42,9 @@ namespace Juliet
// TODO : handle error instead of crashing
Assert(candidateDevice);
g_CurrentDisplayDevice = candidateDevice;
g_CurrentDisplayDevice->Name = candidateFactory->Name;
g_CurrentDisplayDevice = candidateDevice;
g_CurrentDisplayDevice->Arena = arena;
g_CurrentDisplayDevice->Name = candidateFactory->Name;
if (!g_CurrentDisplayDevice->Initialize(g_CurrentDisplayDevice))
{
@@ -66,6 +69,8 @@ namespace Juliet
// Free anything that was freed by the shutdown and then free the display
// no op for now
g_CurrentDisplayDevice->Free(g_CurrentDisplayDevice);
ArenaRelease(g_CurrentDisplayDevice->Arena);
g_CurrentDisplayDevice = nullptr;
}
@@ -73,21 +78,16 @@ namespace Juliet
{
Assert(g_CurrentDisplayDevice->CreatePlatformWindow);
MemoryArena* arena = GetEngineArena();
auto window = ArenaPushType<Window>(arena, ConstString("Window"));
if (!window)
{
return nullptr;
}
auto* arena = g_CurrentDisplayDevice->Arena;
Assert(arena);
auto* window = ArenaPushStruct<Window>(arena);
Assert(window);
window->Width = width;
window->Height = height;
auto titleLen = StringLength(title);
auto buffer = ArenaPushArray<char>(arena, titleLen, ConstString("Window Title Array"));
MemCopy(buffer, title, titleLen);
window->Title.Data = buffer;
window->Title.Size = titleLen;
window->Title = StringCopy(arena, WrapString(title));
g_CurrentDisplayDevice->MainWindow = window;
if (!g_CurrentDisplayDevice->CreatePlatformWindow(g_CurrentDisplayDevice, window))
@@ -109,7 +109,7 @@ namespace Juliet
HideWindow(window);
// We don't free from arena, these are persistent until shutdown.
// TODO: Pop from arena.
window->Title.Data = nullptr;
window->Title.Size = 0;

View File

@@ -10,6 +10,8 @@ namespace Juliet
// Acts as a singleton after Initialize has been called and is freed in Shutdown.
struct DisplayDevice
{
Arena* Arena;
const char* Name = "Unknown";
// Initialize all subsystems needed for the device to works
@@ -33,7 +35,7 @@ namespace Juliet
struct DisplayDeviceFactory
{
const char* Name = "Unknown";
DisplayDevice* (*CreateDevice)(void);
DisplayDevice* (*CreateDevice)(Arena* arena);
};
// TODO : Support more platforms

View File

@@ -16,9 +16,9 @@ namespace Juliet::Win32
void Shutdown(NonNullPtr<DisplayDevice> /*self*/) {}
void Free(NonNullPtr<DisplayDevice> /*self*/) {}
DisplayDevice* CreateDevice()
DisplayDevice* CreateDevice(Arena* arena)
{
auto device = ArenaPushType<DisplayDevice>(GetEngineArena(), ConstString("DisplayDevice"));
auto device = ArenaPushStruct<DisplayDevice>(arena);
if (!device)
{

View File

@@ -63,8 +63,8 @@ namespace Juliet::Win32
int x = CW_USEDEFAULT, y = CW_USEDEFAULT;
const int w = window->Width, h = window->Height;
HWND handle = CreateWindowExA(styleEx, WindowClassPtr, "JULIET TODO PASS TITLE", style, x, y, w, h, nullptr,
nullptr, instance, nullptr);
HWND handle = CreateWindowExA(styleEx, WindowClassPtr, window->Title.Data, style, x, y, w, h, nullptr, nullptr,
instance, nullptr);
PumpEvents(self);

View File

@@ -116,7 +116,7 @@ namespace Juliet
size_t position = ArenaPos(ActiveLog->Arena);
LogScope* scope = ArenaPushStruct<LogScope>(ActiveLog->Arena);
scope->Position = position;
SingleLinkedListPush(ActiveLog->TopScope, scope);
SingleLinkedListPushNext(ActiveLog->TopScope, scope);
}
void LogScopeEnd()
@@ -126,7 +126,7 @@ namespace Juliet
LogScope* scope = ActiveLog->TopScope;
Assert(scope != nullptr);
SingleLinkedListPop(ActiveLog->TopScope);
SingleLinkedListPopNext(ActiveLog->TopScope);
ArenaPopTo(ActiveLog->Arena, scope->Position);
}

View File

@@ -11,7 +11,6 @@
namespace Juliet
{
// TODO Get page size from os kernel call (dwPageSize)
namespace
{
@@ -67,7 +66,25 @@ namespace Juliet
if (current->Reserved < positionPostPush /* flags : chaining allowed */)
{
Arena* newBlock = nullptr;
// TODO : use the free list
{
Arena* prev_block;
for (newBlock = arena->FreeLast, prev_block = nullptr; newBlock != nullptr;
prev_block = newBlock, newBlock = newBlock->Previous)
{
if (newBlock->Reserved >= AlignPow2(newBlock->Position, align) + size)
{
if (prev_block)
{
prev_block->Previous = newBlock->Previous;
}
else
{
arena->FreeLast = newBlock->Previous;
}
break;
}
}
}
if (newBlock == nullptr)
{
@@ -83,13 +100,7 @@ namespace Juliet
}
newBlock->BasePosition = current->BasePosition + current->Reserved;
// Push on the linkedlist
newBlock->Previous = arena->Current;
arena->Current = newBlock;
// TODO: Think about ryan fleur way to push:
// SLLStackPush_N(arena->current, new_block, prev);
// #define SLLStackPush_N(f,n,next) ((n)->next=(f), (f)=(n))
SingleLinkedListPushPrevious(arena->Current, newBlock);
current = newBlock;
positionPrePush = AlignPow2(current->Position, align);
@@ -108,11 +119,11 @@ namespace Juliet
size_t commitPostAligned = positionPostPush + current->CommitSize - 1;
commitPostAligned -= commitPostAligned % current->CommitSize;
size_t commitPostClamped = ClampTop(commitPostAligned, current->Reserved);
// size_t commitSize = commitPostClamped - current->Committed;
// Byte* commitPtr = reinterpret_cast<Byte*>(current) + current->Committed;
size_t commitSize = commitPostClamped - current->Committed;
Byte* commitPtr = reinterpret_cast<Byte*>(current) + current->Committed;
// TODO os_commit / commit_large
// os_commit(commitPtr, commitSize);
// TODO commit_large
Memory::OS_Commit(commitPtr, commitSize);
current->Committed = commitPostClamped;
}
@@ -143,13 +154,20 @@ namespace Juliet
size_t clampedPosition = ClampBottom(k_ArenaHeaderSize, position);
Arena* current = arena->Current;
// TODO : Free list
for (Arena* previous = nullptr; current->BasePosition >= clampedPosition; current = previous)
{
previous = current->Previous;
Memory::OS_Release(current, current->Reserved);
previous = current->Previous;
current->Position = k_ArenaHeaderSize;
SingleLinkedListPushPrevious(arena->FreeLast, current);
}
// No freelist :
// for (Arena* previous = nullptr; current->BasePosition >= clampedPosition; current = previous)
// {
// previous = current->Previous;
// Memory::OS_Release(current, current->Reserved);
// }
arena->Current = current;
size_t newPosition = clampedPosition - current->BasePosition;
Assert(newPosition <= current->Position);

View File

@@ -19,7 +19,8 @@ namespace Juliet::UnitTest
printf("Running Paged Memory Arena Tests...\n");
// New Arena!
Arena* testArena = ArenaAllocate();
ArenaParams param{ .ReserveSize = Megabytes(64llu), .CommitSize = Kilobytes(64llu) };
Arena* testArena = ArenaAllocate(param);
size_t pos = ArenaPos(testArena);
Assert(pos == k_ArenaHeaderSize);
@@ -55,6 +56,65 @@ namespace Juliet::UnitTest
pos = ArenaPos(testArena);
Assert(pos == k_ArenaHeaderSize + sizeof(TestStruct) * 2);
ArenaClear(testArena);
// TEST COMMIT
// Push more than the default commit size
uint8* myArray = ArenaPushArray<uint8>(testArena, 100'000);
*myArray = 5;
Assert(*myArray == 5);
bool isCommitSizeSame = testArena->Committed == (2 * param.CommitSize);
Assert(isCommitSizeSame);
ArenaClear(testArena);
// Push again above default commit size
myArray = ArenaPushArray<uint8>(testArena, 100'000);
// Shoud be zero again
Assert(*myArray == 0);
// Should not have commited more
isCommitSizeSame = testArena->Current->Committed == (2 * param.CommitSize);
Assert(isCommitSizeSame);
ArenaClear(testArena);
// TEST RESERVE
size_t posPreBigArray = ArenaPos(testArena);
size_t reservedSizePreBigArray = testArena->Current->Reserved;
size_t arraySize = Megabytes(100llu);
myArray = ArenaPushArray<uint8>(testArena, arraySize);
// Align and support the header size because we need to allocate a new block + header
size_t reserve_size = AlignPow2(arraySize + k_ArenaHeaderSize, AlignOf(uint8));
Assert(reserve_size == testArena->Current->Position);
Assert(reservedSizePreBigArray == testArena->Current->BasePosition);
// TODO Get page size from os kernel call (dwPageSize)
constexpr uint64 k_PageSize = Kilobytes(4);
size_t reserve_sizeAlignToPage = AlignPow2(reserve_size, k_PageSize);
bool isReserveSizeSame = testArena->Current->Reserved == reserve_sizeAlignToPage;
Assert(isReserveSizeSame);
Assert(testArena->Current->Reserved == testArena->Current->Committed);
size_t basePos = ArenaPos(testArena);
Assert(basePos == reserve_size + testArena->Current->BasePosition);
myStruct = ArenaPushStruct<TestStruct>(testArena);
// Should not delete the current block because we only delete the struct
ArenaPopTo(testArena, basePos);
Assert(testArena->Current->Previous != nullptr);
// Should still not delete the current block. We only remove the array
ArenaPop(testArena, arraySize);
Assert(testArena->Current->Previous != nullptr);
// We should only have the header remaining
Assert(testArena->Current->Position == k_ArenaHeaderSize);
// This one should delete the block
ArenaPopTo(testArena, posPreBigArray);
Assert(testArena->Current->Previous == nullptr);
ArenaRelease(testArena);
// Setup Pool and Arena for Pop Tests