Removed the old Memory Arena and converted all to the new one

This commit is contained in:
2026-02-14 18:09:47 -05:00
parent 6260d9aacf
commit 5a22a172a6
17 changed files with 207 additions and 1126 deletions

View File

@@ -47,15 +47,18 @@ namespace Game
requires EntityConcept<EntityType>
EntityType* MakeEntity(EntityManager& manager, float x, float y)
{
auto* arena = Juliet::GetGameArena();
EntityType* result = Juliet::ArenaPushType<EntityType>(arena, ConstString("EntityType"));
Entity* base = result->Base = Juliet::ArenaPushType<Entity>(arena, ConstString("Entity"));
base->X = x;
base->Y = y;
base->Derived = result;
base->Kind = EntityType::Kind;
auto* arena = manager.Arena;
EntityType* result = Juliet::ArenaPushStruct<EntityType>(arena);
Entity base;
base.X = x;
base.Y = y;
base.Derived = result;
base.Kind = EntityType::Kind;
manager.Entities.PushBack(base);
RegisterEntity(manager, base);
result->Base = manager.Entities.Back();
RegisterEntity(manager, &base);
return result;
}

View File

@@ -11,7 +11,16 @@ namespace Game
EntityID EntityManager::ID = 0;
void InitEntityManager() {}
void InitEntityManager(Juliet::NonNullPtr<Juliet::Arena> arena)
{
Manager.Arena = arena.Get();
Manager.Entities.Create(arena);
}
void ShutdownEntityManager()
{
Manager.Entities.Destroy();
}
EntityManager& GetEntityManager()
{

View File

@@ -1,6 +1,7 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Container/Vector.h>
namespace Game
{
@@ -10,10 +11,14 @@ namespace Game
struct EntityManager
{
static EntityID ID;
// May be this should contains the allocator for each entity types
Juliet::Arena* Arena;
// TODO: Should be a pool
Juliet::VectorArena<Entity, 1024> Entities;
};
void InitEntityManager();
void InitEntityManager(Juliet::NonNullPtr<Juliet::Arena> arena);
void ShutdownEntityManager();
EntityManager& GetEntityManager();
void RegisterEntity(EntityManager& manager, Entity* entity);
} // namespace Game

View File

@@ -31,7 +31,7 @@ namespace Game
using namespace Juliet;
extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
extern "C" JULIET_API void GameInit(GameInitParams* params)
{
// Example allocation in GameArena
struct GameState
@@ -40,7 +40,7 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
int Score;
};
auto* gameState = ArenaPushType<GameState>(GetGameArena(), ConstString("GameState"));
auto* gameState = ArenaPushStruct<GameState>(params->GameArena);
gameState->TotalTime = 0.0f;
gameState->Score = 0;
@@ -49,7 +49,7 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
using namespace Game;
// Entity Use case
InitEntityManager();
InitEntityManager(params->GameArena);
auto& manager = GetEntityManager();
Door* door = MakeEntity<Door>(manager, 10.0f, 2.0f);
door->IsOpened = true;
@@ -69,6 +69,10 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
extern "C" JULIET_API void __cdecl GameShutdown()
{
printf("Shutting down game...\n");
using namespace Game;
ShutdownEntityManager();
}
extern "C" JULIET_API void __cdecl GameUpdate([[maybe_unused]] float deltaTime)

View File

@@ -86,6 +86,7 @@
<CustomBuild Include="include\Core\Math\Vector.h" />
<CustomBuild Include="include\Core\Memory\Allocator.h" />
<CustomBuild Include="include\Core\Memory\MemoryArena.h" />
<CustomBuild Include="include\Core\Memory\MemoryArenaDebug.h" />
<CustomBuild Include="include\Core\Memory\ScratchArena.h" />
<CustomBuild Include="include\Core\Memory\Utils.h" />
<CustomBuild Include="include\Core\Networking\IPAddress.h" />
@@ -157,6 +158,7 @@
<CustomBuild Include="src\Core\Math\MathRound.cpp" />
<CustomBuild Include="src\Core\Memory\Allocator.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArena.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArenaDebug.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArenaTests.cpp" />
<CustomBuild Include="src\Core\Memory\ScratchArena.cpp" />
<CustomBuild Include="src\Core\Networking\NetworkPacket.cpp" />

View File

@@ -106,6 +106,9 @@
<CustomBuild Include="include\Core\Memory\MemoryArena.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="include\Core\Memory\MemoryArenaDebug.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="include\Core\Memory\ScratchArena.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
@@ -318,6 +321,9 @@
<CustomBuild Include="src\Core\Memory\MemoryArena.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\MemoryArenaDebug.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\MemoryArenaTests.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>

View File

@@ -57,9 +57,9 @@ namespace Juliet
}
else if (AllowRealloc && !InternalArena)
{
DataFirst = Data = static_cast<Type*>(ArenaReallocate(Arena, Data, Capacity * sizeof(Type),
newCapacity * sizeof(Type), AlignOf(Type), true
JULIET_DEBUG_ONLY(, "VectorRealloc")));
DataFirst = Data =
static_cast<Type*>(ArenaReallocate(Arena, Data, Capacity * sizeof(Type), newCapacity * sizeof(Type),
AlignOf(Type), true JULIET_DEBUG_ONLY(, "VectorRealloc")));
DataLast = Data + Count - 1;
}
Capacity = newCapacity;
@@ -176,7 +176,9 @@ namespace Juliet
const Type* end() const { return DataFirst + Count; }
Type* First() { return DataFirst; }
Type* Front() { return DataFirst; }
Type* Last() { return DataLast; }
Type* Back() { return DataLast; }
size_t Size() const { return Count; }

View File

@@ -13,12 +13,12 @@ namespace Juliet
All = 0xFb
};
struct MemoryArena;
struct Arena;
struct GameInitParams
{
MemoryArena* GameArena;
MemoryArena* ScratchArena;
Arena* GameArena;
Arena* ScratchArena;
};
void JulietInit(JulietInit_Flags flags);

View File

@@ -5,7 +5,6 @@
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/Memory/Utils.h>
#include <Core/Memory/Utils.h>
#include <Juliet.h>
#include <typeinfo> // Added for typeid
@@ -44,7 +43,7 @@ namespace Juliet
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;)
@@ -65,17 +64,16 @@ namespace Juliet
JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
};
[[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params
JULIET_DEBUG_ONLY(, const char* name),
const std::source_location& loc = std::source_location::current());
void ArenaRelease(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API Arena* ArenaAllocate(const ArenaParams& params JULIET_DEBUG_ONLY(, const char* name),
const std::source_location& loc = std::source_location::current());
JULIET_API void ArenaRelease(NonNullPtr<Arena> arena);
// Raw Push, can be used but templated helpers exists below
// Raw Push, can be used but templated helpers exists below
[[nodiscard]] void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed
JULIET_DEBUG_ONLY(, const char* tag));
[[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
[[nodiscard]] JULIET_API void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align,
bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
[[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
void ArenaClear(NonNullPtr<Arena> arena);
@@ -84,7 +82,8 @@ namespace Juliet
template <typename Type>
[[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena)
{
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
return static_cast<Type*>(
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))
@@ -95,115 +94,7 @@ namespace Juliet
template <typename Type>
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count)
{
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
}
// --- Paged Memory Architecture ---
struct ArenaAllocation;
struct MemoryBlock
{
static constexpr uint32 kMagic = 0xAA55AA55;
uint32 Magic;
MemoryBlock* Next; // Next block in the chain (Arena) or FreeList (Pool)
size_t TotalSize; // Total size of this block (including header)
size_t Used; // Offset relative to the start of Data
#if JULIET_DEBUG
ArenaAllocation* FirstAllocation = nullptr;
uint64 Pad; // Ensure 16-byte alignment (Size 40 -> 48)
#endif
// Data follows immediately.
// We use a helper to access it to avoid C++ flexible array warning issues if strict
uint8* GetData() { return reinterpret_cast<uint8*>(this + 1); }
const uint8* GetData() const { return reinterpret_cast<const uint8*>(this + 1); }
};
struct MemoryPool
{
void* BaseAddress = nullptr;
size_t TotalSize = 0;
MemoryBlock* FreeList = nullptr;
[[nodiscard]] MemoryBlock* AllocateBlock(size_t minCapacity);
void FreeBlock(MemoryBlock* block);
};
struct MemoryArena
{
MemoryPool* BackingPool;
MemoryBlock* CurrentBlock;
MemoryBlock* FirstBlock;
// Marker behavior is now tricky with pages.
// Simple Marker = { Block*, Offset }
};
struct ArenaMarker
{
MemoryBlock* Block;
size_t Offset;
};
JULIET_API void MemoryArenaCreate(MemoryArena* arena, MemoryPool* pool);
JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, String tag);
JULIET_API void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag);
JULIET_API bool ArenaPop(MemoryArena* arena, void* ptr, size_t size);
JULIET_API void ArenaReset(MemoryArena* arena);
JULIET_API ArenaMarker ArenaGetMarker(MemoryArena* arena);
JULIET_API void ArenaResetToMarker(MemoryArena* arena, ArenaMarker marker);
// --- Global Arenas & Management ---
// Returns a global arena that resets every frame.
JULIET_API MemoryArena* GetScratchArena();
// Persistent game arena.
JULIET_API MemoryArena* GetGameArena();
// Internal engine function to reset the scratch arena.
JULIET_API void ScratchArenaReset();
// 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 <typename T>
inline T* ArenaPushType(MemoryArena* arena, String tag)
{
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T), alignof(T), tag));
if (result)
{
MemSet(result, 0, sizeof(T));
}
return result;
}
template <typename T>
inline T* ArenaPushArray(MemoryArena* arena, size_t count, String tag)
{
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T) * count, alignof(T), tag));
if (result)
{
MemSet(result, 0, sizeof(T) * count);
}
return result;
}
template <typename T>
inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount, String tag)
{
return static_cast<T*>(Juliet::ArenaRealloc(arena, static_cast<void*>(oldPtr), sizeof(T) * oldCount,
sizeof(T) * newCount, alignof(T), tag));
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)),
true JULIET_DEBUG_ONLY(, typeid(Type).name())));
}
} // namespace Juliet

View File

@@ -56,7 +56,8 @@ namespace Juliet
basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */;
// Allocate from Scratch Arena (transient)
auto tempDllPath = ArenaPushArray<char>(GetScratchArena(), tempDllMaxBufferSize, ConstString("tempDllPath"));
index_t pos = ArenaPos(code.Arena);
auto tempDllPath = ArenaPushArray<char>(code.Arena, tempDllMaxBufferSize);
for (uint32 attempt = 0; attempt < kMaxAttempts; ++attempt)
{
@@ -96,6 +97,7 @@ namespace Juliet
break;
}
}
ArenaPopTo(code.Arena, pos);
code.Dll = LoadDynamicLibrary(tempDllPath);
if (code.Dll)

View File

@@ -177,7 +177,8 @@ namespace Juliet
commitSize = AlignPow2(size + k_ArenaHeaderSize, align);
}
newBlock = ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize } JULIET_DEBUG_ONLY(, arena->Name));
newBlock =
ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize } JULIET_DEBUG_ONLY(, arena->Name));
}
newBlock->BasePosition = current->BasePosition + current->Reserved;
@@ -220,7 +221,7 @@ namespace Juliet
MemoryZero(result, sizeToZero);
}
}
if (result == nullptr) [[unlikely]]
{
Log(LogLevel::Error, LogCategory::Core, "Fatal Allocation Failure - Unexpected allocation failure");
@@ -271,7 +272,7 @@ 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<size_t>(static_cast<Byte*>(oldPtr) - reinterpret_cast<Byte*>(block));
@@ -326,375 +327,4 @@ namespace Juliet
size_t position = current->BasePosition + current->Position;
return position;
}
namespace UnitTest
{
extern void TestMemoryArena();
}
// --- MemoryPool Implementation ---
// Simple First-Fit Allocator
MemoryBlock* MemoryPool::AllocateBlock(size_t minCapacity)
{
// Require space for Header + Data
size_t totalUnalignedSize = sizeof(MemoryBlock) + minCapacity;
size_t requiredSize = (totalUnalignedSize + 15) & ~static_cast<size_t>(15);
MemoryBlock** prevPtr = &FreeList;
MemoryBlock* curr = FreeList;
while (curr)
{
if (curr->TotalSize >= requiredSize)
{
// Match
// Check if we can split this block?
if (curr->TotalSize >= requiredSize + sizeof(MemoryBlock) + 16)
{
// Split
size_t remainingSize = curr->TotalSize - requiredSize;
MemoryBlock* nextBlock = reinterpret_cast<MemoryBlock*>((uint8*)curr + requiredSize);
nextBlock->Magic = MemoryBlock::kMagic;
nextBlock->TotalSize = remainingSize;
nextBlock->Used = 0;
nextBlock->Next = curr->Next;
// Update FreeList to point to the new remaining block instead of curr
*prevPtr = nextBlock;
// Update curr to be the allocated chunk
curr->TotalSize = requiredSize;
}
else
{
// Take the whole block
*prevPtr = curr->Next;
}
curr->Next = nullptr;
curr->Used = 0;
curr->Magic = MemoryBlock::kMagic;
#if JULIET_DEBUG
curr->FirstAllocation = nullptr;
#endif
#if JULIET_DEBUG
if (curr->TotalSize > sizeof(MemoryBlock))
{
MemSet(curr->GetData(), 0xCD, curr->TotalSize - sizeof(MemoryBlock));
}
#endif
return curr;
}
prevPtr = &curr->Next;
curr = curr->Next;
}
// Out of Memory in Pool
Assert(false, "MemoryPool exhausted!");
return nullptr;
}
void MemoryPool::FreeBlock(MemoryBlock* block)
{
if (!block)
{
return;
}
Assert(block->Magic == MemoryBlock::kMagic);
// Poison Header and Data in Debug
#if JULIET_DEBUG
DebugFreeArenaAllocations(block);
// 0xDD = Dead Data
MemSet(block->GetData(), 0xDD, block->TotalSize - sizeof(MemoryBlock));
block->Magic = 0xDEADBEEF;
#endif
// Insert at Head of FreeList (Simplest, no coalescing yet)
block->Next = FreeList;
FreeList = block;
}
// --- MemoryArena Implementation ---
void MemoryArenaCreate(MemoryArena* arena, MemoryPool* pool)
{
Assert(arena);
Assert(pool);
arena->BackingPool = pool;
arena->CurrentBlock = nullptr;
arena->FirstBlock = nullptr;
}
// Overload for backward compatibility / tests if needed, but we should switch to using Pools.
// NOTE: The previous signature was (Arena*, void* backing, size_t).
// We are changing the API.
void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, [[maybe_unused]] String tag)
{
Assert(arena);
Assert(arena->BackingPool);
// Default Block Size (e.g., 64KB or 1MB? Let's use 16KB for granular tests,
// or larger for prod. Let's make it dynamic or standard constant.
constexpr size_t kDefaultBlockSize = 64 * 1024; // 64KB pages
// Alignment check
Assert((alignment & (alignment - 1)) == 0);
if (!arena->CurrentBlock)
{
// Initial Allocation
size_t allocSize = std::max(size, kDefaultBlockSize);
arena->CurrentBlock = arena->BackingPool->AllocateBlock(allocSize);
arena->FirstBlock = arena->CurrentBlock;
}
// Try allocation in CurrentBlock
MemoryBlock* blk = arena->CurrentBlock;
size_t currentAddr = reinterpret_cast<size_t>(blk->GetData()) + blk->Used;
size_t alignmentOffset = 0;
size_t mask = alignment - 1;
if (currentAddr & mask)
{
alignmentOffset = alignment - (currentAddr & mask);
}
if (blk->Used + alignmentOffset + size > blk->TotalSize - sizeof(MemoryBlock))
{
// Overflow! Request new block.
// Strict minimum: what we need now.
// Better: Max(Default, size) to avoid repeating large allocs for tiny overflow?
size_t allocSize = std::max(size, kDefaultBlockSize);
MemoryBlock* newBlock = arena->BackingPool->AllocateBlock(allocSize);
// Link
blk->Next = newBlock;
arena->CurrentBlock = newBlock;
blk = newBlock;
// Recalc for new block (Used should be 0)
currentAddr = reinterpret_cast<size_t>(blk->GetData());
alignmentOffset = 0;
// newBlock check
if (currentAddr & mask)
{
alignmentOffset = alignment - (currentAddr & mask);
}
}
// Commit
blk->Used += alignmentOffset;
void* ptr = blk->GetData() + blk->Used;
JULIET_DEBUG_ONLY(DebugArenaAddAllocation(blk, size, blk->Used, tag);)
blk->Used += size;
return ptr;
}
void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag)
{
Assert(arena);
Assert(oldPtr);
Assert(newSize != 0);
// Optimized Case: Expanding the LAST allocation in the Current Block
MemoryBlock* blk = arena->CurrentBlock;
uint8* oldBytes = static_cast<uint8*>(oldPtr);
// Is oldPtr inside current block?
if (oldBytes >= blk->GetData() && oldBytes < blk->GetData() + blk->TotalSize - sizeof(MemoryBlock))
{
// Is it the last one?
if (oldBytes + oldSize == blk->GetData() + blk->Used)
{
// Can we expand?
if (blk->Used + (newSize - oldSize) <= blk->TotalSize - sizeof(MemoryBlock))
{
// Yes, expand in place
blk->Used += (newSize - oldSize);
#if JULIET_DEBUG
{
ArenaAllocation* t = blk->FirstAllocation;
while (t && t->Next)
t = t->Next;
if (t) t->Size += (newSize - oldSize);
}
#endif
return oldPtr;
}
}
}
// Fallback: Copy
void* newPtr = ArenaPush(arena, newSize, alignment, tag);
MemCopy(newPtr, oldPtr, std::min(oldSize, newSize));
return newPtr;
}
bool ArenaPop(MemoryArena* arena, void* ptr, size_t size)
{
Assert(arena);
Assert(ptr);
Assert(size);
MemoryBlock* blk = arena->CurrentBlock;
Assert(blk);
uint8* ptrBytes = static_cast<uint8*>(ptr);
uint8* currentTop = blk->GetData() + blk->Used;
// Check if this pointer is exactly at the top of the stack (LIFO)
if (ptrBytes + size == currentTop)
{
// Yes, we can just rewind the Used pointer
blk->Used -= size;
JULIET_DEBUG_ONLY(DebugArenaRemoveLastAllocation(blk);)
return true;
}
return false;
}
void ArenaReset(MemoryArena* arena)
{
Assert(arena);
Assert(arena->FirstBlock);
// Keep FirstBlock, Free the rest.
MemoryBlock* curr = arena->FirstBlock->Next;
while (curr)
{
MemoryBlock* next = curr->Next;
arena->BackingPool->FreeBlock(curr);
curr = next;
}
arena->FirstBlock->Next = nullptr;
arena->FirstBlock->Used = 0;
arena->CurrentBlock = arena->FirstBlock;
#if JULIET_DEBUG
// Poison First Block
DebugFreeArenaAllocations(arena->FirstBlock);
MemSet(arena->FirstBlock->GetData(), 0xCD, arena->FirstBlock->TotalSize - sizeof(MemoryBlock));
#endif
}
ArenaMarker ArenaGetMarker(MemoryArena* arena)
{
Assert(arena);
return { arena->CurrentBlock, arena->CurrentBlock ? arena->CurrentBlock->Used : 0 };
}
void ArenaResetToMarker(MemoryArena* arena, ArenaMarker marker)
{
Assert(arena);
if (!marker.Block)
{
// If marker block is null, it might mean "start" or "empty".
// But if the arena has blocks, this is suspicious.
// If the arena was empty when marker was taken, this is valid.
ArenaReset(arena);
return;
}
// Free blocks *after* the marker block
MemoryBlock* curr = marker.Block->Next;
while (curr)
{
MemoryBlock* next = curr->Next;
arena->BackingPool->FreeBlock(curr);
curr = next;
}
marker.Block->Next = nullptr;
marker.Block->Used = marker.Offset;
arena->CurrentBlock = marker.Block;
}
// --- Global Arenas ---
namespace
{
MemoryPool g_ScratchMemory;
MemoryPool g_GameMemory;
MemoryArena g_ScratchArena;
MemoryArena g_GameArena;
// Backing Buffers
void* g_ScratchBuffer = nullptr;
void* g_EngineBuffer = nullptr;
void* g_GameBuffer = nullptr;
constexpr size_t kScratchSize = Megabytes(64);
constexpr size_t kGameSize = Megabytes(512);
void InitPool(MemoryPool* pool, void* buffer, size_t size)
{
pool->BaseAddress = buffer;
pool->TotalSize = size;
// Create one giant initial block
Assert(size > sizeof(MemoryBlock));
MemoryBlock* block = static_cast<MemoryBlock*>(buffer);
block->Magic = MemoryBlock::kMagic;
block->Next = nullptr;
block->TotalSize = size;
block->Used = 0;
pool->FreeList = block;
}
} // namespace
MemoryArena* GetScratchArena()
{
return &g_ScratchArena;
}
MemoryArena* GetGameArena()
{
return &g_GameArena;
}
void ScratchArenaReset()
{
ArenaReset(&g_ScratchArena);
}
void MemoryArenasInit()
{
g_ScratchBuffer = Malloc(kScratchSize);
g_GameBuffer = Malloc(kGameSize);
InitPool(&g_ScratchMemory, g_ScratchBuffer, kScratchSize);
InitPool(&g_GameMemory, g_GameBuffer, kGameSize);
MemoryArenaCreate(&g_ScratchArena, &g_ScratchMemory);
MemoryArenaCreate(&g_GameArena, &g_GameMemory);
#if JULIET_DEBUG
UnitTest::TestMemoryArena();
#endif
}
void MemoryArenasShutdown()
{
// Technically we should free blocks?
// But since we own the giant buffers, we can just free them.
SafeFree(g_ScratchBuffer);
SafeFree(g_EngineBuffer);
SafeFree(g_GameBuffer);
}
} // namespace Juliet

View File

@@ -1,5 +1,5 @@
#include <Core/Memory/MemoryArenaDebug.h>
#include <Core/Memory/MemoryArena.h> // For Arena definition
#include <Core/Memory/MemoryArenaDebug.h>
#if JULIET_DEBUG
@@ -24,7 +24,8 @@ namespace Juliet
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"));
g_DebugInfoArena = ArenaAllocate(
{ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena"));
}
return ArenaPushStruct<ArenaDebugInfo>(g_DebugInfoArena);
@@ -39,34 +40,6 @@ namespace Juliet
}
}
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<ArenaAllocation>(g_DebugInfoArena);
}
static void FreeArenaAllocation(ArenaAllocation* info)
{
if (info)
{
info->Next = g_ArenaAllocationFreeList;
g_ArenaAllocationFreeList = info;
}
}
// ------------------------
void DebugRegisterArena(NonNullPtr<Arena> arena)
{
// Add to Global List
@@ -123,7 +96,7 @@ namespace Juliet
{
ArenaDebugInfo** prevInfo = &block->FirstDebugInfo;
ArenaDebugInfo* info = block->FirstDebugInfo;
while (info)
{
if (info->Offset == oldOffset) // Found it
@@ -163,75 +136,14 @@ namespace Juliet
ArenaDebugInfo* info = AllocDebugInfo();
if (info)
{
info->Tag = tag ? tag : "Untagged";
info->Size = size;
info->Offset = offset;
info->Next = block->FirstDebugInfo;
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;

View File

@@ -1,10 +1,17 @@
#include <Core/Common/String.h>
#include <Engine/Debug/MemoryDebugger.h>
#include <Core/Common/String.h>
#include <Core/Memory/MemoryArena.h> // Added for Arena definition
#include <Engine/Debug/MemoryDebugger.h>
#include <Core/Container/Vector.h>
#include <Core/Memory/MemoryArena.h> // Added for Arena definition
#include <Core/Memory/MemoryArenaDebug.h>
#include <Engine/Debug/MemoryDebugger.h>
#include <imgui.h>
#if JULIET_DEBUG
namespace Juliet
{
Arena* GetGlobalArenaListHead();
} // namespace Juliet
namespace Juliet::Debug
{
@@ -18,7 +25,6 @@ namespace Juliet::Debug
bool ScrollListToSelected = false;
};
#if JULIET_DEBUG
struct PagedArenaDebugState
{
float Zoom = 1.0f;
@@ -38,63 +44,26 @@ namespace Juliet::Debug
PagedArenaDebugState& GetPagedArenaState(const Arena* arena)
{
if (s_PagedArenaStates.Arena == nullptr)
{
s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States"));
}
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.
// 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)
if (s_PagedArenaStates.Arena == nullptr)
{
return s_GameState;
s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States"));
}
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)
{
uint32 hash = 0;
size_t len = tag.Size;
const char* s = tag.Data;
// Simple FNV-1a style hash
for (size_t i = 0; i < len; ++i)
for (auto& entry : s_PagedArenaStates)
{
hash = hash * 65599 + static_cast<uint8>(s[i]);
if (entry.Arena == arena)
{
return entry.State;
}
}
// Use hash to pick a Hue
float h = static_cast<float>(hash % 360) / 360.0f;
return ImColor::HSV(h, 0.7f, 0.8f);
PagedArenaStateEntry newEntry;
newEntry.Arena = arena;
// Default construct state
newEntry.State = PagedArenaDebugState();
s_PagedArenaStates.PushBack(newEntry);
return s_PagedArenaStates.Last()->State;
}
// Generate a unique color per allocation, using tag + offset + size
@@ -121,364 +90,11 @@ namespace Juliet::Debug
float v = 0.65f + static_cast<float>((hash >> 20) % 25) / 100.0f;
return ImColor::HSV(h, s, v);
}
#endif
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));
// Calculate Stats
size_t totalCapacity = 0;
size_t totalUsed = 0;
size_t blockCount = 0;
MemoryBlock* curr = arena.FirstBlock;
while (curr)
{
totalCapacity += curr->TotalSize;
totalUsed += curr->Used;
blockCount++;
curr = curr->Next;
}
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
// --- 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))
{
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)
{
// 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)
{
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)
{
// Highlight
dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE);
dl->AddRect(aMin, aMax, IM_COL32_BLACK);
}
else
{
dl->AddRectFilled(aMin, aMax, color);
}
// Draw Text if zoomed enough and fits
if (width > 20.0f) // Threshold width to attempt drawing text
{
// 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);
}
}
if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered())
{
outNewHighlight = alloc;
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;
}
}
pos.y += blockHeight + 4;
blk = blk->Next;
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();
}
#else
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Compile with JULIET_DEBUG for Memory Visualization");
#endif
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";
const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena";
PagedArenaDebugState& state = GetPagedArenaState(arena);
if (ImGui::CollapsingHeader(name))
@@ -496,24 +112,24 @@ namespace Juliet::Debug
{
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];
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;
totalUsed += blk->Position;
}
ImGui::Text("Used: %zu / %zu bytes (%zu pages)", totalUsed, totalCapacity, blocks.Size());
@@ -523,8 +139,8 @@ namespace Juliet::Debug
ImGui::Separator();
ImGui::Text("Visual Map");
float blockHeight = 24.0f;
float blockSpacing = 4.0f;
float blockHeight = 24.0f;
float blockSpacing = 4.0f;
float requiredVisHeight = static_cast<float>(blocks.Size()) * (blockHeight + blockSpacing) + 30.0f;
// constrains
if (requiredVisHeight > 300.0f)
@@ -535,7 +151,7 @@ namespace Juliet::Debug
{
requiredVisHeight = 50.0f;
}
if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar))
{
@@ -544,7 +160,7 @@ namespace Juliet::Debug
ImDrawList* dl = ImGui::GetWindowDrawList();
float availWidth = ImGui::GetContentRegionAvail().x;
ImVec2 startPos = ImGui::GetCursorScreenPos();
float virtualWidth = availWidth * state.Zoom;
if (virtualWidth < availWidth)
{
@@ -553,15 +169,15 @@ namespace Juliet::Debug
ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f));
bool isMapHovered = ImGui::IsWindowHovered();
// Zoom with pivot
if (isMapHovered)
{
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f)
{
float mouseXScreen = ImGui::GetMousePos().x;
float activeScrollX = ImGui::GetScrollX();
float mouseXScreen = ImGui::GetMousePos().x;
float activeScrollX = ImGui::GetScrollX();
float viewportLeft = ImGui::GetWindowPos().x;
float mouseRelViewport = mouseXScreen - viewportLeft;
@@ -570,17 +186,11 @@ namespace Juliet::Debug
float ratio = mouseContentPos / virtualWidthOld;
state.Zoom += wheel * 0.2f * state.Zoom;
if (state.Zoom < 0.1f)
{
state.Zoom = 0.1f;
}
if (state.Zoom > 1000000.0f)
{
state.Zoom = 1000000.0f;
}
state.Zoom = std::max(state.Zoom, 0.1f);
state.Zoom = std::min(state.Zoom, 1000000.0f);
float virtualWidthNew = std::max(availWidth * state.Zoom, availWidth);
float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport;
float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport;
ImGui::SetScrollX(desiredScrollX);
}
}
@@ -598,7 +208,7 @@ namespace Juliet::Debug
for (const Arena* searchBlk : blocks)
{
ArenaDebugInfo* search = searchBlk->FirstDebugInfo;
bool found = false;
bool found = false;
while (search)
{
if (search == state.SelectedAlloc)
@@ -614,8 +224,9 @@ namespace Juliet::Debug
float virtualWidthLocal = std::max(availWidth * state.Zoom, availWidth);
if (searchBlk->Reserved > 0)
{
double scale = static_cast<double>(virtualWidthLocal) / static_cast<double>(searchBlk->Reserved);
float xStart = static_cast<float>(static_cast<double>(state.SelectedAlloc->Offset) * scale);
double scale =
static_cast<double>(virtualWidthLocal) / static_cast<double>(searchBlk->Reserved);
float xStart = static_cast<float>(static_cast<double>(state.SelectedAlloc->Offset) * scale);
float centerOffset = availWidth * 0.5f;
ImGui::SetScrollX(xStart - centerOffset);
}
@@ -631,7 +242,7 @@ namespace Juliet::Debug
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));
size_t dataSize = blk->Reserved - k_ArenaHeaderSize;
@@ -642,7 +253,7 @@ namespace Juliet::Debug
// Draw Arena Header
{
float hdrWidth = static_cast<float>(static_cast<double>(k_ArenaHeaderSize) * scale);
hdrWidth = std::max(hdrWidth, 1.0f);
hdrWidth = std::max(hdrWidth, 1.0f);
ImVec2 hMin(pos.x, pos.y + 1);
ImVec2 hMax(pos.x + hdrWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(hMin, hMax, IM_COL32(40, 60, 120, 255));
@@ -650,7 +261,7 @@ namespace Juliet::Debug
if (hdrWidth > 30.0f)
{
const char* hdrLabel = "Header";
ImVec2 textSize = ImGui::CalcTextSize(hdrLabel);
ImVec2 textSize = ImGui::CalcTextSize(hdrLabel);
if (hdrWidth >= textSize.x + 4.0f)
{
float textX = hMin.x + (hdrWidth - textSize.x) * 0.5f;
@@ -670,17 +281,17 @@ namespace Juliet::Debug
}
// Draw Allocations
ArenaDebugInfo* info = blk->FirstDebugInfo;
size_t expectedOffset = k_ArenaHeaderSize;
ArenaDebugInfo* info = blk->FirstDebugInfo;
size_t expectedOffset = k_ArenaHeaderSize;
while (info)
{
// Draw alignment padding gap if present (only for small gaps that are actual alignment)
if (info->Offset > expectedOffset)
{
size_t padSize = info->Offset - expectedOffset;
size_t padSize = info->Offset - expectedOffset;
constexpr size_t k_MaxAlignmentPadding = 256;
float padXStart = pos.x + static_cast<float>(static_cast<double>(expectedOffset) * scale);
float padWidth = static_cast<float>(static_cast<double>(padSize) * scale);
float padWidth = static_cast<float>(static_cast<double>(padSize) * scale);
if (padSize <= k_MaxAlignmentPadding && padWidth >= 1.0f)
{
@@ -688,22 +299,31 @@ namespace Juliet::Debug
ImVec2 pMax(padXStart + padWidth, pos.y + blockHeight - 1);
// Clip
if (pMin.x < rectMin.x) { pMin.x = rectMin.x; }
if (pMax.x > rectMax.x) { pMax.x = rectMax.x; }
if (pMin.x < rectMin.x)
{
pMin.x = rectMin.x;
}
if (pMax.x > rectMax.x)
{
pMax.x = rectMax.x;
}
// Draw hatched pattern (diagonal lines) - clipped to visible area
float visLeft = ImGui::GetWindowPos().x;
float visRight = visLeft + ImGui::GetWindowSize().x;
float visLeft = ImGui::GetWindowPos().x;
float visRight = visLeft + ImGui::GetWindowSize().x;
float clippedLeft = std::max(pMin.x, visLeft);
float clippedRight = std::min(pMax.x, visRight);
dl->AddRectFilled(pMin, pMax, IM_COL32(30, 30, 30, 200));
if (clippedLeft < clippedRight)
{
float step = 6.0f;
float step = 6.0f;
float startX = clippedLeft - (pMax.y - pMin.y);
startX = pMin.x + step * std::floor((startX - pMin.x) / step);
if (startX < pMin.x) { startX = pMin.x; }
startX = pMin.x + step * std::floor((startX - pMin.x) / step);
if (startX < pMin.x)
{
startX = pMin.x;
}
int lineCount = 0;
for (float lx = startX; lx < clippedRight && lineCount < 500; lx += step, ++lineCount)
@@ -711,7 +331,11 @@ namespace Juliet::Debug
float ly0 = pMin.y;
float lx1 = lx + (pMax.y - pMin.y);
float ly1 = pMax.y;
if (lx1 > pMax.x) { ly1 = pMin.y + (pMax.x - lx); lx1 = pMax.x; }
if (lx1 > pMax.x)
{
ly1 = pMin.y + (pMax.x - lx);
lx1 = pMax.x;
}
dl->AddLine(ImVec2(lx, ly0), ImVec2(lx1, ly1), IM_COL32(80, 80, 80, 180));
}
}
@@ -720,7 +344,7 @@ namespace Juliet::Debug
if (padWidth > 30.0f)
{
const char* padLabel = "Pad";
ImVec2 textSize = ImGui::CalcTextSize(padLabel);
ImVec2 textSize = ImGui::CalcTextSize(padLabel);
if (padWidth >= textSize.x + 4.0f)
{
float textX = pMin.x + (padWidth - textSize.x) * 0.5f;
@@ -746,7 +370,7 @@ namespace Juliet::Debug
float xStart = pos.x + static_cast<float>(static_cast<double>(info->Offset) * scale);
float width = static_cast<float>(static_cast<double>(info->Size) * scale);
width = std::max(width, 1.0f);
ImVec2 aMin(xStart, pos.y + 1);
ImVec2 aMax(xStart + width, pos.y + blockHeight - 1);
@@ -759,9 +383,9 @@ namespace Juliet::Debug
{
aMax.x = rectMax.x;
}
ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size);
bool isSelected = (state.SelectedAlloc == info);
ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size);
bool isSelected = (state.SelectedAlloc == info);
if (isSelected)
{
@@ -777,7 +401,7 @@ namespace Juliet::Debug
if (width > 20.0f && info->Tag)
{
const char* tagStr = info->Tag;
size_t tagLen = 0;
size_t tagLen = 0;
while (tagStr[tagLen] != '\0')
{
++tagLen;
@@ -785,13 +409,13 @@ namespace Juliet::Debug
ImVec2 textSize = ImGui::CalcTextSize(tagStr, tagStr + tagLen);
if (width >= textSize.x + 4.0f)
{
float textX = aMin.x + (width - textSize.x) * 0.5f;
float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f;
float textX = aMin.x + (width - textSize.x) * 0.5f;
float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f;
ImU32 textColor = IM_COL32_BLACK;
dl->AddText(ImVec2(textX, textY), textColor, tagStr, tagStr + tagLen);
}
}
if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered())
{
ImGui::BeginTooltip();
@@ -811,11 +435,11 @@ namespace Juliet::Debug
}
ImGui::EndTooltip();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
state.SelectedAlloc = info;
state.ScrollListToSelected = true;
state.SelectedAlloc = info;
state.ScrollListToSelected = true;
state.ScrollVisualToSelected = false;
}
}
@@ -823,26 +447,26 @@ namespace Juliet::Debug
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 fxStart = pos.x + static_cast<float>(static_cast<double>(freeNode->Position) * scale);
float fWidth = static_cast<float>(static_cast<double>(freeNode->Size) * scale);
ImVec2 fMin(fxStart, pos.y + 1);
ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
freeNode = freeNode->Next;
}
ArenaFreeNode* freeNode = blk->FreeNodes;
while (freeNode)
{
float fxStart = pos.x + static_cast<float>(static_cast<double>(freeNode->Position) * scale);
float fWidth = static_cast<float>(static_cast<double>(freeNode->Size) * scale);
ImVec2 fMin(fxStart, pos.y + 1);
ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
freeNode = freeNode->Next;
}
}
}
pos.y += blockHeight + blockSpacing;
ImGui::PopID();
}
@@ -866,9 +490,9 @@ namespace Juliet::Debug
}
}
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float currentNeededHeight = static_cast<float>(totalAllocCount) * lineHeight;
float maxHeight = lineHeight * 25.0f;
float maxHeight = lineHeight * 25.0f;
float listHeight = currentNeededHeight;
if (listHeight > maxHeight)
@@ -885,7 +509,8 @@ namespace Juliet::Debug
int blkIdx = 0;
for (const Arena* blk : blocks)
{
if (ImGui::TreeNode(reinterpret_cast<const void*>(blk), "Page %d (%zu bytes, pos %zu)", blkIdx, blk->Reserved, blk->Position))
if (ImGui::TreeNode(reinterpret_cast<const void*>(blk), "Page %d (%zu bytes, pos %zu)",
blkIdx, blk->Reserved, blk->Position))
{
ArenaDebugInfo* info = blk->FirstDebugInfo;
if (!info)
@@ -919,9 +544,9 @@ namespace Juliet::Debug
if (ImGui::IsItemClicked())
{
state.SelectedAlloc = info;
state.SelectedAlloc = info;
state.ScrollVisualToSelected = true;
state.ScrollListToSelected = false;
state.ScrollListToSelected = false;
}
info = info->Next;
@@ -938,32 +563,25 @@ namespace Juliet::Debug
}
ImGui::PopID();
}
#endif
ArenaAllocation* s_ConfirmedHovered = nullptr;
} // namespace
void DebugDrawMemoryArena()
{
#if JULIET_DEBUG
// Cross-frame hover state
static ArenaAllocation* s_ConfirmedHovered = nullptr;
ArenaAllocation* frameHovered = nullptr;
#else
ArenaAllocation* s_ConfirmedHovered = nullptr;
ArenaAllocation* frameHovered = nullptr;
#endif
ArenaAllocation* frameHovered = nullptr;
if (ImGui::Begin("Memory Debugger"))
{
if (ImGui::BeginTabBar("ArenaTabs"))
{
#if JULIET_DEBUG
if (ImGui::BeginTabItem("Paged Arenas"))
{
Arena* head = GetGlobalArenaListHead();
if (!head)
{
ImGui::Text("No active paged arenas.");
ImGui::Text("No active paged arenas.");
}
else
{
@@ -973,7 +591,9 @@ namespace Juliet::Debug
if (a->Name)
{
bool isTest = (a->Name[0] == 'T' && a->Name[1] == 'e' && a->Name[2] == 's' && a->Name[3] == 't');
bool isDebugInfo = (a->Name[0] == 'D' && a->Name[1] == 'e' && a->Name[2] == 'b' && a->Name[3] == 'u' && a->Name[4] == 'g' && a->Name[5] == ' ' && a->Name[6] == 'I');
bool isDebugInfo =
(a->Name[0] == 'D' && a->Name[1] == 'e' && a->Name[2] == 'b' && a->Name[3] == 'u' &&
a->Name[4] == 'g' && a->Name[5] == ' ' && a->Name[6] == 'I');
if (isTest || isDebugInfo)
{
continue;
@@ -984,19 +604,14 @@ namespace Juliet::Debug
}
ImGui::EndTabItem();
}
#endif
if (ImGui::BeginTabItem("Legacy Arenas"))
{
DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
ImGui::End();
#if JULIET_DEBUG
s_ConfirmedHovered = frameHovered;
#endif
}
} // namespace Juliet::Debug
#endif

View File

@@ -18,7 +18,10 @@
namespace Juliet
{
#if JULIET_DEBUG
extern void RunUnitTests();
namespace UnitTest
{
extern void RunUnitTests();
}
#endif
namespace
@@ -134,10 +137,9 @@ namespace Juliet
void InitializeEngine(JulietInit_Flags flags)
{
InitializeLogManager();
MemoryArenasInit();
#if JULIET_DEBUG
RunUnitTests();
UnitTest::RunUnitTests();
#endif
InitFilesystem();
@@ -150,7 +152,6 @@ namespace Juliet
JulietShutdown();
ShutdownFilesystem();
MemoryArenasShutdown();
ShutdownLogManager();
}
@@ -185,9 +186,6 @@ namespace Juliet
// Render tick
RenderFrame();
// Reset scratch arena at end of frame
ScratchArenaReset();
}
}
} // namespace Juliet

View File

@@ -5,7 +5,7 @@
#include <Core/Common/CoreUtils.h>
#include <Core/Container/Vector.h>
namespace Juliet
namespace Juliet::UnitTest
{
namespace
{
@@ -294,5 +294,5 @@ namespace Juliet
ArenaRelease(externalArena);
}
}
} // namespace Juliet
} // namespace Juliet::UnitTest
#endif

View File

@@ -5,19 +5,21 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
namespace Juliet
namespace Juliet::UnitTest
{
// Forward declare the VectorUnitTest function
void VectorUnitTest();
void TestMemoryArena();
void RunUnitTests()
{
LogMessage(LogCategory::Core, "Starting Unit Tests...");
TestMemoryArena();
VectorUnitTest();
LogMessage(LogCategory::Core, "Unit Tests Completed Successfully.");
}
} // namespace Juliet
} // namespace Juliet::UnitTest
#endif

View File

@@ -198,8 +198,8 @@ void JulietApplication::Init()
if ((Running = GameCode.IsValid))
{
GameInitParams params;
params.GameArena = GetGameArena();
params.ScratchArena = GetScratchArena();
params.GameArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Game Arena"));
params.ScratchArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Scratch Arena"));
Game.Init(&params);
}
}