feat(util): SafeSignal class for cross-thread signals with arguments

Implement a wrapper over Glib::Dispatcher that passes the arguments to
the signal consumer via synchronized `std::queue`.
Arguments are always passed by value and the return type of the signal
is expected to be `void`.
This commit is contained in:
Aleksei Bavshin 2020-12-28 17:26:55 -08:00
parent 22ff26252b
commit 285a264aae
No known key found for this signature in database
GPG Key ID: 4F071603387A382A

View File

@ -0,0 +1,59 @@
#pragma once
#include <glibmm/dispatcher.h>
#include <sigc++/signal.h>
#include <functional>
#include <mutex>
#include <queue>
#include <tuple>
#include <type_traits>
namespace waybar {
/**
* Thread-safe signal wrapper.
* Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.
*/
template <typename... Args>
struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
public:
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
template <typename... EmitArgs>
void emit(EmitArgs&&... args) {
{
std::unique_lock lock(mutex_);
queue_.emplace(std::forward<EmitArgs>(args)...);
}
dp_.emit();
}
template <typename... EmitArgs>
inline void operator()(EmitArgs&&... args) {
emit(std::forward<EmitArgs>(args)...);
}
protected:
using signal_t = sigc::signal<void(std::decay_t<Args>...)>;
using arg_tuple_t = std::tuple<std::decay_t<Args>...>;
// ensure that unwrapped methods are not accessible
using signal_t::emit_reverse;
using signal_t::make_slot;
void handle_event() {
auto fn = signal_t::make_slot();
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
auto args = queue_.front();
queue_.pop();
lock.unlock();
std::apply(fn, args);
}
}
Glib::Dispatcher dp_;
std::mutex mutex_;
std::queue<arg_tuple_t> queue_;
};
} // namespace waybar