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 047a9a7..351ed6f 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 @@ -54,6 +56,82 @@ enum class Status { Error_MessageTruncated = 2, }; +namespace detail { + +template struct BasicLogData { + LogData mLogData{}; + size_t mSequenceNumber{}; + std::array mMessage{}; +}; + +template +struct has_try_enqueue_by_move : std::false_type {}; + +template +struct has_try_enqueue_by_move< + T, std::void_t().try_enqueue( + std::declval()))>> : std::true_type {}; + +template +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 {}; + +template +struct has_try_dequeue().try_dequeue( + std::declval()))>> + : std::true_type {}; + +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 +// 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. @@ -61,9 +139,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 +147,38 @@ 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!! + * + * 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)` */ template &SequenceNumber> + std::atomic &SequenceNumber, + template class QType = rtlog_SPSC> class Logger { public: + 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 - `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. * @@ -261,13 +363,7 @@ class Logger { } private: - struct InternalLogData { - LogData mLogData{}; - size_t mSequenceNumber{}; - std::array mMessage{}; - }; - - moodycamel::ReaderWriterQueue mQueue{MaxNumMessages}; + InternalQType mQueue{MaxNumMessages}; }; /**