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 /// ## Specify an initial value
39 /// ```
40 /// use awaitable_bool::AwaitableBool;
41 ///
42 /// let initially_true = AwaitableBool::new(true);
43 /// let initially_false = AwaitableBool::new(false);
44 /// ```
45 ///
46 /// ## Use an existing [`AtomicBool`] to make an [`AwaitableBool`]
47 /// ```
48 /// use std::sync::atomic::AtomicBool;
49 /// use awaitable_bool::AwaitableBool;
50 ///
51 /// let atomic_bool = AtomicBool::new(false);
52 /// let awaitable_bool = AwaitableBool::new(atomic_bool);
53 /// ```
54 ///
55 /// [`AtomicBool`]: std::sync::atomic::AtomicBool
56 pub fn new<IntoAtomicBool: Into<AtomicBool>>(value: IntoAtomicBool) -> Self {
57 value.into().into()
58 }
59
60 /// Set the `AwaitableBool` to `true`
61 /// (with [`Release`] ordering if not already `true`
62 /// and [`Relaxed`] ordering if it is).
63 ///
64 /// This wakes all tasks waiting for [`wait_true`].
65 /// It also wakes those waiting for [`wait`] if the value wasn't already `true`.
66 ///
67 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
68 /// [`Release`]: core::sync::atomic::Ordering::Release
69 /// [`wait`]: AwaitableBool::wait
70 /// [`wait_true`]: AwaitableBool::wait_true
71 pub fn set_true(&self) {
72 if self
73 .bool
74 .compare_exchange(false, true, Ordering::Release, Ordering::Relaxed)
75 .is_ok()
76 {
77 self.notify.notify_waiters();
78 }
79 }
80
81 /// Set the `AwaitableBool` to `false`
82 /// (with [`Release`] ordering if not already `false`
83 /// and [`Relaxed`] ordering if it is).
84 ///
85 /// This wakes all tasks waiting for [`wait_false`].
86 /// It also wakes those waiting for [`wait`] if the value wasn't already `false`.
87 ///
88 /// [`Relaxed`]: core::sync::atomic::Ordering::Relaxed
89 /// [`Release`]: core::sync::atomic::Ordering::Release
90 /// [`wait`]: AwaitableBool::wait
91 /// [`wait_false`]: AwaitableBool::wait_false
92 pub fn set_false(&self) {
93 if self
94 .bool
95 .compare_exchange(true, false, Ordering::Release, Ordering::Relaxed)
96 .is_ok()
97 {
98 self.notify.notify_waiters();
99 }
100 }
101
102 /// Set the `AwaitableBool` to the inverse of its current value (i.e. `false` if `true` or `true` if `false`)
103 /// (with [`Release`] ordering).
104 ///
105 /// This wakes all tasks waiting for [`wait`].
106 /// It also wakes those waiting for [`wait_true`] if the value was just changed from `false` to `true`,
107 /// or those waiting for [`wait_false`] if the value was just changed from `true` to `false`.
108 ///
109 /// [`Release`]: core::sync::atomic::Ordering::Release
110 /// [`wait`]: AwaitableBool::wait
111 /// [`wait_false`]: AwaitableBool::wait_false
112 /// [`wait_true`]: AwaitableBool::wait_true
113 pub fn toggle(&self) {
114 // Until AtomicBool::fetch_not is stable
115 self.bool.fetch_xor(true, Ordering::Release);
116
117 self.notify.notify_waiters();
118 }
119
120 /// Get the current value of the `AwaitableBool`
121 /// (with [`Acquire`] ordering).
122 ///
123 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
124 #[inline]
125 pub fn load(&self) -> bool {
126 self.bool.load(Ordering::Acquire)
127 }
128
129 /// Check if the `AwaitableBool`'s value is currently `true`
130 /// (with [`Acquire`] ordering).
131 ///
132 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
133 #[inline]
134 pub fn is_true(&self) -> bool {
135 self.load()
136 }
137 /// Check if the `AwaitableBool`'s value is currently `false`
138 /// (with [`Acquire`] ordering).
139 ///
140 /// [`Acquire`]: core::sync::atomic::Ordering::Acquire
141 #[inline]
142 pub fn is_false(&self) -> bool {
143 !(self.load())
144 }
145
146 /// Wait for this [`AwaitableBool`]'s value to change.
147 ///
148 /// Use [`load`] after to know what it changed to.
149 ///
150 /// [`load`]: AwaitableBool::load
151 pub async fn wait(&self) {
152 self.notify.notified().await;
153 }
154
155 /// Wait for this [`AwaitableBool`]'s value to become `true`.
156 /// This returns immediately if it's already `true`.
157 pub async fn wait_true(&self) {
158 let wait_fut = self.wait();
159 if self.is_false() {
160 wait_fut.await;
161 }
162 }
163 /// Wait for this [`AwaitableBool`]'s value to become `false`.
164 /// This returns immediately if it's already `false`.
165 pub async fn wait_false(&self) {
166 let wait_fut = self.wait();
167 if self.is_true() {
168 wait_fut.await;
169 }
170 }
171
172 /// Consume this [`AwaitableBool`] to get the contained [`AtomicBool`].
173 ///
174 /// [`AtomicBool`] also has an [`into_inner`] method to get its contained [`bool`].
175 ///
176 /// [`AtomicBool`]: std::sync::atomic::AtomicBool
177 /// [`into_inner`]: std::sync::atomic::AtomicBool::into_inner
178 #[inline]
179 pub const fn into_inner(self) -> AtomicBool {
180 self.bool
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn initializing_as_true() {
190 let awaitable = AwaitableBool::new(true);
191
192 assert!(awaitable.is_true());
193 assert!(!awaitable.is_false());
194 }
195
196 #[test]
197 fn initializing_as_false() {
198 let awaitable = AwaitableBool::new(false);
199
200 assert!(awaitable.is_false());
201 assert!(!awaitable.is_true());
202 }
203
204 #[tokio::test]
205 async fn waiting_for_true_when_true_is_immediate() {
206 let awaitable = AwaitableBool::new(true);
207
208 awaitable.wait_true().await;
209 }
210
211 #[tokio::test]
212 async fn waiting_for_false_when_false_is_immediate() {
213 let awaitable = AwaitableBool::new(false);
214
215 awaitable.wait_false().await;
216 }
217}