Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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_ANY_EXECUTOR_HPP
11 : #define BOOST_CAPY_ANY_EXECUTOR_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/coro.hpp>
15 :
16 : #include <concepts>
17 : #include <coroutine>
18 : #include <memory>
19 : #include <type_traits>
20 : #include <typeinfo>
21 :
22 : namespace boost {
23 : namespace capy {
24 :
25 : class execution_context;
26 : template<typename> class strand;
27 :
28 : namespace detail {
29 :
30 : template<typename T>
31 : struct is_strand_type : std::false_type {};
32 :
33 : template<typename E>
34 : struct is_strand_type<strand<E>> : std::true_type {};
35 :
36 : } // detail
37 :
38 : /** A type-erased wrapper for executor objects.
39 :
40 : This class provides type erasure for any executor type, enabling
41 : runtime polymorphism with automatic memory management via shared
42 : ownership. It stores a shared pointer to a polymorphic wrapper,
43 : allowing executors of different types to be stored uniformly
44 : while satisfying the full `Executor` concept.
45 :
46 : @par Value Semantics
47 :
48 : This class has value semantics with shared ownership. Copy and
49 : move operations are cheap, simply copying the internal shared
50 : pointer. Multiple `any_executor` instances may share the same
51 : underlying executor. Move operations do not invalidate the
52 : source; there is no moved-from state.
53 :
54 : @par Default State
55 :
56 : A default-constructed `any_executor` holds no executor. Calling
57 : executor operations on a default-constructed instance results
58 : in undefined behavior. Use `operator bool()` to check validity.
59 :
60 : @par Thread Safety
61 :
62 : The `any_executor` itself is thread-safe for concurrent reads.
63 : Concurrent modification requires external synchronization.
64 : Executor operations are safe to call concurrently if the
65 : underlying executor supports it.
66 :
67 : @par Executor Concept
68 :
69 : This class satisfies the `Executor` concept, making it usable
70 : anywhere a concrete executor is expected.
71 :
72 : @par Example
73 : @code
74 : any_executor exec = ctx.get_executor();
75 : if(exec)
76 : {
77 : auto& context = exec.context();
78 : exec.post(my_coroutine);
79 : }
80 : @endcode
81 :
82 : @see executor_ref, Executor
83 : */
84 : class any_executor
85 : {
86 : struct impl_base;
87 :
88 : std::shared_ptr<impl_base> p_;
89 :
90 : struct impl_base
91 : {
92 16 : virtual ~impl_base() = default;
93 : virtual execution_context& context() const noexcept = 0;
94 : virtual void on_work_started() const noexcept = 0;
95 : virtual void on_work_finished() const noexcept = 0;
96 : virtual std::coroutine_handle<> dispatch(std::coroutine_handle<>) const = 0;
97 : virtual void post(std::coroutine_handle<>) const = 0;
98 : virtual bool equals(impl_base const*) const noexcept = 0;
99 : virtual std::type_info const& target_type() const noexcept = 0;
100 : };
101 :
102 : template<class Ex>
103 : struct impl final : impl_base
104 : {
105 : Ex ex_;
106 :
107 : template<class Ex1>
108 16 : explicit impl(Ex1&& ex)
109 16 : : ex_(std::forward<Ex1>(ex))
110 : {
111 16 : }
112 :
113 5 : execution_context& context() const noexcept override
114 : {
115 5 : return const_cast<Ex&>(ex_).context();
116 : }
117 :
118 0 : void on_work_started() const noexcept override
119 : {
120 0 : ex_.on_work_started();
121 0 : }
122 :
123 0 : void on_work_finished() const noexcept override
124 : {
125 0 : ex_.on_work_finished();
126 0 : }
127 :
128 1 : std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const override
129 : {
130 1 : return ex_.dispatch(h);
131 : }
132 :
133 15 : void post(std::coroutine_handle<> h) const override
134 : {
135 15 : ex_.post(h);
136 15 : }
137 :
138 8 : bool equals(impl_base const* other) const noexcept override
139 : {
140 8 : if(target_type() != other->target_type())
141 0 : return false;
142 8 : return ex_ == static_cast<impl const*>(other)->ex_;
143 : }
144 :
145 17 : std::type_info const& target_type() const noexcept override
146 : {
147 17 : return typeid(Ex);
148 : }
149 : };
150 :
151 : public:
152 : /** Default constructor.
153 :
154 : Constructs an empty `any_executor`. Calling any executor
155 : operations on a default-constructed instance results in
156 : undefined behavior.
157 :
158 : @par Postconditions
159 : @li `!*this`
160 : */
161 : any_executor() = default;
162 :
163 : /** Copy constructor.
164 :
165 : Creates a new `any_executor` sharing ownership of the
166 : underlying executor with `other`.
167 :
168 : @par Postconditions
169 : @li `*this == other`
170 : */
171 9 : any_executor(any_executor const&) = default;
172 :
173 : /** Copy assignment operator.
174 :
175 : Shares ownership of the underlying executor with `other`.
176 :
177 : @par Postconditions
178 : @li `*this == other`
179 : */
180 2 : any_executor& operator=(any_executor const&) = default;
181 :
182 : /** Constructs from any executor type.
183 :
184 : Allocates storage for a copy of the given executor and
185 : stores it internally. The executor must satisfy the
186 : `Executor` concept.
187 :
188 : @param ex The executor to wrap. A copy is stored internally.
189 :
190 : @par Postconditions
191 : @li `*this` is valid
192 : */
193 : template<class Ex>
194 : requires (
195 : !std::same_as<std::decay_t<Ex>, any_executor> &&
196 : !detail::is_strand_type<std::decay_t<Ex>>::value &&
197 : std::copy_constructible<std::decay_t<Ex>>)
198 16 : any_executor(Ex&& ex)
199 16 : : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
200 : {
201 16 : }
202 :
203 : /** Returns true if this instance holds a valid executor.
204 :
205 : @return `true` if constructed with an executor, `false` if
206 : default-constructed.
207 : */
208 6 : explicit operator bool() const noexcept
209 : {
210 6 : return p_ != nullptr;
211 : }
212 :
213 : /** Returns a reference to the associated execution context.
214 :
215 : @return A reference to the execution context.
216 :
217 : @pre This instance holds a valid executor.
218 : */
219 5 : execution_context& context() const noexcept
220 : {
221 5 : return p_->context();
222 : }
223 :
224 : /** Informs the executor that work is beginning.
225 :
226 : Must be paired with a subsequent call to `on_work_finished()`.
227 :
228 : @pre This instance holds a valid executor.
229 : */
230 0 : void on_work_started() const noexcept
231 : {
232 0 : p_->on_work_started();
233 0 : }
234 :
235 : /** Informs the executor that work has completed.
236 :
237 : @pre A preceding call to `on_work_started()` was made.
238 : @pre This instance holds a valid executor.
239 : */
240 0 : void on_work_finished() const noexcept
241 : {
242 0 : p_->on_work_finished();
243 0 : }
244 :
245 : /** Dispatches a coroutine handle through the wrapped executor.
246 :
247 : Invokes the executor's `dispatch()` operation with the given
248 : coroutine handle, returning a handle suitable for symmetric
249 : transfer.
250 :
251 : @param h The coroutine handle to dispatch for resumption.
252 :
253 : @return A coroutine handle that the caller may use for symmetric
254 : transfer, or `std::noop_coroutine()` if the executor
255 : posted the work for later execution.
256 :
257 : @pre This instance holds a valid executor.
258 : */
259 1 : coro dispatch(coro h) const
260 : {
261 1 : return p_->dispatch(h);
262 : }
263 :
264 : /** Posts a coroutine handle to the wrapped executor.
265 :
266 : Posts the coroutine handle to the executor for later execution
267 : and returns. The caller should transfer to `std::noop_coroutine()`
268 : after calling this.
269 :
270 : @param h The coroutine handle to post for resumption.
271 :
272 : @pre This instance holds a valid executor.
273 : */
274 15 : void post(coro h) const
275 : {
276 15 : p_->post(h);
277 15 : }
278 :
279 : /** Compares two executor wrappers for equality.
280 :
281 : Two `any_executor` instances are equal if they both hold
282 : executors of the same type that compare equal, or if both
283 : are empty.
284 :
285 : @param other The executor to compare against.
286 :
287 : @return `true` if both wrap equal executors of the same type,
288 : or both are empty.
289 : */
290 10 : bool operator==(any_executor const& other) const noexcept
291 : {
292 10 : if(!p_ && !other.p_)
293 1 : return true;
294 9 : if(!p_ || !other.p_)
295 1 : return false;
296 8 : return p_->equals(other.p_.get());
297 : }
298 :
299 : /** Returns the type_info of the wrapped executor.
300 :
301 : @return The `std::type_info` of the stored executor type,
302 : or `typeid(void)` if empty.
303 : */
304 2 : std::type_info const& target_type() const noexcept
305 : {
306 2 : if(!p_)
307 1 : return typeid(void);
308 1 : return p_->target_type();
309 : }
310 : };
311 :
312 : } // capy
313 : } // boost
314 :
315 : #endif
|