From f71fa49016397613bd43fa45fdd8df1e206d3881 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sat, 21 Dec 2024 08:47:32 -0800 Subject: [PATCH 1/4] Extract queue type to template variable --- include/rtlog/rtlog.h | 57 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/include/rtlog/rtlog.h b/include/rtlog/rtlog.h index 047a9a7..25db7b4 100644 --- a/include/rtlog/rtlog.h +++ b/include/rtlog/rtlog.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include #if !defined(RTLOG_USE_FMTLIB) && !defined(RTLOG_USE_STB) // The default behavior to match legacy behavior is to use STB @@ -47,6 +49,12 @@ namespace rtlog { +template struct BasicLogData { + LogData mLogData{}; + size_t mSequenceNumber{}; + std::array mMessage{}; +}; + enum class Status { Success = 0, @@ -54,6 +62,30 @@ enum class Status { Error_MessageTruncated = 2, }; +namespace detail { +template +struct has_try_enqueue : std::false_type {}; + +template +struct has_try_enqueue().try_enqueue( + std::declval()))>> + : std::true_type {}; + +template +inline constexpr bool has_try_enqueue_v = has_try_enqueue::value; + +template +struct has_try_dequeue : std::false_type {}; + +template +struct has_try_dequeue().try_dequeue( + std::declval()))>> + : std::true_type {}; + +template +inline constexpr bool has_try_dequeue_v = has_try_dequeue::value; +} // namespace detail + /** * @brief A logger class for logging messages. * This class allows you to log messages of type LogData. @@ -61,9 +93,6 @@ enum class Status { * format string you want to log. For instance: The log level, the log region, * the file name, the line number, etc. See examples or tests for some ideas. * - * TODO: Currently is built on a single input/single output queue. Do not call - * Log or PrintAndClearLogQueue from multiple threads. - * * @tparam LogData The type of the data to be logged. * @tparam MaxNumMessages The maximum number of messages that can be enqueud at * once. If this number is exceeded, the logger will return an error. @@ -72,11 +101,23 @@ enum class Status { * @tparam SequenceNumber This number is incremented when the message is * enqueued. It is assumed that your non-realtime logger increments and logs it * on Log. + * @tparam QType is the configurable underlying queue. By default it is a SPSC + * queue from moodycamel. WARNING! It is up to the user to ensure this queue + * type is real-time safe!! */ template &SequenceNumber> + std::atomic &SequenceNumber, + template class QType = moodycamel::ReaderWriterQueue> class Logger { public: + using InternalLogData = BasicLogData; + using InternalQType = QType; + + static_assert(detail::has_try_enqueue_v, + "QType must have a try_enqueue method"); + static_assert(detail::has_try_dequeue_v, + "QType must have a try_dequeue method"); + /* * @brief Logs a message with the given format and input data. * @@ -261,13 +302,7 @@ class Logger { } private: - struct InternalLogData { - LogData mLogData{}; - size_t mSequenceNumber{}; - std::array mMessage{}; - }; - - moodycamel::ReaderWriterQueue mQueue{MaxNumMessages}; + InternalQType mQueue{MaxNumMessages}; }; /** From adb2e22cceea15a940e3f3a964c7521a07b094ab Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sun, 22 Dec 2024 09:07:32 -0700 Subject: [PATCH 2/4] Working on older compilers --- include/rtlog/rtlog.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/include/rtlog/rtlog.h b/include/rtlog/rtlog.h index 25db7b4..fec9d23 100644 --- a/include/rtlog/rtlog.h +++ b/include/rtlog/rtlog.h @@ -86,6 +86,13 @@ template inline constexpr bool has_try_dequeue_v = has_try_dequeue::value; } // namespace detail +// On earlier versions of compilers (especially clang) you cannot +// rely on defaulted template template parameters working as intended +// This overload explicitly has 1 template paramter which is what +// `Logger` expects, it uses the default 512 from ReaderWriterQueue as +// the hardcoded MaxBlockSize +template using rtlog_SPSC = moodycamel::ReaderWriterQueue; + /** * @brief A logger class for logging messages. * This class allows you to log messages of type LogData. @@ -104,10 +111,15 @@ inline constexpr bool has_try_dequeue_v = has_try_dequeue::value; * @tparam QType is the configurable underlying queue. By default it is a SPSC * queue from moodycamel. WARNING! It is up to the user to ensure this queue * type is real-time safe!! + * + * Requirements on QType: + * 1. Is real-time safe + * 2. Accepts one type template paramter for the type to be queued + * 3. Has methods `try_enqueue` and `try_dequeue` */ template &SequenceNumber, - template class QType = moodycamel::ReaderWriterQueue> + template class QType = rtlog_SPSC> class Logger { public: using InternalLogData = BasicLogData; From 342164f4dd15f9bd4b796ad1f590c7f8b93dcff9 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Mon, 23 Dec 2024 15:05:37 -0700 Subject: [PATCH 3/4] Add more explicit template parameter restrictions --- include/rtlog/rtlog.h | 79 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/include/rtlog/rtlog.h b/include/rtlog/rtlog.h index fec9d23..ce0169d 100644 --- a/include/rtlog/rtlog.h +++ b/include/rtlog/rtlog.h @@ -49,12 +49,6 @@ namespace rtlog { -template struct BasicLogData { - LogData mLogData{}; - size_t mSequenceNumber{}; - std::array mMessage{}; -}; - enum class Status { Success = 0, @@ -63,16 +57,41 @@ enum class Status { }; namespace detail { + +template struct BasicLogData { + LogData mLogData{}; + size_t mSequenceNumber{}; + std::array mMessage{}; +}; + template -struct has_try_enqueue : std::false_type {}; +struct has_try_enqueue_by_move : std::false_type {}; template -struct has_try_enqueue().try_enqueue( - std::declval()))>> - : std::true_type {}; +struct has_try_enqueue_by_move< + T, std::void_t().try_enqueue( + std::declval()))>> : std::true_type {}; template -inline constexpr bool has_try_enqueue_v = has_try_enqueue::value; +inline constexpr bool has_try_enqueue_by_move_v = + has_try_enqueue_by_move::value; + +template +struct has_try_enqueue_by_value : std::false_type {}; + +template +struct has_try_enqueue_by_value< + T, std::void_t().try_enqueue( + std::declval()))>> : std::true_type { +}; + +template +inline constexpr bool has_try_enqueue_by_value_v = + has_try_enqueue_by_value::value; + +template +inline constexpr bool has_try_enqueue_v = + has_try_enqueue_by_move_v || has_try_enqueue_by_value_v; template struct has_try_dequeue : std::false_type {}; @@ -84,6 +103,26 @@ struct has_try_dequeue().try_dequeue( template inline constexpr bool has_try_dequeue_v = has_try_dequeue::value; + +template +struct has_value_type : std::false_type {}; + +template +struct has_value_type> : std::true_type { +}; + +template +inline constexpr bool has_value_type_v = has_value_type::value; + +template +struct has_int_constructor : std::false_type {}; + +template +struct has_int_constructor()))>> + : std::true_type {}; + +template +inline constexpr bool has_int_constructor_v = has_int_constructor::value; } // namespace detail // On earlier versions of compilers (especially clang) you cannot @@ -115,20 +154,28 @@ template using rtlog_SPSC = moodycamel::ReaderWriterQueue; * Requirements on QType: * 1. Is real-time safe * 2. Accepts one type template paramter for the type to be queued - * 3. Has methods `try_enqueue` and `try_dequeue` + * 3. Has a constructor that takes an integer which will be the queue's capacity + * 4. Has methods `bool try_enqueue(T &&item)` and/or `bool try_enqueue(const T &item)` and `bool try_dequeue(T &item)` */ template &SequenceNumber, template class QType = rtlog_SPSC> class Logger { public: - using InternalLogData = BasicLogData; + using InternalLogData = detail::BasicLogData; using InternalQType = QType; + static_assert( + detail::has_int_constructor_v, + "QType must have a constructor that takes an int - `QType(int)`"); + static_assert(detail::has_value_type_v, + "QType must have a value_type - `using value_type = T;`"); static_assert(detail::has_try_enqueue_v, - "QType must have a try_enqueue method"); - static_assert(detail::has_try_dequeue_v, - "QType must have a try_dequeue method"); + "QType must have a try_enqueue method - `bool try_enqueue(T " + "&&item)` and/or `bool try_enqueue(const T &item)`"); + static_assert( + detail::has_try_dequeue_v, + "QType must have a try_dequeue method - `bool try_dequeue(T &item)`"); /* * @brief Logs a message with the given format and input data. From cb61a739c88f09e722cebcecd7ea7d64fbea319b Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Mon, 23 Dec 2024 15:40:41 -0700 Subject: [PATCH 4/4] Add custom queue example, more readme --- README.md | 31 ++++++++++++ examples/CMakeLists.txt | 1 + examples/custom_queue_example/CMakeLists.txt | 29 ++++++++++++ .../custom_queue_example/customqueuemain.cpp | 47 +++++++++++++++++++ include/rtlog/rtlog.h | 6 ++- 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 examples/custom_queue_example/CMakeLists.txt create mode 100644 examples/custom_queue_example/customqueuemain.cpp diff --git a/README.md b/README.md index 4fe8c51..4b5a8b4 100644 --- a/README.md +++ b/README.md @@ -127,3 +127,34 @@ Or alternatively spin up a `rtlog::LogProcessingThread` ```c++ rtlog::LogProcessingThread thread(logger, PrintMessage, std::chrono::milliseconds(10)); ``` + +## Customizing the queue type + +If you don't want to use the SPSC moodycamel queue, you can provide your own queue type. + +** IT IS UP TO YOU TO ENSURE THE QUEUE YOU PROVIDE IS LOCK-FREE AND REAL-TIME SAFE ** + +The queue must have the following: +```c++ +template +class MyQueue +{ +public: + using value_type = T; + + MyQueue(int capacity); + bool try_dequeue(T& item); // MUST return false if the queue is empty + + bool try_enqueue(T&& item); + // OR + bool try_enqueue(const T& item); +}; +``` + +Then, when creating the logger, provide the queue type as a template parameter: + +```c++ +using RealtimeLogger = rtlog::Logger; +``` + +You can see an example of wrapping a known rt-safe queue in `examples/custom_queue_example`. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4075de1..522d6e5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(everlog) +add_subdirectory(custom_queue_example) diff --git a/examples/custom_queue_example/CMakeLists.txt b/examples/custom_queue_example/CMakeLists.txt new file mode 100644 index 0000000..5e3ada1 --- /dev/null +++ b/examples/custom_queue_example/CMakeLists.txt @@ -0,0 +1,29 @@ +if (NOT TARGET farbot) + include(FetchContent) + + FetchContent_Declare(farbot + GIT_REPOSITORY https://github.com/hogliux/farbot + GIT_TAG 0416705394720c12f0d02e55c144e4f69bb06912 + ) + # Note we do not "MakeAvailable" here, because farbot does not fully work via FetchContent + if(NOT farbot_POPULATED) + FetchContent_Populate(farbot) + endif() + add_library(farbot INTERFACE) + add_library(farbot::farbot ALIAS farbot) + + target_include_directories(farbot INTERFACE + $ + $ + ) +endif() + +add_executable(custom_queue_example + customqueuemain.cpp +) + +target_link_libraries(custom_queue_example + PRIVATE + rtlog::rtlog + farbot::farbot +) diff --git a/examples/custom_queue_example/customqueuemain.cpp b/examples/custom_queue_example/customqueuemain.cpp new file mode 100644 index 0000000..0bf6973 --- /dev/null +++ b/examples/custom_queue_example/customqueuemain.cpp @@ -0,0 +1,47 @@ +#include +#include + +template class FarbotMPSCQueueWrapper { + farbot::fifo // producer_failure_mode + + mQueue; + +public: + using value_type = T; + + FarbotMPSCQueueWrapper(int capacity) : mQueue(capacity) {} + + bool try_enqueue(T &&item) { return mQueue.push(std::move(item)); } + bool try_dequeue(T &item) { return mQueue.pop(item); } +}; + +struct LogData {}; + +std::atomic gSequenceNumber{0}; + +int main() { + rtlog::Logger + logger; + logger.Log({}, "Hello, World!"); + + logger.PrintAndClearLogQueue( + [](const LogData &data, size_t sequenceNumber, const char *fstring, ...) { + (void)data; + std::array buffer; + + va_list args; + va_start(args, fstring); + vsnprintf(buffer.data(), buffer.size(), fstring, args); + va_end(args); + + printf("{%zu} %s\n", sequenceNumber, buffer.data()); + }); + + return 0; +} diff --git a/include/rtlog/rtlog.h b/include/rtlog/rtlog.h index ce0169d..351ed6f 100644 --- a/include/rtlog/rtlog.h +++ b/include/rtlog/rtlog.h @@ -154,8 +154,10 @@ template using rtlog_SPSC = moodycamel::ReaderWriterQueue; * Requirements on QType: * 1. Is real-time safe * 2. Accepts one type template paramter for the type to be queued - * 3. Has a constructor that takes an integer which will be the queue's capacity - * 4. Has methods `bool try_enqueue(T &&item)` and/or `bool try_enqueue(const T &item)` and `bool try_dequeue(T &item)` + * 3. Has a constructor that takes an integer which will be the queue's + * capacity + * 4. Has methods `bool try_enqueue(T &&item)` and/or `bool + * try_enqueue(const T &item)` and `bool try_dequeue(T &item)` */ template &SequenceNumber,