|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <glibmm/dispatcher.h> |
| 4 | +#include <sigc++/signal.h> |
| 5 | + |
| 6 | +#include <functional> |
| 7 | +#include <mutex> |
| 8 | +#include <queue> |
| 9 | +#include <thread> |
| 10 | +#include <tuple> |
| 11 | +#include <type_traits> |
| 12 | +#include <utility> |
| 13 | + |
| 14 | +namespace waybar { |
| 15 | + |
| 16 | +/** |
| 17 | + * Thread-safe signal wrapper. |
| 18 | + * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments. |
| 19 | + */ |
| 20 | +template <typename... Args> |
| 21 | +struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> { |
| 22 | + public: |
| 23 | + SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } |
| 24 | + |
| 25 | + template <typename... EmitArgs> |
| 26 | + void emit(EmitArgs&&... args) { |
| 27 | + if (main_tid_ == std::this_thread::get_id()) { |
| 28 | + /* |
| 29 | + * Bypass the queue if the method is called the main thread. |
| 30 | + * Ensures that events emitted from the main thread are processed synchronously and saves a |
| 31 | + * few CPU cycles on locking/queuing. |
| 32 | + * As a downside, this makes main thread events prioritized over the other threads and |
| 33 | + * disrupts chronological order. |
| 34 | + */ |
| 35 | + signal_t::emit(std::forward<EmitArgs>(args)...); |
| 36 | + } else { |
| 37 | + { |
| 38 | + std::unique_lock lock(mutex_); |
| 39 | + queue_.emplace(std::forward<EmitArgs>(args)...); |
| 40 | + } |
| 41 | + dp_.emit(); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + template <typename... EmitArgs> |
| 46 | + inline void operator()(EmitArgs&&... args) { |
| 47 | + emit(std::forward<EmitArgs>(args)...); |
| 48 | + } |
| 49 | + |
| 50 | + protected: |
| 51 | + using signal_t = sigc::signal<void(std::decay_t<Args>...)>; |
| 52 | + using slot_t = decltype(std::declval<signal_t>().make_slot()); |
| 53 | + using arg_tuple_t = std::tuple<std::decay_t<Args>...>; |
| 54 | + // ensure that unwrapped methods are not accessible |
| 55 | + using signal_t::emit_reverse; |
| 56 | + using signal_t::make_slot; |
| 57 | + |
| 58 | + void handle_event() { |
| 59 | + for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { |
| 60 | + auto args = queue_.front(); |
| 61 | + queue_.pop(); |
| 62 | + lock.unlock(); |
| 63 | + std::apply(cached_fn_, args); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + Glib::Dispatcher dp_; |
| 68 | + std::mutex mutex_; |
| 69 | + std::queue<arg_tuple_t> queue_; |
| 70 | + const std::thread::id main_tid_ = std::this_thread::get_id(); |
| 71 | + // cache functor for signal emission to avoid recreating it on each event |
| 72 | + const slot_t cached_fn_ = make_slot(); |
| 73 | +}; |
| 74 | + |
| 75 | +} // namespace waybar |
0 commit comments