90.91% Lines (30/33) 90.00% Functions (9/10)
TLA Baseline Branch
Line Hits Code Line Hits Code
  1 + //
  2 + // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
  3 + //
  4 + // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5 + // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6 + //
  7 + // Official repository: https://github.com/cppalliance/capy
  8 + //
  9 +
  10 + #ifndef BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP
  11 + #define BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP
  12 +
  13 + #include <boost/capy/asio/detail/continuation.hpp>
  14 + #include <boost/capy/ex/any_executor.hpp>
  15 + #include <boost/capy/ex/execution_context.hpp>
  16 + #include <memory_resource>
  17 +
  18 +
  19 + namespace boost {
  20 + namespace capy {
  21 +
  22 + /** @addtogroup asio
  23 + * @{
  24 + */
  25 +
  26 + namespace detail
  27 + {
  28 +
  29 + /** @brief Service that bridges capy's execution_context with Asio's.
  30 + * @internal
  31 + *
  32 + * This service inherits from both capy's service and the target Asio
  33 + * execution context, allowing capy executors wrapped in `asio_executor_adapter`
  34 + * to be queried for their Asio execution context.
  35 + *
  36 + * @tparam ExecutionContext The Asio execution_context type (boost::asio or standalone)
  37 + */
  38 + template<typename ExecutionContext>
  39 + struct asio_adapter_context_service
  40 + : execution_context::service,
  41 + // shutdown is protected
  42 + ExecutionContext
  43 + {
HITGNC   44 + 1 asio_adapter_context_service(boost::capy::execution_context &) {}
HITGNC   45 + 1 void shutdown() override {ExecutionContext::shutdown();}
  46 + };
  47 +
  48 + }
  49 +
  50 +
  51 + /** @brief Adapts a capy executor to be usable with Asio.
  52 + *
  53 + * `asio_executor_adapter` wraps a capy executor and exposes it as an
  54 + * Asio-compatible executor. This allows capy coroutines and executors to
  55 + * interoperate seamlessly with Asio's async operations.
  56 + *
  57 + * The adapter tracks execution properties (blocking behavior and work tracking)
  58 + * as compile-time template parameters for zero-overhead property queries.
  59 + *
  60 + * @tparam Executor The underlying capy executor type (default: `capy::any_executor`)
  61 + * @tparam Allocator The allocator type for handler allocation
  62 + * (default: `std::pmr::polymorphic_allocator<void>`)
  63 + * @tparam Bits Compile-time bitfield encoding blocking and work-tracking properties
  64 + *
  65 + * @par Execution Properties
  66 + * The adapter supports the standard Asio execution properties:
  67 + * - `blocking`: `possibly` (default), `never`, or `always`
  68 + * - `outstanding_work`: `untracked` (default) or `tracked`
  69 + * - `allocator`: Custom allocator for handler allocation
  70 + * - `context`: Returns the associated capy execution_context
  71 + *
  72 + * @par Example
  73 + * @code
  74 + * // Wrap a capy executor for use with Asio
  75 + * capy::any_executor capy_exec = ...;
  76 + * asio_executor_adapter<> asio_exec(capy_exec);
  77 + *
  78 + * // Use with Asio operations
  79 + * asio::post(asio_exec, []{ std::cout << "Hello from capy!\n"; });
  80 + *
  81 + * // Require non-blocking execution
  82 + * auto never_blocking = asio::require(asio_exec,
  83 + * asio::execution::blocking.never);
  84 + * @endcode
  85 + *
  86 + * @see wrap_asio_executor For the reverse direction (Asio to capy)
  87 + */
  88 + template<typename Executor = capy::any_executor,
  89 + typename Allocator = std::pmr::polymorphic_allocator<void>,
  90 + int Bits = 0>
  91 + struct asio_executor_adapter
  92 + {
  93 + /// @name Blocking Property Constants
  94 + /// @{
  95 + constexpr static int blocking_possibly = 0b000; ///< May block the caller
  96 + constexpr static int blocking_never = 0b001; ///< Never blocks the caller
  97 + constexpr static int blocking_always = 0b010; ///< Always blocks until complete
  98 + constexpr static int blocking_mask = 0b011; ///< Mask for blocking bits
  99 + /// @}
  100 +
  101 + /// @name Work Tracking Property Constants
  102 + /// @{
  103 + constexpr static int work_untracked = 0b000; ///< Work is not tracked
  104 + constexpr static int work_tracked = 0b100; ///< Outstanding work is tracked
  105 + constexpr static int work_mask = 0b100; ///< Mask for work tracking bit
  106 + /// @}
  107 +
  108 +
  109 + /// @name Constructors
  110 + /// @{
  111 +
  112 + /** @brief Copy constructor from adapter with different property bits.
  113 + *
  114 + * Creates a copy with potentially different execution properties.
  115 + * If this adapter tracks work, `on_work_started()` is called.
  116 + *
  117 + * @tparam Bits_ The source adapter's property bits
  118 + * @param rhs The source adapter to copy from
  119 + */
  120 + template<int Bits_>
HITGNC   121 + 14 asio_executor_adapter(
  122 + const asio_executor_adapter<Executor, Allocator, Bits_> & rhs)
  123 + noexcept(std::is_nothrow_copy_constructible_v<Executor>)
HITGNC   124 + 14 : executor_(rhs.executor_), allocator_(rhs.allocator_)
  125 + {
  126 + if constexpr((Bits & work_mask) == work_tracked)
HITGNC   127 + 8 executor_.on_work_started();
HITGNC   128 + 14 }
  129 +
  130 + /** @brief Move constructor from adapter with different property bits.
  131 + *
  132 + * Moves from another adapter with potentially different properties.
  133 + * If this adapter tracks work, `on_work_started()` is called.
  134 + *
  135 + * @tparam Bits_ The source adapter's property bits
  136 + * @param rhs The source adapter to move from
  137 + */
  138 + template<int Bits_>
HITGNC   139 + 27 asio_executor_adapter(
  140 + asio_executor_adapter<Executor, Allocator, Bits_> && rhs)
  141 + noexcept(std::is_nothrow_move_constructible_v<Executor>)
HITGNC   142 + 27 : executor_(std::move(rhs.executor_))
HITGNC   143 + 27 , allocator_(std::move(rhs.allocator_))
  144 + {
  145 + if constexpr((Bits & work_mask) == work_tracked)
HITGNC   146 + 12 executor_.on_work_started();
HITGNC   147 + 27 }
  148 +
  149 + /** @brief Constructs from executor and allocator.
  150 + *
  151 + * @param executor The capy executor to wrap
  152 + * @param alloc The allocator for handler allocation
  153 + */
  154 + asio_executor_adapter(Executor executor, const Allocator & alloc)
  155 + noexcept(std::is_nothrow_move_constructible_v<Executor>
  156 + && std::is_nothrow_copy_constructible_v<Allocator>)
  157 + : executor_(std::move(executor)), allocator_(alloc)
  158 + {
  159 + if constexpr((Bits & work_mask) == work_tracked)
  160 + executor_.on_work_started();
  161 + }
  162 +
  163 + /** @brief Constructs from adapter with different allocator.
  164 + *
  165 + * @tparam OtherAllocator The source adapter's allocator type
  166 + * @param executor The source adapter
  167 + * @param alloc The new allocator to use
  168 + */
  169 + template<typename OtherAllocator>
HITGNC   170 + 10 explicit asio_executor_adapter(
  171 + asio_executor_adapter<Executor, OtherAllocator, Bits> executor,
  172 + const Allocator & alloc)
  173 + noexcept(std::is_nothrow_move_constructible_v<Executor> &&
  174 + std::is_nothrow_copy_constructible_v<Allocator>)
HITGNC   175 + 10 : executor_(std::move(executor.executor_)), allocator_(alloc)
  176 + {
  177 + if constexpr((Bits & work_mask) == work_tracked)
HITGNC   178 + 3 executor_.on_work_started();
HITGNC   179 + 10 }
  180 +
  181 +
  182 + /** @brief Constructs from a capy executor.
  183 + *
  184 + * The allocator is obtained from the executor's context frame allocator.
  185 + *
  186 + * @param executor The capy executor to wrap
  187 + */
HITGNC   188 + 12 asio_executor_adapter(Executor executor)
  189 + noexcept(std::is_nothrow_move_constructible_v<Executor>)
HITGNC   190 + 12 : executor_(std::move(executor))
HITGNC   191 + 12 , allocator_(executor_.context().get_frame_allocator())
  192 + {
  193 + if constexpr((Bits & work_mask) == work_tracked)
  194 + executor_.on_work_started();
HITGNC   195 + 12 }
  196 +
  197 + /** @brief Destructor.
  198 + *
  199 + * If work tracking is enabled, calls `on_work_finished()`.
  200 + */
HITGNC   201 + 82 ~asio_executor_adapter()
  202 + {
  203 + if constexpr((Bits & work_mask) == work_tracked)
HITGNC   204 + 26 executor_.on_work_finished();
HITGNC   205 + 82 }
  206 +
  207 + /// @}
  208 +
  209 + /// @name Assignment
  210 + /// @{
  211 +
  212 + /** @brief Copy assignment from adapter with different property bits.
  213 + *
  214 + * Properly handles work tracking when changing executors.
  215 + *
  216 + * @tparam Bits_ The source adapter's property bits
  217 + * @param rhs The source adapter
  218 + * @return Reference to this adapter
  219 + */
  220 + template<int Bits_>
  221 + asio_executor_adapter & operator=(
  222 + const asio_executor_adapter<Executor, Allocator, Bits_> & rhs)
  223 + {
  224 +
  225 + if constexpr((Bits & work_mask) == work_tracked)
  226 + if (rhs.executor_ != executor_)
  227 + {
  228 + rhs.executor_.on_work_started();
  229 + executor_.on_work_finished();
  230 + }
  231 +
  232 + executor_ = rhs.executor_;
  233 + allocator_ = rhs.allocator_;
  234 + }
  235 +
  236 + /// @}
  237 +
  238 + /// @name Comparison
  239 + /// @{
  240 +
  241 + /** @brief Equality comparison.
  242 + * @param rhs The adapter to compare with
  243 + * @return `true` if both executor and allocator are equal
  244 + */
MISUNC   245 + bool operator==(const asio_executor_adapter & rhs) const noexcept
  246 + {
MISUNC   247 + return executor_ == rhs.executor_
MISUNC   248 + && allocator_ == rhs.allocator_;
  249 + }
  250 +
  251 + /** @brief Inequality comparison.
  252 + * @param rhs The adapter to compare with
  253 + * @return `true` if executor or allocator differs
  254 + */
  255 + bool operator!=(const asio_executor_adapter & rhs) const noexcept
  256 + {
  257 + return executor_ != rhs.executor_
  258 + && allocator_ != rhs.allocator_;
  259 + }
  260 +
  261 + /// @}
  262 +
  263 + /// @name Execution
  264 + /// @{
  265 +
  266 + /** @brief Executes a function according to the blocking property.
  267 + *
  268 + * The execution behavior depends on the `Bits` template parameter:
  269 + * - `blocking_never`: Posts the function for deferred execution
  270 + * - `blocking_possibly`: Dispatches (may run inline or post)
  271 + * - `blocking_always`: Executes the function inline immediately
  272 + *
  273 + * @tparam Function The callable type
  274 + * @param f The function to execute
  275 + */
  276 + template <typename Function>
HITGNC   277 + 11 void execute(Function&& f) const
  278 + {
  279 + if constexpr ((Bits & blocking_mask) == blocking_never)
HITGNC   280 + 6 executor_.post(
HITGNC   281 + 6 detail::make_continuation(std::forward<Function>(f), allocator_));
  282 + else if constexpr((Bits & blocking_mask) == blocking_possibly)
HITGNC   283 + 5 executor_.dispatch(
HITGNC   284 + 5 detail::make_continuation(std::forward<Function>(f), allocator_)
HITGNC   285 + 5 ).resume();
  286 + else if constexpr((Bits & blocking_mask) == blocking_always)
  287 + std::forward<Function>(f)();
HITGNC   288 + 11 }
  289 +
  290 + /// @}
  291 +
  292 + /// @name Accessors
  293 + /// @{
  294 +
  295 + /** @brief Returns the associated execution context.
  296 + * @return Reference to the capy execution_context
  297 + */
HITGNC   298 + 1 execution_context & context() const {return executor_.context(); }
  299 +
  300 + /** @brief Returns the associated allocator.
  301 + * @return Copy of the allocator
  302 + */
  303 + Allocator get_allocator() const noexcept {return allocator_;}
  304 +
  305 + /** @brief Returns the underlying capy executor.
  306 + * @return Copy of the wrapped executor
  307 + */
  308 + const Executor get_capy_executor() const {return executor_;}
  309 +
  310 + /// @}
  311 + private:
  312 +
  313 + template<typename, typename, int>
  314 + friend struct asio_executor_adapter;
  315 + Executor executor_;
  316 + Allocator allocator_;
  317 +
  318 + };
  319 +
  320 + /** @} */ // end of asio group
  321 +
  322 + }
  323 + }
  324 +
  325 +
  326 + #endif //BOOST_CAPY_ASIO_EXECUTOR_ADAPTER_HPP
  327 +