#pragma once #include "util/MoveTracker.hpp" #include "util/Repeat.hpp" #include "util/async/Concepts.hpp" #include "util/async/Outcome.hpp" #include "util/async/context/impl/Cancellation.hpp" #include "util/async/context/impl/Timer.hpp" #include #include #include #include #include #include #include #include #include #include namespace util::async { namespace impl { template class BasicOperation { protected: std::future future_; public: using DataType = typename OutcomeType::DataType; explicit BasicOperation(OutcomeType* outcome) : future_{outcome->getStdFuture()} { } BasicOperation(BasicOperation&&) = default; BasicOperation(BasicOperation const&) = delete; [[nodiscard]] auto get() { return future_.get(); } void wait() { future_.wait(); } }; template struct BasicScheduledOperation : util::MoveTracker { class State { std::mutex m_; std::condition_variable ready_; std::optional op_{std::nullopt}; public: void emplace(auto&& op) { std::scoped_lock const lock{m_}; op_.emplace(std::forward(op)); ready_.notify_all(); } [[nodiscard]] OpType& get() { std::unique_lock lock{m_}; ready_.wait(lock, [this] { return op_.has_value(); }); return *op_; // NOLINT(bugprone-unchecked-optional-access) } }; std::shared_ptr state = std::make_shared(); typename CtxType::Timer timer; BasicScheduledOperation(auto& executor, auto delay, auto&& fn) : timer( executor, delay, [state = state, fn = std::forward(fn)](auto ec) mutable { state->emplace(fn(ec)); } ) { } ~BasicScheduledOperation() override { if (not wasMoved()) abort(); } BasicScheduledOperation(BasicScheduledOperation const&) = default; BasicScheduledOperation& operator=(BasicScheduledOperation const&) = default; BasicScheduledOperation(BasicScheduledOperation&&) = default; BasicScheduledOperation& operator=(BasicScheduledOperation&&) = default; [[nodiscard]] auto get() { return state->get().get(); } void wait() noexcept { state->get().wait(); } void cancel() noexcept { timer.cancel(); } void requestStop() noexcept requires(SomeStoppableOperation) { state->get().requestStop(); } void abort() noexcept { cancel(); if constexpr (SomeStoppableOperation) requestStop(); } }; } // namespace impl /** * @brief The `future` side of async operations that can be stopped * * @tparam RetType The return type of the operation * @tparam StopSourceType The type of the stop source */ template class StoppableOperation : public impl::BasicOperation>, public util::MoveTracker { using OutcomeType = StoppableOutcome; StopSourceType stopSource_; public: /** * @brief Construct a new Stoppable Operation object * * @param outcome The outcome to wrap */ explicit StoppableOperation(OutcomeType* outcome) : impl::BasicOperation(outcome), stopSource_(outcome->getStopSource()) { } ~StoppableOperation() override { if (not wasMoved()) requestStop(); } StoppableOperation(StoppableOperation const&) = delete; StoppableOperation& operator=(StoppableOperation const&) = delete; StoppableOperation(StoppableOperation&&) = default; StoppableOperation& operator=(StoppableOperation&&) = default; /** @brief Requests the operation to stop */ void requestStop() noexcept { stopSource_.requestStop(); } }; /** * @brief The `future` side of async operations that cannot be stopped * * @tparam RetType The return type of the operation */ template using Operation = impl::BasicOperation>; /** * @brief The `future` side of async operations that can be scheduled * * @tparam CtxType The type of the execution context * @tparam OpType The type of the wrapped operation */ template using ScheduledOperation = impl::BasicScheduledOperation; /** * @brief The `future` side of async operations that automatically repeat until aborted * * @note The current implementation requires the user provided function to return void and to take * no arguments. There is also no mechanism to request the repeating task to stop from inside of the * user provided block of code. * * @tparam CtxType The type of the execution context */ template class RepeatingOperation : public util::MoveTracker { util::Repeat repeat_; std::function action_; public: /** * @brief Construct a new Repeating Operation object * @note The first invocation of the user-provided function happens with no delay * * @param executor The executor to operate on * @param interval Time to wait before repeating the user-provided block of code * @param fn The function to execute repeatedly */ template RepeatingOperation(auto& executor, std::chrono::steady_clock::duration interval, FnType&& fn) : repeat_(executor) , action_([fn = std::forward(fn), &executor] { boost::asio::post(executor, fn); }) { repeat_.start(interval, action_); } ~RepeatingOperation() override { if (not wasMoved()) abort(); } RepeatingOperation(RepeatingOperation const&) = delete; RepeatingOperation& operator=(RepeatingOperation const&) = delete; RepeatingOperation(RepeatingOperation&&) = default; RepeatingOperation& operator=(RepeatingOperation&&) = default; /** * @brief Aborts the operation and the repeating timer * @note This call blocks until the underlying timer is cancelled * @warning Calling this from inside of the repeating operation yields a deadlock */ void abort() noexcept { repeat_.stop(); } /** * @brief Force-invoke the operation * @note The action is scheduled on the underlying context/strand * @warning The code of the user-provided action is expected to take care of thread-safety * unless this operation is scheduled through a strand */ void invoke() { action_(); } }; } // namespace util::async