Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ add_library(Caffeine
src/core/Timer.cpp
src/core/GameLoop.cpp
src/input/InputManager.cpp
src/debug/LogSystem.cpp
src/debug/Profiler.cpp
)

target_include_directories(Caffeine PUBLIC
Expand Down
126 changes: 126 additions & 0 deletions src/debug/LogSystem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "LogSystem.hpp"
#include <cstdio>
#include <cstring>

namespace Caffeine::Debug {

LogSystem& LogSystem::instance() {
static LogSystem s;
return s;
}

LogSystem::LogSystem() {}

void LogSystem::log(LogLevel level, const char* category, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlog(level, category, fmt, args);
va_end(args);
}

void LogSystem::vlog(LogLevel level, const char* category, const char* fmt, va_list args) {
std::lock_guard<std::mutex> lock(m_mutex);

if (level < m_minLevel) return;

if (category) {
const CategoryEntry* entry = findCategory(category);
if (entry && !entry->enabled) return;
}

char buffer[MAX_MESSAGE_LENGTH];
vsnprintf(buffer, MAX_MESSAGE_LENGTH, fmt, args);

for (usize i = 0; i < m_sinkCount; ++i) {
if (m_sinks[i]) {
m_sinks[i](level, category ? category : "", buffer);
}
}
}

void LogSystem::setLevel(LogLevel minLevel) {
std::lock_guard<std::mutex> lock(m_mutex);
m_minLevel = minLevel;
}

LogLevel LogSystem::getLevel() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_minLevel;
}

void LogSystem::setCategoryEnabled(const char* category, bool enabled) {
std::lock_guard<std::mutex> lock(m_mutex);

CategoryEntry* entry = findCategory(category);
if (entry) {
entry->enabled = enabled;
return;
}

if (m_categoryCount < MAX_CATEGORIES) {
auto& e = m_categories[m_categoryCount];
strncpy(e.name, category, sizeof(e.name) - 1);
e.name[sizeof(e.name) - 1] = '\0';
e.enabled = enabled;
++m_categoryCount;
}
}

bool LogSystem::isCategoryEnabled(const char* category) const {
std::lock_guard<std::mutex> lock(m_mutex);
const CategoryEntry* entry = findCategory(category);
if (entry) return entry->enabled;
return true;
}

void LogSystem::addSink(SinkFn sink) {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_sinkCount < MAX_SINKS) {
m_sinks[m_sinkCount] = std::move(sink);
++m_sinkCount;
}
}

void LogSystem::clearSinks() {
std::lock_guard<std::mutex> lock(m_mutex);
for (usize i = 0; i < m_sinkCount; ++i) {
m_sinks[i] = nullptr;
}
m_sinkCount = 0;
}

usize LogSystem::sinkCount() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_sinkCount;
}

const char* LogSystem::levelToString(LogLevel level) {
switch (level) {
case LogLevel::Trace: return "TRACE";
case LogLevel::Info: return "INFO";
case LogLevel::Warn: return "WARN";
case LogLevel::Error: return "ERROR";
case LogLevel::Fatal: return "FATAL";
}
return "UNKNOWN";
}

LogSystem::CategoryEntry* LogSystem::findCategory(const char* category) {
for (usize i = 0; i < m_categoryCount; ++i) {
if (strcmp(m_categories[i].name, category) == 0) {
return &m_categories[i];
}
}
return nullptr;
}

const LogSystem::CategoryEntry* LogSystem::findCategory(const char* category) const {
for (usize i = 0; i < m_categoryCount; ++i) {
if (strcmp(m_categories[i].name, category) == 0) {
return &m_categories[i];
}
}
return nullptr;
}

} // namespace Caffeine::Debug
76 changes: 76 additions & 0 deletions src/debug/LogSystem.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#pragma once

#include "../core/Types.hpp"

#include <cstdarg>
#include <functional>
#include <mutex>

namespace Caffeine::Debug {

enum class LogLevel : u8 {
Trace = 0,
Info,
Warn,
Error,
Fatal
};

class LogSystem {
public:
static LogSystem& instance();

void log(LogLevel level, const char* category, const char* fmt, ...);
void vlog(LogLevel level, const char* category, const char* fmt, va_list args);

void setLevel(LogLevel minLevel);
LogLevel getLevel() const;

void setCategoryEnabled(const char* category, bool enabled);
bool isCategoryEnabled(const char* category) const;

using SinkFn = std::function<void(LogLevel, const char*, const char*)>;
void addSink(SinkFn sink);
void clearSinks();
usize sinkCount() const;

static const char* levelToString(LogLevel level);

private:
LogSystem();
~LogSystem() = default;
LogSystem(const LogSystem&) = delete;
LogSystem& operator=(const LogSystem&) = delete;

struct CategoryEntry {
char name[64];
bool enabled;
};

static constexpr usize MAX_CATEGORIES = 64;
static constexpr usize MAX_SINKS = 16;
static constexpr usize MAX_MESSAGE_LENGTH = 2048;

LogLevel m_minLevel = LogLevel::Trace;
CategoryEntry m_categories[MAX_CATEGORIES]{};
usize m_categoryCount = 0;
SinkFn m_sinks[MAX_SINKS]{};
usize m_sinkCount = 0;
mutable std::mutex m_mutex;

CategoryEntry* findCategory(const char* category);
const CategoryEntry* findCategory(const char* category) const;
};

} // namespace Caffeine::Debug

#ifdef CF_DEBUG
#define CF_TRACE(cat, ...) Caffeine::Debug::LogSystem::instance().log(Caffeine::Debug::LogLevel::Trace, cat, __VA_ARGS__)
#else
#define CF_TRACE(cat, ...) ((void)0)
#endif

#define CF_INFO(cat, ...) Caffeine::Debug::LogSystem::instance().log(Caffeine::Debug::LogLevel::Info, cat, __VA_ARGS__)
#define CF_WARN(cat, ...) Caffeine::Debug::LogSystem::instance().log(Caffeine::Debug::LogLevel::Warn, cat, __VA_ARGS__)
#define CF_ERROR(cat, ...) Caffeine::Debug::LogSystem::instance().log(Caffeine::Debug::LogLevel::Error, cat, __VA_ARGS__)
#define CF_FATAL(cat, ...) Caffeine::Debug::LogSystem::instance().log(Caffeine::Debug::LogLevel::Fatal, cat, __VA_ARGS__)
105 changes: 105 additions & 0 deletions src/debug/Profiler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "Profiler.hpp"
#include <cstring>
#include <algorithm>

namespace Caffeine::Debug {

Profiler& Profiler::instance() {
static Profiler s;
return s;
}

void Profiler::beginScope(const char* name) {
if (!m_enabled) return;

InternalScopeData* scope = findOrCreateScope(name);
if (!scope) return;

scope->activeTimer.reset();
scope->activeTimer.start();
}

void Profiler::endScope(const char* name) {
if (!m_enabled) return;

InternalScopeData* scope = findScope(name);
if (!scope) return;

scope->activeTimer.stop();
f64 ms = scope->activeTimer.elapsed().millis();

scope->callCount++;
scope->totalMs += ms;
if (ms < scope->minMs) scope->minMs = ms;
if (ms > scope->maxMs) scope->maxMs = ms;
}

void Profiler::report(Vector<ScopeStats>& out) const {
out.clear();
for (usize i = 0; i < m_scopeCount; ++i) {
const auto& s = m_scopes[i];
ScopeStats stats;
stats.name = s.name;
stats.callCount = s.callCount;
stats.totalMs = s.totalMs;
stats.avgMs = (s.callCount > 0) ? s.totalMs / static_cast<f64>(s.callCount) : 0.0;
stats.minMs = s.minMs;
stats.maxMs = s.maxMs;
out.pushBack(stats);
}
}

void Profiler::reset() {
m_scopeCount = 0;
for (usize i = 0; i < MAX_SCOPES; ++i) {
m_scopes[i] = InternalScopeData{};
}
}

usize Profiler::scopeCount() const {
return m_scopeCount;
}

Profiler::InternalScopeData* Profiler::findScope(const char* name) {
for (usize i = 0; i < m_scopeCount; ++i) {
if (strcmp(m_scopes[i].name, name) == 0) {
return &m_scopes[i];
}
}
return nullptr;
}

const Profiler::InternalScopeData* Profiler::findScope(const char* name) const {
for (usize i = 0; i < m_scopeCount; ++i) {
if (strcmp(m_scopes[i].name, name) == 0) {
return &m_scopes[i];
}
}
return nullptr;
}

Profiler::InternalScopeData* Profiler::findOrCreateScope(const char* name) {
InternalScopeData* existing = findScope(name);
if (existing) return existing;

if (m_scopeCount >= MAX_SCOPES) return nullptr;

auto& scope = m_scopes[m_scopeCount];
scope.name = name;
scope.callCount = 0;
scope.totalMs = 0.0;
scope.minMs = 1e18;
scope.maxMs = 0.0;
++m_scopeCount;
return &scope;
}

ProfileScope::ProfileScope(const char* name) : m_name(name) {
Profiler::instance().beginScope(m_name);
}

ProfileScope::~ProfileScope() {
Profiler::instance().endScope(m_name);
}

} // namespace Caffeine::Debug
72 changes: 72 additions & 0 deletions src/debug/Profiler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include "../core/Types.hpp"
#include "../core/Timer.hpp"
#include "../containers/HashMap.hpp"
#include "../containers/Vector.hpp"

namespace Caffeine::Debug {

class Profiler {
public:
static Profiler& instance();

void beginScope(const char* name);
void endScope(const char* name);

struct ScopeStats {
const char* name = nullptr;
u64 callCount = 0;
f64 totalMs = 0.0;
f64 avgMs = 0.0;
f64 minMs = 1e18;
f64 maxMs = 0.0;
};

void report(Vector<ScopeStats>& out) const;
void reset();

void setEnabled(bool enabled) { m_enabled = enabled; }
bool isEnabled() const { return m_enabled; }

usize scopeCount() const;

private:
Profiler() = default;
~Profiler() = default;
Profiler(const Profiler&) = delete;
Profiler& operator=(const Profiler&) = delete;

struct InternalScopeData {
const char* name = nullptr;
u64 callCount = 0;
f64 totalMs = 0.0;
f64 minMs = 1e18;
f64 maxMs = 0.0;
Core::Timer activeTimer;
};

bool m_enabled = true;

static constexpr usize MAX_SCOPES = 256;
InternalScopeData m_scopes[MAX_SCOPES]{};
usize m_scopeCount = 0;

InternalScopeData* findScope(const char* name);
const InternalScopeData* findScope(const char* name) const;
InternalScopeData* findOrCreateScope(const char* name);
};

class ProfileScope {
public:
explicit ProfileScope(const char* name);
~ProfileScope();

private:
const char* m_name;
};

} // namespace Caffeine::Debug

#define CF_PROFILE_SCOPE(name) \
Caffeine::Debug::ProfileScope _cfProfileScope_##__LINE__(name)
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_executable(CaffeineTest
test_timer.cpp
test_gameloop.cpp
test_input.cpp
test_debug.cpp
)

target_link_libraries(CaffeineTest PRIVATE Caffeine)
Expand Down
Loading
Loading