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