awaitable_bool/lib.rs
1//! See [`AwaitableBool`] for all documentation.
2use core::sync::atomic::{AtomicBool, Ordering};
3
4use tokio::sync::Notify;
5
6/// A bool whose value changes can be waited on.
7///
8/// Internally, it uses [`AtomicBool`]
9/// and [`tokio::sync::Notify`].
10///
11/// Because of that, most methods only need a shared reference (`&` as opposed to `&mut`) to self,
12/// so sharing between tasks or threads should be cheap and easy.
13/// This struct doesn't implement [`Clone`], so place it in an [`Arc`]
14/// (no lock protecting it necessary per the previous sentence) if cloning is needed.
15///
16/// [`Arc`]: std::sync::Arc
17/// [`AtomicBool`]: std::sync::atomic::AtomicBool
18#[derive(Debug, Default)]
19pub struct AwaitableBool {
20 bool: AtomicBool,
21 notify: Notify,
22}
23
24impl<T: Into<AtomicBool>> From<T> for AwaitableBool {
25 fn from(value: T) -> Self {
26 Self {
27 bool: value.into(),
28 notify: Notify::new(),
29 }
30 }
31}
32
33impl AwaitableBool {
34 /// Creates a new `AwaitableBool`.
35 ///
36 /// # Examples
37 ///
38 /// ```
39 /// use awaitable_bool::AwaitableBool;
40 ///
41 /// let awaitable_true = AwaitableBool::new(true);
42 /// let awaitable_false = AwaitableBool::new(false);
43 /// ```
44 pub fn new<IntoAtomicBool: Into<AtomicBool>>(value: IntoAtomicBool) -> Self {
45 value.into().into()
46 }
47
48 /// Set the `AwaitableBool` to `true`
49 /// (with [`Release`] ordering if not already `true`
50 /// and [`Acquire`] ordering if it is).
51 ///
52 /// This wakes all tasks waiting for [`wait_true`].
53 /// It also wakes those waiting for [`wait`] if the value wasn't already `true`.
54 ///
55 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
56 /// [`Release`]: core::sync::atomic::Ordering::Release
57 /// [`wait`]: AwaitableBool::wait
58 /// [`wait_true`]: AwaitableBool::wait_true
59 pub fn set_true(&self) {
60 if self
61 .bool
62 .compare_exchange(false, true, Ordering::Release, Ordering::Acquire)
63 .is_ok()
64 {
65 self.notify.notify_waiters();
66 }
67 }
68
69 /// Set the `AwaitableBool` to `false`
70 /// (with [`Release`] ordering if not already `false`
71 /// and [`Acquire`] ordering if it is).
72 ///
73 /// This wakes all tasks waiting for [`wait_false`].
74 /// It also wakes those waiting for [`wait`] if the value wasn't already `false`.
75 ///
76 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
77 /// [`Release`]: core::sync::atomic::Ordering::Release
78 /// [`wait`]: AwaitableBool::wait
79 /// [`wait_false`]: AwaitableBool::wait_false
80 pub fn set_false(&self) {
81 if self
82 .bool
83 .compare_exchange(true, false, Ordering::Release, Ordering::Acquire)
84 .is_ok()
85 {
86 self.notify.notify_waiters();
87 }
88 }
89
90 /// Set the `AwaitableBool` to the inverse of its current value (i.e. `false` if `true` or `true` if `false`)
91 /// (with [`AcqRel`] ordering).
92 ///
93 /// This wakes all tasks waiting for [`wait`].
94 /// It also wakes those waiting for [`wait_true`] if the value was just changed from `false` to `true`,
95 /// or those waiting for [`wait_false`] if the value was just changed from `true` to `false`.
96 ///
97 /// [`AcqRel`]: core::sync::atomic::Ordering::AcqRel
98 /// [`wait`]: AwaitableBool::wait
99 /// [`wait_false`]: AwaitableBool::wait_false
100 /// [`wait_true`]: AwaitableBool::wait_true
101 pub fn toggle(&self) {
102 // Until AtomicBool::fetch_not is stable
103 self.bool.fetch_xor(true, Ordering::AcqRel);
104
105 self.notify.notify_waiters();
106 }
107
108 /// Get the current value of the `AwaitableBool`
109 /// (with [`Acquire`] ordering).
110 ///
111 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
112 #[inline]
113 pub fn load(&self) -> bool {
114 self.bool.load(Ordering::Acquire)
115 }
116
117 /// Check if the `AwaitableBool`'s value is currently `true`
118 /// (with [`Acquire`] ordering).
119 ///
120 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
121 #[inline]
122 pub fn is_true(&self) -> bool {
123 self.load()
124 }
125 /// Check if the `AwaitableBool`'s value is currently `false`
126 /// (with [`Acquire`] ordering).
127 ///
128 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
129 #[inline]
130 pub fn is_false(&self) -> bool {
131 !(self.load())
132 }
133
134 /// Wait for this [`AwaitableBool`]'s value to change.
135 ///
136 /// Use [`load`] after to know what it changed to.
137 ///
138 /// [`load`]: AwaitableBool::load
139 pub async fn wait(&self) {
140 self.notify.notified().await;
141 }
142
143 /// Wait for this [`AwaitableBool`]'s value to become `true`.
144 /// This returns immediately if it's already `true`.
145 pub async fn wait_true(&self) {
146 if self.is_false() {
147 self.wait().await;
148 }
149 }
150 /// Wait for this [`AwaitableBool`]'s value to become `false`.
151 /// This returns immediately if it's already `false`.
152 pub async fn wait_false(&self) {
153 if self.is_true() {
154 self.wait().await;
155 }
156 }
157
158 /// Consume this [`AwaitableBool`] to get the contained [`AtomicBool`].
159 ///
160 /// [`AtomicBool`] also has an [`into_inner`] method to get its contained [`bool`].
161 ///
162 /// [`AtomicBool`]: std::sync::atomic::AtomicBool
163 /// [`into_inner`]: std::sync::atomic::AtomicBool::into_inner
164 #[inline]
165 pub const fn into_inner(self) -> AtomicBool {
166 self.bool
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn initializing_as_true() {
176 let awaitable = AwaitableBool::new(true);
177
178 assert!(awaitable.is_true());
179 assert!(!awaitable.is_false());
180 }
181
182 #[test]
183 fn initializing_as_false() {
184 let awaitable = AwaitableBool::new(false);
185
186 assert!(awaitable.is_false());
187 assert!(!awaitable.is_true());
188 }
189
190 #[tokio::test]
191 async fn waiting_for_true_when_true_is_immediate() {
192 let awaitable = AwaitableBool::new(true);
193
194 awaitable.wait_true().await;
195 }
196
197 #[tokio::test]
198 async fn waiting_for_false_when_false_is_immediate() {
199 let awaitable = AwaitableBool::new(false);
200
201 awaitable.wait_false().await;
202 }
203}