From 16120dd8651f44187192afd7943c651dd18db8f4 Mon Sep 17 00:00:00 2001 From: Patedam Date: Sat, 14 Feb 2026 15:57:01 -0500 Subject: [PATCH] Made memory arena debugger show the new arenas. Made by gemini --- Juliet/include/Core/Container/Vector.h | 3 +- Juliet/include/Core/Memory/MemoryArena.h | 42 ++- Juliet/include/Core/Memory/MemoryArenaDebug.h | 50 ++++ Juliet/include/Juliet.h | 2 +- Juliet/src/Core/ImGui/ImGuiService.cpp | 2 +- Juliet/src/Core/Memory/MemoryArena.cpp | 119 ++++----- Juliet/src/Core/Memory/MemoryArenaDebug.cpp | 242 ++++++++++++++++++ Juliet/src/Core/Memory/MemoryArenaTests.cpp | 6 +- Juliet/src/Engine/Debug/MemoryDebugger.cpp | 236 ++++++++++++++++- 9 files changed, 605 insertions(+), 97 deletions(-) create mode 100644 Juliet/include/Core/Memory/MemoryArenaDebug.h create mode 100644 Juliet/src/Core/Memory/MemoryArenaDebug.cpp diff --git a/Juliet/include/Core/Container/Vector.h b/Juliet/include/Core/Container/Vector.h index f35e46c..fecf6f4 100644 --- a/Juliet/include/Core/Container/Vector.h +++ b/Juliet/include/Core/Container/Vector.h @@ -58,7 +58,8 @@ namespace Juliet else if (AllowRealloc && !InternalArena) { DataFirst = Data = static_cast(ArenaReallocate(Arena, Data, Capacity * sizeof(Type), - newCapacity * sizeof(Type), AlignOf(Type), true)); + newCapacity * sizeof(Type), AlignOf(Type), true + JULIET_DEBUG_ONLY(, "VectorRealloc"))); DataLast = Data + Count - 1; } Capacity = newCapacity; diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 43ac87e..606f2a2 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include // Added for typeid namespace Juliet { @@ -15,6 +17,10 @@ namespace Juliet struct ArenaFreeNode; +#if JULIET_DEBUG + struct ArenaDebugInfo; +#endif + struct Arena { Arena* Previous; @@ -36,8 +42,13 @@ namespace Juliet bool AllowRealloc : 1; - JULIET_DEBUG_ONLY(uint16 LostNodeCount); + JULIET_DEBUG_ONLY(uint16 LostNodeCount;) JULIET_DEBUG_ONLY(bool CanReserveMore : 1;) + + JULIET_DEBUG_ONLY(Arena* GlobalNext;) + JULIET_DEBUG_ONLY(Arena* GlobalPrev;) + JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;) + JULIET_DEBUG_ONLY(const char* Name;) }; static_assert(sizeof(Arena) <= k_ArenaHeaderSize); @@ -54,14 +65,17 @@ namespace Juliet JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;) }; - [[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params = {}, + [[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params = {} + JULIET_DEBUG_ONLY(, const char* name = "Unnamed Arena"), const std::source_location& loc = std::source_location::current()); void ArenaRelease(NonNullPtr arena); // Raw Push, can be used but templated helpers exists below - [[nodiscard]] void* ArenaPush(NonNullPtr arena, size_t size, size_t align, bool shouldBeZeroed); - [[nodiscard]] void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, - size_t align, bool shouldBeZeroed); + // Raw Push, can be used but templated helpers exists below + [[nodiscard]] void* ArenaPush(NonNullPtr arena, size_t size, size_t align, bool shouldBeZeroed + JULIET_DEBUG_ONLY(, const char* tag)); + [[nodiscard]] void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, + size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag)); void ArenaPopTo(NonNullPtr arena, size_t position); void ArenaPop(NonNullPtr arena, size_t amount); void ArenaClear(NonNullPtr arena); @@ -70,7 +84,7 @@ namespace Juliet template [[nodiscard]] Type* ArenaPushStruct(NonNullPtr arena) { - return static_cast(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true)); + return static_cast(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name()))); } // #define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0)) @@ -81,17 +95,11 @@ namespace Juliet template [[nodiscard]] Type* ArenaPushArray(NonNullPtr arena, size_t count) { - return static_cast(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true)); + return static_cast(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true JULIET_DEBUG_ONLY(, typeid(Type).name()))); } // --- Paged Memory Architecture --- - struct ArenaAllocation - { - size_t Offset; - size_t Size; - String Tag; - ArenaAllocation* Next; - }; + struct ArenaAllocation; struct MemoryBlock { @@ -159,9 +167,15 @@ namespace Juliet // Internal engine function to initialize memory arenas. void MemoryArenasInit(); + // Internal engine function to shutdown memory arenas. // Internal engine function to shutdown memory arenas. void MemoryArenasShutdown(); +#if JULIET_DEBUG + JULIET_API Arena* GetGlobalArenaListHead(); +#endif + + template inline T* ArenaPushType(MemoryArena* arena, String tag) { diff --git a/Juliet/include/Core/Memory/MemoryArenaDebug.h b/Juliet/include/Core/Memory/MemoryArenaDebug.h new file mode 100644 index 0000000..c61e72c --- /dev/null +++ b/Juliet/include/Core/Memory/MemoryArenaDebug.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#if JULIET_DEBUG + +namespace Juliet +{ + struct Arena; + struct MemoryBlock; + + // Arena (Struct) + struct ArenaDebugInfo + { + const char* Tag; + size_t Offset; + size_t Size; + ArenaDebugInfo* Next; + }; + + // MemoryArena (Pool-based) + struct ArenaAllocation + { + size_t Offset; + size_t Size; + String Tag; + ArenaAllocation* Next; + }; + + // Arena (Struct) + void DebugRegisterArena(NonNullPtr arena); + void DebugUnregisterArena(NonNullPtr arena); + void DebugArenaSetDebugName(NonNullPtr arena, const char* name); + bool IsDebugInfoArena(const Arena* arena); // To prevent recursion + void DebugArenaFreeBlock(Arena* block); // To clear all debug infos in a block + void DebugArenaRemoveAllocation(Arena* block, size_t oldOffset); + void DebugArenaPopTo(Arena* block, size_t newPosition); + void DebugArenaAddDebugInfo(Arena* block, size_t size, size_t offset, const char* tag); + + // MemoryArena (Pool-based) + void DebugFreeArenaAllocations(MemoryBlock* blk); + void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag); + void DebugArenaRemoveLastAllocation(MemoryBlock* blk); + +} // namespace Juliet + +#endif diff --git a/Juliet/include/Juliet.h b/Juliet/include/Juliet.h index e200816..7f9f266 100644 --- a/Juliet/include/Juliet.h +++ b/Juliet/include/Juliet.h @@ -24,7 +24,7 @@ #define JULIET_DEBUG_ONLY(...) __VA_ARGS__ #else #define JULIET_DEBUG 0 -#define JULIET_DEBUG_ONLY(expr) +#define JULIET_DEBUG_ONLY(...) #endif // Manual override to disable ImGui diff --git a/Juliet/src/Core/ImGui/ImGuiService.cpp b/Juliet/src/Core/ImGui/ImGuiService.cpp index 667632b..9bc478c 100644 --- a/Juliet/src/Core/ImGui/ImGuiService.cpp +++ b/Juliet/src/Core/ImGui/ImGuiService.cpp @@ -27,7 +27,7 @@ namespace Juliet::ImGuiService void* ImGuiAllocWrapper(size_t size, void* /*user_data*/) { - return ArenaPush(g_ImGuiArena, size, 8, false); + return ArenaPush(g_ImGuiArena, size, 8, false JULIET_DEBUG_ONLY(, "ImGuiAlloc")); } void ImGuiFreeWrapper(void* /*ptr*/, void* /*user_data*/) diff --git a/Juliet/src/Core/Memory/MemoryArena.cpp b/Juliet/src/Core/Memory/MemoryArena.cpp index e2d534d..9aa2bfd 100644 --- a/Juliet/src/Core/Memory/MemoryArena.cpp +++ b/Juliet/src/Core/Memory/MemoryArena.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include // For std::max @@ -33,7 +34,7 @@ namespace Juliet // https://github.com/EpicGamesExt/raddebugger/blob/master/src/base/base_arena.c - Arena* ArenaAllocate(const ArenaParams& params, const std::source_location& loc) + Arena* ArenaAllocate(const ArenaParams& params JULIET_DEBUG_ONLY(, const char* name), const std::source_location& loc) { Log(LogLevel::Message, LogCategory::Core, "Allocating from %s : %ul", loc.file_name(), loc.line()); @@ -61,22 +62,34 @@ namespace Juliet arena->FreeNodes = nullptr; arena->AllowRealloc = params.AllowRealloc; +#if JULIET_DEBUG arena->CanReserveMore = params.CanReserveMore; + arena->FirstDebugInfo = nullptr; + + DebugArenaSetDebugName(arena, name); + DebugRegisterArena(arena); +#endif return arena; } void ArenaRelease(NonNullPtr arena) { + JULIET_DEBUG_ONLY(DebugUnregisterArena(arena);) + for (Arena *node = arena->Current, *previous = nullptr; node != nullptr; node = previous) { previous = node->Previous; + + JULIET_DEBUG_ONLY(DebugArenaFreeBlock(node);) + Memory::OS_Release(node, node->Reserved); } } - void* ArenaPush(NonNullPtr arena, size_t size, size_t align, bool shouldBeZeroed) + void* ArenaPush(NonNullPtr arena, size_t size, size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag)) { + // Assert(IsPowerOfTwo(align)); Arena* current = arena->Current; size_t positionPrePush = AlignPow2(current->Position, align); size_t positionPostPush = positionPrePush + size; @@ -104,7 +117,12 @@ namespace Juliet next->Previous = previous; } - JULIET_DEBUG_ONLY(remainingSize > 0 ? ++current->LostNodeCount : current->LostNodeCount); +#if JULIET_DEBUG + if (remainingSize > 0) + { + ++current->LostNodeCount; + } +#endif } else { @@ -158,7 +176,8 @@ namespace Juliet reserveSize = AlignPow2(size + k_ArenaHeaderSize, align); commitSize = AlignPow2(size + k_ArenaHeaderSize, align); } - newBlock = ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize }); + + newBlock = ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize } JULIET_DEBUG_ONLY(, arena->Name)); } newBlock->BasePosition = current->BasePosition + current->Reserved; @@ -201,19 +220,22 @@ namespace Juliet MemoryZero(result, sizeToZero); } } - - // If alloc failed, log and assert + if (result == nullptr) [[unlikely]] { + Log(LogLevel::Error, LogCategory::Core, "Fatal Allocation Failure - Unexpected allocation failure"); Assert(false, "Fatal Allocation Failure - Unexpected allocation failure"); } + JULIET_DEBUG_ONLY(if (!IsDebugInfoArena(arena)) { DebugArenaAddDebugInfo(current, size, positionPrePush, tag); }) + return result; } - void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, size_t align, bool shouldBeZeroed) + void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, size_t align, + bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag)) { - void* result = ArenaPush(arena, newSize, align, shouldBeZeroed); + void* result = ArenaPush(arena, newSize, align, shouldBeZeroed JULIET_DEBUG_ONLY(, tag)); // Find the correct block to release Arena* block = nullptr; @@ -249,6 +271,12 @@ namespace Juliet block->FreeNodes->Previous = freeNode; } block->FreeNodes = freeNode; + +#if JULIET_DEBUG + // Remove the debug info for the old allocation (since it's now free) + size_t oldOffset = static_cast(static_cast(oldPtr) - reinterpret_cast(block)); + DebugArenaRemoveAllocation(block, oldOffset); +#endif } return result; } @@ -262,20 +290,18 @@ namespace Juliet { previous = current->Previous; current->Position = k_ArenaHeaderSize; + + JULIET_DEBUG_ONLY(DebugArenaFreeBlock(current);) + SingleLinkedListPushPrevious(arena->FreeBlockLast, 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); current->Position = newPosition; + + JULIET_DEBUG_ONLY(DebugArenaPopTo(current, newPosition);) } void ArenaPop(NonNullPtr arena, size_t amount) @@ -308,23 +334,6 @@ namespace Juliet // --- MemoryPool Implementation --- -#if JULIET_DEBUG - static void FreeDebugAllocations(MemoryBlock* blk) - { - if (!blk) - { - return; - } - ArenaAllocation* curr = blk->FirstAllocation; - while (curr) - { - ArenaAllocation* next = curr->Next; - SafeFree(curr); - curr = next; - } - blk->FirstAllocation = nullptr; - } -#endif // Simple First-Fit Allocator MemoryBlock* MemoryPool::AllocateBlock(size_t minCapacity) @@ -401,7 +410,7 @@ namespace Juliet // Poison Header and Data in Debug #if JULIET_DEBUG - FreeDebugAllocations(block); + DebugFreeArenaAllocations(block); // 0xDD = Dead Data MemSet(block->GetData(), 0xDD, block->TotalSize - sizeof(MemoryBlock)); block->Magic = 0xDEADBEEF; @@ -485,23 +494,7 @@ namespace Juliet blk->Used += alignmentOffset; void* ptr = blk->GetData() + blk->Used; -#if JULIET_DEBUG - ArenaAllocation* node = (ArenaAllocation*)Malloc(sizeof(ArenaAllocation)); - node->Offset = blk->Used; - node->Size = size; - node->Tag = tag; - node->Next = nullptr; - - if (!blk->FirstAllocation) - blk->FirstAllocation = node; - else - { - ArenaAllocation* t = blk->FirstAllocation; - while (t->Next) - t = t->Next; - t->Next = node; - } -#endif + JULIET_DEBUG_ONLY(DebugArenaAddAllocation(blk, size, blk->Used, tag);) blk->Used += size; @@ -565,29 +558,7 @@ namespace Juliet { // Yes, we can just rewind the Used pointer blk->Used -= size; -#if JULIET_DEBUG - { - ArenaAllocation* t = blk->FirstAllocation; - ArenaAllocation* prev = nullptr; - while (t && t->Next) - { - prev = t; - t = t->Next; - } - if (t) - { - SafeFree(t); - if (prev) - { - prev->Next = nullptr; - } - else - { - blk->FirstAllocation = nullptr; - } - } - } -#endif + JULIET_DEBUG_ONLY(DebugArenaRemoveLastAllocation(blk);) return true; } @@ -614,7 +585,7 @@ namespace Juliet #if JULIET_DEBUG // Poison First Block - FreeDebugAllocations(arena->FirstBlock); + DebugFreeArenaAllocations(arena->FirstBlock); MemSet(arena->FirstBlock->GetData(), 0xCD, arena->FirstBlock->TotalSize - sizeof(MemoryBlock)); #endif } diff --git a/Juliet/src/Core/Memory/MemoryArenaDebug.cpp b/Juliet/src/Core/Memory/MemoryArenaDebug.cpp new file mode 100644 index 0000000..38a66fe --- /dev/null +++ b/Juliet/src/Core/Memory/MemoryArenaDebug.cpp @@ -0,0 +1,242 @@ +#include +#include // For Arena definition + +#if JULIET_DEBUG + +namespace Juliet +{ + Arena* g_ArenaGlobalHead = nullptr; + Arena* g_DebugInfoArena = nullptr; + ArenaDebugInfo* g_DebugInfoFreeList = nullptr; + ArenaAllocation* g_ArenaAllocationFreeList = nullptr; + + // --- Internal Helpers --- + static ArenaDebugInfo* AllocDebugInfo() + { + if (g_DebugInfoFreeList) + { + ArenaDebugInfo* info = g_DebugInfoFreeList; + g_DebugInfoFreeList = info->Next; + info->Next = nullptr; + return info; + } + + if (!g_DebugInfoArena) + { + // Create a dedicated arena for debug info + g_DebugInfoArena = ArenaAllocate({ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena")); + } + + return ArenaPushStruct(g_DebugInfoArena); + } + + static void FreeDebugInfo(ArenaDebugInfo* info) + { + if (info) + { + info->Next = g_DebugInfoFreeList; + g_DebugInfoFreeList = info; + } + } + + static ArenaAllocation* AllocArenaAllocation() + { + if (g_ArenaAllocationFreeList) + { + ArenaAllocation* info = g_ArenaAllocationFreeList; + g_ArenaAllocationFreeList = info->Next; + info->Next = nullptr; + return info; + } + + if (!g_DebugInfoArena) + { + g_DebugInfoArena = ArenaAllocate({ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena")); + } + + return ArenaPushStruct(g_DebugInfoArena); + } + + static void FreeArenaAllocation(ArenaAllocation* info) + { + if (info) + { + info->Next = g_ArenaAllocationFreeList; + g_ArenaAllocationFreeList = info; + } + } + // ------------------------ + + void DebugRegisterArena(NonNullPtr arena) + { + // Add to Global List + // Note: Not thread safe, assuming single thread for now as per context + if (g_ArenaGlobalHead) + { + g_ArenaGlobalHead->GlobalPrev = arena; + } + arena->GlobalNext = g_ArenaGlobalHead; + arena->GlobalPrev = nullptr; + g_ArenaGlobalHead = arena; + } + + void DebugUnregisterArena(NonNullPtr arena) + { + // Remove from Global List + if (arena->GlobalPrev) + { + arena->GlobalPrev->GlobalNext = arena->GlobalNext; + } + else + { + g_ArenaGlobalHead = arena->GlobalNext; + } + if (arena->GlobalNext) + { + arena->GlobalNext->GlobalPrev = arena->GlobalPrev; + } + } + + void DebugArenaSetDebugName(NonNullPtr arena, const char* name) + { + arena->Name = name; + } + + bool IsDebugInfoArena(const Arena* arena) + { + return arena == g_DebugInfoArena; + } + + void DebugArenaFreeBlock(Arena* block) + { + ArenaDebugInfo* info = block->FirstDebugInfo; + while (info) + { + ArenaDebugInfo* nextInfo = info->Next; + FreeDebugInfo(info); + info = nextInfo; + } + block->FirstDebugInfo = nullptr; + } + + void DebugArenaRemoveAllocation(Arena* block, size_t oldOffset) + { + ArenaDebugInfo** prevInfo = &block->FirstDebugInfo; + ArenaDebugInfo* info = block->FirstDebugInfo; + + while (info) + { + if (info->Offset == oldOffset) // Found it + { + *prevInfo = info->Next; + FreeDebugInfo(info); + break; + } + prevInfo = &info->Next; + info = info->Next; + } + } + + void DebugArenaPopTo(Arena* block, size_t newPosition) + { + ArenaDebugInfo** prevInfo = &block->FirstDebugInfo; + ArenaDebugInfo* info = block->FirstDebugInfo; + while (info) + { + if (info->Offset >= newPosition) + { + ArenaDebugInfo* toFree = info; + *prevInfo = info->Next; // Unlink + info = info->Next; // Advance + FreeDebugInfo(toFree); + } + else + { + prevInfo = &info->Next; + info = info->Next; + } + } + } + + void DebugArenaAddDebugInfo(Arena* block, size_t size, size_t offset, const char* tag) + { + ArenaDebugInfo* info = AllocDebugInfo(); + if (info) + { + info->Tag = tag ? tag : "Untagged"; + info->Size = size; + info->Offset = offset; + info->Next = block->FirstDebugInfo; + block->FirstDebugInfo = info; + } + } + + // Moved to top + + void DebugFreeArenaAllocations(MemoryBlock* blk) + { + if (!blk) + { + return; + } + ArenaAllocation* curr = blk->FirstAllocation; + while (curr) + { + ArenaAllocation* next = curr->Next; + FreeArenaAllocation(curr); + curr = next; + } + blk->FirstAllocation = nullptr; + } + + void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag) + { + ArenaAllocation* node = AllocArenaAllocation(); + node->Offset = offset; + node->Size = size; + node->Tag = tag; + node->Next = nullptr; + + if (!blk->FirstAllocation) + blk->FirstAllocation = node; + else + { + ArenaAllocation* t = blk->FirstAllocation; + while (t->Next) + t = t->Next; + t->Next = node; + } + } + + void DebugArenaRemoveLastAllocation(MemoryBlock* blk) + { + ArenaAllocation* t = blk->FirstAllocation; + ArenaAllocation* prev = nullptr; + while (t && t->Next) + { + prev = t; + t = t->Next; + } + if (t) + { + FreeArenaAllocation(t); + if (prev) + { + prev->Next = nullptr; + } + else + { + blk->FirstAllocation = nullptr; + } + } + } + + // Accessor for global list head (if needed by other files) + Arena* GetGlobalArenaListHead() + { + return g_ArenaGlobalHead; + } + +} // namespace Juliet + +#endif diff --git a/Juliet/src/Core/Memory/MemoryArenaTests.cpp b/Juliet/src/Core/Memory/MemoryArenaTests.cpp index 1f1083f..d40abd2 100644 --- a/Juliet/src/Core/Memory/MemoryArenaTests.cpp +++ b/Juliet/src/Core/Memory/MemoryArenaTests.cpp @@ -124,7 +124,7 @@ namespace Juliet::UnitTest char* secondCharArray = ArenaPushArray(arena, 128); char* thirdCharArray = ArenaPushArray(arena, 128); - secondCharArray = static_cast(ArenaReallocate(arena, secondCharArray, 128, 256, alignof(char), true)); + secondCharArray = static_cast(ArenaReallocate(arena, secondCharArray, 128, 256, alignof(char), true JULIET_DEBUG_ONLY(, "ReallocChar"))); char* fourthCharArray = ArenaPushArray(arena, 128); @@ -145,7 +145,7 @@ namespace Juliet::UnitTest MemSet(large, 'A', largeSize); size_t smallSize = 50; - char* smallData = static_cast(ArenaReallocate(arena, large, largeSize, smallSize, alignof(char), true)); + char* smallData = static_cast(ArenaReallocate(arena, large, largeSize, smallSize, alignof(char), true JULIET_DEBUG_ONLY(, "ResizeSmall"))); for (size_t i = 0; i < smallSize; ++i) { @@ -154,7 +154,7 @@ namespace Juliet::UnitTest // Allocate next block (should be immediately after 'small') // Don't zero it, if overflow happened it will contain 'A's - char* next = static_cast(ArenaPush(arena, 50, alignof(char), false)); + char* next = static_cast(ArenaPush(arena, 50, alignof(char), false JULIET_DEBUG_ONLY(, "OverflowCheck"))); bool corrupted = false; for(size_t i = 0; i < 50; ++i) diff --git a/Juliet/src/Engine/Debug/MemoryDebugger.cpp b/Juliet/src/Engine/Debug/MemoryDebugger.cpp index a519065..f8c74cd 100644 --- a/Juliet/src/Engine/Debug/MemoryDebugger.cpp +++ b/Juliet/src/Engine/Debug/MemoryDebugger.cpp @@ -1,8 +1,10 @@ #include #include -#include +#include +#include // Added for Arena definition +#include +#include -#include namespace Juliet::Debug { @@ -16,6 +18,49 @@ namespace Juliet::Debug bool ScrollListToSelected = false; }; +#if JULIET_DEBUG + struct PagedArenaDebugState + { + float Zoom = 1.0f; + ArenaDebugInfo* SelectedAlloc = nullptr; + bool ScrollVisualToSelected = false; + bool ScrollListToSelected = false; + }; + + struct PagedArenaStateEntry + { + const Arena* Arena; + PagedArenaDebugState State; + }; + + // Static Vector for lookup + VectorArena s_PagedArenaStates; + + PagedArenaDebugState& GetPagedArenaState(const Arena* arena) + { + if (s_PagedArenaStates.Arena == nullptr) + { + s_PagedArenaStates.Create(); + } + + for (auto& entry : s_PagedArenaStates) + { + if (entry.Arena == arena) + { + return entry.State; + } + } + + PagedArenaStateEntry newEntry; + newEntry.Arena = arena; + // Default construct state + newEntry.State = PagedArenaDebugState(); + + s_PagedArenaStates.PushBack(newEntry); + return s_PagedArenaStates.Last()->State; + } +#endif + ArenaDebugState& GetState(const String& name) { // Simple static map-like storage using standard map would be better but we minimize deps. @@ -403,6 +448,164 @@ namespace Juliet::Debug ImGui::PopID(); } } + +#if JULIET_DEBUG + void DrawPagedArena(const Arena* arena) + { + ImGui::PushID(arena); + const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena"; + PagedArenaDebugState& state = GetPagedArenaState(arena); + + if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) + { + // Collect Blocks (Pages) in order (Oldest -> Newest) + // Arena->Previous chain goes Newest -> Oldest. + // Collect Blocks (Pages) in order (Oldest -> Newest) + // Arena->Previous chain goes Newest -> Oldest. + static VectorArena blocks; + if (blocks.Arena == nullptr) + { + blocks.Create(); + } + blocks.Clear(); + + for (const Arena* curr = arena->Current; curr != nullptr; curr = curr->Previous) + { + blocks.PushBack(curr); + } + + // Reverse manually + size_t count = blocks.Count; + for (size_t i = 0; i < count / 2; ++i) + { + const Arena* temp = blocks[i]; + blocks[i] = blocks[count - 1 - i]; + blocks[count - 1 - i] = temp; + } + + // Calculate Stats + size_t totalCapacity = 0; + size_t totalUsed = 0; + + for (const Arena* blk : blocks) + { + totalCapacity += blk->Reserved; + totalUsed += blk->Position; + } + + ImGui::Text("Used: %zu / %zu bytes (%zu pages)", totalUsed, totalCapacity, blocks.Size()); + ImGui::SliderFloat("Zoom", &state.Zoom, 0.1f, 1000.0f, "%.2f"); + + // --- Visual View --- + ImGui::Separator(); + ImGui::Text("Visual Map"); + + float blockHeight = 24.0f; + float blockSpacing = 4.0f; + float requiredVisHeight = (float)blocks.Size() * (blockHeight + blockSpacing) + 30.0f; + // constrains + if (requiredVisHeight > 300.0f) requiredVisHeight = 300.0f; + if (requiredVisHeight < 50.0f) requiredVisHeight = 50.0f; + + if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true, + ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) + { + ImDrawList* dl = ImGui::GetWindowDrawList(); + float availWidth = ImGui::GetContentRegionAvail().x; + ImVec2 startPos = ImGui::GetCursorScreenPos(); + + float virtualWidth = availWidth * state.Zoom; + if (virtualWidth < availWidth) virtualWidth = availWidth; + ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f)); + + // Input Handling (Simple Pan/Zoom) + bool isMapHovered = ImGui::IsWindowHovered(); + if (isMapHovered && ImGui::GetIO().MouseWheel != 0.0f) + { + state.Zoom += ImGui::GetIO().MouseWheel * 0.2f * state.Zoom; + if (state.Zoom < 0.1f) state.Zoom = 0.1f; + if (state.Zoom > 1000.0f) state.Zoom = 1000.0f; + } + + ImVec2 pos = startPos; + for (const Arena* blk : blocks) + { + ImGui::PushID(blk); + ImVec2 rectMin = pos; + ImVec2 rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight); + + dl->AddRect(rectMin, rectMax, IM_COL32(100, 100, 100, 255)); // Block Border + + size_t dataSize = blk->Reserved - k_ArenaHeaderSize; // Approximate + if (dataSize > 0) + { + double scale = (double)virtualWidth / (double)blk->Reserved; + + // Draw Allocations + ArenaDebugInfo* info = blk->FirstDebugInfo; + while (info) + { + ImGui::PushID(info); + float xStart = pos.x + (float)((double)info->Offset * scale); + float width = (float)((double)info->Size * scale); + width = std::max(width, 1.0f); + + ImVec2 aMin(xStart, pos.y + 1); + ImVec2 aMax(xStart + width, pos.y + blockHeight - 1); + + ImU32 color = GetColorForTag(ConstString(info->Tag)); + if (state.SelectedAlloc == info) + dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE); + else + dl->AddRectFilled(aMin, aMax, color); + + if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("%s", info->Tag); + ImGui::Text("Size: %zu", info->Size); + ImGui::Text("Offset: %zu", info->Offset); + ImGui::EndTooltip(); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + state.SelectedAlloc = info; + } + } + + info = info->Next; + ImGui::PopID(); + } + + // Draw Free Nodes (Holes) if Realloc is enabled + if (arena->AllowRealloc && blk->FreeNodes) + { + ArenaFreeNode* freeNode = blk->FreeNodes; + while(freeNode) + { + float xStart = pos.x + (float)((double)freeNode->Position * scale); + float width = (float)((double)freeNode->Size * scale); + + ImVec2 fMin(xStart, pos.y + 1); + ImVec2 fMax(xStart + width, pos.y + blockHeight - 1); + + dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200)); + + freeNode = freeNode->Next; + } + } + } + + pos.y += blockHeight + blockSpacing; + ImGui::PopID(); + } + } + ImGui::EndChild(); + } + ImGui::PopID(); + } +#endif + } // namespace void DebugDrawMemoryArena() @@ -418,7 +621,34 @@ namespace Juliet::Debug if (ImGui::Begin("Memory Debugger")) { - DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered); + if (ImGui::BeginTabBar("ArenaTabs")) + { + if (ImGui::BeginTabItem("Legacy Arenas")) + { + DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered); + ImGui::EndTabItem(); + } + +#if JULIET_DEBUG + if (ImGui::BeginTabItem("Paged Arenas")) + { + Arena* head = GetGlobalArenaListHead(); + if (!head) + { + ImGui::Text("No active paged arenas."); + } + else + { + for (Arena* a = head; a != nullptr; a = a->GlobalNext) + { + DrawPagedArena(a); + } + } + ImGui::EndTabItem(); + } +#endif + ImGui::EndTabBar(); + } } ImGui::End();