TLA Line data Source 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 : {
44 HIT 1 : asio_adapter_context_service(boost::capy::execution_context &) {}
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_>
121 14 : asio_executor_adapter(
122 : const asio_executor_adapter<Executor, Allocator, Bits_> & rhs)
123 : noexcept(std::is_nothrow_copy_constructible_v<Executor>)
124 14 : : executor_(rhs.executor_), allocator_(rhs.allocator_)
125 : {
126 : if constexpr((Bits & work_mask) == work_tracked)
127 8 : executor_.on_work_started();
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_>
139 27 : asio_executor_adapter(
140 : asio_executor_adapter<Executor, Allocator, Bits_> && rhs)
141 : noexcept(std::is_nothrow_move_constructible_v<Executor>)
142 27 : : executor_(std::move(rhs.executor_))
143 27 : , allocator_(std::move(rhs.allocator_))
144 : {
145 : if constexpr((Bits & work_mask) == work_tracked)
146 12 : executor_.on_work_started();
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>
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>)
175 10 : : executor_(std::move(executor.executor_)), allocator_(alloc)
176 : {
177 : if constexpr((Bits & work_mask) == work_tracked)
178 3 : executor_.on_work_started();
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 : */
188 12 : asio_executor_adapter(Executor executor)
189 : noexcept(std::is_nothrow_move_constructible_v<Executor>)
190 12 : : executor_(std::move(executor))
191 12 : , allocator_(executor_.context().get_frame_allocator())
192 : {
193 : if constexpr((Bits & work_mask) == work_tracked)
194 : executor_.on_work_started();
195 12 : }
196 :
197 : /** @brief Destructor.
198 : *
199 : * If work tracking is enabled, calls `on_work_finished()`.
200 : */
201 82 : ~asio_executor_adapter()
202 : {
203 : if constexpr((Bits & work_mask) == work_tracked)
204 26 : executor_.on_work_finished();
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 : */
245 MIS 0 : bool operator==(const asio_executor_adapter & rhs) const noexcept
246 : {
247 0 : return executor_ == rhs.executor_
248 0 : && 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>
277 HIT 11 : void execute(Function&& f) const
278 : {
279 : if constexpr ((Bits & blocking_mask) == blocking_never)
280 6 : executor_.post(
281 6 : detail::make_continuation(std::forward<Function>(f), allocator_));
282 : else if constexpr((Bits & blocking_mask) == blocking_possibly)
283 5 : executor_.dispatch(
284 5 : detail::make_continuation(std::forward<Function>(f), allocator_)
285 5 : ).resume();
286 : else if constexpr((Bits & blocking_mask) == blocking_always)
287 : std::forward<Function>(f)();
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 : */
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 :
|