use core::sync::atomic::{AtomicI32, Ordering};
use crate::{ptr_macros::ptr_to_field, sync::Mutex};
pub struct Once {
done: AtomicI32,
mutex: Mutex<()>,
}
impl Once {
#[must_use]
pub const unsafe fn init(this: *const Self) -> Once {
let mutex_ptr = ptr_to_field!(this, mutex);
Once {
done: AtomicI32::new(0),
mutex: unsafe { Mutex::init(mutex_ptr, ()) },
}
}
pub fn call_once<F: FnOnce()>(&self, f: F) {
if self.done.load(Ordering::Acquire) == 0 {
let _guard = self.mutex.lock();
if self.done.load(Ordering::Relaxed) == 0 {
f();
self.done.store(1, Ordering::Release);
}
}
}
pub fn is_completed(&self) -> bool {
self.done.load(Ordering::Acquire) != 0
}
}
#[macro_export]
macro_rules! once {
($name: ident) => {
static $name: $crate::sync::Once = {
let ptr = &raw const $name;
unsafe { $crate::sync::Once::init(ptr) }
};
};
}
#[cfg(test)]
mod tests {
#[test]
fn fast_path() {
let mut x = 0i32;
once!(ONCE);
ONCE.call_once(|| {
x += 1;
});
assert!(ONCE.is_completed());
ONCE.call_once(|| {
x += 1;
});
assert_eq!(x, 1);
}
}