You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
6.7 KiB
189 lines
6.7 KiB
//! A value-wrapping condvar, controlled via mutex / rw-lock
|
|
#pragma once
|
|
|
|
#include <condition_variable>
|
|
|
|
#include "error.hh"
|
|
#include "mutex.hh"
|
|
|
|
namespace fx::condvar {
|
|
template< typename Mutex, typename T = void>
|
|
struct mutex_cond;
|
|
|
|
template<typename T>
|
|
struct mutex_cond<std::shared_mutex, T> {
|
|
using wrapper_type = shared::Mutex<T>;
|
|
using condvar_type = std::condition_variable_any;
|
|
using mutex_type = std::shared_mutex;
|
|
};
|
|
|
|
template<typename T>
|
|
struct mutex_cond<std::mutex, T> {
|
|
using wrapper_type = Mutex<T>;
|
|
using condvar_type = std::condition_variable;
|
|
using mutex_type = std::mutex;
|
|
};
|
|
|
|
template<typename T>
|
|
struct mutex_cond<Mutex<T>, T> : mutex_cond<std::mutex, T> {};
|
|
|
|
template<typename T>
|
|
struct mutex_cond<shared::Mutex<T>, T> : mutex_cond<std::shared_mutex, T> {};
|
|
|
|
/*template<typename M, typename T>
|
|
struct mutex_cond<mutex::Mutex<M, T>, T> {
|
|
using wrapper_type = mutex::Mutex<M, T>;
|
|
using mutex_type = typename wrapper_type::mutex_type;
|
|
};*/
|
|
|
|
template<typename M>
|
|
concept CondMutex = requires{ typename mutex_cond<M>::condvar_type; };
|
|
|
|
template<typename M, typename T>
|
|
struct CondVar : public mutex_cond<M, T>::wrapper_type {
|
|
using condvar_type = mutex_cond<M, T>::condvar_type;
|
|
|
|
using mutex_cond<M, T>::wrapper_type::wrapper_type;
|
|
using mutex_cond<M, T>::wrapper_type::with_lock;
|
|
|
|
inline void notify_one() { m_cond.notify_one(); }
|
|
inline void notify_all() { m_cond.notify_all(); }
|
|
|
|
template<typename F>
|
|
requires(std::is_invocable_v<F, T&>)
|
|
inline decltype(auto) wait_then(F&& func)
|
|
noexcept(std::is_nothrow_invocable_v<F, T&>)
|
|
{
|
|
std::unique_lock ulk{ this->m_mutex };
|
|
m_cond.wait(ulk);
|
|
// Unique lock makes this raw access safe
|
|
return std::forward<F>(func)(*this->get_raw_ptr());
|
|
}
|
|
|
|
template<typename F>
|
|
inline decltype(auto) wait_then(F&& func, auto const& predicate)
|
|
noexcept(std::is_nothrow_invocable_v<F, T&> and std::is_nothrow_invocable_v<decltype(predicate), T const&>)
|
|
requires(std::is_invocable_v<F, T&> and std::is_invocable_v<decltype(predicate), T const&>)
|
|
{
|
|
std::unique_lock ulk{ this->m_mutex };
|
|
m_cond.wait(ulk, [&, this] { return predicate(*this->get_ptr()); });
|
|
// Unique lock makes this raw access safe
|
|
return std::forward<F>(func)(*this->get_raw_ptr());
|
|
|
|
}
|
|
|
|
inline
|
|
condvar_type& get_condvar() noexcept { return m_cond; }
|
|
|
|
inline
|
|
condvar_type const& get_condvar() const noexcept { return m_cond; }
|
|
protected:
|
|
condvar_type m_cond{};
|
|
};
|
|
}
|
|
|
|
namespace fx {
|
|
template<typename T>
|
|
struct CondVar final : condvar::CondVar<std::mutex, T> {
|
|
using condvar::CondVar<std::mutex, T>::CondVar;
|
|
|
|
// using condvar::CondVar<std::mutex, T>::try_wait_then; // Only available for `shared::CondVar`
|
|
using condvar::CondVar<std::mutex, T>::wait_then;
|
|
};
|
|
|
|
namespace shared {
|
|
template<typename T>
|
|
struct CondVar final : condvar::CondVar<std::shared_mutex, T> {
|
|
using condvar::CondVar<std::shared_mutex, T>::CondVar;
|
|
|
|
using condvar::CondVar<std::shared_mutex, T>::wait_then;
|
|
|
|
template<typename F>
|
|
inline bool try_wait_then(F&& func, auto const& predicate, std::stop_token st = std::stop_token{})
|
|
noexcept(std::is_nothrow_invocable_v<F, T&> and std::is_nothrow_invocable_v<decltype(predicate), T const&>)
|
|
requires(std::is_invocable_v<F, T&> and std::is_invocable_v<decltype(predicate), T const&>)
|
|
{
|
|
std::unique_lock ulk{ this->m_mutex };
|
|
if(! this->m_cond.wait(ulk, st, [&, this] { return predicate(*this->get_ptr()); }))
|
|
// Abandon attempt at locking
|
|
return false;
|
|
// Unique lock makes raw access safe
|
|
else if constexpr(std::is_convertible_v<std::invoke_result_t<F, T&>, bool>)
|
|
return std::forward<F>(func)(*this->get_raw_ptr());
|
|
else std::forward<F>(func)(*this->get_raw_ptr());
|
|
return true;
|
|
}
|
|
template<typename F>
|
|
inline auto wait_then(F&& func, auto const& predicate, std::stop_token st)
|
|
noexcept(std::is_nothrow_invocable_v<F, T&> and std::is_nothrow_invocable_v<decltype(predicate), T const&>)
|
|
requires(std::is_invocable_v<F, T&> and std::is_invocable_v<decltype(predicate), T const&>)
|
|
{
|
|
std::unique_lock ulk{ this->m_mutex };
|
|
if(! this->m_cond.wait(ulk, st, [&, this] { return predicate(*this->get_ptr()); }))
|
|
error::throw_cancelled();
|
|
|
|
return std::forward<F>(func)(*this->get_raw_ptr());
|
|
}
|
|
|
|
|
|
|
|
//NOTE: _shared() cannot be `const` because `m_cond.wait()` is not `const`. It also takes a signal (`notify_one()`) permit regardless of how that wake is used, so.
|
|
template<typename F>
|
|
requires(std::is_invocable_v<F, const T&>)
|
|
inline decltype(auto) wait_then_shared(F&& func)
|
|
noexcept(std::is_nothrow_invocable_v<F, const T&>)
|
|
{
|
|
std::shared_lock ulk{ this->m_mutex };
|
|
this->m_cond.wait(ulk);
|
|
// Shared lock makes this only `const` access safe
|
|
return std::forward<F>(func)(*this->get_ptr());
|
|
}
|
|
|
|
template<typename F>
|
|
inline decltype(auto) wait_then_shared(F&& func, auto const& predicate)
|
|
noexcept(std::is_nothrow_invocable_v<F, const T&> and std::is_nothrow_invocable_v<decltype(predicate), T const&>)
|
|
requires(std::is_invocable_v<F, const T&> and std::is_invocable_v<decltype(predicate), T const&>)
|
|
{
|
|
std::shared_lock ulk{ this->m_mutex };
|
|
this->m_cond.wait(ulk, [&, this] { return predicate(*this->get_ptr()); });
|
|
// Shared lock makes this only `const` access safe
|
|
return std::forward<F>(func)(*this->get_ptr());
|
|
|
|
}
|
|
|
|
template<typename F>
|
|
inline bool try_wait_then_shared(F&& func, auto const& predicate, std::stop_token st = std::stop_token{})
|
|
noexcept(std::is_nothrow_invocable_v<F, const T&> and std::is_nothrow_invocable_v<decltype(predicate), T const&>)
|
|
requires(std::is_invocable_v<F, const T&> and std::is_invocable_v<decltype(predicate), T const&>)
|
|
{
|
|
std::shared_lock ulk{ this->m_mutex };
|
|
if(! this->m_cond.wait(ulk, st, [&, this] { return predicate(*this->get_ptr()); }))
|
|
// Abandon attempt at locking
|
|
return false;
|
|
// Shared lock makes only `const` access safe
|
|
else if constexpr(std::is_convertible_v<std::invoke_result_t<F, T const&>, bool>)
|
|
return std::forward<F>(func)(*this->get_ptr());
|
|
else std::forward<F>(func)(*this->get_ptr());
|
|
return true;
|
|
}
|
|
|
|
template<typename F>
|
|
inline decltype(auto) wait_then_shared(F&& func, auto const& predicate, std::stop_token st)
|
|
noexcept(std::is_nothrow_invocable_v<F, const T&> and std::is_nothrow_invocable_v<decltype(predicate), T const&>)
|
|
requires(std::is_invocable_v<F, const T&> and std::is_invocable_v<decltype(predicate), T const&>)
|
|
{
|
|
std::shared_lock ulk{ this->m_mutex };
|
|
if(! this->m_cond.wait(ulk, st, [&, this] { return predicate(*this->get_ptr()); }))
|
|
error::throw_cancelled();
|
|
|
|
return std::forward<F>(func)(*this->get_ptr());
|
|
}
|
|
|
|
};
|
|
}
|
|
template<typename T>
|
|
using SharedCondVar = shared::CondVar<T>;
|
|
}
|
|
|
|
|