diff --git a/Game/Entity/Entity.h b/Game/Entity/Entity.h index eb6cffc..317a7a1 100644 --- a/Game/Entity/Entity.h +++ b/Game/Entity/Entity.h @@ -47,15 +47,18 @@ namespace Game requires EntityConcept EntityType* MakeEntity(EntityManager& manager, float x, float y) { - auto* arena = Juliet::GetGameArena(); - EntityType* result = Juliet::ArenaPushType(arena, ConstString("EntityType")); - Entity* base = result->Base = Juliet::ArenaPushType(arena, ConstString("Entity")); - base->X = x; - base->Y = y; - base->Derived = result; - base->Kind = EntityType::Kind; + auto* arena = manager.Arena; + EntityType* result = Juliet::ArenaPushStruct(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; } diff --git a/Game/Entity/EntityManager.cpp b/Game/Entity/EntityManager.cpp index 556412a..f7f467a 100644 --- a/Game/Entity/EntityManager.cpp +++ b/Game/Entity/EntityManager.cpp @@ -11,7 +11,16 @@ namespace Game EntityID EntityManager::ID = 0; - void InitEntityManager() {} + void InitEntityManager(Juliet::NonNullPtr arena) + { + Manager.Arena = arena.Get(); + Manager.Entities.Create(arena); + } + + void ShutdownEntityManager() + { + Manager.Entities.Destroy(); + } EntityManager& GetEntityManager() { diff --git a/Game/Entity/EntityManager.h b/Game/Entity/EntityManager.h index e2e0d42..acc508d 100644 --- a/Game/Entity/EntityManager.h +++ b/Game/Entity/EntityManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include 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 Entities; }; - void InitEntityManager(); + void InitEntityManager(Juliet::NonNullPtr arena); + void ShutdownEntityManager(); EntityManager& GetEntityManager(); void RegisterEntity(EntityManager& manager, Entity* entity); } // namespace Game diff --git a/Game/game.cpp b/Game/game.cpp index f741572..df4c4b0 100644 --- a/Game/game.cpp +++ b/Game/game.cpp @@ -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(GetGameArena(), ConstString("GameState")); + auto* gameState = ArenaPushStruct(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(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) diff --git a/Juliet/Juliet.vcxproj b/Juliet/Juliet.vcxproj index a8e099e..1d0f787 100644 --- a/Juliet/Juliet.vcxproj +++ b/Juliet/Juliet.vcxproj @@ -86,6 +86,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/Juliet/Juliet.vcxproj.filters b/Juliet/Juliet.vcxproj.filters index 2394074..0357371 100644 --- a/Juliet/Juliet.vcxproj.filters +++ b/Juliet/Juliet.vcxproj.filters @@ -106,6 +106,9 @@ include\Core\Memory + + include\Core\Memory + include\Core\Memory @@ -318,6 +321,9 @@ src\Core\Memory + + src\Core\Memory + src\Core\Memory diff --git a/Juliet/include/Core/Container/Vector.h b/Juliet/include/Core/Container/Vector.h index 8508559..fc85ba5 100644 --- a/Juliet/include/Core/Container/Vector.h +++ b/Juliet/include/Core/Container/Vector.h @@ -57,9 +57,9 @@ namespace Juliet } else if (AllowRealloc && !InternalArena) { - DataFirst = Data = static_cast(ArenaReallocate(Arena, Data, Capacity * sizeof(Type), - newCapacity * sizeof(Type), AlignOf(Type), true - JULIET_DEBUG_ONLY(, "VectorRealloc"))); + DataFirst = Data = + static_cast(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; } diff --git a/Juliet/include/Core/JulietInit.h b/Juliet/include/Core/JulietInit.h index ce3c85f..573246a 100644 --- a/Juliet/include/Core/JulietInit.h +++ b/Juliet/include/Core/JulietInit.h @@ -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); diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 77f47b8..204c9e1 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include // 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); + [[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); // 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, size_t size, size_t align, bool shouldBeZeroed - JULIET_DEBUG_ONLY(, const char* tag)); - [[nodiscard]] void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, - size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag)); + [[nodiscard]] JULIET_API void* ArenaPush(NonNullPtr arena, size_t size, size_t align, + bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag)); + [[nodiscard]] void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, + size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag)); void ArenaPopTo(NonNullPtr arena, size_t position); void ArenaPop(NonNullPtr arena, size_t amount); void ArenaClear(NonNullPtr arena); @@ -84,7 +82,8 @@ namespace Juliet template [[nodiscard]] Type* ArenaPushStruct(NonNullPtr arena) { - return static_cast(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name()))); + return static_cast( + ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name()))); } // #define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0)) @@ -95,115 +94,7 @@ namespace Juliet template [[nodiscard]] Type* ArenaPushArray(NonNullPtr arena, size_t count) { - return static_cast(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(this + 1); } - const uint8* GetData() const { return reinterpret_cast(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 - inline T* ArenaPushType(MemoryArena* arena, String tag) - { - T* result = static_cast(ArenaPush(arena, sizeof(T), alignof(T), tag)); - - if (result) - { - MemSet(result, 0, sizeof(T)); - } - return result; - } - - template - inline T* ArenaPushArray(MemoryArena* arena, size_t count, String tag) - { - T* result = static_cast(ArenaPush(arena, sizeof(T) * count, alignof(T), tag)); - - if (result) - { - MemSet(result, 0, sizeof(T) * count); - } - return result; - } - - template - inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount, String tag) - { - return static_cast(Juliet::ArenaRealloc(arena, static_cast(oldPtr), sizeof(T) * oldCount, - sizeof(T) * newCount, alignof(T), tag)); + return static_cast(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), + true JULIET_DEBUG_ONLY(, typeid(Type).name()))); } } // namespace Juliet diff --git a/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp b/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp index 849c0c6..be1a8ea 100644 --- a/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp +++ b/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp @@ -56,7 +56,8 @@ namespace Juliet basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */; // Allocate from Scratch Arena (transient) - auto tempDllPath = ArenaPushArray(GetScratchArena(), tempDllMaxBufferSize, ConstString("tempDllPath")); + index_t pos = ArenaPos(code.Arena); + auto tempDllPath = ArenaPushArray(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) diff --git a/Juliet/src/Core/Memory/MemoryArena.cpp b/Juliet/src/Core/Memory/MemoryArena.cpp index 9aa2bfd..81510be 100644 --- a/Juliet/src/Core/Memory/MemoryArena.cpp +++ b/Juliet/src/Core/Memory/MemoryArena.cpp @@ -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(static_cast(oldPtr) - reinterpret_cast(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(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((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(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(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(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(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(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 diff --git a/Juliet/src/Core/Memory/MemoryArenaDebug.cpp b/Juliet/src/Core/Memory/MemoryArenaDebug.cpp index 38a66fe..b4b8060 100644 --- a/Juliet/src/Core/Memory/MemoryArenaDebug.cpp +++ b/Juliet/src/Core/Memory/MemoryArenaDebug.cpp @@ -1,5 +1,5 @@ -#include #include // For Arena definition +#include #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(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(g_DebugInfoArena); - } - - static void FreeArenaAllocation(ArenaAllocation* info) - { - if (info) - { - info->Next = g_ArenaAllocationFreeList; - g_ArenaAllocationFreeList = info; - } - } - // ------------------------ - void DebugRegisterArena(NonNullPtr 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; diff --git a/Juliet/src/Engine/Debug/MemoryDebugger.cpp b/Juliet/src/Engine/Debug/MemoryDebugger.cpp index 48b4c27..367b5fd 100644 --- a/Juliet/src/Engine/Debug/MemoryDebugger.cpp +++ b/Juliet/src/Engine/Debug/MemoryDebugger.cpp @@ -1,10 +1,17 @@ #include -#include -#include -#include // Added for Arena definition -#include #include +#include // Added for Arena definition +#include +#include +#include + +#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(s[i]); + if (entry.Arena == arena) + { + return entry.State; + } } - // Use hash to pick a Hue - float h = static_cast(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((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(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(virtualWidthLocal) / static_cast(searchBlk->Reserved); - float xStart = static_cast(static_cast(state.SelectedAlloc->Offset) * scale); + double scale = + static_cast(virtualWidthLocal) / static_cast(searchBlk->Reserved); + float xStart = static_cast(static_cast(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(static_cast(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(static_cast(expectedOffset) * scale); - float padWidth = static_cast(static_cast(padSize) * scale); + float padWidth = static_cast(static_cast(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(static_cast(info->Offset) * scale); float width = static_cast(static_cast(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(static_cast(freeNode->Position) * scale); - float fWidth = static_cast(static_cast(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(static_cast(freeNode->Position) * scale); + float fWidth = static_cast(static_cast(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(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(blk), "Page %d (%zu bytes, pos %zu)", blkIdx, blk->Reserved, blk->Position)) + if (ImGui::TreeNode(reinterpret_cast(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 diff --git a/Juliet/src/Engine/Engine.cpp b/Juliet/src/Engine/Engine.cpp index 6d1f90b..bc6cb1f 100644 --- a/Juliet/src/Engine/Engine.cpp +++ b/Juliet/src/Engine/Engine.cpp @@ -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 diff --git a/Juliet/src/UnitTest/Container/VectorUnitTest.cpp b/Juliet/src/UnitTest/Container/VectorUnitTest.cpp index 22f7795..93c1768 100644 --- a/Juliet/src/UnitTest/Container/VectorUnitTest.cpp +++ b/Juliet/src/UnitTest/Container/VectorUnitTest.cpp @@ -5,7 +5,7 @@ #include #include -namespace Juliet +namespace Juliet::UnitTest { namespace { @@ -294,5 +294,5 @@ namespace Juliet ArenaRelease(externalArena); } } -} // namespace Juliet +} // namespace Juliet::UnitTest #endif diff --git a/Juliet/src/UnitTest/RunUnitTests.cpp b/Juliet/src/UnitTest/RunUnitTests.cpp index 8d2ce06..504195d 100644 --- a/Juliet/src/UnitTest/RunUnitTests.cpp +++ b/Juliet/src/UnitTest/RunUnitTests.cpp @@ -5,19 +5,21 @@ #include #include -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 diff --git a/JulietApp/main.cpp b/JulietApp/main.cpp index be29479..daf9ab4 100644 --- a/JulietApp/main.cpp +++ b/JulietApp/main.cpp @@ -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); } }