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_EXECUTOR_REF_HPP
12 : #define BOOST_CAPY_EXECUTOR_REF_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/detail/type_id.hpp>
16 : #include <boost/capy/continuation.hpp>
17 : #include <concepts>
18 : #include <coroutine>
19 : #include <type_traits>
20 : #include <utility>
21 :
22 : namespace boost {
23 : namespace capy {
24 :
25 : class execution_context;
26 :
27 : namespace detail {
28 :
29 : /** Virtual function table for type-erased executor operations. */
30 : struct executor_vtable
31 : {
32 : execution_context& (*context)(void const*) noexcept;
33 : void (*on_work_started)(void const*) noexcept;
34 : void (*on_work_finished)(void const*) noexcept;
35 : void (*post)(void const*, continuation&);
36 : std::coroutine_handle<> (*dispatch)(void const*, continuation&);
37 : bool (*equals)(void const*, void const*) noexcept;
38 : detail::type_info const* type_id;
39 : };
40 :
41 : /** Vtable instance for a specific executor type. */
42 : template<class Ex>
43 : inline constexpr executor_vtable vtable_for = {
44 : // context
45 HIT 1 : [](void const* p) noexcept -> execution_context& {
46 1 : return const_cast<Ex*>(static_cast<Ex const*>(p))->context();
47 : },
48 : // on_work_started
49 2 : [](void const* p) noexcept {
50 1 : const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started();
51 : },
52 : // on_work_finished
53 2 : [](void const* p) noexcept {
54 1 : const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_finished();
55 : },
56 : // post
57 34610 : [](void const* p, continuation& c) {
58 17305 : static_cast<Ex const*>(p)->post(c);
59 : },
60 : // dispatch
61 122 : [](void const* p, continuation& c) -> std::coroutine_handle<> {
62 122 : return static_cast<Ex const*>(p)->dispatch(c);
63 : },
64 : // equals
65 1 : [](void const* a, void const* b) noexcept -> bool {
66 1 : return *static_cast<Ex const*>(a) == *static_cast<Ex const*>(b);
67 : },
68 : // type_id
69 : &detail::type_id<Ex>()
70 : };
71 :
72 : } // detail
73 :
74 : /** Forwards `dispatch`/`post`/`context` calls through a non-owning, type-erased executor pointer.
75 :
76 : This class provides type erasure for any executor type, enabling
77 : runtime polymorphism without virtual functions or allocation.
78 : It stores a pointer to the original executor and a pointer to a
79 : static vtable. Executors of different types are therefore stored
80 : uniformly, while satisfying the full `Executor` concept.
81 :
82 : @par Reference Semantics
83 : This class has reference semantics: it does not allocate or own
84 : the wrapped executor. Copy operations copy the internal
85 : pointers. The caller must ensure the referenced executor outlives
86 : all `executor_ref` instances that wrap it.
87 :
88 : @par Thread Safety
89 : The `executor_ref` itself is not thread-safe for concurrent
90 : modification, but its executor operations are safe to call
91 : concurrently if the underlying executor supports it.
92 :
93 : @par Executor Concept
94 : This class satisfies the `Executor` concept, making it usable
95 : anywhere a concrete executor is expected.
96 :
97 : @par Example
98 : @code
99 : void store_executor(executor_ref ex)
100 : {
101 : if(ex)
102 : ex.post(my_continuation);
103 : }
104 :
105 : thread_pool ctx;
106 : store_executor(ctx.get_executor());
107 : @endcode
108 :
109 : @see any_executor, Executor
110 : */
111 : class executor_ref
112 : {
113 : void const* ex_ = nullptr;
114 : detail::executor_vtable const* vt_ = nullptr;
115 :
116 : public:
117 : /** Construct a default instance.
118 :
119 : Constructs an empty `executor_ref`. `operator bool()` and
120 : `operator==()` report the empty state; `context()`,
121 : `on_work_started()`, `on_work_finished()`, `dispatch()`,
122 : `post()`, and `target()` are undefined behavior until an
123 : executor is assigned.
124 : */
125 3528 : executor_ref() = default;
126 :
127 : /** Construct a copy.
128 :
129 : Copies the internal pointers, preserving identity.
130 : This enables the same-executor optimization when passing
131 : executor_ref through coroutine chains.
132 :
133 : @param other The reference to copy.
134 : */
135 : executor_ref(executor_ref const& other) = default;
136 :
137 : /** Copy assignment operator.
138 :
139 : @param other The reference to copy.
140 :
141 : @return A reference to `*this`.
142 : */
143 : executor_ref& operator=(executor_ref const& other) = default;
144 :
145 : /** Constructs from any executor type.
146 :
147 : Captures a reference to the given executor and stores a pointer
148 : to the type-specific vtable. The executor must remain valid for
149 : the lifetime of this `executor_ref` instance.
150 :
151 : @param ex The executor to wrap. Must satisfy the `Executor`
152 : concept. A pointer to this object is stored
153 : internally; the executor must outlive this wrapper.
154 : */
155 : #if defined(__GNUC__) && !defined(__clang__)
156 : // GCC constraint satisfaction caching bug workaround
157 : template<class Ex,
158 : std::enable_if_t<!std::is_same_v<
159 : std::decay_t<Ex>, executor_ref>, int> = 0>
160 : #else
161 : template<class Ex>
162 : requires (!std::same_as<std::decay_t<Ex>, executor_ref>)
163 : #endif
164 32430 : executor_ref(Ex const& ex) noexcept
165 32430 : : ex_(&ex)
166 32430 : , vt_(&detail::vtable_for<Ex>)
167 : {
168 32430 : }
169 :
170 : /** Returns true if this instance holds a valid executor.
171 :
172 : @return `true` if constructed with an executor, `false` if
173 : default-constructed.
174 : */
175 6 : explicit operator bool() const noexcept
176 : {
177 6 : return ex_ != nullptr;
178 : }
179 :
180 : /** Returns a reference to the associated execution context.
181 :
182 : @return A reference to the execution context.
183 :
184 : @pre This instance was constructed with a valid executor.
185 : */
186 1 : execution_context& context() const noexcept
187 : {
188 1 : return vt_->context(ex_);
189 : }
190 :
191 : /** Informs the executor that work is beginning.
192 :
193 : Must be paired with a subsequent call to `on_work_finished()`.
194 :
195 : @pre This instance was constructed with a valid executor.
196 : */
197 1 : void on_work_started() const noexcept
198 : {
199 1 : vt_->on_work_started(ex_);
200 1 : }
201 :
202 : /** Informs the executor that work has completed.
203 :
204 : @pre A preceding call to `on_work_started()` was made.
205 : @pre This instance was constructed with a valid executor.
206 : */
207 1 : void on_work_finished() const noexcept
208 : {
209 1 : vt_->on_work_finished(ex_);
210 1 : }
211 :
212 : /** Dispatches a continuation through the wrapped executor.
213 :
214 : Returns a handle for symmetric transfer. If running in the
215 : executor's thread, returns `c.h`. Otherwise, posts the
216 : continuation for later execution and returns
217 : `std::noop_coroutine()`.
218 :
219 : @param c The continuation to dispatch for resumption.
220 : Must remain at a stable address until dequeued.
221 :
222 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
223 :
224 : @pre This instance was constructed with a valid executor.
225 : */
226 122 : std::coroutine_handle<> dispatch(continuation& c) const
227 : {
228 122 : return vt_->dispatch(ex_, c);
229 : }
230 :
231 : /** Posts a continuation to the wrapped executor.
232 :
233 : Posts the continuation to the executor for later execution
234 : and returns. The caller should transfer to `std::noop_coroutine()`
235 : after calling this.
236 :
237 : @param c The continuation to post for resumption.
238 : Must remain at a stable address until dequeued.
239 :
240 : @pre This instance was constructed with a valid executor.
241 : */
242 17305 : void post(continuation& c) const
243 : {
244 17305 : vt_->post(ex_, c);
245 17305 : }
246 :
247 : /** Compares two executor references for equality.
248 :
249 : Two `executor_ref` instances are equal if they wrap
250 : executors of the same type that compare equal.
251 :
252 : @param other The executor reference to compare against.
253 :
254 : @return `true` if both wrap equal executors of the same type.
255 : */
256 7 : bool operator==(executor_ref const& other) const noexcept
257 : {
258 7 : if (ex_ == other.ex_)
259 5 : return true;
260 2 : if (vt_ != other.vt_)
261 1 : return false;
262 1 : return vt_->equals(ex_, other.ex_);
263 : }
264 :
265 : /** Return a pointer to the wrapped executor if it matches
266 : the requested type.
267 :
268 : Performs a type check against the stored executor and
269 : returns a typed pointer when the types match, or
270 : `nullptr` otherwise. Analogous to
271 : `std::any_cast< Executor >( &a )`.
272 :
273 : @tparam Executor The executor type to retrieve.
274 :
275 : @return A pointer to the underlying executor, or
276 : `nullptr` if the type does not match.
277 : */
278 : template< typename Executor >
279 2 : const Executor* target() const
280 : {
281 2 : if ( *vt_->type_id == detail::type_id< Executor >() )
282 1 : return static_cast< Executor const* >( ex_ );
283 1 : return nullptr;
284 : }
285 :
286 : /// @copydoc target() const
287 : template< typename Executor>
288 2 : Executor* target()
289 : {
290 2 : if ( *vt_->type_id == detail::type_id< Executor >() )
291 : return const_cast< Executor* >(
292 1 : static_cast< Executor const* >( ex_ ));
293 1 : return nullptr;
294 : }
295 : };
296 :
297 : } // capy
298 : } // boost
299 :
300 : #endif
|