Updated memory debugger to have a better display of memory
Made with gemini
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user