use super::wait_queue::WaitQueue;
use std::cell::UnsafeCell;
use std::future::Future;
use std::sync::atomic::{AtomicU8, Ordering};
use std::task::{Context, Poll};
const UNINIT: u8 = 0;
const INITIALIZING: u8 = 1;
const INIT: u8 = 2;
#[repr(align(64))]
pub struct OnceCell<T> {
state: AtomicU8,
data: UnsafeCell<Option<T>>,
wait: WaitQueue,
}
unsafe impl<T: Send> Send for OnceCell<T> {}
unsafe impl<T: Send + Sync> Sync for OnceCell<T> {}
impl<T> Default for OnceCell<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> OnceCell<T> {
#[must_use]
pub const fn new() -> Self {
Self {
state: AtomicU8::new(UNINIT),
data: UnsafeCell::new(None),
wait: WaitQueue::new(),
}
}
#[must_use]
pub const fn new_with(value: T) -> Self {
Self {
state: AtomicU8::new(INIT),
data: UnsafeCell::new(Some(value)),
wait: WaitQueue::new(),
}
}
#[must_use]
#[inline(always)]
pub fn get(&self) -> Option<&T> {
(self.state.load(Ordering::Acquire) == INIT)
.then(|| unsafe { (*self.data.get()).as_ref() })
.flatten()
}
#[inline(always)]
pub fn set(&self, value: T) -> Result<(), T> {
if self
.state
.compare_exchange(UNINIT, INITIALIZING, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return Err(value);
}
unsafe {
*self.data.get() = Some(value);
}
self.state.store(INIT, Ordering::Release);
self.wait.wake_all();
Ok(())
}
pub async fn get_or_init<F, Fut>(&self, init: F) -> &T
where
F: FnOnce() -> Fut,
Fut: Future<Output = T>,
T: Send + Sync,
{
loop {
if let Some(v) = self.get() {
return v;
}
if self
.state
.compare_exchange(UNINIT, INITIALIZING, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let value = init().await;
let value_ref = unsafe {
let slot = &mut *self.data.get();
*slot = Some(value);
slot.as_ref().unwrap_unchecked()
};
self.state.store(INIT, Ordering::Release);
self.wait.wake_all();
return value_ref;
}
std::future::poll_fn(|cx| self.poll_wait_for_init(cx)).await;
}
}
#[inline]
fn poll_wait_for_init(&self, cx: &Context<'_>) -> Poll<()> {
if self.state.load(Ordering::Acquire) == INIT {
return Poll::Ready(());
}
let token = self.wait.register(cx.waker());
if self.state.load(Ordering::Acquire) == INIT {
self.wait.cancel(token);
return Poll::Ready(());
}
Poll::Pending
}
#[inline(always)]
pub fn into_inner(self) -> Option<T> {
self.data.into_inner()
}
}