From f95ba51c133b1463d71433383ca065bcd8f9ad2a Mon Sep 17 00:00:00 2001 From: Patedam Date: Sat, 17 Jan 2026 21:09:23 -0500 Subject: [PATCH] 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 --- .agent/rules/coding-guidelines.md | 10 ++ .agent/workflows/build.md | 6 +- .agent/workflows/launch.md | 6 + .agent/workflows/recompile_shaders.md | 2 +- AgentData/MemoryArena.md | 49 ++++++++ Game/Entity/Entity.h | 8 +- Game/Game.bff | 2 +- Game/game.cpp | 35 +++--- Juliet/Juliet.vcxproj | 4 + Juliet/Juliet.vcxproj.filters | 12 ++ Juliet/include/Core/JulietInit.h | 8 ++ Juliet/include/Core/Memory/MemoryArena.h | 61 +++++++++ Juliet/include/Core/Memory/ScratchArena.h | 0 Juliet/src/Core/HAL/Display/Display.cpp | 16 +-- .../HAL/Display/Win32/Win32DisplayDevice.cpp | 10 +- .../Core/HAL/Display/Win32/Win32Window.cpp | 7 +- Juliet/src/Core/HotReload/HotReload.cpp | 14 ++- .../Core/HotReload/Win32/Win32HotReload.cpp | 14 ++- Juliet/src/Core/Juliet.cpp | 15 +++ Juliet/src/Core/Memory/EngineArena.h | 10 ++ Juliet/src/Core/Memory/MemoryArena.cpp | 116 ++++++++++++++++++ Juliet/src/Core/Memory/MemoryArenaTests.cpp | 67 ++++++++++ Juliet/src/Core/Memory/ScratchArena.cpp | 0 JulietApp/JulietApp.bff | 1 + JulietApp/JulietApp.vcxproj | 12 +- JulietApp/main.cpp | 12 +- misc/launch.bat | 23 +++- misc/recompile_shaders.bat | 1 - 28 files changed, 462 insertions(+), 59 deletions(-) create mode 100644 .agent/rules/coding-guidelines.md create mode 100644 .agent/workflows/launch.md create mode 100644 AgentData/MemoryArena.md create mode 100644 Juliet/include/Core/Memory/MemoryArena.h create mode 100644 Juliet/include/Core/Memory/ScratchArena.h create mode 100644 Juliet/src/Core/Memory/EngineArena.h create mode 100644 Juliet/src/Core/Memory/MemoryArena.cpp create mode 100644 Juliet/src/Core/Memory/MemoryArenaTests.cpp create mode 100644 Juliet/src/Core/Memory/ScratchArena.cpp diff --git a/.agent/rules/coding-guidelines.md b/.agent/rules/coding-guidelines.md new file mode 100644 index 0000000..8dd0945 --- /dev/null +++ b/.agent/rules/coding-guidelines.md @@ -0,0 +1,10 @@ +--- +trigger: always_on +--- + +No exceptions +Use [[nodiscard]] +auto is allowed but when its a pointer add the * and when reference adds the & +Member variable are CamelCase +Types are CamelCase +Functions are CamelCase. \ No newline at end of file diff --git a/.agent/workflows/build.md b/.agent/workflows/build.md index 6a569a1..b535a3a 100644 --- a/.agent/workflows/build.md +++ b/.agent/workflows/build.md @@ -1,17 +1,15 @@ --- description: Build the Juliet project using FastBuild --- +// turbo-all This workflow sets up the Juliet build environment and runs `fbuild`. 1. To build a specific configuration (e.g., msvc-Debug): -// turbo `cmd /c "misc\shell.bat & fbuild msvc-Debug"` 2. To build the default (msvc): -// turbo `cmd /c "misc\shell.bat & fbuild msvc"` 3. To see all available targets: -// turbo -`cmd /c "misc\shell.bat & fbuild -targets"` +`cmd /c "misc\shell.bat & fbuild -showtargets"` diff --git a/.agent/workflows/launch.md b/.agent/workflows/launch.md new file mode 100644 index 0000000..c645cd4 --- /dev/null +++ b/.agent/workflows/launch.md @@ -0,0 +1,6 @@ +--- +description: Launch the Juliet application +--- + +1. Run the launch script + misc\launch.bat autoclose \ No newline at end of file diff --git a/.agent/workflows/recompile_shaders.md b/.agent/workflows/recompile_shaders.md index cfe9859..eafc359 100644 --- a/.agent/workflows/recompile_shaders.md +++ b/.agent/workflows/recompile_shaders.md @@ -1,9 +1,9 @@ --- description: Recompile shaders for the Juliet project --- +// turbo-all This workflow recompiles all shaders using the `recompile_shaders.bat` script. 1. Recompile all shaders: -// turbo `cmd /c "misc\shell.bat & misc\recompile_shaders.bat"` diff --git a/AgentData/MemoryArena.md b/AgentData/MemoryArena.md new file mode 100644 index 0000000..8cd012a --- /dev/null +++ b/AgentData/MemoryArena.md @@ -0,0 +1,49 @@ +# Memory System Status & Migration Plan + +## Completed Work + +### Core Systems +- **MemoryArena**: Implemented linear allocator with alignment, markers, and reset support. +- **Global Arenas**: + - `ScratchArena` (64MB): Transient per-frame memory. + - `EngineArena` (256MB): Persistent engine memory (internal). + - `GameArena` (512MB): Persistent game memory (exported to Game DLL). +- **Helpers**: Added `ArenaPushType` and `ArenaPushArray` with automatic zero-initialization. + +### Migrated Subsystems +- **Display**: `Win32Window` and `Win32DisplayDevice` now use `EngineArena`. +- **Game Entities**: `Entity.h` uses `GameArena` for entity allocation; manual `free` calls removed from `game.cpp`. + +## Remaining Work + +The following subsystems still use legacy `malloc`/`calloc`/`realloc`/`free` and need to be migrated. + +### Hot Reload System +- **Files**: `Core/HotReload/HotReload.cpp`, `Core/HotReload/Win32/Win32HotReload.cpp` +- **Allocations**: `DLLFullPath`, `LockFullPath`, `tempDllPath`. +- **Strategy**: + - Use `EngineArena` for persistent paths (`DLLFullPath`, `LockFullPath`). + - Use `ScratchArena` for temporary paths (`tempDllPath`). + +### IO System +- **Files**: `Core/HAL/IO/IOStream.cpp`, `Core/HAL/IO/Win32/Win32IOStream.cpp` +- **Allocations**: `IOStream` instance, data buffers, `Win32IOStreamDataPayload`. +- **Challenge**: `Realloc` is used for growing buffers. +- **Strategy**: + - `IOStream` struct -> `ScratchArena` (if transient) or `EngineArena`. + - Buffers: Evaluate if `ArenaPush` with large enough capacity is sufficient, or implement a growable buffer on top of arena (or use `std::vector` with custom allocator if absolutely needed, but prefer simple fixed max size if possible). + +### Graphics / Debug +- **Files**: `Graphics/DebugDisplayRenderer.cpp` +- **Allocations**: `DepthTestedVertices`, `OverlayVertices`. +- **Strategy**: Use `EngineArena` or a dedicated `RenderArena` if these are persistent. If per-frame, move to `ScratchArena`. + +### Shader Compiler +- **Files**: `JulietShaderCompiler/ShaderCompiler.cpp` +- **Allocations**: Argument arrays, file buffers. +- **Strategy**: Use `ScratchArena` for all compilation tasks as they are transient. + +### Filesystem +- **Files**: `Core/HAL/Filesystem/Filesystem.cpp` +- **Allocations**: `CachedBasePath`. +- **Strategy**: Migrate to `EngineArena` (persistent). diff --git a/Game/Entity/Entity.h b/Game/Entity/Entity.h index 03c167a..f1ba924 100644 --- a/Game/Entity/Entity.h +++ b/Game/Entity/Entity.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,8 @@ constexpr Juliet::Class entityKind##entity(#entity, sizeof(#entity) / sizeof(char)); \ const Juliet::Class* entity::Kind = &entityKind##entity; + + namespace Game { using DerivedType = void*; @@ -46,8 +49,9 @@ namespace Game requires EntityConcept EntityType* MakeEntity(EntityManager& manager, float x, float y) { - EntityType* result = static_cast(Juliet::Calloc(1, sizeof(EntityType))); - Entity* base = result->Base = static_cast(Juliet::Calloc(1, sizeof(Entity))); + auto* arena = Juliet::GetGameArena(); + EntityType* result = Juliet::ArenaPushType(arena); + Entity* base = result->Base = Juliet::ArenaPushType(arena); base->X = x; base->Y = y; base->Derived = result; diff --git a/Game/Game.bff b/Game/Game.bff index 177d84e..634e139 100644 --- a/Game/Game.bff +++ b/Game/Game.bff @@ -38,7 +38,7 @@ // Extra Compiler Options .CompilerOptions + ' "-IJuliet/include"' + ' "-IGame"' - // + ' -DGAME_EXPORT' // I'm just always exporting anyway but just in case + .CompilerOptions + ' -DJULIET_EXPORT' #if __WINDOWS__ .CompilerOptions + ' -DJULIET_WIN32' diff --git a/Game/game.cpp b/Game/game.cpp index 8a6703f..dafbada 100644 --- a/Game/game.cpp +++ b/Game/game.cpp @@ -4,11 +4,11 @@ #undef max #include +#include #include -#include +#include #include #include -#include // Test code namespace Game @@ -31,8 +31,21 @@ namespace Game using namespace Juliet; -extern "C" __declspec(dllexport) void __cdecl GameInit() +extern "C" JULIET_API void GameInit(GameInitParams* /*params*/) { + // Example allocation in GameArena + struct GameState + { + float TotalTime; + int Score; + }; + + auto* gameState = ArenaPushType(GetGameArena()); + gameState->TotalTime = 0.0f; + gameState->Score = 0; + + printf("Game Arena Allocated: %p\n", gameState); + using namespace Game; // Entity Use case @@ -41,8 +54,8 @@ extern "C" __declspec(dllexport) void __cdecl GameInit() Door* door = MakeEntity(manager, 10.0f, 2.0f); door->IsOpened = true; - Entity* ent = door->Base; - [[maybe_unused]] Door* stillDoor = DownCast(ent); + Entity* ent = door->Base; + [[maybe_unused]] Door* stillDoor = DownCast(ent); Assert(door == stillDoor); Rock* rock = MakeEntity(manager, 1.f, 2.f); @@ -51,20 +64,14 @@ extern "C" __declspec(dllexport) void __cdecl GameInit() printf("Door is %s\n", door->IsOpened ? "Opened" : "Closed"); printf("Rock has %d health points\n", rock->Health); - - // Have to manually free for now because im not using arenas or anything - free(door->Base); - free(door); - free(rock->Base); - free(rock); } -extern "C" __declspec(dllexport) void __cdecl GameShutdown() +extern "C" JULIET_API void __cdecl GameShutdown() { printf("Shutting down game...\n"); } -extern "C" __declspec(dllexport) void __cdecl GameUpdate([[maybe_unused]] float deltaTime) +extern "C" JULIET_API void __cdecl GameUpdate([[maybe_unused]] float deltaTime) { - //printf("Updating game...\n"); + // printf("Updating game...\n"); } diff --git a/Juliet/Juliet.vcxproj b/Juliet/Juliet.vcxproj index 77159bb..8da716c 100644 --- a/Juliet/Juliet.vcxproj +++ b/Juliet/Juliet.vcxproj @@ -57,6 +57,7 @@ + @@ -118,6 +119,9 @@ + + + diff --git a/Juliet/Juliet.vcxproj.filters b/Juliet/Juliet.vcxproj.filters index 4168253..4f9afb0 100644 --- a/Juliet/Juliet.vcxproj.filters +++ b/Juliet/Juliet.vcxproj.filters @@ -91,6 +91,9 @@ include\Core\Memory + + include\Core\Memory + include\Core\Memory @@ -273,6 +276,15 @@ src\Core\Memory + + src\Core\Memory + + + src\Core\Memory + + + src\Core\Memory + src\Core\Networking diff --git a/Juliet/include/Core/JulietInit.h b/Juliet/include/Core/JulietInit.h index 330ab30..ce3c85f 100644 --- a/Juliet/include/Core/JulietInit.h +++ b/Juliet/include/Core/JulietInit.h @@ -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 diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h new file mode 100644 index 0000000..0e4da06 --- /dev/null +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include + +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 + inline T* ArenaPushType(MemoryArena* arena) + { + T* result = static_cast(ArenaPush(arena, sizeof(T), alignof(T))); + if (result) + { + MemSet(result, 0, sizeof(T)); + } + return result; + } + + template + inline T* ArenaPushArray(MemoryArena* arena, size_t count) + { + T* result = static_cast(ArenaPush(arena, sizeof(T) * count, alignof(T))); + if (result) + { + MemSet(result, 0, sizeof(T) * count); + } + return result; + } +} // namespace Juliet diff --git a/Juliet/include/Core/Memory/ScratchArena.h b/Juliet/include/Core/Memory/ScratchArena.h new file mode 100644 index 0000000..e69de29 diff --git a/Juliet/src/Core/HAL/Display/Display.cpp b/Juliet/src/Core/HAL/Display/Display.cpp index f71b3dd..2359d72 100644 --- a/Juliet/src/Core/HAL/Display/Display.cpp +++ b/Juliet/src/Core/HAL/Display/Display.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include namespace Juliet @@ -72,7 +74,8 @@ namespace Juliet { Assert(g_CurrentDisplayDevice->CreatePlatformWindow); - auto window = static_cast(Calloc(1, sizeof(Window))); + MemoryArena* arena = GetEngineArena(); + auto window = ArenaPushType(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(Calloc(titleLen, sizeof(char))); + auto buffer = ArenaPushArray(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; } diff --git a/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp b/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp index dd5698a..be3dc76 100644 --- a/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp +++ b/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include namespace Juliet::Win32 { @@ -12,14 +14,12 @@ namespace Juliet::Win32 return true; } void Shutdown(NonNullPtr /*self*/) {} - void Free(NonNullPtr self) - { - Juliet::Free(self.Get()); - } + void Free(NonNullPtr /*self*/) {} DisplayDevice* CreateDevice() { - auto device = static_cast(Calloc(1, sizeof(DisplayDevice))); + auto device = ArenaPushType(GetEngineArena()); + if (!device) { return nullptr; diff --git a/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp b/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp index 3b82a57..c81719f 100644 --- a/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp +++ b/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include namespace Juliet::Win32 { @@ -12,7 +14,8 @@ namespace Juliet::Win32 bool SetupWindowState(NonNullPtr /*self*/, NonNullPtr window, HWND handle) { - auto state = static_cast(Calloc(1, sizeof(Window32State))); + auto state = ArenaPushType(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; } diff --git a/Juliet/src/Core/HotReload/HotReload.cpp b/Juliet/src/Core/HotReload/HotReload.cpp index b62ede9..88f3d93 100644 --- a/Juliet/src/Core/HotReload/HotReload.cpp +++ b/Juliet/src/Core/HotReload/HotReload.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #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(Calloc(dllFullPathLength, sizeof(char))); + code.DLLFullPath.Data = ArenaPushArray(GetEngineArena(), dllFullPathLength); int writtenSize = snprintf(CStr(code.DLLFullPath), dllFullPathLength, "%s%s", CStr(basePath), CStr(dllName)); if (writtenSize < static_cast(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(Calloc(lockPathLength, sizeof(char))); + code.LockFullPath.Data = ArenaPushArray(GetEngineArena(), lockPathLength); writtenSize = snprintf(CStr(code.LockFullPath), lockPathLength, "%s%s", CStr(basePath), CStr(lockFilename)); if (writtenSize < static_cast(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) diff --git a/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp b/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp index 19029c1..8adce57 100644 --- a/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp +++ b/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace Juliet { @@ -53,7 +54,10 @@ namespace Juliet const size_t tempDllMaxBufferSize = basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */; - auto tempDllPath = static_cast(Calloc(tempDllMaxBufferSize, sizeof(char))); + + // Allocate from Scratch Arena (transient) + auto tempDllPath = ArenaPushArray(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(writtenSize) + 1 < basePathLength + static_cast(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) diff --git a/Juliet/src/Core/Juliet.cpp b/Juliet/src/Core/Juliet.cpp index 96bc778..4abc22a 100644 --- a/Juliet/src/Core/Juliet.cpp +++ b/Juliet/src/Core/Juliet.cpp @@ -3,6 +3,7 @@ #include #include #include +#include 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 diff --git a/Juliet/src/Core/Memory/EngineArena.h b/Juliet/src/Core/Memory/EngineArena.h new file mode 100644 index 0000000..e64a49d --- /dev/null +++ b/Juliet/src/Core/Memory/EngineArena.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace Juliet +{ + // Persistent engine-only arena. + // Not exported to the Game DLL. + MemoryArena* GetEngineArena(); +} // namespace Juliet diff --git a/Juliet/src/Core/Memory/MemoryArena.cpp b/Juliet/src/Core/Memory/MemoryArena.cpp new file mode 100644 index 0000000..3043fcb --- /dev/null +++ b/Juliet/src/Core/Memory/MemoryArena.cpp @@ -0,0 +1,116 @@ +#include +#include +#include + +namespace Juliet +{ + void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size) + { + Assert(arena); + Assert(backingMemory); + arena->Data = static_cast(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(arena->Data + arena->Offset); + size_t offset = (currentPtr + (alignment - 1)) & ~(alignment - 1); + size_t newOffset = offset - reinterpret_cast(arena->Data) + size; + + if (newOffset > arena->Size) + { + Assert(false, "Memory Arena overflow"); + return nullptr; + } + + void* result = arena->Data + (offset - reinterpret_cast(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 diff --git a/Juliet/src/Core/Memory/MemoryArenaTests.cpp b/Juliet/src/Core/Memory/MemoryArenaTests.cpp new file mode 100644 index 0000000..4ab8642 --- /dev/null +++ b/Juliet/src/Core/Memory/MemoryArenaTests.cpp @@ -0,0 +1,67 @@ +#include +#include +#include + +#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(p3); + void* p4 = ArenaPush(&arena, 1, 16); + size_t addr2 = reinterpret_cast(p4); + Assert((addr2 % 16) == 0); + + // 3. Template Helpers + struct TestData + { + int a; + float b; + }; + TestData* data = ArenaPushType(&arena); + Assert(data != nullptr); + data->a = 10; + data->b = 20.0f; + + TestData* dataArray = ArenaPushArray(&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 diff --git a/Juliet/src/Core/Memory/ScratchArena.cpp b/Juliet/src/Core/Memory/ScratchArena.cpp new file mode 100644 index 0000000..e69de29 diff --git a/JulietApp/JulietApp.bff b/JulietApp/JulietApp.bff index 573851b..5d2def8 100644 --- a/JulietApp/JulietApp.bff +++ b/JulietApp/JulietApp.bff @@ -4,6 +4,7 @@ .ProjectName = 'JulietApp' .ProjectPath = 'JulietApp' .JulietIncludePath = ' "-IJuliet/include"' + + ' "-IJuliet/src"' .ProjectDefPath = '$_WORKING_DIR_$/$ProjectName$/$ProjectName$.def' // Library diff --git a/JulietApp/JulietApp.vcxproj b/JulietApp/JulietApp.vcxproj index 44f9063..55bebce 100644 --- a/JulietApp/JulietApp.vcxproj +++ b/JulietApp/JulietApp.vcxproj @@ -376,7 +376,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_WIN32; - ..\;..\Juliet\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; + ..\;..\Juliet\include;..\Juliet\src;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ $(SolutionDir)\Intermediate @@ -386,7 +386,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_WIN32; - ..\;..\Juliet\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; + ..\;..\Juliet\include;..\Juliet\src;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ $(SolutionDir)\Intermediate @@ -396,7 +396,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_WIN32; - ..\;..\Juliet\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; + ..\;..\Juliet\include;..\Juliet\src;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ $(SolutionDir)\Intermediate @@ -406,7 +406,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_WIN32; - ..\;..\Juliet\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; + ..\;..\Juliet\include;..\Juliet\src;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 $(SolutionDir)\bin\$(Configuration)\ $(SolutionDir)\Intermediate @@ -416,7 +416,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_WIN32; - ..\;..\Juliet\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; + ..\;..\Juliet\include;..\Juliet\src;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 $(SolutionDir)\bin\$(Configuration)\ $(SolutionDir)\Intermediate @@ -426,7 +426,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_WIN32; - ..\;..\Juliet\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; + ..\;..\Juliet\include;..\Juliet\src;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 $(SolutionDir)\bin\$(Configuration)\ $(SolutionDir)\Intermediate diff --git a/JulietApp/main.cpp b/JulietApp/main.cpp index 31657e0..b71dd9d 100644 --- a/JulietApp/main.cpp +++ b/JulietApp/main.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include // TODO : Replace with message box from framework + call main and not winmain + subsystem @@ -33,7 +35,7 @@ using namespace Juliet; namespace { - using GameInit_t = void (*)(void); + using GameInit_t = void (*)(GameInitParams*); using GameShutdown_t = void (*)(void); using GameUpdate_t = void (*)(float deltaTime); struct GameFunctionTable @@ -158,7 +160,10 @@ void JulietApplication::Init() InitHotReloadCode(GameCode, ConstString("Game.dll"), ConstString("Game_Temp.dll"), ConstString("lock.tmp")); if ((Running = GameCode.IsValid)) { - Game.Init(); + GameInitParams params; + params.GameArena = GetGameArena(); + params.ScratchArena = GetScratchArena(); + Game.Init(¶ms); } // Initialize DebugDisplay @@ -383,6 +388,9 @@ void JulietApplication::Update() // Submit Commands SubmitCommandLists(cmdList); + + // Reset Scratch Arena at the end of the frame + ScratchArenaReset(); } bool JulietApplication::IsRunning() diff --git a/misc/launch.bat b/misc/launch.bat index 60ccf29..bfcc702 100644 --- a/misc/launch.bat +++ b/misc/launch.bat @@ -2,8 +2,21 @@ setlocal :: --- 1. Argument Parsing Logic --- -set "ARG1=%~1" -set "ARG2=%~2" +set "ARG1=" +set "ARG2=" +set "AUTOCLOSE=0" + +for %%x in (%*) do ( + if /I "%%~x"=="autoclose" ( + set "AUTOCLOSE=1" + ) else ( + if not defined ARG1 ( + set "ARG1=%%~x" + ) else if not defined ARG2 ( + set "ARG2=%%~x" + ) + ) +) :: Set Defaults set "COMPILER_TYPE=clang" @@ -44,6 +57,12 @@ if exist "%APP_DIR%\%APP_EXE%" ( pushd "%APP_DIR%" start "" "%APP_EXE%" popd + + if "%AUTOCLOSE%"=="1" ( + echo [AUTOCLOSE] Waiting 5 seconds... + timeout /t 5 /nobreak >nul + taskkill /IM "%APP_EXE%" /F >nul 2>&1 + ) ) else ( echo [ERROR] Executable not found at: %APP_DIR%\%APP_EXE% echo Please build first: fbuild JulietApp-%PLATFORM%-%CONFIG% diff --git a/misc/recompile_shaders.bat b/misc/recompile_shaders.bat index 81b7062..0d59e8a 100644 --- a/misc/recompile_shaders.bat +++ b/misc/recompile_shaders.bat @@ -73,5 +73,4 @@ for %%F in ("%SOURCE_DIR%\*.hlsl") do ( echo. echo Operation terminee. -pause endlocal \ No newline at end of file