use core::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::Notify;
#[derive(Debug, Default)]
pub struct AwaitableBool {
bool: AtomicBool,
notify: Notify,
}
impl<T: Into<AtomicBool>> From<T> for AwaitableBool {
fn from(value: T) -> Self {
Self {
bool: value.into(),
notify: Notify::new(),
}
}
}
impl AwaitableBool {
pub fn new<IntoAtomicBool: Into<AtomicBool>>(value: IntoAtomicBool) -> Self {
value.into().into()
}
pub fn set_true(&self) {
if self
.bool
.compare_exchange(false, true, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
self.notify.notify_waiters();
}
}
pub fn set_false(&self) {
if self
.bool
.compare_exchange(true, false, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
self.notify.notify_waiters();
}
}
pub fn toggle(&self) {
self.bool.fetch_xor(true, Ordering::Release);
self.notify.notify_waiters();
}
#[inline]
pub fn load(&self) -> bool {
self.bool.load(Ordering::Acquire)
}
#[inline]
pub fn is_true(&self) -> bool {
self.load()
}
#[inline]
pub fn is_false(&self) -> bool {
!(self.load())
}
pub async fn wait(&self) {
self.notify.notified().await;
}
pub async fn wait_true(&self) {
let wait_fut = self.wait();
if self.is_false() {
wait_fut.await;
}
}
pub async fn wait_false(&self) {
let wait_fut = self.wait();
if self.is_true() {
wait_fut.await;
}
}
#[inline]
pub const fn into_inner(self) -> AtomicBool {
self.bool
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn initializing_as_true() {
let awaitable = AwaitableBool::new(true);
assert!(awaitable.is_true());
assert!(!awaitable.is_false());
}
#[test]
fn initializing_as_false() {
let awaitable = AwaitableBool::new(false);
assert!(awaitable.is_false());
assert!(!awaitable.is_true());
}
#[tokio::test]
async fn waiting_for_true_when_true_is_immediate() {
let awaitable = AwaitableBool::new(true);
awaitable.wait_true().await;
}
#[tokio::test]
async fn waiting_for_false_when_false_is_immediate() {
let awaitable = AwaitableBool::new(false);
awaitable.wait_false().await;
}
}