From 0a755540711c11d3447802f9a0db73a7d896649f Mon Sep 17 00:00:00 2001 From: Patedam Date: Sun, 25 Jan 2026 15:19:39 -0500 Subject: [PATCH] Updated memory debugger to have a better display of memory Made with gemini --- Juliet/src/Engine/Debug/MemoryDebugger.cpp | 417 ++++++++++++++++----- 1 file changed, 322 insertions(+), 95 deletions(-) diff --git a/Juliet/src/Engine/Debug/MemoryDebugger.cpp b/Juliet/src/Engine/Debug/MemoryDebugger.cpp index 58d61dc..c47498b 100644 --- a/Juliet/src/Engine/Debug/MemoryDebugger.cpp +++ b/Juliet/src/Engine/Debug/MemoryDebugger.cpp @@ -9,6 +9,33 @@ namespace Juliet::Debug { namespace { + struct ArenaDebugState + { + float Zoom = 1.0f; + ArenaAllocation* SelectedAlloc = nullptr; + bool ScrollVisualToSelected = false; + bool ScrollListToSelected = false; + }; + + ArenaDebugState& GetState(const String& name) + { + // Simple static map-like storage using standard map would be better but we minimize deps. + // Just use a few static vars since we have known 3 arenas. + static ArenaDebugState s_GameState; + static ArenaDebugState s_EngineState; + static ArenaDebugState s_ScratchState; + + if (StringCompare(name, ConstString("Game Arena")) == 0) + { + return s_GameState; + } + if (StringCompare(name, ConstString("Engine Arena")) == 0) + { + return s_EngineState; + } + return s_ScratchState; + } + #if JULIET_DEBUG // Generate a stable color from a string tag uint32 GetColorForTag(const String& tag) @@ -30,6 +57,8 @@ namespace Juliet::Debug void DrawMemoryArena(String name, const MemoryArena& arena, [[maybe_unused]] ArenaAllocation* currentHighlight, [[maybe_unused]] ArenaAllocation*& outNewHighlight) { + ArenaDebugState& state = GetState(name); + if (ImGui::CollapsingHeader(CStr(name), ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::PushID(CStr(name)); @@ -50,125 +79,324 @@ namespace Juliet::Debug ImGui::Text("Used: %zu / %zu bytes (%zu blocks)", totalUsed, totalCapacity, blockCount); + // Zoom Control + ImGui::SliderFloat("Zoom", &state.Zoom, 0.1f, 1000.0f, "%.2f"); + #if JULIET_DEBUG - // --- Tree View --- - if (ImGui::TreeNode("Allocations List")) + // --- Visual View (Scrollable + Zoom) --- + ImGui::Separator(); + ImGui::Text("Visual Map"); + + // Calculate Dynamic Height + // Height = blockCount * (blockHeight + spacing) + padding + // Constrain between minHeight and maxHeight + float blockHeight = 24.0f; + float blockSpacing = 4.0f; + // Add extra padding to avoid vertical scrollbar triggering due to varying style padding + float requiredVisHeight = (float)blockCount * (blockHeight + blockSpacing) + 30.0f; + if (requiredVisHeight > 300.0f) requiredVisHeight = 300.0f; + if (requiredVisHeight < 50.0f) requiredVisHeight = 50.0f; + + // Use ImGuiWindowFlags_NoScrollbar if we fit? + // No, we want horizontal scrollbar. If we provide just enough height, vertical shouldn't show. + if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true, + ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) { - MemoryBlock* blk = arena.FirstBlock; - int blkIdx = 0; + ImGui::SetScrollY(0.0f); // Lock Vertical Scroll to separate Zoom from Scroll + + ImDrawList* dl = ImGui::GetWindowDrawList(); + float availWidth = ImGui::GetContentRegionAvail().x; + ImVec2 startPos = ImGui::GetCursorScreenPos(); + + // Reserve space for the virtual width FIRST to satisfy layout + // Also reserve explicit height > window height to ensure Child captures MouseWheel (stops bubbling) + float virtualWidth = availWidth * state.Zoom; + if (virtualWidth < availWidth) virtualWidth = availWidth; + ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f)); + + bool isMapHovered = ImGui::IsWindowHovered(); + + // Interaction Logic + // 1. Zoom with Pivot + if (isMapHovered) + { + float wheel = ImGui::GetIO().MouseWheel; + if (wheel != 0.0f) + { + // Pivot Logic + float mouseXScreen = ImGui::GetMousePos().x; + float activeScrollX = ImGui::GetScrollX(); + + // Viewport Left + float viewportLeft = ImGui::GetWindowPos().x; + float mouseRelViewport = mouseXScreen - viewportLeft; + + // Current Ratio + float virtualWidthOld = std::max(availWidth * state.Zoom, availWidth); + float mouseContentPos = activeScrollX + mouseRelViewport; + float ratio = mouseContentPos / virtualWidthOld; + + // Apply Zoom + state.Zoom += wheel * 0.2f * state.Zoom; + if (state.Zoom < 0.1f) state.Zoom = 0.1f; + if (state.Zoom > 1000.0f) state.Zoom = 1000.0f; + + // New Ratio + float virtualWidthNew = std::max(availWidth * state.Zoom, availWidth); + + // flNewScroll + MouseRel = Ratio * NewWidth + float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport; + ImGui::SetScrollX(desiredScrollX); + } + } + + // 2. Pan (Left, Right, or Middle Mouse) + if (isMapHovered && (ImGui::IsMouseDragging(ImGuiMouseButton_Left) || ImGui::IsMouseDragging(ImGuiMouseButton_Right) || + ImGui::IsMouseDragging(ImGuiMouseButton_Middle))) + { + ImGui::SetScrollX(ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x); + } + + // 3. Jump to Selected (Sync from List) + if (state.ScrollVisualToSelected && state.SelectedAlloc) + { + MemoryBlock* searchBlk = arena.FirstBlock; + while (searchBlk) + { + ArenaAllocation* search = searchBlk->FirstAllocation; + bool found = false; + while (search) + { + if (search == state.SelectedAlloc) + { + found = true; + break; + } + search = search->Next; + } + + if (found) + { + float virtualWidthLocal = std::max(availWidth * state.Zoom, availWidth); + size_t dataSize = searchBlk->TotalSize - sizeof(MemoryBlock); + if (dataSize > 0) + { + double scale = (double)virtualWidthLocal / (double)dataSize; + float xStart = (float)((double)state.SelectedAlloc->Offset * scale); + + // Scroll to center xStart + float centerOffset = availWidth * 0.5f; + ImGui::SetScrollX(xStart - centerOffset); + } + state.ScrollVisualToSelected = false; // Consumed + break; + } + searchBlk = searchBlk->Next; + } + } + + MemoryBlock* blk = arena.FirstBlock; + ImVec2 pos = startPos; + + int bIdx = 0; while (blk) { - if (ImGui::TreeNode((void*)blk, "Block %d (%zu bytes)", blkIdx, blk->TotalSize)) + // Draw Block Frame + ImVec2 rectMin = pos; + auto rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight); + + // Border Color + ImU32 borderColor = (uint32)ImColor::HSV(((float)bIdx * 0.1f), 0.0f, 0.7f); + dl->AddRect(rectMin, rectMax, borderColor); + + size_t dataSize = blk->TotalSize - sizeof(MemoryBlock); + if (dataSize > 0) { + double scale = (double)virtualWidth / (double)dataSize; + ArenaAllocation* alloc = blk->FirstAllocation; while (alloc) { - bool isHovered = (currentHighlight == alloc); - if (isHovered) + float xStart = pos.x + (float)((double)alloc->Offset * scale); + float width = (float)((double)alloc->Size * scale); + width = std::max(width, 1.0f); + + auto aMin = ImVec2(xStart, pos.y + 1); + auto aMax = ImVec2(xStart + width, pos.y + blockHeight - 1); + + // Clip visually to block bounds (simplistic) + if (aMin.x < rectMin.x) aMin.x = rectMin.x; + if (aMax.x > rectMax.x) aMax.x = rectMax.x; + + ImU32 color = GetColorForTag(alloc->Tag); + + bool isSelected = (state.SelectedAlloc == alloc); + bool isHovered = (currentHighlight == alloc); + + if (isSelected || isHovered) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 0, 1)); + // Highlight + dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE); + dl->AddRect(aMin, aMax, IM_COL32_BLACK); + } + else + { + dl->AddRectFilled(aMin, aMax, color); } - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; - if (isHovered) + // Draw Text if zoomed enough and fits + if (width > 20.0f) // Threshold width to attempt drawing text { - flags |= ImGuiTreeNodeFlags_Selected; + // Need to check actual text size + ImVec2 textSize = ImGui::CalcTextSize(alloc->Tag.Data, alloc->Tag.Data + alloc->Tag.Size); + if (width >= textSize.x + 4.0f) + { + // Center text + float textX = aMin.x + (width - textSize.x) * 0.5f; + float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f; + + // Contrast color + ImU32 textColor = IM_COL32_BLACK; + dl->AddText(ImVec2(textX, textY), textColor, alloc->Tag.Data, + alloc->Tag.Data + alloc->Tag.Size); + } } - // Use implicit string length from String struct - ImGui::TreeNodeEx(alloc, flags, "[%zu] %.*s (%zu bytes)", alloc->Offset, - (int)alloc->Tag.Size, alloc->Tag.Data, alloc->Size); - - if (ImGui::IsItemHovered()) + if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered()) { outNewHighlight = alloc; - } - if (isHovered) - { - ImGui::PopStyleColor(); + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + state.SelectedAlloc = alloc; + state.ScrollListToSelected = true; // Sync to list + state.ScrollVisualToSelected = false; + } + + ImGui::BeginTooltip(); + ImGui::Text("Tag: %.*s", (int)alloc->Tag.Size, alloc->Tag.Data); + ImGui::Text("Size: %zu bytes", alloc->Size); + ImGui::Text("Offset: %zu", alloc->Offset); + + uint8* dataPtr = blk->GetData() + alloc->Offset; + ImGui::Text("Data: "); + for (int i = 0; i < 16 && i < (int)alloc->Size; ++i) + { + ImGui::SameLine(); + ImGui::Text("%02X", dataPtr[i]); + } + + ImGui::EndTooltip(); } alloc = alloc->Next; } - ImGui::TreePop(); } + + pos.y += blockHeight + 4; blk = blk->Next; - blkIdx++; + bIdx++; } + // Final spacing + ImGui::Dummy(ImVec2(0, pos.y - startPos.y)); + } + ImGui::EndChild(); + + // --- Tree View (Scrollable) --- + ImGui::Separator(); + if (ImGui::TreeNode("Allocations List")) + { + // Calculate item count for Dynamic Height + int totalAllocCount = 0; + MemoryBlock* countBlk = arena.FirstBlock; + while (countBlk) + { + // Add 1 for Block Node + totalAllocCount++; + // If Block Node is Open? We don't know if we don't query it. + // But actually we are inside a Child window inside the TreeNode. + // We want the Child Window to be sized appropriately. + + // Simplification: Just count ALL allocations to assume "expanded" state or just use a max cap. + // User wants "Grow until 25 elements". + // This implies if we have 5 items, height is small. If 100, height is capped at 25. + // We'll just count total allocations + blocks as a rough estimate of potential height. + ArenaAllocation* countAlloc = countBlk->FirstAllocation; + while (countAlloc) + { + totalAllocCount++; + countAlloc = countAlloc->Next; + } + countBlk = countBlk->Next; + } + + float lineHeight = ImGui::GetTextLineHeightWithSpacing(); + float currentNeededHeight = (float)totalAllocCount * lineHeight; + float maxHeight = lineHeight * 25.0f; + + float listHeight = currentNeededHeight; + if (listHeight > maxHeight) listHeight = maxHeight; + if (listHeight < lineHeight) listHeight = lineHeight; // Min 1 line + + if (ImGui::BeginChild("AllocList", ImVec2(0, listHeight), true)) + { + MemoryBlock* blk = arena.FirstBlock; + int blkIdx = 0; + while (blk) + { + if (ImGui::TreeNode((void*)blk, "Block %d (%zu bytes)", blkIdx, blk->TotalSize)) + { + ArenaAllocation* alloc = blk->FirstAllocation; + while (alloc) + { + bool isSelected = (state.SelectedAlloc == alloc); + bool isHovered = (currentHighlight == alloc); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (isSelected || isHovered) + { + flags |= ImGuiTreeNodeFlags_Selected; + } + + if (isSelected && state.ScrollListToSelected) + { + ImGui::SetScrollHereY(); + state.ScrollListToSelected = false; // Consumed + } + + // Color Square + ImU32 color = GetColorForTag(alloc->Tag); + ImGui::ColorButton("##color", ImColor(color), + ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, + ImVec2(12, 12)); + ImGui::SameLine(); + + // Use implicit string length from String struct + ImGui::TreeNodeEx(alloc, flags, "[%zu] %.*s (%zu bytes)", alloc->Offset, + (int)alloc->Tag.Size, alloc->Tag.Data, alloc->Size); + + if (ImGui::IsItemClicked()) + { + state.SelectedAlloc = alloc; + state.ScrollVisualToSelected = true; // Sync to visual + state.ScrollListToSelected = false; + } + if (ImGui::IsItemHovered()) + { + outNewHighlight = alloc; + } + + alloc = alloc->Next; + } + ImGui::TreePop(); + } + blk = blk->Next; + blkIdx++; + } + } + ImGui::EndChild(); + ImGui::TreePop(); } - - // --- Visual View --- - ImGui::Spacing(); - - MemoryBlock* blk = arena.FirstBlock; - ImDrawList* dl = ImGui::GetWindowDrawList(); - float availWidth = ImGui::GetContentRegionAvail().x; - float blockHeight = 24.0f; - ImVec2 startPos = ImGui::GetCursorScreenPos(); - ImVec2 pos = startPos; - - int bIdx = 0; - while (blk) - { - // Draw Block Frame - ImVec2 rectMin = pos; - auto rectMax = ImVec2(pos.x + availWidth, pos.y + blockHeight); - - // Border Color - ImU32 borderColor = (uint32)ImColor::HSV(((float)bIdx * 0.1f), 0.0f, 0.7f); - dl->AddRect(rectMin, rectMax, borderColor); - - size_t dataSize = blk->TotalSize - sizeof(MemoryBlock); - if (dataSize > 0) - { - double scale = (double)availWidth / (double)dataSize; - - ArenaAllocation* alloc = blk->FirstAllocation; - while (alloc) - { - float xStart = pos.x + (float)((double)alloc->Offset * scale); - float width = (float)((double)alloc->Size * scale); - width = std::max(width, 1.0f); - - auto aMin = ImVec2(xStart, pos.y + 1); - auto aMax = ImVec2(xStart + width, pos.y + blockHeight - 1); - - ImU32 color = GetColorForTag(alloc->Tag); - - if (currentHighlight == alloc) - { - // Highlight - dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE); - dl->AddRect(aMin, aMax, IM_COL32_BLACK); - } - else - { - dl->AddRectFilled(aMin, aMax, color); - } - - // Hit Test - if (ImGui::IsMouseHoveringRect(aMin, aMax)) - { - outNewHighlight = alloc; - ImGui::BeginTooltip(); - ImGui::Text("Tag: %.*s", (int)alloc->Tag.Size, alloc->Tag.Data); - ImGui::Text("Size: %zu bytes", alloc->Size); - ImGui::Text("Offset: %zu", alloc->Offset); - ImGui::EndTooltip(); - } - - alloc = alloc->Next; - } - } - - pos.y += blockHeight + 4; - blk = blk->Next; - bIdx++; - } - - // Reserve space for the custom drawing - ImGui::Dummy(ImVec2(0, (pos.y - startPos.y))); #else ImGui::TextColored(ImVec4(1, 0, 0, 1), "Compile with JULIET_DEBUG for Memory Visualization"); #endif @@ -193,7 +421,6 @@ namespace Juliet::Debug { DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered); DrawMemoryArena(ConstString("Engine Arena"), *GetEngineArena(), s_ConfirmedHovered, frameHovered); - DrawMemoryArena(ConstString("Scratch Arena"), *GetScratchArena(), s_ConfirmedHovered, frameHovered); } ImGui::End();