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