Removed the old Memory Arena and converted all to the new one
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
@@ -501,8 +117,8 @@ namespace Juliet::Debug
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -513,7 +129,7 @@ namespace Juliet::Debug
|
||||
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)
|
||||
@@ -560,8 +176,8 @@ namespace Juliet::Debug
|
||||
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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -760,8 +384,8 @@ 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,8 +409,8 @@ 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);
|
||||
}
|
||||
@@ -814,8 +438,8 @@ namespace Juliet::Debug
|
||||
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
state.SelectedAlloc = info;
|
||||
state.ScrollListToSelected = true;
|
||||
state.SelectedAlloc = info;
|
||||
state.ScrollListToSelected = true;
|
||||
state.ScrollVisualToSelected = false;
|
||||
}
|
||||
}
|
||||
@@ -827,19 +451,19 @@ namespace Juliet::Debug
|
||||
// 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);
|
||||
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);
|
||||
ImVec2 fMin(fxStart, pos.y + 1);
|
||||
ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
|
||||
|
||||
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
|
||||
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
|
||||
|
||||
freeNode = freeNode->Next;
|
||||
}
|
||||
freeNode = freeNode->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user