diff options
Diffstat (limited to 'source/10-common')
-rw-r--r-- | source/10-common/Log.cpp | 116 | ||||
-rw-r--r-- | source/10-common/Log.hpp | 55 | ||||
-rw-r--r-- | source/10-common/RingBuffer.hpp | 174 |
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; + } +}; |