aboutsummaryrefslogtreecommitdiff
path: root/source/10-common
diff options
context:
space:
mode:
Diffstat (limited to 'source/10-common')
-rw-r--r--source/10-common/Log.cpp116
-rw-r--r--source/10-common/Log.hpp55
-rw-r--r--source/10-common/RingBuffer.hpp174
3 files changed, 345 insertions, 0 deletions
diff --git a/source/10-common/Log.cpp b/source/10-common/Log.cpp
new file mode 100644
index 0000000..83d81e9
--- /dev/null
+++ b/source/10-common/Log.cpp
@@ -0,0 +1,116 @@
+#include "Log.hpp"
+
+#include "Macros.hpp"
+
+#include <robin_hood.h>
+#include <algorithm>
+#include <cstdio>
+
+namespace ProjectBrussel_UNITY_ID {
+using namespace Log;
+
+const char* MapMessageLevelToString(MessageLevel level) {
+ switch (level) {
+ using enum MessageLevel;
+ case Debug: return "DEBUG";
+ case Info: return "INFO";
+ case Warning: return "WARN";
+ case Error: return "ERROR";
+ default: UNREACHABLE;
+ }
+}
+
+void PrintMessage(const Message& msg) {
+ using namespace std::chrono;
+
+ auto t = system_clock::to_time_t(msg.time);
+ char timeStr[128];
+ strftime(timeStr, sizeof(timeStr), "%H:%M:%S", localtime(&t));
+ printf("[%s][%s][%s:%u] %.*s\n",
+ MapMessageLevelToString(msg.level),
+ timeStr,
+ msg.srcLoc.function_name(),
+ msg.srcLoc.line(),
+ PRINTF_STRING_VIEW(msg.text));
+}
+
+MessageBufferId gNextBufferId = 0;
+robin_hood::unordered_map<MessageBufferId, MessageBuffer*> gBuffers;
+} // namespace ProjectBrussel_UNITY_ID
+
+namespace Log {
+bool gPrintToStdOut = true;
+#if BRUSSEL_DEV_ENV
+MessageBuffer gDefaultBuffer;
+MessageBufferId gDefaultBufferId;
+#endif
+} // namespace Log
+
+Log::MessageBufferId Log::RegisterBuffer(MessageBuffer& buffer) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ auto id = gNextBufferId++;
+ gBuffers.try_emplace(id, &buffer);
+ return id;
+}
+
+void Log::UnregisterBuffer(MessageBufferId id) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ gBuffers.erase(id);
+}
+
+Log::MessageBuffer* Log::GetBuffer(MessageBufferId id) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ auto iter = gBuffers.find(id);
+ if (iter != gBuffers.end()) {
+ return iter->second;
+ } else {
+ return nullptr;
+ }
+}
+
+void Log::DumpRegisteredBuffers() {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ puts("================ BEGIN LOG BUFFER DUMP ================");
+ for (const auto& [id, buffer] : gBuffers) {
+ printf("Buffer #%d at %p\n", id, buffer);
+ printf("Buffer size: %zu\n", buffer->messages.capacity());
+ bool needsWrapAround = buffer->messages.GetHeadIdx() >= buffer->messages.GetTailIdx();
+ if (needsWrapAround) {
+ printf("Fill size: %zu in [%zu,%zu) and [0,%zu)\n",
+ buffer->messages.size(),
+ // First chunk: [begin,end)
+ buffer->messages.GetHeadIdx(),
+ buffer->messages.capacity(),
+ // Second chunk: [0,end)
+ buffer->messages.GetTailIdx());
+ } else {
+ printf("Fill size: %zu in [%zu,%zu)\n",
+ buffer->messages.size(),
+ // [begin,end)
+ buffer->messages.GetHeadIdx(),
+ buffer->messages.GetTailIdx());
+ }
+ for (const auto& msg : buffer->messages) {
+ // Indent log messages in this buffer
+ printf("\t");
+ PrintMessage(msg);
+ }
+ }
+ puts("================ END LOG BUFFER DUMP ================");
+}
+
+void Log::Add(const Message& msg) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ if (gPrintToStdOut) {
+ PrintMessage(msg);
+ }
+
+ for (auto& [_, buffer] : gBuffers) {
+ buffer->messages.push_back(msg);
+ }
+}
diff --git a/source/10-common/Log.hpp b/source/10-common/Log.hpp
new file mode 100644
index 0000000..aeba984
--- /dev/null
+++ b/source/10-common/Log.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "RingBuffer.hpp"
+
+#include <fmt/format.h>
+#include <chrono>
+#include <source_location>
+#include <string_view>
+
+// NOTE: we keep this on one line so std::soruce_location reports the correct information
+#define GENERIC_LOG(lvl, fmtString, ...) Log::Add(Log::Message{ .level = lvl, .time = std::chrono::system_clock::now(), .srcLoc = std::source_location::current(), .text = fmt::format(fmtString __VA_OPT__(, ) __VA_ARGS__) })
+
+#define LOG_DEBUG(...) GENERIC_LOG(Log::MessageLevel::Debug, __VA_ARGS__)
+#define LOG_INFO(...) GENERIC_LOG(Log::MessageLevel::Info, __VA_ARGS__)
+#define LOG_WARNING(...) GENERIC_LOG(Log::MessageLevel::Warning, __VA_ARGS__)
+#define LOG_ERROR(...) GENERIC_LOG(Log::MessageLevel::Error, __VA_ARGS__)
+
+namespace Log {
+enum class MessageLevel {
+ Debug,
+ Info,
+ Warning,
+ Error,
+};
+
+struct Message {
+ MessageLevel level;
+ std::chrono::time_point<std::chrono::system_clock> time;
+ std::source_location srcLoc;
+ std::string text;
+};
+
+/// A mRing buffer of log messages for programmatic inspection at runtime.
+struct MessageBuffer {
+ RingBuffer<Message> messages;
+};
+
+/// Unique ID identifying a currently registered MessageBuffer.
+using MessageBufferId = int;
+
+MessageBufferId RegisterBuffer(MessageBuffer& buffer);
+void UnregisterBuffer(MessageBufferId id);
+MessageBuffer* GetBuffer(MessageBufferId id);
+void DumpRegisteredBuffers();
+
+extern bool gPrintToStdOut;
+#if BRUSSEL_DEV_ENV
+// NOTE: initialized in main.cpp
+extern MessageBuffer gDefaultBuffer;
+extern MessageBufferId gDefaultBufferId;
+#endif
+
+// TODO improve this interface: don't copy std::string when there is in fact no MessageBuffer registered
+void Add(const Message& msg);
+} // namespace Log
diff --git a/source/10-common/RingBuffer.hpp b/source/10-common/RingBuffer.hpp
new file mode 100644
index 0000000..c4ff851
--- /dev/null
+++ b/source/10-common/RingBuffer.hpp
@@ -0,0 +1,174 @@
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+
+template <typename T>
+class RingBuffer {
+public:
+ class Sentinel {};
+ class Iterator {
+ public:
+ friend class RingBuffer;
+ using difference_type = ptrdiff_t;
+ using value_type = T;
+ using pointer = T*;
+ using reference = T&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ private:
+ RingBuffer* mContainer;
+ size_t mCurr;
+ bool mNeedsWrapAround;
+ bool mHasWrapped = false;
+
+ public:
+ // NOTE: default constructed Iterator is in an undefined state
+
+ T& operator*() const {
+ return mContainer->mRing[mCurr];
+ }
+
+ Iterator& operator++() {
+ assert(*this != Sentinel{});
+ ++mCurr;
+ if (mNeedsWrapAround && mCurr == mContainer->mCapacity) {
+ mHasWrapped = true;
+ mCurr = 0;
+ }
+ return *this;
+ }
+
+ bool operator==(const Iterator& that) const {
+ assert(this->mContainer == that.mContainer);
+ return this->mCurr == that.mCurr;
+ }
+
+ bool operator==(const Sentinel&) const {
+ return mCurr == mContainer->mTailIdx && (!mNeedsWrapAround || mHasWrapped);
+ }
+ };
+
+ using value_type = T;
+ using reference = T&;
+ using const_reference = const T&;
+ using iterator = Iterator;
+ // using const_iterator = void; // TODO
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+
+private:
+ T* mRing = nullptr;
+ size_t mHeadIdx = 0;
+ size_t mTailIdx = 0;
+ size_t mCapacity = 0;
+ size_t mSize = 0;
+
+public:
+ RingBuffer() noexcept = default;
+
+ ~RingBuffer() noexcept {
+ delete mRing;
+ }
+
+ RingBuffer(const RingBuffer&) noexcept = delete;
+ RingBuffer& operator=(const RingBuffer&) noexcept = delete;
+
+ RingBuffer(RingBuffer&& that) noexcept
+ : mRing{ that.mRing }
+ , mHeadIdx{ that.mHeadIdx }
+ , mTailIdx{ that.mTailIdx }
+ , mCapacity{ that.mCapacity }
+ , mSize{ that.mSize } {
+ that.mRing = nullptr;
+ }
+
+ RingBuffer& operator=(RingBuffer&& that) noexcept {
+ if (this != &that) {
+ auto oldThisRing = this->mRing;
+ this->mRing = that.mRing;
+ that.mRing = nullptr;
+ delete oldThisRing;
+
+ this->mHeadIdx = that.mHeadIdx;
+ this->mTailIdx = that.mTailIdx;
+ this->mCapacity = that.mCapacity;
+ this->mSize = that.mSize;
+ }
+
+ return *this;
+ }
+
+ Iterator begin() {
+ Iterator it;
+ it.mContainer = this;
+ it.mCurr = mHeadIdx;
+ it.mNeedsWrapAround = mHeadIdx >= mTailIdx;
+ return it;
+ }
+
+ Sentinel end() {
+ return Sentinel{};
+ }
+
+ const T& operator[](size_t i) const {
+ size_t idx = mHeadIdx + i;
+ if (idx >= mCapacity) {
+ idx -= mCapacity;
+ }
+ return mRing[idx];
+ }
+ T& operator[](size_t i) { return const_cast<T&>(const_cast<const RingBuffer&>(*this)[i]); }
+
+ void push_back(T t) {
+ if (mTailIdx == mCapacity) {
+ // Ring buffer is filled to the right, warp around to the beginning
+ // mHeadIdx > 0 must be true, since we checked that as condition (1) above
+ mRing[0] = std::move(t);
+ mTailIdx = 1;
+ } else {
+ mRing[mTailIdx] = std::move(t);
+ mTailIdx += 1;
+ }
+
+ // Push mHeadIdx backwards if overwrote element in a filled buffer
+ bool bufferFilled = mSize == mCapacity;
+ if (bufferFilled && mTailIdx > mHeadIdx) {
+ mHeadIdx += 1;
+ if (mHeadIdx == mCapacity) {
+ mHeadIdx = 0;
+ }
+ }
+
+ if (!bufferFilled) {
+ ++mSize;
+ }
+ }
+
+ [[nodiscard]] size_t capacity() const {
+ return mCapacity;
+ }
+
+ [[nodiscard]] size_t size() const {
+ return mSize;
+ }
+
+ [[nodiscard]] T* GetBuffer() const { return mRing; }
+ [[nodiscard]] size_t GetHeadIdx() const { return mHeadIdx; }
+ [[nodiscard]] size_t GetTailIdx() const { return mTailIdx; }
+
+ void resize(size_t newCapacity) {
+ auto size = this->size();
+
+ auto oldRing = mRing;
+ auto newRing = mRing = new T[newCapacity];
+ std::rotate_copy(oldRing, oldRing + mHeadIdx, oldRing + mCapacity, newRing);
+ delete oldRing;
+
+ mCapacity = newCapacity;
+ mHeadIdx = 0;
+ mTailIdx = size;
+ }
+};