From 84f82ba4781c24db01fa76ac5ff536cf5696adce Mon Sep 17 00:00:00 2001 From: Patedam Date: Sat, 7 Feb 2026 16:21:18 -0500 Subject: [PATCH] More memory arena unit test Starting to converting the display system to the new memory arena. --- Juliet/include/Core/Memory/MemoryArena.h | 2 + Juliet/include/Core/Memory/Utils.h | 10 ++- Juliet/src/Core/HAL/Display/Display.cpp | 32 +++++----- Juliet/src/Core/HAL/Display/DisplayDevice.h | 4 +- .../HAL/Display/Win32/Win32DisplayDevice.cpp | 4 +- .../Core/HAL/Display/Win32/Win32Window.cpp | 4 +- Juliet/src/Core/Logging/LogManager.cpp | 4 +- Juliet/src/Core/Memory/MemoryArena.cpp | 50 ++++++++++----- Juliet/src/Core/Memory/MemoryArenaTests.cpp | 62 ++++++++++++++++++- 9 files changed, 130 insertions(+), 42 deletions(-) diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 533e0ae..4d5dd4d 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -29,6 +29,8 @@ namespace Juliet uint64 Committed; uint64 Reserved; + + Arena* FreeLast; }; static_assert(sizeof(Arena) <= k_ArenaHeaderSize); diff --git a/Juliet/include/Core/Memory/Utils.h b/Juliet/include/Core/Memory/Utils.h index 98f7677..d3f26c2 100644 --- a/Juliet/include/Core/Memory/Utils.h +++ b/Juliet/include/Core/Memory/Utils.h @@ -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; } diff --git a/Juliet/src/Core/HAL/Display/Display.cpp b/Juliet/src/Core/HAL/Display/Display.cpp index 35c3bd7..3e661db 100644 --- a/Juliet/src/Core/HAL/Display/Display.cpp +++ b/Juliet/src/Core/HAL/Display/Display.cpp @@ -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(arena, ConstString("Window")); - if (!window) - { - return nullptr; - } + auto* arena = g_CurrentDisplayDevice->Arena; + Assert(arena); + + auto* window = ArenaPushStruct(arena); + Assert(window); + window->Width = width; window->Height = height; - auto titleLen = StringLength(title); - auto buffer = ArenaPushArray(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; diff --git a/Juliet/src/Core/HAL/Display/DisplayDevice.h b/Juliet/src/Core/HAL/Display/DisplayDevice.h index 8ef08b8..ce1f6e8 100644 --- a/Juliet/src/Core/HAL/Display/DisplayDevice.h +++ b/Juliet/src/Core/HAL/Display/DisplayDevice.h @@ -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 diff --git a/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp b/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp index e418494..295287f 100644 --- a/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp +++ b/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp @@ -16,9 +16,9 @@ namespace Juliet::Win32 void Shutdown(NonNullPtr /*self*/) {} void Free(NonNullPtr /*self*/) {} - DisplayDevice* CreateDevice() + DisplayDevice* CreateDevice(Arena* arena) { - auto device = ArenaPushType(GetEngineArena(), ConstString("DisplayDevice")); + auto device = ArenaPushStruct(arena); if (!device) { diff --git a/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp b/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp index 45a2912..2669002 100644 --- a/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp +++ b/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp @@ -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); diff --git a/Juliet/src/Core/Logging/LogManager.cpp b/Juliet/src/Core/Logging/LogManager.cpp index ff48b2a..7a021b2 100644 --- a/Juliet/src/Core/Logging/LogManager.cpp +++ b/Juliet/src/Core/Logging/LogManager.cpp @@ -116,7 +116,7 @@ namespace Juliet size_t position = ArenaPos(ActiveLog->Arena); LogScope* scope = ArenaPushStruct(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); } diff --git a/Juliet/src/Core/Memory/MemoryArena.cpp b/Juliet/src/Core/Memory/MemoryArena.cpp index c370df1..469e3d4 100644 --- a/Juliet/src/Core/Memory/MemoryArena.cpp +++ b/Juliet/src/Core/Memory/MemoryArena.cpp @@ -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(current) + current->Committed; + size_t commitSize = commitPostClamped - current->Committed; + Byte* commitPtr = reinterpret_cast(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); diff --git a/Juliet/src/Core/Memory/MemoryArenaTests.cpp b/Juliet/src/Core/Memory/MemoryArenaTests.cpp index f1a153a..bf9a4bc 100644 --- a/Juliet/src/Core/Memory/MemoryArenaTests.cpp +++ b/Juliet/src/Core/Memory/MemoryArenaTests.cpp @@ -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(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(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(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(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