Converted log manager to use arenas

This commit is contained in:
2026-02-02 22:13:50 -05:00
parent 45ef1134f7
commit 4d66261c9f
11 changed files with 228 additions and 114 deletions

View File

@@ -18,7 +18,7 @@ namespace Juliet
NonNullPtr(Type* ptr)
: InternalPtr(ptr)
{
Assert(ptr && "Tried to initialize a NonNullPtr with a null pointer");
Assert(ptr, "Tried to initialize a NonNullPtr with a null pointer");
}
template <typename OtherType>
@@ -26,13 +26,13 @@ namespace Juliet
NonNullPtr(const NonNullPtr<OtherType>& otherPtr)
: InternalPtr(otherPtr.Get())
{
Assert(InternalPtr && "Fatal Error: Assigned a non null ptr using another NonNullPtr but its was null.");
Assert(InternalPtr, "Fatal Error: Assigned a non null ptr using another NonNullPtr but its was null.");
}
// Assignment
NonNullPtr& operator=(Type* ptr)
{
Assert(ptr && "Tried to assign a null pointer to a NonNullPtr!");
Assert(ptr, "Tried to assign a null pointer to a NonNullPtr!");
InternalPtr = ptr;
return *this;
}
@@ -48,25 +48,25 @@ namespace Juliet
// Accessors
operator Type*() const
{
Assert(InternalPtr && "NonNullPtr: Internal Pointer is Null");
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
Type* Get() const
{
Assert(InternalPtr && "NonNullPtr: Internal Pointer is Null");
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
Type& operator*() const
{
Assert(InternalPtr && "NonNullPtr: Internal Pointer is Null");
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return *InternalPtr;
}
Type* operator->() const
{
Assert(InternalPtr && "NonNullPtr: Internal Pointer is Null");
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}

View File

@@ -1,10 +1,13 @@
#pragma once
#include <Core/Common/NonNullPtr.h>
#include <Core/Math/MathUtils.h>
#include <Core/Memory/Utils.h>
namespace Juliet
{
struct Arena;
#define ConstString(str) { const_cast<char*>((str)), sizeof(str) - 1 }
#define CStr(str) ((str).Data)
#define InplaceString(name, size) \
@@ -38,6 +41,20 @@ namespace Juliet
size_t Capacity;
};
struct StringListNode
{
StringListNode* Next;
String Content;
};
struct StringList
{
StringListNode* First;
StringListNode* Last;
size_t NodeCount;
size_t Size;
};
constexpr uint32 kInvalidUTF8 = 0xFFFD;
inline size_t StringLength(String str)
@@ -140,6 +157,7 @@ namespace Juliet
extern JULIET_API bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst, bool nullTerminate);
extern JULIET_API bool ConvertString(String from, String to, String src, StringBuffer& dst, bool nullTerminate);
JULIET_API String StringCopy(NonNullPtr<Arena> arena, String str);
} // namespace Juliet
#ifdef UNIT_TEST

View File

@@ -1,54 +1,26 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Memory/MemoryArena.h>
#include <Juliet.h>
// TODO : Juliet strings
#include <string>
// TODO Juliet Containers + Allocators...
// TODO: Juliet chrono, because it prevents me from doing #define global static
#ifdef global
#undef global
#endif
#include <chrono>
#define global static
#include <deque>
namespace Juliet
{
enum class LogLevel : uint8;
enum class LogCategory : uint8;
class LogManager final
{
public:
LogManager();
void Init();
void Shutdown();
bool GetIsInitialized() const { return IsInitialized; }
private:
struct Entry
{
std::string Value;
std::chrono::system_clock::time_point Time;
LogLevel Level;
LogCategory Category;
Entry(std::string& value, LogLevel level, LogCategory category);
};
std::deque<Entry> Entries;
bool IsInitialized : 1;
friend void Log(LogLevel level, LogCategory category, const char* fmt, va_list args);
void AddEntry(Entry&& entry);
static void OutputLog(Entry& entry);
};
extern void JULIET_API InitializeLogManager();
extern void JULIET_API ShutdownLogManager();
extern void JULIET_API LogScopeBegin();
// TODO everything that happened in there to export them to file or something
extern void JULIET_API LogScopeEnd();
extern void JULIET_API Log(LogLevel level, LogCategory category, const char* fmt, ...);
extern void JULIET_API LogMessage(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogWarning(LogCategory category, const char* fmt, ...);

View File

@@ -55,6 +55,17 @@ namespace Juliet
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true));
}
// #define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0))
// #define push_array_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (1))
// #define push_array_no_zero(a, T, c) push_array_no_zero_aligned(a, T, c, Max(8, AlignOf(T)))
// #define push_array(a, T, c) push_array_aligned(a, T, c, Max(8, AlignOf(T)))
template <typename Type>
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count)
{
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true));
}
// --- Paged Memory Architecture ---
struct ArenaAllocation
{

View File

@@ -19,7 +19,63 @@ namespace Juliet
return size ? *left - *right : 0;
}
// TODO: homemade versions
// Single linked list
void SingleLinkedListPush(auto*& stackTop, auto* node)
{
node->Next = stackTop;
stackTop = node;
}
void SingleLinkedListPop(auto*& stackTop)
{
stackTop = stackTop->Next;
}
template <typename SingleLinkedListType>
struct SingleLinkedListNode
{
SingleLinkedListType* Next;
};
// Double linked list
template <typename QueueType, typename QueueTypeNode>
void Enqueue(QueueType& queue, QueueTypeNode* node)
{
if (queue.First == nullptr)
{
queue.First = queue.Last = node;
node->Next = nullptr;
}
else
{
queue.Last->Next = node, queue.Last = node;
node->Next = nullptr;
}
queue.Nodecount += 1;
}
template <typename QueueType>
struct QueueNode
{
QueueType* Next;
};
#define DECLARE_QUEUE(type) \
struct type##Queue \
{ \
type* First; \
type* Last; \
size_t Nodecount; \
size_t Size; \
};
#define SLLQueuePush(f, l, n) SLLQueuePush_NZ(0, f, l, n, next)
#define SLLQueuePush_NZ(nil, f, l, n, next) \
(CheckNil(nil, f) ? ((f) = (l) = (n), SetNil(nil, (n)->next)) : ((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
// TODO: homemade versions
#define MemSet memset
#define MemCopy memcpy

View File

@@ -2,6 +2,7 @@
#include <Core/Common/String.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/Utils.h>
namespace Juliet
@@ -487,4 +488,15 @@ namespace Juliet
return ConvertString(sourceFormat, destFormat, src, dst, nullTerminate);
}
String StringCopy(NonNullPtr<Arena> arena, String str)
{
String result;
result.Size = str.Size;
result.Data = ArenaPushArray<char>(arena, str.Size + 1);
MemCopy(result.Data, str.Data, str.Size);
result.Data[result.Size] = 0;
return result;
}
} // namespace Juliet

View File

@@ -4,7 +4,6 @@
#include <Core/Memory/Allocator.h>
#include <Core/Memory/EngineArena.h>
#include <Core/Memory/MemoryArena.h>
#include <format>
namespace Juliet
{

View File

@@ -1,9 +1,13 @@
#include <chrono>
#include <queue>
#include <Core/HAL/Display/DisplayDevice.h>
#include <Core/HAL/Event/SystemEvent.h>
#pragma push_macro("global")
#undef global
#include <chrono>
#pragma pop_macro("global")
#include <queue>
namespace Juliet
{
namespace

View File

@@ -2,10 +2,10 @@
#include <Core/Logging/LogTypes.h>
#include <chrono>
#include <cstdarg>
// Begin Todo JULIET debug output
#include <cinttypes>
#include <Core/Memory/MemoryArena.h>
#ifdef JULIET_WIN32
#include <Core/HAL/Win32.h>
@@ -13,61 +13,59 @@
#endif
// End Todo
// TODO Juliet chrono
namespace Juliet
{
namespace
{
LogManager LogManagerSingleton;
constexpr size_t kLogBufferSize = 1024;
} // namespace
LogManager::Entry::Entry(std::string& value, LogLevel level, LogCategory category)
: Value(value)
, Level(level)
, Category(category)
struct LogsEntry : public QueueNode<LogsEntry>
{
// TODO Juliet clock
Time = std::chrono::system_clock::now();
String Value;
std::chrono::system_clock::time_point Time;
LogLevel Level;
LogCategory Category;
};
DECLARE_QUEUE(LogsEntry);
// A log scope accumulates log until end of scope.
// Can be used to accumulate then write in a log file each frame.
struct LogScope
{
LogScope* Next;
size_t Position;
LogsEntryQueue Entries;
};
// Logs contains the current scopes and the arena
struct Logs
{
Arena* Arena = nullptr;
LogScope* TopScope = nullptr;
};
Logs* ActiveLog = nullptr;
Logs* LogAllocate()
{
Arena* arena = ArenaAllocate();
Logs* logs = ArenaPushStruct<Logs>(arena);
logs->Arena = arena;
return logs;
}
LogManager::LogManager()
: IsInitialized(false)
void LogRelease(NonNullPtr<Logs> logs)
{
ArenaRelease(logs->Arena);
}
void LogManager::Init()
{
IsInitialized = true;
Log(LogLevel::Message, LogCategory::Engine, "Initializing Log Manager");
}
void LogManager::Shutdown()
{
Log(LogLevel::Message, LogCategory::Engine, "Shutting down Log Manager");
Entries.clear();
IsInitialized = false;
}
void LogManager::AddEntry(Entry&& entry)
{
if (Entries.size() >= kLogBufferSize)
{
Entries.pop_front();
}
Entries.push_back(std::move(entry));
}
void LogManager::OutputLog(Entry& entry)
void OutputLog(LogsEntry& entry)
{
// TODO Juliet Output io for each platform
// {:%F} is YYYY-MM-DD, {:%T} is HH:MM:SS.ffffff
std::string timestamp = std::format("[{:%F %T}] ", entry.Time);
std::string fullMessage = timestamp + entry.Value + "\n";
std::string fullMessage = timestamp + CStr(entry.Value) + "\n";
#ifdef JULIET_WIN32
OutputDebugStringA(fullMessage.c_str());
@@ -75,6 +73,25 @@ namespace Juliet
printf("%s", fullMessage.c_str());
}
void PushLogEntry(NonNullPtr<Logs> logs, String value, LogLevel level, LogCategory category)
{
Arena* arena = logs->Arena;
String str = StringCopy(arena, value);
// Push onto the current scope
LogsEntry* newEntry = ArenaPushStruct<LogsEntry>(arena);
newEntry->Time = std::chrono::system_clock::now();
newEntry->Level = level;
newEntry->Category = category;
newEntry->Value = str;
Enqueue(logs->TopScope->Entries, newEntry);
logs->TopScope->Entries.Size += sizeof(LogsEntry) + str.Size;
OutputLog(*newEntry);
}
} // namespace
void InitializeLogManager()
{
#ifdef JULIET_WIN32
@@ -82,12 +99,36 @@ namespace Juliet
SetConsoleCP(CP_UTF8);
#endif
LogManagerSingleton.Init();
ActiveLog = LogAllocate();
LogScopeBegin();
}
void ShutdownLogManager()
{
LogManagerSingleton.Shutdown();
LogScopeEnd();
LogRelease(ActiveLog);
}
void LogScopeBegin()
{
Assert(ActiveLog != nullptr);
size_t position = ArenaPos(ActiveLog->Arena);
LogScope* scope = ArenaPushStruct<LogScope>(ActiveLog->Arena);
scope->Position = position;
SingleLinkedListPush(ActiveLog->TopScope, scope);
}
void LogScopeEnd()
{
Assert(ActiveLog != nullptr);
LogScope* scope = ActiveLog->TopScope;
Assert(scope != nullptr);
SingleLinkedListPop(ActiveLog->TopScope);
ArenaPopTo(ActiveLog->Arena, scope->Position);
}
void Log(LogLevel level, LogCategory category, const char* fmt, va_list args)
@@ -104,12 +145,12 @@ namespace Juliet
while (std::getline(ss, individualLine, '\n'))
{
LogManager::Entry entry(individualLine, level, category);
LogManagerSingleton.OutputLog(entry);
if (LogManagerSingleton.GetIsInitialized())
// Logs can happen before the active log is set, in that case, we can write to the console but not register the entry.
// One case is the Arena Allocate that calls logs and will happen before the logs are properly set because logs need to allocate an arena.
if (ActiveLog != nullptr)
{
LogManagerSingleton.AddEntry(std::move(entry));
String julietstr = WrapString(individualLine.c_str());
PushLogEntry(ActiveLog, julietstr, level, category);
}
}
}

View File

@@ -140,18 +140,18 @@ namespace Juliet
void ArenaPopTo(NonNullPtr<Arena> arena, size_t position)
{
size_t bbbPosition = ClampBottom(k_ArenaHeaderSize, position);
size_t clampedPosition = ClampBottom(k_ArenaHeaderSize, position);
Arena* current = arena->Current;
// TODO : Free list
for (Arena* previous = nullptr; current->BasePosition >= bbbPosition; current = previous)
for (Arena* previous = nullptr; current->BasePosition >= clampedPosition; current = previous)
{
previous = current->Previous;
Memory::OS_Release(current, current->Reserved);
}
arena->Current = current;
size_t newPosition = bbbPosition - current->BasePosition;
size_t newPosition = clampedPosition - current->BasePosition;
Assert(newPosition <= current->Position);
current->Position = newPosition;
}

View File

@@ -58,8 +58,9 @@ namespace Juliet::D3D12::Internal
{
heap->Handle->Release();
}
SafeFree(heap->FreeIndices);
Free(heap.Get());
// TODO: should not free because its on an arena, but need to use arena properly
// SafeFree(heap->FreeIndices);
// Free(heap.Get());
}
bool AssignDescriptor(D3D12DescriptorHeap* heap, D3D12Descriptor& outDescriptor)