ion 0.12.0
Atomic concurrency support library
Loading...
Searching...
No Matches
signal.hpp
Go to the documentation of this file.
1/**
2 * @defgroup ion_signals Signals
3 * Thread safe atomic signals.
4 *
5 * @code
6 * #include <cstdint>
7 * #include <thread>
8 *
9 * #include <ion/signal.hpp>
10 *
11 *
12 * int main() {
13 * auto source = ion::SignalSource<int>();
14 *
15 * // To avoid race-condition between storing new signal value and what token
16 * // initializes to we create the token in the main thread.
17 * std::thread thread([token=ion::SignalToken<int>(&source)]() mutable {
18 * auto current = Wait(token);
19 * std::cout << "Got signal: " << current << std::endl;
20 * });
21 *
22 * // Update signal
23 * source.Store(42);
24 *
25 * thread.join();
26 * }
27 * @endcode
28 * @ingroup ion
29 *
30 * @file
31 * @ingroup ion_signals
32 *
33 * @brief ion::{SignalSource, SignalToken} and algorithms
34 *
35 * @copyright
36 * SPDX-FileCopyrightText: 2023-2025 European Southern Observatory (ESO)
37
38 * SPDX-License-Identifier: LGPL-3.0-only
39 */
40#ifndef ION_SIGNAL_HPP
41#define ION_SIGNAL_HPP
42#include <atomic>
43#include <cassert>
44#include <cstdint>
45#include <optional>
46#include <tuple>
47#include <type_traits>
48
49#include <ion/detail/macros.hpp>
50
51namespace ion {
52
53/**
54 * Atomic signal source.
55 *
56 * See @ref ion_signals for example.
57 *
58 * @tparam T Value type of atomic signal. Must be trivial type and
59 * `std::atomic<T>::is_always_lock_free == true`.
60 * @relates SignalToken
61 * @thread_safe
62 * @ingroup ion_signals
63 * @headerfile <> <ion/signal.hpp>
64 */
65template <class T>
67public:
68 using ValueType = T;
69
70 /**
71 * Construct signal source.
72 * @param initial Initial value.
73 */
74 explicit SignalSource(ValueType initial = ValueType{}) noexcept;
75
76 /**
77 * SignalSource requires stable address so copy and move operations are disabled.
78 */
79 SignalSource(SignalSource const&) = delete;
80 SignalSource& operator=(SignalSource const&) = delete;
81
82 /**
83 * Load signal value.
84 *
85 * @returns current value.
86 * @memory_order{Relaxed memory order without any synchronization.}
87 */
88 [[nodiscard]] auto Load() const noexcept -> ValueType;
89
90 /**
91 * Store signal value.
92 *
93 * @param value New value to store.
94 * @memory_order{Relaxed memory order without any synchronization.}
95 */
96 void Store(ValueType value) noexcept;
97
98 /**
99 * Set signal value using read-modify-write semantics.
100 *
101 * @note Weak version is allowed to spuriously fail which will yield better performance on some
102 * platforms.
103 *
104 * @param [in,out] expected Reference to value expected to be found in the signal. This value is
105 * updated if the bitwise comparison failed.
106 * @param [in] desired Value to write if expected value comparison succeeds.
107 * @returns true if exchange was made.
108 * @returns false otherwise.
109 * @memory_order{Relaxed memory order without any synchronization.}
110 *
111 * @sa CompareExchangeTransform()
112 */
113 [[nodiscard]] auto CompareExchangeWeak(ValueType& expected, ValueType desired) noexcept -> bool;
114
115 /**
116 * Set signal value using read-modify-write semantics.
117 *
118 * @param [in,out] expected Reference to value expected to be found in the signal. This value is
119 * updated if the bitwise comparison failed.
120 * @param [in] desired Desired value to write.
121 * @returns true if exchange was made.
122 * @returns false otherwise.
123 * @memory_order{Relaxed memory order without any synchronization.}
124 *
125 * @sa CompareExchangeTransform()
126 */
127 [[nodiscard]] auto
128 CompareExchangeStrong(ValueType& expected, ValueType desired) noexcept -> bool;
129
130private:
131 using SignalType = std::atomic<ValueType>;
132 static_assert(SignalType::is_always_lock_free);
133
134 SignalType m_signal;
135};
136
137/**
138 * Performs atomic compare-exchange transformation of signal value.
139 *
140 * @note Provided transform operation `op` will potentially be invoked any number of times - until
141 * underlying compare exchange operation is successful.
142 *
143 * Example where `S::second` is reset to `0` without clobbering `S::first` - even if signal is
144 * modified from multiple threads.
145 * @code
146 * struct S {
147 * std::uint32_t first;
148 * std::uint32_t second;
149 * };
150 *
151 * void ResetSecond(ion::SignalSource<S>& signal) {
152 * CompareExchangeTransform(s, [](S value) noexcept {
153 * // Leave value.first as is.
154 * value.second = 0u;
155 * return value;
156 * });
157 * }
158 *
159 * @endcode
160 * @param signal Signal to transform value on.
161 * @param op transform operation.
162 * @return Transformed value that was successfully stored in signal.
163 *
164 * @tparam UnaryOperation Noexcept callable invocable as `T callable(T const& t) noexcept`,
165 * (which means `T callable(T t) noexcept` is also valid).
166 *
167 * @ingroup ion_signals
168 * @relatesalso SignalSource
169 * @headerfile <> <ion/signal.hpp>
170 */
171template <class T, class UnaryOperation>
172auto CompareExchangeTransform(SignalSource<T>& signal, UnaryOperation op) noexcept -> T;
173
174/**
175 * Signal token that has an associated SignalSource and last known value.
176 *
177 * A SignalToken is valid (`IsValid() == true`) if it is associated with a SignalSource.
178 *
179 * Moving a SignalToken will leave the moved-from object invalid.
180 *
181 * See @ref ion_signals for example.
182 * @thread_compatible
183 * @ingroup ion_signals
184 * @headerfile <> <ion/signal.hpp>
185 */
186template <class T>
188public:
189 using ValueType = T;
190
191 /**
192 * @name Initialization.
193 */
194 /// @{
195 /**
196 * Default-initialized token.
197 *
198 * @post `IsValid() == false`
199 */
200 SignalToken() noexcept = default;
201
202 /**
203 * Initialize token.
204 *
205 * @param signal Associated signal.
206 * @param last Optional value to set as last known signal value. If `nullopt` is used and
207 * `signal` is valid the last value will be initialized from signal source.
208 */
209 explicit SignalToken(SignalSource<T> const* signal,
210 std::optional<ValueType> last = std::nullopt) noexcept;
211 SignalToken(SignalToken const&) noexcept = default;
212
213 auto operator=(SignalToken const&) noexcept -> SignalToken& = default;
214 /**
215 * Move-construct token, leaving `other` in an invalid state.
216 *
217 * @param other SignalToken to move from.
218 * @post `other.IsValid() == false`
219 */
220 SignalToken(SignalToken&& other) noexcept;
221
222 /**
223 * Move-assign token, leaving `other` in an invalid state.
224 *
225 * @param other SignalToken to move from.
226 * @post `other.IsValid() == false`
227 */
228 auto operator=(SignalToken&& other) noexcept -> SignalToken&;
229 /// @}
230
231 /**
232 * @name Observers
233 */
234 /// @{
235 /**
236 * @returns true if SignalToken is associated with a SignalSource.
237 * @returns false otherwise.
238 */
239 [[nodiscard]] constexpr auto IsValid() const noexcept -> bool;
240
241 /**
242 * Loads signal value but *does not* set last value.
243 *
244 * @pre `IsValid() == true`
245 * @sa Update()
246 * @memory_order{Loads value from associated signal source using relaxed memory order (no
247 * synchronize-with relationships).}
248 */
249 [[nodiscard]] auto Load() const noexcept -> ValueType;
250
251 /**
252 * @return Signal source associated with this SignalToken.
253 */
254 [[nodiscard]] constexpr auto GetSource() const -> SignalSource<ValueType> const*;
255
256 /**
257 * Get last observed value.
258 */
259 [[nodiscard]] constexpr auto GetLast() const noexcept -> ValueType;
260 /// @}
261 /**
262 * @name Modifiers
263 */
264 /// @{
265 /**
266 * Set last observed value.
267 *
268 * @param last Value to set as "last" known value from signal.
269 */
270 constexpr void SetLast(ValueType last) noexcept;
271
272 /**
273 * Load from signal and set last value.
274 *
275 * @pre IsValid() == true
276 * @return New signal source value.
277 * @post `r.Update() == r.GetLast()`
278 * @sa Load()
279 * @memory_order{Loads value from associated signal source using relaxed memory order (no
280 * synchronize-with relationships).}
281 */
282 auto Update() noexcept -> ValueType;
283 /// @}
284
285private:
286 SignalSource<ValueType> const* m_signal = nullptr;
287 ValueType m_last = ValueType();
288};
289
290/**
291 * Equality comparison.
292 *
293 * @param lhs Left hand side of comparison.
294 * @param rhs RIght hand side of comparison.
295 * @returns true if both `lhs` and `rhs` are associated with the same signal source and have the
296 * same last value.
297 * @returns false otherwise.
298 * @tparam T ValueType that must provide `bool operator==(T const&, T const&) noexcept`.
299 *
300 * @ingroup ion_signals
301 * @relatesalso SignalToken
302 */
303template <class T>
304[[nodiscard]] constexpr auto
305operator==(SignalToken<T> const& lhs, SignalToken<T> const& rhs) noexcept -> bool;
306
307/**
308 * Inequality comparison.
309 *
310 * @param lhs Left hand side of comparison.
311 * @param rhs RIght hand side of comparison.
312 * @returns false if both `lhs` and `rhs` are associated with the same signal source and have the
313 * same last value.
314 * @returns true otherwise.
315 * @tparam T ValueType that must provide `bool operator==(T const&, T const&) noexcept`.
316 *
317 * @ingroup ion_signals
318 * @relatesalso SignalToken
319 */
320template <class T>
321[[nodiscard]] constexpr auto
322operator!=(SignalToken<T> const& lhs, SignalToken<T> const& rhs) noexcept -> bool;
323
324/**
325 * Represents signal source previous (old) and current (new) value.
326 * @ingroup ion_signals
327 */
328template <class T>
329struct Status {
330 using ValueType = T;
331
332 ValueType previous;
333 ValueType current;
334};
335
336/**
337 * Represents wait result from one of multiple possible signals.
338 *
339 * @sa WaitAny()
340 * @ingroup ion_signals
341 */
342template <class T1, class T2>
344 /**
345 * Index in *result* that unblocked `WaitAny()`
346 */
347 std::size_t index;
348 /**
349 * Result of wait operation.
350 */
351 std::tuple<T1, T2> result;
352};
353
354/**
355 * @name Tag-invoke types
356 */
357/// @{
358/**
359 * Select algorithm overload that returns signal status.
360 * @ingroup ion_signals
361 */
362struct StatusTag {
363 explicit StatusTag() = default;
364};
365
366/**
367 * Constant instance of StatusTag.
368 * @ingroup ion_signals
369 */
370inline constexpr StatusTag status_tag{}; // NOLINT(readability-identifier-naming)
371
372/**
373 * Select algorithm overload that returns signal difference.
374 * @ingroup ion_signals
375 */
376struct DifferenceTag {
377 explicit DifferenceTag() = default;
378};
379
380/**
381 * Constant instance of DifferenceTag.
382 * @ingroup ion_signals
383 */
384inline constexpr DifferenceTag difference_tag{}; // NOLINT(readability-identifier-naming)
385/// @}
386
387/**
388 * @name Wait and yield current signal value.
389 */
390/// @{
391
392/**
393 * Wait until signal source changes and return current value.
394 *
395 * @code
396 * void Example(ion::SignalToken<int>& token) {
397 * assert(token.IsValid());
398 * int current = Wait(token);
399 * }
400 * @endcode
401 *
402 * @pre `token.IsValid() == true`
403 *
404 * @param token Signal token.
405 * @returns New signal source value.
406 *
407 * @relatesalso SignalToken
408 * @ingroup ion_signals
409 * @headerfile <> <ion/signal.hpp>
410 */
411template <class T>
412auto Wait(SignalToken<T>& token) noexcept -> T;
413
414/**
415 * Wait with predicate.
416 *
417 * Example using a predicate that waits until signal value is even.
418 * @code
419 * void Example(ion::SignalToken<int>& token) {
420 * assert(token.IsValid());
421 * int current = Wait(token, [](int value) noexcept -> bool {
422 * return value % 2;
423 * });
424 * }
425 * @endcode
426 *
427 * @pre `token.IsValid() == true`
428 *
429 * @param token Signal token.
430 * @param stop_waiting Predicate invoked with each new signal value and should return true when to
431 * stop waiting.
432 * @returns New signal source value.
433 *
434 * @relatesalso SignalToken
435 * @ingroup ion_signals
436 * @headerfile <> <ion/signal.hpp>
437 */
438template <class T, class Predicate>
439auto Wait(SignalToken<T>& token, Predicate&& stop_waiting) noexcept -> T;
440/// @}
441
442/**
443 * @name Wait and yield Status<T>.
444 */
445/// @{
446
447/**
448 * Wait until signal source changes and return current and previous values.
449 *
450 * @code
451 * void Example(ion::SignalToken<int>& token) {
452 * assert(token.IsValid());
453 * ion::Status<int> status = Wait(ion::status_tag, token);
454 * // or
455 * auto [previous, current] = Wait(ion::status_tag, token);
456 * }
457 * @endcode
458 *
459 * @pre `token.IsValid() == true`
460 *
461 * @param token Signal token.
462 * @returns Status containing current and previous values.
463 *
464 * @relatesalso SignalToken
465 * @ingroup ion_signals
466 * @headerfile <> <ion/signal.hpp>
467 */
468template <class T>
469auto Wait(StatusTag, SignalToken<T>& token) noexcept -> Status<T>;
470
471/**
472 * Wait with predicate
473 *
474 * @pre `token.IsValid() == true`
475 *
476 * @param token Signal token.
477 * @returns Status containing current and previous values.
478 * @param stop_waiting Predicate invoked with each new signal value and should return true when to
479 * stop waiting.
480 * @tparam Predicate Callable invokable with signature `bool(T)`.
481 * @see Wait(StatusTag,SignalToken<T>&)
482 * @relatesalso SignalToken
483 * @ingroup ion_signals
484 * @headerfile <> <ion/signal.hpp>
485 */
486template <class T, class Predicate>
487auto Wait(StatusTag, SignalToken<T>& token, Predicate&& stop_waiting) noexcept -> Status<T>;
488/// @}
489
490/**
491 * @name Wait and yield changes since last.
492 */
493/// @{
494/**
495 * Wait until signal source changes and return difference (current - previous) value.
496 *
497 * @note Requires that `T t2=t1-t0` is a valid expression where `t0..t2` denotes an identifier
498 * of type `T`.
499 *
500 * @code
501 * void Example(ion::SignalToken<int>& token) {
502 * assert(token.IsValid());
503 * int difference = Wait(ion::difference_tag, token);
504 * }
505 * @endcode
506 *
507 * @pre `token.IsValid() == true`
508 *
509 * @param token Signal token.
510 * @returns Difference between current and previous signal value (current - previous).
511 *
512 * @relatesalso SignalToken
513 * @ingroup ion_signals
514 * @headerfile <> <ion/signal.hpp>
515 */
516template <class T>
517auto Wait(DifferenceTag, SignalToken<T>& token) noexcept -> T;
518
519/**
520 * Wait until signal source changes and return difference (current - previous) value.
521 *
522 * @note Requires that `T t2=t1-t0` is a valid expression where `t0..t2` denotes an identifier
523 * of type `T`.
524 *
525 * @code
526 * void Example(ion::SignalToken<int>& token) {
527 * assert(token.IsValid());
528 * int difference = Wait(ion::difference_tag, token);
529 * }
530 * @endcode
531 *
532 * @pre `token.IsValid() == true`
533 *
534 * @param token Signal token.
535 * @param stop_waiting Predicate invoked with each new signal value and should return true when to
536 * stop waiting.
537 * @returns Difference between current and previous signal value (current - previous).
538 *
539 * @tparam Predicate Callable invokable with signature `bool(T)`.
540 * @relatesalso SignalToken
541 * @ingroup ion_signals
542 * @headerfile <> <ion/signal.hpp>
543 */
544template <class T, class Predicate>
545auto Wait(DifferenceTag, SignalToken<T>& token, Predicate&& stop_waiting) noexcept -> T;
546/// @}
547
548/**
549 * @name Wait for either of two signals.
550 *
551 * *Effects* Load and compare each of the signals with previous value. The first one to change will
552 * be recorded in the `WaitAnyResult::index`. Depending on the selected overload the result of
553 * `WaitAnyResult::result` is either the *current*, *difference* or the *Status* containing both
554 * previous and current value.
555 */
556/// @{
557/**
558 * Wait for either of two signals to change.
559 *
560 * @pre `token1.IsValid() && token2.IsValid()`
561 *
562 * @param token1 First signal token.
563 * @param token2 Second signal token.
564 * @returns Wait result in which member `index` specify which of the two signals has a new value.
565 * Member `result` contains the current value of the signals.
566 *
567 * @relatesalso SignalToken
568 * @ingroup ion_signals
569 * @headerfile <> <ion/signal.hpp>
570 */
571template <class T1, class T2>
573
574/**
575 * Wait for either of two signals to change.
576 *
577 * @pre `token1.IsValid() && token2.IsValid()`
578 *
579 * @param token1 First signal token.
580 * @param token2 Second signal token.
581 * @returns Wait result in which member `index` specify which of the two signals has a new value.
582 * Member `result` contains the current and previous value of the signals.
583 *
584 * @relatesalso SignalToken
585 * @ingroup ion_signals
586 * @headerfile <> <ion/signal.hpp>
587 */
588template <class T1, class T2>
589auto WaitAny(StatusTag, SignalToken<T1>& token1, SignalToken<T2>& token2) noexcept
591
592/**
593 * Wait for either of two signals to change.
594 *
595 * @pre `token1.IsValid() && token2.IsValid()`
596 *
597 * @param token1 First signal token.
598 * @param token2 Second signal token.
599 * @returns Wait result in which member `index` specify which of the two signals has a new value.
600 * Member `result` contains the difference as "current value" - "previous value" of the signals.
601 *
602 * @relatesalso SignalToken
603 * @ingroup ion_signals
604 * @headerfile <> <ion/signal.hpp>
605 */
606template <class T1, class T2>
607auto WaitAny(DifferenceTag, SignalToken<T1>& token1, SignalToken<T2>& token2) noexcept
609/// @}
610
611#if !defined(DOXYGEN)
612template <class T>
613SignalSource<T>::SignalSource(ValueType initial) noexcept : m_signal(initial) {
614}
615
616template <class T>
617auto SignalSource<T>::Load() const noexcept -> ValueType {
618 return m_signal.load(std::memory_order_relaxed);
619}
620
621template <class T>
622void SignalSource<T>::Store(ValueType value) noexcept {
623 m_signal.store(value, std::memory_order_relaxed);
624}
625
626template <class T>
627auto SignalSource<T>::CompareExchangeWeak(ValueType& expected, ValueType desired) noexcept -> bool {
628 return m_signal.compare_exchange_weak(expected, desired, std::memory_order_relaxed);
629}
630
631template <class T>
632auto SignalSource<T>::CompareExchangeStrong(ValueType& expected, ValueType desired) noexcept
633 -> bool {
634 return m_signal.compare_exchange_strong(expected, desired, std::memory_order_relaxed);
635}
636
637template <class T, class UnaryOperation>
638[[maybe_unused]] auto
639CompareExchangeTransform(SignalSource<T>& signal, UnaryOperation op) noexcept -> T {
640 static_assert(
641 std::is_nothrow_invocable_r<T, UnaryOperation, T>::value,
642 "UnaryOperation must be noexcept invocable with signature `T op(T const&) noexcept`");
643 auto expected = signal.Load();
644 T desired = expected;
645 while (!signal.CompareExchangeWeak(expected, (desired = op(expected)))) {
646 ION_PAUSE();
647 }
648 return desired;
649}
650
651template <class T>
652SignalToken<T>::SignalToken(SignalToken&& other) noexcept
653 : m_signal(other.m_signal), m_last(other.m_last) {
654 other.m_signal = nullptr;
655}
656template <class T>
657SignalToken<T>::SignalToken(SignalSource<T> const* signal, std::optional<ValueType> last) noexcept
658 : m_signal(signal), m_last(last.value_or(ValueType{})) {
659 if (!last.has_value() && m_signal) {
660 m_last = m_signal->Load();
661 }
662}
663
664template <class T>
665constexpr auto SignalToken<T>::IsValid() const noexcept -> bool {
666 return m_signal != nullptr;
667}
668
669template <class T>
670auto SignalToken<T>::Load() const noexcept -> ValueType {
671 assert(IsValid());
672 return m_signal->Load();
673}
674
675template <class T>
676constexpr auto SignalToken<T>::GetSource() const -> SignalSource<ValueType> const* {
677 return m_signal;
678}
679
680template <class T>
681constexpr auto SignalToken<T>::GetLast() const noexcept -> ValueType {
682 return m_last;
683}
684
685template <class T>
686constexpr void SignalToken<T>::SetLast(ValueType last) noexcept {
687 m_last = last;
688}
689
690template <class T>
691auto SignalToken<T>::Update() noexcept -> ValueType {
692 m_last = Load();
693 return m_last;
694}
695
696template <class T>
697auto SignalToken<T>::operator=(SignalToken&& other) noexcept -> SignalToken& {
698 m_signal = other.m_signal;
699 m_last = other.m_last;
700 other.m_signal = nullptr;
701 return *this;
702}
703
704template <class T>
705constexpr bool operator==(SignalToken<T> const& lhs, SignalToken<T> const& rhs) noexcept {
706 return lhs.GetSource() == rhs.GetSource() && lhs.GetLast() == rhs.GetLast();
707}
708
709template <class T>
710constexpr bool operator!=(SignalToken<T> const& lhs, SignalToken<T> const& rhs) noexcept {
711 return !(lhs == rhs);
712}
713
714template <class T>
715auto Wait(SignalToken<T>& token) noexcept -> T {
716 return Wait(StatusTag{}, token).current;
717}
718
719template <class T, class Predicate>
720auto Wait(SignalToken<T>& token, Predicate&& stop_waiting) noexcept -> T {
721 return Wait(StatusTag{}, token, std::forward<Predicate>(stop_waiting)).current;
722}
723
724template <class T>
725auto Wait(StatusTag, SignalToken<T>& token) noexcept -> Status<T> {
726 auto previous = token.GetLast();
727 auto current = token.Load();
728 for (; current == previous; current = token.Load()) {
729 ION_PAUSE();
730 }
731
732 token.SetLast(current);
733 return {previous, current};
734}
735
736template <class T, class Predicate>
737auto Wait(StatusTag, SignalToken<T>& token, Predicate&& stop_waiting) noexcept -> Status<T> {
738 static_assert(std::is_invocable_r<bool, Predicate, T>::value,
739 "Predicate must be invocable with signature `bool(T current_value)`");
740 auto previous = token.GetLast();
741 auto current = token.Load();
742 while (!stop_waiting(current)) {
743 current = Wait(token);
744 };
745 // If wait condition is already satisfied Wait() is never called in the body of the loop and
746 // "current" value is never updated, hence we store it here again.
747 token.SetLast(current);
748 return {previous, current};
749}
750
751template <class T>
752auto Wait(DifferenceTag, SignalToken<T>& token) noexcept -> T {
753 static_assert(std::is_same_v<T, decltype(T() - T())>);
754 auto [previous, current] = Wait(StatusTag{}, token);
755 return current - previous;
756}
757
758template <class T, class Predicate>
759auto Wait(DifferenceTag, SignalToken<T>& token, Predicate&& stop_waiting) noexcept -> T {
760 static_assert(std::is_same_v<T, decltype(T() - T())>);
761 auto [previous, current] = Wait(StatusTag{}, token, std::forward<Predicate>(stop_waiting));
762 return current - previous;
763}
764
765template <class T1, class T2>
766auto WaitAny(StatusTag, SignalToken<T1>& token1, SignalToken<T2>& token2) noexcept
767 -> WaitAnyResult<Status<T1>, Status<T2>> {
768 std::size_t index;
769
770 Status<T1> first = {token1.GetLast(), token1.Load()};
771 Status<T2> second = {token2.GetLast(), token2.Load()};
772
773 while (true) {
774 if (first.current != first.previous) {
775 index = 0u;
776 break;
777 }
778 if (second.current != second.previous) {
779 index = 1u;
780 break;
781 }
782
783 ION_PAUSE();
784 first.current = token1.Load();
785 second.current = token2.Load();
786 }
787
788 token1.SetLast(first.current);
789 token2.SetLast(second.current);
790 return {index, {first, second}};
791}
792
793template <class T1, class T2>
794auto WaitAny(DifferenceTag, SignalToken<T1>& token1, SignalToken<T2>& token2) noexcept
795 -> WaitAnyResult<T1, T2> {
796 auto r = WaitAny(StatusTag{}, token1, token2);
797 return {r.index,
798 {std::get<0>(r.result).current - std::get<0>(r.result).previous,
799 std::get<1>(r.result).current - std::get<1>(r.result).previous}};
800}
801
802template <class T1, class T2>
803auto WaitAny(SignalToken<T1>& token1, SignalToken<T2>& token2) noexcept -> WaitAnyResult<T1, T2> {
804 auto r = WaitAny(StatusTag{}, token1, token2);
805
806 return {r.index, {std::get<0>(r.result).current, std::get<1>(r.result).current}};
807}
808
809#endif // if !defined(DOXYGEN)
810} // namespace ion
811#endif // ION_SIGNAL_HPP
Atomic signal source.
Definition signal.hpp:66
auto Load() const noexcept -> ValueType
Load signal value.
SignalSource(SignalSource const &)=delete
SignalSource requires stable address so copy and move operations are disabled.
SignalSource(ValueType initial=ValueType{}) noexcept
Construct signal source.
void Store(ValueType value) noexcept
Store signal value.
auto CompareExchangeWeak(ValueType &expected, ValueType desired) noexcept -> bool
Set signal value using read-modify-write semantics.
auto CompareExchangeStrong(ValueType &expected, ValueType desired) noexcept -> bool
Set signal value using read-modify-write semantics.
auto Load() const noexcept -> ValueType
auto Update() noexcept -> ValueType
constexpr void SetLast(ValueType last) noexcept
Set last observed value.
SignalToken() noexcept=default
Default-initialized token.
constexpr auto IsValid() const noexcept -> bool
constexpr auto GetLast() const noexcept -> ValueType
Get last observed value.
constexpr auto GetSource() const -> SignalSource< ValueType > const *
auto Wait(DifferenceTag, SignalToken< T > &token) noexcept -> T
Wait until signal source changes and return difference (current - previous) value.
constexpr auto operator!=(SignalToken< T > const &lhs, SignalToken< T > const &rhs) noexcept -> bool
Inequality comparison.
auto WaitAny(DifferenceTag, SignalToken< T1 > &token1, SignalToken< T2 > &token2) noexcept -> WaitAnyResult< T1, T2 >
Wait for either of two signals to change.
auto Wait(DifferenceTag, SignalToken< T > &token, Predicate &&stop_waiting) noexcept -> T
Wait until signal source changes and return difference (current - previous) value.
auto WaitAny(SignalToken< T1 > &token1, SignalToken< T2 > &token2) noexcept -> WaitAnyResult< T1, T2 >
Wait for either of two signals to change.
auto CompareExchangeTransform(SignalSource< ValueType > &signal, UnaryOperation op) noexcept -> ValueType
auto Wait(StatusTag, SignalToken< T > &token) noexcept -> Status< T >
Wait until signal source changes and return current and previous values.
constexpr auto operator==(SignalToken< T > const &lhs, SignalToken< T > const &rhs) noexcept -> bool
Equality comparison.
auto Wait(StatusTag, SignalToken< T > &token, Predicate &&stop_waiting) noexcept -> Status< T >
Wait with predicate.
auto WaitAny(StatusTag, SignalToken< T1 > &token1, SignalToken< T2 > &token2) noexcept -> WaitAnyResult< Status< T1 >, Status< T2 > >
Wait for either of two signals to change.
auto Wait(SignalToken< T > &token) noexcept -> T
Wait until signal source changes and return current value.
constexpr DifferenceTag difference_tag
Constant instance of DifferenceTag.
Definition signal.hpp:384
constexpr StatusTag status_tag
Constant instance of StatusTag.
Definition signal.hpp:370
auto Wait(SignalToken< T > &token, Predicate &&stop_waiting) noexcept -> T
Wait with predicate.
Select algorithm overload that returns signal difference.
Definition signal.hpp:376
Select algorithm overload that returns signal status.
Definition signal.hpp:362
Represents signal source previous (old) and current (new) value.
Definition signal.hpp:329
Represents wait result from one of multiple possible signals.
Definition signal.hpp:343
std::tuple< T1, T2 > result
Result of wait operation.
Definition signal.hpp:351
std::size_t index
Index in result that unblocked WaitAny()
Definition signal.hpp:347