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}