MT Core (C++)
Core library for replacing C++ standard in project usage
Loading...
Searching...
No Matches
select.hpp
Go to the documentation of this file.
1/*
2
3Copyright 2025 Matthew Tolman
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17*/
18
19#ifndef MTCORE_THREAD_SELECT_HPP
20#define MTCORE_THREAD_SELECT_HPP
21
24#include <chrono>
25
26namespace mtcore::thread {
34
47
55 template<SelectOption... Options>
56 struct Select;
57
72 template<SelectOption... Options>
73 Result<void, SelectErrors> select(Options... opts);
74
79 template<Sender S>
81 using Message = typename S::Message;
84 bool valid = true;
85
87 auto res = channel.try_send(m);
88 if (res.is_success()) {
89 return success();
90 }
91 auto err = res.error().code;
92 if constexpr (has_closed_error<std::remove_const_t<decltype(err)>>) {
93 if (err == decltype(err)::CHANNEL_CLOSED) {
94 valid = false;
96 }
97 }
99 }
100
102 [[nodiscard]] bool prevents_deadlock() const noexcept { return valid; }
103 };
104 static_assert(is_select_option<SendOptionNoCallback<Channel<int>>>, "SendOptionNoCallback is invalid!");
105
112 template<Sender S, typename Callback>
113 struct SendOption {
114 using Message = typename S::Message;
117 Callback callback;
118 bool valid = true;
119
121 auto res = channel.try_send(m);
122 if (res.is_success()) {
123 callback();
124 return success();
125 }
126 auto err = res.error().code;
127 if constexpr (has_closed_error<std::remove_const_t<decltype(err)>>) {
128 if (err == decltype(err)::CHANNEL_CLOSED) {
129 valid = false;
131 }
132 }
134 }
135
137 [[nodiscard]] bool prevents_deadlock() const noexcept { return valid; }
138 };
139 static_assert(is_select_option<SendOption<Channel<int>, void (*)(void)>>, "SendOption is invalid!");
140
153 template<Sender S, typename Callback>
154 SendOption<S, Callback> send_to(S &to, typename S::Message msg, const Callback &callback) {
155 return SendOption<S, Callback>{to, msg, callback};
156 }
157
166 template<Sender S>
167 SendOptionNoCallback<S> send_to(S &to, typename S::Message msg) {
168 return SendOptionNoCallback<S>{to, msg};
169 }
170
179 template<typename Clock, typename Duration, typename Callback>
181 std::chrono::time_point<Clock, Duration> time_point;
182 Callback callback;
183
191
192 // No need to wait for us if everything else is no longer valid
193 [[nodiscard]] bool prevents_deadlock() const noexcept { return false; }
194 };
195 static_assert(is_select_option<BeforeOption<std::chrono::system_clock, std::chrono::nanoseconds, void (*)(void)>>,
196 "BeforeOption is invalid!");
197
204 template<typename Clock, typename Duration>
206 std::chrono::time_point<Clock, Duration> time_point;
207
214
215 // No need to wait for us if everything else is no longer valid
216 [[nodiscard]] bool prevents_deadlock() const noexcept { return false; }
217 };
219 "BeforeOptionNoCallback is invalid!");
220
232 template<typename Clock, typename Duration, typename Callback>
233 BeforeOption<Clock, Duration, Callback> before(const std::chrono::time_point<Clock, Duration> &tp,
234 const Callback &callback) {
235 return BeforeOption<Clock, Duration, Callback>{tp, callback};
236 }
237
247 template<typename Clock, typename Duration>
248 BeforeOptionNoCallback<Clock, Duration> before(const std::chrono::time_point<Clock, Duration> &tp) {
250 }
251
262 template<typename Rep, typename Per, typename Callback>
263 auto timeout(const std::chrono::duration<Rep, Per> &tp, const Callback &callback) {
264 return before(std::chrono::steady_clock::now() + tp, callback);
265 }
266
275 template<typename Rep, typename Per>
276 auto timeout(const std::chrono::duration<Rep, Per> &tp) {
277 return before(std::chrono::steady_clock::now() + tp);
278 }
279
285 template<Receiver R, typename Callback>
287 using Message = typename R::Message;
289 Callback callback;
290
292 auto res = channel.try_receive();
293 if (res.is_success()) {
294 callback(res.value());
295 return success();
296 }
298 }
299
300 [[nodiscard]] bool prevents_deadlock() const noexcept { return true; }
301 };
302 static_assert(is_select_option<ReceiveOption<Channel<int>, void (*)(int)>>, "ReceiveOption is invalid!");
303
315 template<Receiver R, typename Callback>
316 ReceiveOption<R, Callback> receive_from(R &from, const Callback &callback) {
317 return ReceiveOption<R, Callback>{from, callback};
318 }
319
320 namespace impl {
321 template<SelectOption... Options>
322 Result<void, SelectErrors> exec(Select<Options...> &options);
323
324 template<typename T>
325 concept SelectImpl = requires(T &t, const T &ct) {
326 { t.run_step() } -> std::same_as<Result<void, SelectErrors>>;
327 { ct.prevents_deadlock() } -> std::same_as<bool>;
328 { ct.skip() } -> std::same_as<bool>;
329 { t() } -> std::same_as<Result<void, SelectErrors>>;
330 };
331
332 template<typename T>
333 struct IsSelectImpl;
334
335 template<SelectImpl T>
336 struct IsSelectImpl<T> : std::true_type {};
337
338 template<typename T>
339 struct IsSelectImpl : std::false_type {};
340
341 template<typename T>
342 constexpr bool is_select_impl = IsSelectImpl<T>::value;
343 } // namespace impl
344
350 template<>
351 struct Select<> {
353 [[nodiscard]] Result<void, SelectErrors> operator()() { return impl::exec(*this); }
354
355 [[nodiscard]] bool prevents_deadlock() const noexcept { return false; }
356 [[nodiscard]] bool skip() const noexcept { return false; }
357 };
358 static_assert(impl::is_select_impl<Select<>>, "Invalid select specialization");
359
367 template<SelectOption CurOption, SelectOption... Options>
368 struct Select<CurOption, Options...> {
369 CurOption val;
370 bool shouldSkip = false;
371 Select<Options...> next;
372
373 Select(CurOption val, Options... vals) : val(val), next({vals...}) {}
374
376 if (!skip()) {
377 auto res = val.attempt();
378 if (res.is_success()) {
379 return success();
380 }
381 SelectOptionErrors err = res.error().code;
382 if (err == SelectOptionErrors::TIMEOUT) {
384 }
385 if (err == SelectOptionErrors::SKIP) {
386 shouldSkip = true;
387 }
388 }
389 return next.run_step();
390 }
391
392 [[nodiscard]] bool prevents_deadlock() const noexcept {
393 return (val.prevents_deadlock() && !skip()) || next.prevents_deadlock();
394 }
395
396 [[nodiscard]] bool skip() const noexcept { return shouldSkip; }
397
398 [[nodiscard]] Result<void, SelectErrors> operator()() { return impl::exec(*this); }
399 };
400 static_assert(impl::is_select_impl<Select<SendOptionNoCallback<Channel<int>>>>, "Invalid select specialization");
401
402 static_assert(impl::is_select_impl<Select<SendOption<Channel<int>, void (*)(void)>>>,
403 "Invalid select specialization");
404
405 static_assert(impl::is_select_impl<Select<ReceiveOption<Channel<int>, void (*)(int)>>>,
406 "Invalid select specialization");
407
408 static_assert(
409 impl::is_select_impl<Select<BeforeOption<std::chrono::system_clock, std::chrono::nanoseconds, void (*)(void)>>>,
410 "Invalid select specialization");
411
412 static_assert(
413 impl::is_select_impl<Select<BeforeOptionNoCallback<std::chrono::system_clock, std::chrono::nanoseconds>>>,
414 "Invalid select specialization");
415
416 template<SelectOption... Options>
417 [[nodiscard]] Result<void, SelectErrors> select(Options... opts) {
418 return Select<Options...>{opts...}();
419 }
420
421 namespace impl {
422 template<SelectOption... Options>
423 Result<void, SelectErrors> exec(Select<Options...> &sel) {
424 while (sel.prevents_deadlock()) {
425 auto res = sel.run_step();
426 if (res.is_error()) {
427 if (res.error().code == SelectErrors::SELECT_TIMEOUT) {
429 }
430 if (res.error().code == SelectErrors::DEADLOCK_DETECTED) {
432 }
433 // let something else run while we wait
434 std::this_thread::yield();
435 continue;
436 }
437 else {
438 return success();
439 }
440 }
442 }
443 } // namespace impl
444} // namespace mtcore::thread
445
446#endif // MTCORE_THREAD_SELECT_HPP
Checks if something qualifies as an option to a select statement Allows defining custom options and c...
SelectOptionErrors
Errors indicating control flow from a select option ATTEMPT_FAILED indicates to try the next option S...
Definition select.hpp:42
SelectErrors
Errors for when a select statement fails SELECT_TIMEOUT and DEADLOCK_DETECTED are the only ones which...
Definition select.hpp:33
Success< void > success()
Creates a successful void Result object.
Definition result.hpp:398
Error< Underlying > error(Underlying err)
Creates an error.
Definition result.hpp:425
SendOption< S, Callback > send_to(S &to, typename S::Message msg, const Callback &callback)
Creates a select case which will try to send a message to a sender Option will call a callback on suc...
Definition select.hpp:154
ReceiveOption< R, Callback > receive_from(R &from, const Callback &callback)
Creates a receive option for select which will execute when a message is ready.
Definition select.hpp:316
Result< void, SelectErrors > select(Options... opts)
Creates a "select statement" similar to Go's select statement, except this statement runs on thread-b...
Definition select.hpp:417
BeforeOption< Clock, Duration, Callback > before(const std::chrono::time_point< Clock, Duration > &tp, const Callback &callback)
The before option will timeout a select if an option is not hit before the target time point.
Definition select.hpp:233
auto timeout(const std::chrono::duration< Rep, Per > &tp, const Callback &callback)
Does a timeout relative to current time (e.g.
Definition select.hpp:263
constexpr bool is_select_option
Checks if a type can be used in a select statement as an option.
constexpr bool has_closed_error
Checks if an error type has a recognized closed error.
Thread-related namespace The methods and classes provided by this class are thread-safe Classes and m...
Represents a Result that may have an error (error code) or a success value A type of "void" means the...
Definition result.hpp:170
Option to add a timeout on select to ensure a path runs before a timeout If the timeout is hit,...
Definition select.hpp:205
bool prevents_deadlock() const noexcept
Definition select.hpp:216
Result< void, SelectOptionErrors > attempt()
Definition select.hpp:208
std::chrono::time_point< Clock, Duration > time_point
Definition select.hpp:206
Option to add a timeout on select to ensure a path runs before a timeout If the timeout is hit,...
Definition select.hpp:180
bool prevents_deadlock() const noexcept
Definition select.hpp:193
Result< void, SelectOptionErrors > attempt()
Definition select.hpp:184
std::chrono::time_point< Clock, Duration > time_point
Definition select.hpp:181
Select option to receive a message and calls a callback on receive.
Definition select.hpp:286
Result< void, SelectOptionErrors > attempt()
Definition select.hpp:291
typename R::Message Message
Definition select.hpp:287
bool prevents_deadlock() const noexcept
Definition select.hpp:300
Result< void, SelectErrors > operator()()
Definition select.hpp:398
Result< void, SelectErrors > run_step()
Definition select.hpp:375
Select(CurOption val, Options... vals)
Definition select.hpp:373
bool skip() const noexcept
Definition select.hpp:356
Result< void, SelectErrors > run_step()
Definition select.hpp:352
Result< void, SelectErrors > operator()()
Definition select.hpp:353
bool prevents_deadlock() const noexcept
Definition select.hpp:355
Underlying type for a select statement Use the "select" method directly for instantiation.
Definition select.hpp:56
Select option to send a message without calling a callback on send.
Definition select.hpp:80
bool prevents_deadlock() const noexcept
Checks if option is still valid (invalidates on channel close)
Definition select.hpp:102
Result< void, SelectOptionErrors > attempt()
Definition select.hpp:86
Select option to send a message.
Definition select.hpp:113
Result< void, SelectOptionErrors > attempt()
Definition select.hpp:120
typename S::Message Message
Definition select.hpp:114
bool prevents_deadlock() const noexcept
Checks if option is still valid (invalidates on channel close)
Definition select.hpp:137