Added a basic MemoryArena.

Added one scratch, one engine and one game arena.
Converted the game alloc to arena + the display stuff.
WIP

Made using Antigravity+gemini
This commit is contained in:
2026-01-17 21:09:23 -05:00
parent 98783b7e8f
commit f95ba51c13
28 changed files with 462 additions and 59 deletions

View File

@@ -57,6 +57,7 @@
<CustomBuild Include="include\Core\Math\Shape.h" />
<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\Utils.h" />
<CustomBuild Include="include\Core\Networking\IPAddress.h" />
<CustomBuild Include="include\Core\Networking\NetworkPacket.h" />
@@ -118,6 +119,9 @@
<CustomBuild Include="src\Core\Math\Math_Private.h" />
<CustomBuild Include="src\Core\Math\MathRound.cpp" />
<CustomBuild Include="src\Core\Memory\Allocator.cpp" />
<CustomBuild Include="src\Core\Memory\EngineArena.h" />
<CustomBuild Include="src\Core\Memory\MemoryArena.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArenaTests.cpp" />
<CustomBuild Include="src\Core\Networking\NetworkPacket.cpp" />
<CustomBuild Include="src\Core\Networking\Socket.cpp" />
<CustomBuild Include="src\Core\Networking\SocketPlatformImpl.h" />

View File

@@ -91,6 +91,9 @@
<CustomBuild Include="include\Core\Memory\Allocator.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="include\Core\Memory\MemoryArena.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="include\Core\Memory\Utils.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
@@ -273,6 +276,15 @@
<CustomBuild Include="src\Core\Memory\Allocator.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\EngineArena.h">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\MemoryArena.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\MemoryArenaTests.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Networking\NetworkPacket.cpp">
<Filter>src\Core\Networking</Filter>
</CustomBuild>

View File

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

View File

@@ -0,0 +1,61 @@
#pragma once
#include <Juliet.h>
#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Memory/Utils.h>
namespace Juliet
{
struct MemoryArena
{
uint8* Data;
size_t Size;
size_t Offset;
};
JULIET_API void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size);
JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment = 16);
JULIET_API void ArenaReset(MemoryArena* arena);
JULIET_API size_t ArenaGetMarker(MemoryArena* arena);
JULIET_API void ArenaResetToMarker(MemoryArena* arena, size_t 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.
void MemoryArenasShutdown();
template <typename T>
inline T* ArenaPushType(MemoryArena* arena)
{
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T), alignof(T)));
if (result)
{
MemSet(result, 0, sizeof(T));
}
return result;
}
template <typename T>
inline T* ArenaPushArray(MemoryArena* arena, size_t count)
{
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T) * count, alignof(T)));
if (result)
{
MemSet(result, 0, sizeof(T) * count);
}
return result;
}
} // namespace Juliet

View File

@@ -2,6 +2,8 @@
#include <Core/HAL/Display/Display_Private.h>
#include <Core/HAL/Display/DisplayDevice.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/EngineArena.h>
#include <format>
namespace Juliet
@@ -72,7 +74,8 @@ namespace Juliet
{
Assert(g_CurrentDisplayDevice->CreatePlatformWindow);
auto window = static_cast<Window*>(Calloc(1, sizeof(Window)));
MemoryArena* arena = GetEngineArena();
auto window = ArenaPushType<Window>(arena);
if (!window)
{
return nullptr;
@@ -80,9 +83,8 @@ namespace Juliet
window->Width = width;
window->Height = height;
// TODO String creator that copy
auto titleLen = StringLength(title);
auto buffer = static_cast<char*>(Calloc(titleLen, sizeof(char)));
auto buffer = ArenaPushArray<char>(arena, titleLen);
MemCopy(buffer, title, titleLen);
window->Title.Data = buffer;
@@ -91,7 +93,8 @@ namespace Juliet
g_CurrentDisplayDevice->MainWindow = window;
if (!g_CurrentDisplayDevice->CreatePlatformWindow(g_CurrentDisplayDevice, window))
{
// TODO : Destroy
// Note: We don't "free" from arena easily, but since this is catastrophic
// and persistent, we just leak the small amount of arena space or handle it if we had a marker.
return nullptr;
}
@@ -107,13 +110,12 @@ namespace Juliet
HideWindow(window);
// TODO : Free string function
SafeFree(window->Title.Data);
// We don't free from arena, these are persistent until shutdown.
window->Title.Data = nullptr;
window->Title.Size = 0;
g_CurrentDisplayDevice->DestroyPlatformWindow(g_CurrentDisplayDevice, window);
Free(window.Get());
g_CurrentDisplayDevice->MainWindow = nullptr;
}

View File

@@ -2,6 +2,8 @@
#include <Core/HAL/Display/Win32/Win32DisplayEvent.h>
#include <Core/HAL/Display/Win32/Win32Window.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/EngineArena.h>
#include <Core/Memory/Utils.h>
namespace Juliet::Win32
{
@@ -12,14 +14,12 @@ namespace Juliet::Win32
return true;
}
void Shutdown(NonNullPtr<DisplayDevice> /*self*/) {}
void Free(NonNullPtr<DisplayDevice> self)
{
Juliet::Free(self.Get());
}
void Free(NonNullPtr<DisplayDevice> /*self*/) {}
DisplayDevice* CreateDevice()
{
auto device = static_cast<DisplayDevice*>(Calloc(1, sizeof(DisplayDevice)));
auto device = ArenaPushType<DisplayDevice>(GetEngineArena());
if (!device)
{
return nullptr;

View File

@@ -2,6 +2,8 @@
#include <Core/HAL/Display/Win32/Win32Window.h>
#include <Core/HAL/Display/Window.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/EngineArena.h>
#include <Core/Memory/Utils.h>
namespace Juliet::Win32
{
@@ -12,7 +14,8 @@ namespace Juliet::Win32
bool SetupWindowState(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window, HWND handle)
{
auto state = static_cast<Window32State*>(Calloc(1, sizeof(Window32State)));
auto state = ArenaPushType<Window32State>(GetEngineArena());
window->State = state;
state->Handle = handle;
state->Window = window;
@@ -31,8 +34,6 @@ namespace Juliet::Win32
{
ReleaseDC(state->Handle, state->HDC);
DestroyWindow(state->Handle);
SafeFree(state);
}
window->State = nullptr;
}

View File

@@ -3,6 +3,8 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/EngineArena.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Thread/Thread.h>
#define MAX_TRIES 100
@@ -21,11 +23,11 @@ namespace Juliet
// First allocate all the full path.
// TODO: Add path composition into filesystem + string format + string builder
const size_t dllFullPathLength = basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings
code.DLLFullPath.Data = static_cast<char*>(Calloc(dllFullPathLength, sizeof(char)));
code.DLLFullPath.Data = ArenaPushArray<char>(GetEngineArena(), dllFullPathLength);
int writtenSize = snprintf(CStr(code.DLLFullPath), dllFullPathLength, "%s%s", CStr(basePath), CStr(dllName));
if (writtenSize < static_cast<int>(dllFullPathLength) - 1)
{
SafeFree(code.DLLFullPath.Data);
// Arena memory persists, no free needed
Log(LogLevel::Error, LogCategory::Core, "Cannot create DLL Full Path");
return;
}
@@ -33,12 +35,12 @@ namespace Juliet
// Lock filename path
const size_t lockPathLength = basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings
code.LockFullPath.Data = static_cast<char*>(Calloc(lockPathLength, sizeof(char)));
code.LockFullPath.Data = ArenaPushArray<char>(GetEngineArena(), lockPathLength);
writtenSize = snprintf(CStr(code.LockFullPath), lockPathLength, "%s%s", CStr(basePath), CStr(lockFilename));
if (writtenSize < static_cast<int>(lockPathLength) - 1)
{
code.LockFullPath.Size = 0;
SafeFree(code.LockFullPath.Data);
// Arena memory persists, no free needed
Log(LogLevel::Error, LogCategory::Core, "Cannot create lock file full path");
return;
}
@@ -52,9 +54,9 @@ namespace Juliet
UnloadCode(code);
code.DLLFullPath.Size = 0;
SafeFree(code.DLLFullPath.Data);
// Arena memory persists until engine shutdown
code.LockFullPath.Size = 0;
SafeFree(code.LockFullPath.Data);
// Arena memory persists until engine shutdown
}
void ReloadCode(HotReloadCode& code)

View File

@@ -5,6 +5,7 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h>
namespace Juliet
{
@@ -53,7 +54,10 @@ namespace Juliet
const size_t tempDllMaxBufferSize =
basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */;
auto tempDllPath = static_cast<char*>(Calloc(tempDllMaxBufferSize, sizeof(char)));
// Allocate from Scratch Arena (transient)
auto tempDllPath = ArenaPushArray<char>(GetScratchArena(), tempDllMaxBufferSize);
for (uint32 attempt = 0; attempt < kMaxAttempts; ++attempt)
{
// int to char
@@ -61,7 +65,7 @@ namespace Juliet
int idLength = snprintf(idToStr, sizeof(idToStr), "%u", code.UniqueID);
if (idLength < 0)
{
SafeFree(tempDllPath);
// Scratch memory, no free needed
Log(LogLevel::Error, LogCategory::Core, "Cannot create temp full path");
return;
}
@@ -70,14 +74,14 @@ namespace Juliet
CStr(code.TransientDLLName));
if (writtenSize < 0)
{
SafeFree(tempDllPath);
// Scratch memory, no free needed
Log(LogLevel::Error, LogCategory::Core, "Cannot create temp full path");
return;
}
if (static_cast<size_t>(writtenSize) + 1 < basePathLength + static_cast<size_t>(idLength) + code.TransientDLLName.Size)
{
SafeFree(tempDllPath);
// Scratch memory, no free needed
Log(LogLevel::Error, LogCategory::Core, "Cannot create temp full path");
return;
}
@@ -109,7 +113,7 @@ namespace Juliet
}
}
SafeFree(tempDllPath);
// Scratch memory, no free needed
}
if (!code.IsValid)

View File

@@ -3,6 +3,7 @@
#include <Core/HAL/Display/Display_Private.h>
#include <Core/HAL/Filesystem/Filesystem_Private.h>
#include <Core/JulietInit.h>
#include <Core/Memory/MemoryArena.h>
namespace Juliet
{
@@ -38,9 +39,22 @@ namespace Juliet
}
} // namespace
#if JULIET_DEBUG
namespace UnitTest
{
extern void TestMemoryArena();
}
#endif
void JulietInit(JulietInit_Flags flags)
{
// Mandatory systems
MemoryArenasInit();
#if JULIET_DEBUG
UnitTest::TestMemoryArena();
#endif
InitFilesystem();
// Optional systems
@@ -61,6 +75,7 @@ namespace Juliet
}
ShutdownFilesystem();
MemoryArenasShutdown();
}
} // namespace Juliet

View File

@@ -0,0 +1,10 @@
#pragma once
#include <Core/Memory/MemoryArena.h>
namespace Juliet
{
// Persistent engine-only arena.
// Not exported to the Game DLL.
MemoryArena* GetEngineArena();
} // namespace Juliet

View File

@@ -0,0 +1,116 @@
#include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/Utils.h>
namespace Juliet
{
void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size)
{
Assert(arena);
Assert(backingMemory);
arena->Data = static_cast<uint8*>(backingMemory);
arena->Size = size;
arena->Offset = 0;
}
void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment)
{
Assert(arena);
// Alignment must be power of 2
Assert((alignment & (alignment - 1)) == 0);
size_t currentPtr = reinterpret_cast<size_t>(arena->Data + arena->Offset);
size_t offset = (currentPtr + (alignment - 1)) & ~(alignment - 1);
size_t newOffset = offset - reinterpret_cast<size_t>(arena->Data) + size;
if (newOffset > arena->Size)
{
Assert(false, "Memory Arena overflow");
return nullptr;
}
void* result = arena->Data + (offset - reinterpret_cast<size_t>(arena->Data));
arena->Offset = newOffset;
return result;
}
void ArenaReset(MemoryArena* arena)
{
Assert(arena);
arena->Offset = 0;
}
size_t ArenaGetMarker(MemoryArena* arena)
{
Assert(arena);
return arena->Offset;
}
void ArenaResetToMarker(MemoryArena* arena, size_t marker)
{
Assert(arena);
Assert(marker <= arena->Offset);
arena->Offset = marker;
}
// --- Global Arenas & Management ---
namespace
{
MemoryArena g_ScratchArena;
MemoryArena g_EngineArena;
MemoryArena g_GameArena;
void* g_ScratchBacking = nullptr;
void* g_EngineBacking = nullptr;
void* g_GameBacking = nullptr;
constexpr size_t kScratchSize = 64 * 1024 * 1024; // 64MB
constexpr size_t kEngineSize = 256 * 1024 * 1024; // 256MB
constexpr size_t kGameSize = 512 * 1024 * 1024; // 512MB
} // namespace
MemoryArena* GetScratchArena()
{
return &g_ScratchArena;
}
MemoryArena* GetEngineArena()
{
return &g_EngineArena;
}
MemoryArena* GetGameArena()
{
return &g_GameArena;
}
void ScratchArenaReset()
{
ArenaReset(&g_ScratchArena);
}
void MemoryArenasInit()
{
// TODO: Use the VirtualAlloc API for this on windows
g_ScratchBacking = Malloc(kScratchSize);
MemSet(g_ScratchBacking, 0, kScratchSize);
g_EngineBacking = Malloc(kEngineSize);
MemSet(g_EngineBacking, 0, kEngineSize);
g_GameBacking = Malloc(kGameSize);
MemSet(g_GameBacking, 0, kGameSize);
MemoryArenaCreate(&g_ScratchArena, g_ScratchBacking, kScratchSize);
MemoryArenaCreate(&g_EngineArena, g_EngineBacking, kEngineSize);
MemoryArenaCreate(&g_GameArena, g_GameBacking, kGameSize);
}
void MemoryArenasShutdown()
{
SafeFree(g_ScratchBacking);
SafeFree(g_EngineBacking);
SafeFree(g_GameBacking);
}
} // namespace Juliet

View File

@@ -0,0 +1,67 @@
#include <cstdio>
#include <Core/Common/CoreUtils.h>
#include <Core/Memory/MemoryArena.h>
#if JULIET_DEBUG
namespace Juliet::UnitTest
{
void TestMemoryArena()
{
// 1. Core Arena Functionality
uint8 buffer[1024];
MemoryArena arena;
MemoryArenaCreate(&arena, buffer, 1024);
Assert(arena.Offset == 0);
Assert(arena.Size == 1024);
void* p1 = ArenaPush(&arena, 100);
Assert(p1 != nullptr);
Assert(arena.Offset >= 100);
size_t marker = ArenaGetMarker(&arena);
void* p2 = ArenaPush(&arena, 200);
Assert(p2 != nullptr);
Assert(arena.Offset >= marker + 200);
ArenaResetToMarker(&arena, marker);
Assert(arena.Offset == marker);
ArenaReset(&arena);
Assert(arena.Offset == 0);
// 2. Alignment Test
void* p3 = ArenaPush(&arena, 1, 1);
[[maybe_unused]] size_t addr = reinterpret_cast<size_t>(p3);
void* p4 = ArenaPush(&arena, 1, 16);
size_t addr2 = reinterpret_cast<size_t>(p4);
Assert((addr2 % 16) == 0);
// 3. Template Helpers
struct TestData
{
int a;
float b;
};
TestData* data = ArenaPushType<TestData>(&arena);
Assert(data != nullptr);
data->a = 10;
data->b = 20.0f;
TestData* dataArray = ArenaPushArray<TestData>(&arena, 10);
Assert(dataArray != nullptr);
// 4. Scratch Arena
MemoryArena* scratch = GetScratchArena();
Assert(scratch != nullptr);
void* sp = ArenaPush(scratch, 100);
Assert(sp != nullptr);
ScratchArenaReset();
Assert(scratch->Offset == 0);
printf("All MemoryArena tests passed.\n");
}
} // namespace Juliet::UnitTest
#endif

View File