Skip to content

Commit

Permalink
Changed default queue to farbot, added Multi and Single Writer varian…
Browse files Browse the repository at this point in the history
…ts (#33)
  • Loading branch information
cjappl authored Jan 13, 2025
1 parent 710abba commit 36381f9
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 106 deletions.
24 changes: 17 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,24 @@ target_include_directories(${PROJECT_NAME} INTERFACE
$<INSTALL_INTERFACE:include>
)

# Try to find the package ReaderWriterQueue, if not found fetch with FetchContent
find_package(ReaderWriterQueue QUIET)
if(NOT TARGET readerwriterqueue)
if (NOT TARGET farbot)
include(FetchContent)
FetchContent_Declare(ReaderWriterQueue
GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue

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
$<BUILD_INTERFACE:${farbot_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
FetchContent_MakeAvailable(ReaderWriterQueue)
endif()

if(NOT RTSAN_USE_FMTLIB AND NOT TARGET stb::stb)
Expand Down Expand Up @@ -89,7 +99,7 @@ endif()

target_link_libraries(rtlog
INTERFACE
readerwriterqueue
farbot::farbot
stb::stb
$<$<BOOL:${RTLOG_USE_FMTLIB}>:fmt::fmt>
)
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ Slides:
- Ability to log messages of any type and size from the real-time thread
- Statically allocated memory at compile time, no allocations in the real-time thread
- Support for printf-style format specifiers (using [a version of the printf family](https://github.com/nothings/stb/blob/master/stb_sprintf.h) that doesn't hit the `localeconv` lock) OR support for modern libfmt formatting.
- Efficient thread-safe logging using a [lock free queue](https://github.com/cameron314/readerwriterqueue).
- Efficient thread-safe logging using a [lock free queue](https://github.com/hogliux/farbot).

## Requirements

- A C++17 compatible compiler
- The C++17 standard library
- moodycamel::ReaderWriterQueue (will be downloaded via cmake if not provided)
- farbot::fifo (will be downloaded via cmake if not provided)
- stb's vsnprintf (will be downloaded via cmake if not provided) OR libfmt if cmake is run with the `RTSAN_USE_FMTLIB` option

## Installation via CMake
Expand Down Expand Up @@ -130,7 +130,23 @@ Or alternatively spin up a `rtlog::LogProcessingThread`
## Customizing the queue type
If you don't want to use the SPSC moodycamel queue, you can provide your own queue type.
rtlog provides two queue type variants: `rtlog::SingleRealtimeWriterQueueType` (SPSC - default) and `rtlog::MultiRealtimeWriterQueueType` (MPSC). It is always assummed that you have one log printing thread. These may be used by specifying them:
```cpp
using SingleWriterRtLoggerType = rtlog::Logger<ExampleLogData, MAX_NUM_LOG_MESSAGES, MAX_LOG_MESSAGE_LENGTH, gSequenceNumber, SingleRealtimeWriterQueueType>;
SingleWriterRtLoggerType logger;
logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Audio}, "Hello, world! %i", 42);
...
using MultiWriterRtLoggerType = rtlog::Logger<ExampleLogData, MAX_NUM_LOG_MESSAGES, MAX_LOG_MESSAGE_LENGTH, gSequenceNumber, MultiRealtimeWriterQueueType>;
MultiWriterRtLoggerType logger;
logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Audio}, "Hello, world! %i", 42);
```

If you don't want to use either of these defaults, you may provide your own queue.

** IT IS UP TO YOU TO ENSURE THE QUEUE YOU PROVIDE IS LOCK-FREE AND REAL-TIME SAFE **

Expand Down
23 changes: 6 additions & 17 deletions examples/custom_queue_example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
if (NOT TARGET farbot)
find_package(ReaderWriterQueue QUIET)
if(NOT TARGET readerwriterqueue)
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
$<BUILD_INTERFACE:${farbot_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
FetchContent_Declare(ReaderWriterQueue
GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue
)
FetchContent_MakeAvailable(ReaderWriterQueue)
endif()

add_executable(custom_queue_example
Expand All @@ -25,5 +14,5 @@ add_executable(custom_queue_example
target_link_libraries(custom_queue_example
PRIVATE
rtlog::rtlog
farbot::farbot
readerwriterqueue
)
25 changes: 10 additions & 15 deletions examples/custom_queue_example/customqueuemain.cpp
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
#include <farbot/fifo.hpp>
#include <rtlog/rtlog.h>

template <typename T> class FarbotMPSCQueueWrapper {
farbot::fifo<T,
farbot::fifo_options::concurrency::single, // Consumer
farbot::fifo_options::concurrency::multiple, // Producer
farbot::fifo_options::full_empty_failure_mode::
return_false_on_full_or_empty, // consumer_failure_mode
farbot::fifo_options::full_empty_failure_mode::
overwrite_or_return_default> // producer_failure_mode
#include <readerwriterqueue.h>

mQueue;
template <typename T> class CustomQueue {

// technically we could use readerwriterqueue "unwrapped" but showing this off
// in the CustomQueue wrapper for documentation purposes
moodycamel::ReaderWriterQueue<T> mQueue;

public:
using value_type = T;

FarbotMPSCQueueWrapper(int capacity) : mQueue(capacity) {}
CustomQueue(int capacity) : mQueue(capacity) {}

bool try_enqueue(T &&item) { return mQueue.push(std::move(item)); }
bool try_dequeue(T &item) { return mQueue.pop(item); }
bool try_enqueue(T &&item) { return mQueue.try_enqueue(std::move(item)); }
bool try_dequeue(T &item) { return mQueue.try_dequeue(item); }
};

struct LogData {};

std::atomic<size_t> gSequenceNumber{0};

int main() {
rtlog::Logger<LogData, 128, 128, gSequenceNumber, FarbotMPSCQueueWrapper>
logger;
rtlog::Logger<LogData, 128, 128, gSequenceNumber, CustomQueue> logger;
logger.Log({}, "Hello, World!");

logger.PrintAndClearLogQueue(
Expand Down
47 changes: 39 additions & 8 deletions include/rtlog/rtlog.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include <fmt/format.h>
#endif // RTLOG_USE_FMTLIB

#include <readerwriterqueue.h>
#include <farbot/fifo.hpp>

#ifdef RTLOG_USE_STB
#ifndef STB_SPRINTF_IMPLEMENTATION
Expand Down Expand Up @@ -125,12 +125,40 @@ template <typename T>
inline constexpr bool has_int_constructor_v = has_int_constructor<T>::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 <typename T> using rtlog_SPSC = moodycamel::ReaderWriterQueue<T, 512>;
template <typename T, farbot::fifo_options::concurrency producer_concurrency,
farbot::fifo_options::full_empty_failure_mode producer_failure_mode>
class FarbotFifoType {
farbot::fifo<T,
farbot::fifo_options::concurrency::single, // Consumer
producer_concurrency, // Producer
farbot::fifo_options::full_empty_failure_mode::
return_false_on_full_or_empty, // consumer_failure_mode
producer_failure_mode> // producer_failure_mode

mQueue;

public:
using value_type = T;

FarbotFifoType(int capacity) : mQueue(capacity) {}

bool try_enqueue(T &&item) { return mQueue.push(std::move(item)); }
bool try_dequeue(T &item) { return mQueue.pop(item); }
};

template <typename T>
using SingleRealtimeWriterQueueType =
FarbotFifoType<T, farbot::fifo_options::concurrency::single,
farbot::fifo_options::full_empty_failure_mode::
return_false_on_full_or_empty>;

// NOTE: This version overwrites on full, which is a requirement to make writing
// real-time safe.
// This means it will never report Error_QueueFull.
template <typename T>
using MultiRealtimeWriterQueueType = FarbotFifoType<
T, farbot::fifo_options::concurrency::multiple,
farbot::fifo_options::full_empty_failure_mode::overwrite_or_return_default>;

/**
* @brief A logger class for logging messages.
Expand Down Expand Up @@ -161,12 +189,15 @@ template <typename T> using rtlog_SPSC = moodycamel::ReaderWriterQueue<T, 512>;
*/
template <typename LogData, size_t MaxNumMessages, size_t MaxMessageLength,
std::atomic<std::size_t> &SequenceNumber,
template <typename> class QType = rtlog_SPSC>
template <typename> class QType = SingleRealtimeWriterQueueType>
class Logger {
public:
using InternalLogData = detail::BasicLogData<LogData, MaxMessageLength>;
using InternalQType = QType<InternalLogData>;

static_assert(MaxNumMessages > 0);
static_assert((MaxNumMessages & (MaxNumMessages - 1)) == 0,
"MaxNumMessages must be a power of 2");
static_assert(
detail::has_int_constructor_v<InternalQType>,
"QType must have a constructor that takes an int - `QType(int)`");
Expand Down
Loading

0 comments on commit 36381f9

Please sign in to comment.