use crate::{Asset, AssetCache, BoxedError, SharedString, Storable, asset::DirLoadable};
use once_cell::sync::OnceCell;
use std::{cell::UnsafeCell, fmt, mem::ManuallyDrop};
union State<U, T> {
uninit: ManuallyDrop<U>,
init: ManuallyDrop<T>,
}
#[cfg_attr(docsrs, doc(cfg(feature = "utils")))]
pub struct OnceInitCell<U, T> {
once: OnceCell<()>,
data: UnsafeCell<State<U, T>>,
}
unsafe impl<U, T> Sync for OnceInitCell<U, T>
where
T: Send + Sync,
U: Send,
{
}
impl<U, T> std::panic::UnwindSafe for OnceInitCell<U, T>
where
T: std::panic::UnwindSafe,
U: std::panic::UnwindSafe,
{
}
impl<U, T> std::panic::RefUnwindSafe for OnceInitCell<U, T>
where
T: std::panic::UnwindSafe + std::panic::RefUnwindSafe,
U: std::panic::UnwindSafe,
{
}
impl<U, T> OnceInitCell<U, T> {
#[inline]
pub const fn new(value: U) -> Self {
Self {
once: OnceCell::new(),
data: UnsafeCell::new(State {
uninit: ManuallyDrop::new(value),
}),
}
}
#[inline]
pub const fn with_value(value: T) -> Self {
Self {
once: OnceCell::with_value(()),
data: UnsafeCell::new(State {
init: ManuallyDrop::new(value),
}),
}
}
#[inline]
unsafe fn get_unchecked(&self) -> &T {
unsafe { &(*self.data.get()).init }
}
#[inline]
pub fn get(&self) -> Option<&T> {
match self.once.get() {
Some(_) => unsafe { Some(self.get_unchecked()) },
None => None,
}
}
pub fn wait(&self) -> &T {
self.once.wait();
unsafe { self.get_unchecked() }
}
#[inline]
pub fn get_or_init(&self, f: impl FnOnce(&mut U) -> T) -> &T {
match self.get_or_try_init(|u| Ok::<_, std::convert::Infallible>(f(u))) {
Ok(v) => v,
Err(never) => match never {},
}
}
pub fn get_or_try_init<E>(&self, f: impl FnOnce(&mut U) -> Result<T, E>) -> Result<&T, E> {
if std::mem::needs_drop::<U>() {
self.get_or_try_init_default(f)
} else {
self.get_or_try_init_no_drop(f)
}
}
fn get_or_try_init_default<E>(&self, f: impl FnOnce(&mut U) -> Result<T, E>) -> Result<&T, E> {
unsafe {
let mut uninit_value = None;
self.once.get_or_try_init(|| {
let state = &mut *self.data.get();
let value = f(&mut state.uninit)?;
let new_state = State {
init: ManuallyDrop::new(value),
};
let uninit = std::mem::replace(state, new_state).uninit;
uninit_value = Some(ManuallyDrop::into_inner(uninit));
Ok(())
})?;
if let Some(value) = uninit_value {
drop_cold(value);
}
Ok(self.get_unchecked())
}
}
fn get_or_try_init_no_drop<E>(&self, f: impl FnOnce(&mut U) -> Result<T, E>) -> Result<&T, E> {
unsafe {
self.once.get_or_try_init(|| {
let state = &mut *self.data.get();
let value = f(&mut state.uninit)?;
*state = State {
init: ManuallyDrop::new(value),
};
Ok(())
})?;
Ok(self.get_unchecked())
}
}
}
impl<U, T> Drop for OnceInitCell<U, T> {
fn drop(&mut self) {
unsafe {
let data = self.data.get_mut();
match self.once.get_mut() {
Some(_) => ManuallyDrop::drop(&mut data.init),
None => ManuallyDrop::drop(&mut data.uninit),
}
}
}
}
impl<U, T> Default for OnceInitCell<U, T>
where
U: Default,
{
fn default() -> Self {
Self::new(U::default())
}
}
impl<U, T: fmt::Debug> fmt::Debug for OnceInitCell<U, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.get() {
Some(data) => f.debug_tuple("OnceInitCell").field(data).finish(),
None => f.write_str("OnceInitCell(<uninit>)"),
}
}
}
impl<U: Asset, T: Storable> Asset for OnceInitCell<U, T> {
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError> {
Ok(OnceInitCell::new(U::load(cache, id)?))
}
const HOT_RELOADED: bool = U::HOT_RELOADED;
}
impl<U: Asset, T: Storable> Asset for OnceInitCell<Option<U>, T> {
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError> {
Ok(OnceInitCell::new(Some(U::load(cache, id)?)))
}
const HOT_RELOADED: bool = U::HOT_RELOADED;
}
impl<U: DirLoadable, T: Storable> DirLoadable for OnceInitCell<U, T> {
fn select_ids(cache: &AssetCache, id: &SharedString) -> std::io::Result<Vec<SharedString>> {
U::select_ids(cache, id)
}
fn sub_directories(
cache: &AssetCache,
id: &SharedString,
f: impl FnMut(&str),
) -> std::io::Result<()> {
U::sub_directories(cache, id, f)
}
}
impl<U: DirLoadable, T: Storable> DirLoadable for OnceInitCell<Option<U>, T> {
fn select_ids(cache: &AssetCache, id: &SharedString) -> std::io::Result<Vec<SharedString>> {
U::select_ids(cache, id)
}
fn sub_directories(
cache: &AssetCache,
id: &SharedString,
f: impl FnMut(&str),
) -> std::io::Result<()> {
U::sub_directories(cache, id, f)
}
}
#[cold]
fn drop_cold<T>(_x: T) {}