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