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}