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

//! 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>;
}