TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/capy
9 : //
10 :
11 : #ifndef BOOST_CAPY_ANY_EXECUTOR_HPP
12 : #define BOOST_CAPY_ANY_EXECUTOR_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/continuation.hpp>
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 : /** Forwards `dispatch`/`post`/`context` calls through a shared, type-erased executor pointer.
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, 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.
57 : `operator bool()`, `operator==`, and `target_type()` report the
58 : empty state. `context()`, `on_work_started()`, `on_work_finished()`,
59 : `dispatch()`, and `post()` are undefined behavior until an
60 : executor is assigned.
61 :
62 : @par Thread Safety
63 :
64 : The `any_executor` itself is thread-safe for concurrent reads.
65 : Concurrent modification requires external synchronization.
66 : Executor operations are safe to call concurrently if the
67 : underlying executor supports it.
68 :
69 : @par Executor Concept
70 :
71 : This class satisfies the `Executor` concept, making it usable
72 : anywhere a concrete executor is expected.
73 :
74 : @par Example
75 : @par !example example
76 :
77 :
78 : @see executor_ref, Executor
79 : */
80 : class any_executor
81 : {
82 : struct impl_base;
83 :
84 : std::shared_ptr<impl_base> p_;
85 :
86 : struct impl_base
87 : {
88 HIT 20 : virtual ~impl_base() = default;
89 : virtual execution_context& context() const noexcept = 0;
90 : virtual void on_work_started() const noexcept = 0;
91 : virtual void on_work_finished() const noexcept = 0;
92 : virtual std::coroutine_handle<> dispatch(continuation&) const = 0;
93 : virtual void post(continuation&) const = 0;
94 : virtual bool equals(impl_base const*) const noexcept = 0;
95 : virtual std::type_info const& target_type() const noexcept = 0;
96 : };
97 :
98 : template<class Ex>
99 : struct impl final : impl_base
100 : {
101 : Ex ex_;
102 :
103 : template<class Ex1>
104 20 : explicit impl(Ex1&& ex)
105 20 : : ex_(std::forward<Ex1>(ex))
106 : {
107 20 : }
108 :
109 6 : execution_context& context() const noexcept override
110 : {
111 6 : return const_cast<Ex&>(ex_).context();
112 : }
113 :
114 5 : void on_work_started() const noexcept override
115 : {
116 5 : ex_.on_work_started();
117 5 : }
118 :
119 5 : void on_work_finished() const noexcept override
120 : {
121 5 : ex_.on_work_finished();
122 5 : }
123 :
124 5 : std::coroutine_handle<> dispatch(continuation& c) const override
125 : {
126 5 : return ex_.dispatch(c);
127 : }
128 :
129 15 : void post(continuation& c) const override
130 : {
131 15 : ex_.post(c);
132 15 : }
133 :
134 9 : bool equals(impl_base const* other) const noexcept override
135 : {
136 9 : if(target_type() != other->target_type())
137 1 : return false;
138 8 : return ex_ == static_cast<impl const*>(other)->ex_;
139 : }
140 :
141 19 : std::type_info const& target_type() const noexcept override
142 : {
143 19 : return typeid(Ex);
144 : }
145 : };
146 :
147 : public:
148 : /** Construct a default instance.
149 :
150 : Constructs an empty `any_executor`. `operator bool()` reports
151 : the empty state; `context()`, `on_work_started()`,
152 : `on_work_finished()`, `dispatch()`, and `post()` are undefined
153 : behavior until an executor is assigned.
154 :
155 : @par Postconditions
156 : @li `!*this`
157 : */
158 2 : any_executor() = default;
159 :
160 : /** Construct a copy.
161 :
162 : Creates a new `any_executor` sharing ownership of the
163 : underlying executor with `other`.
164 :
165 : @param other The executor to copy.
166 :
167 : @par Postconditions
168 : @li `*this == other`
169 : */
170 33 : any_executor(any_executor const& other) = default;
171 :
172 : /** Copy assignment operator.
173 :
174 : Shares ownership of the underlying executor with `other`.
175 :
176 : @param other The executor to copy.
177 :
178 : @return A reference to `*this`.
179 :
180 : @par Postconditions
181 : @li `*this == other`
182 : */
183 6 : any_executor& operator=(any_executor const& other) = default;
184 :
185 : /** Constructs from any executor type.
186 :
187 : Allocates storage for a copy of the given executor and
188 : stores it internally. The executor must satisfy the
189 : `Executor` concept.
190 :
191 : @param ex The executor to wrap. A copy is stored internally.
192 :
193 : @par Postconditions
194 : @li `*this` is valid
195 : */
196 : template<class Ex>
197 : requires (
198 : !std::same_as<std::decay_t<Ex>, any_executor> &&
199 : !detail::is_strand_type<std::decay_t<Ex>>::value &&
200 : std::copy_constructible<std::decay_t<Ex>>)
201 20 : any_executor(Ex&& ex)
202 20 : : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
203 : {
204 20 : }
205 :
206 : /** Returns true if this instance holds a valid executor.
207 :
208 : @return `true` if constructed with an executor, `false` if
209 : default-constructed.
210 : */
211 6 : explicit operator bool() const noexcept
212 : {
213 6 : return p_ != nullptr;
214 : }
215 :
216 : /** Returns a reference to the associated execution context.
217 :
218 : @return A reference to the execution context.
219 :
220 : @pre This instance holds a valid executor.
221 : */
222 6 : execution_context& context() const noexcept
223 : {
224 6 : return p_->context();
225 : }
226 :
227 : /** Informs the executor that work is beginning.
228 :
229 : Must be paired with a subsequent call to `on_work_finished()`.
230 :
231 : @pre This instance holds a valid executor.
232 : */
233 5 : void on_work_started() const noexcept
234 : {
235 5 : p_->on_work_started();
236 5 : }
237 :
238 : /** Informs the executor that work has completed.
239 :
240 : @pre A preceding call to `on_work_started()` was made.
241 : @pre This instance holds a valid executor.
242 : */
243 5 : void on_work_finished() const noexcept
244 : {
245 5 : p_->on_work_finished();
246 5 : }
247 :
248 : /** Dispatches a continuation through the wrapped executor.
249 :
250 : Returns a handle for symmetric transfer. If running in the
251 : executor's thread, returns `c.h`. Otherwise, posts the
252 : continuation for later execution and returns
253 : `std::noop_coroutine()`.
254 :
255 : @param c The continuation to dispatch for resumption.
256 : Must remain at a stable address until dequeued.
257 :
258 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
259 :
260 : @pre This instance holds a valid executor.
261 : */
262 5 : std::coroutine_handle<> dispatch(continuation& c) const
263 : {
264 5 : return p_->dispatch(c);
265 : }
266 :
267 : /** Posts a continuation to the wrapped executor.
268 :
269 : Posts the continuation to the executor for later execution
270 : and returns. The caller should transfer to `std::noop_coroutine()`
271 : after calling this.
272 :
273 : @param c The continuation to post for resumption.
274 : Must remain at a stable address until dequeued.
275 :
276 : @pre This instance holds a valid executor.
277 : */
278 15 : void post(continuation& c) const
279 : {
280 15 : p_->post(c);
281 15 : }
282 :
283 : /** Compares two executor wrappers for equality.
284 :
285 : Two `any_executor` instances are equal if they both hold
286 : executors of the same type that compare equal, or if both
287 : are empty.
288 :
289 : @param other The executor to compare against.
290 :
291 : @return `true` if both wrap equal executors of the same type,
292 : or both are empty.
293 : */
294 11 : bool operator==(any_executor const& other) const noexcept
295 : {
296 11 : if(!p_ && !other.p_)
297 1 : return true;
298 10 : if(!p_ || !other.p_)
299 1 : return false;
300 9 : return p_->equals(other.p_.get());
301 : }
302 :
303 : /** Returns the type_info of the wrapped executor.
304 :
305 : @return The `std::type_info` of the stored executor type,
306 : or `typeid(void)` if empty.
307 : */
308 2 : std::type_info const& target_type() const noexcept
309 : {
310 2 : if(!p_)
311 1 : return typeid(void);
312 1 : return p_->target_type();
313 : }
314 : };
315 :
316 : } // capy
317 : } // boost
318 :
319 : #endif
|