//! A value-wrapping condvar, controlled via mutex / rw-lock #pragma once #include #include "error.hh" #include "mutex.hh" namespace fx::condvar { template< typename Mutex, typename T = void> struct mutex_cond; template struct mutex_cond { using wrapper_type = shared::Mutex; using condvar_type = std::condition_variable_any; using mutex_type = std::shared_mutex; }; template struct mutex_cond { using wrapper_type = Mutex; using condvar_type = std::condition_variable; using mutex_type = std::mutex; }; template struct mutex_cond, T> : mutex_cond {}; template struct mutex_cond, T> : mutex_cond {}; /*template struct mutex_cond, T> { using wrapper_type = mutex::Mutex; using mutex_type = typename wrapper_type::mutex_type; };*/ template concept CondMutex = requires{ typename mutex_cond::condvar_type; }; template struct CondVar : public mutex_cond::wrapper_type { using condvar_type = mutex_cond::condvar_type; using mutex_cond::wrapper_type::wrapper_type; using mutex_cond::wrapper_type::with_lock; inline void notify_one() { m_cond.notify_one(); } inline void notify_all() { m_cond.notify_all(); } template requires(std::is_invocable_v) inline decltype(auto) wait_then(F&& func) noexcept(std::is_nothrow_invocable_v) { std::unique_lock ulk{ this->m_mutex }; m_cond.wait(ulk); // Unique lock makes this raw access safe return std::forward(func)(*this->get_raw_ptr()); } template inline decltype(auto) wait_then(F&& func, auto const& predicate) noexcept(std::is_nothrow_invocable_v and std::is_nothrow_invocable_v) requires(std::is_invocable_v and std::is_invocable_v) { 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(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 struct CondVar final : condvar::CondVar { using condvar::CondVar::CondVar; // using condvar::CondVar::try_wait_then; // Only available for `shared::CondVar` using condvar::CondVar::wait_then; }; namespace shared { template struct CondVar final : condvar::CondVar { using condvar::CondVar::CondVar; using condvar::CondVar::wait_then; template inline bool try_wait_then(F&& func, auto const& predicate, std::stop_token st = std::stop_token{}) noexcept(std::is_nothrow_invocable_v and std::is_nothrow_invocable_v) requires(std::is_invocable_v and std::is_invocable_v) { 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, bool>) return std::forward(func)(*this->get_raw_ptr()); else std::forward(func)(*this->get_raw_ptr()); return true; } template inline auto wait_then(F&& func, auto const& predicate, std::stop_token st) noexcept(std::is_nothrow_invocable_v and std::is_nothrow_invocable_v) requires(std::is_invocable_v and std::is_invocable_v) { 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(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 requires(std::is_invocable_v) inline decltype(auto) wait_then_shared(F&& func) noexcept(std::is_nothrow_invocable_v) { std::shared_lock ulk{ this->m_mutex }; this->m_cond.wait(ulk); // Shared lock makes this only `const` access safe return std::forward(func)(*this->get_ptr()); } template inline decltype(auto) wait_then_shared(F&& func, auto const& predicate) noexcept(std::is_nothrow_invocable_v and std::is_nothrow_invocable_v) requires(std::is_invocable_v and std::is_invocable_v) { 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(func)(*this->get_ptr()); } template inline bool try_wait_then_shared(F&& func, auto const& predicate, std::stop_token st = std::stop_token{}) noexcept(std::is_nothrow_invocable_v and std::is_nothrow_invocable_v) requires(std::is_invocable_v and std::is_invocable_v) { 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, bool>) return std::forward(func)(*this->get_ptr()); else std::forward(func)(*this->get_ptr()); return true; } template inline decltype(auto) wait_then_shared(F&& func, auto const& predicate, std::stop_token st) noexcept(std::is_nothrow_invocable_v and std::is_nothrow_invocable_v) requires(std::is_invocable_v and std::is_invocable_v) { 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(func)(*this->get_ptr()); } }; } template using SharedCondVar = shared::CondVar; }