#![allow(clippy::needless_return)]
use dioxus_core::CapturedError;
use std::{hint::black_box, prelude::rust_2024::Future, sync::atomic::AtomicBool};
pub struct Lazy<T> {
value: std::sync::OnceLock<T>,
started_initialization: AtomicBool,
constructor: Option<fn() -> Result<T, CapturedError>>,
_phantom: std::marker::PhantomData<T>,
}
impl<T: Send + Sync + 'static> Lazy<T> {
#[allow(clippy::self_named_constructors)]
pub const fn lazy() -> Self {
Self {
_phantom: std::marker::PhantomData,
constructor: None,
started_initialization: AtomicBool::new(false),
value: std::sync::OnceLock::new(),
}
}
pub const fn new<F, G, E>(constructor: F) -> Self
where
F: Fn() -> G + Copy,
G: Future<Output = Result<T, E>> + Send + 'static,
E: Into<CapturedError>,
{
if std::mem::size_of::<F>() != 0 {
panic!("The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.");
}
black_box(constructor);
Self {
_phantom: std::marker::PhantomData,
value: std::sync::OnceLock::new(),
started_initialization: AtomicBool::new(false),
constructor: Some(blocking_initialize::<T, F, G, E>),
}
}
pub fn set(&self, pool: T) -> Result<(), CapturedError> {
let res = self.value.set(pool);
if res.is_err() {
return Err(anyhow::anyhow!("Lazy value is already initialized.").into());
}
Ok(())
}
pub fn try_set(&self, pool: T) -> Result<(), T> {
self.value.set(pool)
}
pub fn initialize(&self) -> Result<(), CapturedError> {
if let Some(constructor) = self.constructor {
if self
.started_initialization
.swap(true, std::sync::atomic::Ordering::SeqCst)
{
self.value.wait();
return Ok(());
}
self.set(constructor().unwrap())?;
}
Ok(())
}
pub fn get(&self) -> &T {
if self.constructor.is_none() {
return self.value.get().expect("Lazy value is not initialized. Make sure to call `initialize` before dereferencing.");
};
if self.value.get().is_none() {
self.initialize().expect("Failed to initialize lazy value");
}
self.value.get().unwrap()
}
}
impl<T: Send + Sync + 'static> Default for Lazy<T> {
fn default() -> Self {
Self::lazy()
}
}
impl<T: Send + Sync + 'static> std::ops::Deref for Lazy<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<T: std::fmt::Debug + Send + Sync + 'static> std::fmt::Debug for Lazy<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Lazy").field("value", self.get()).finish()
}
}
fn blocking_initialize<T, F, G, E>() -> Result<T, CapturedError>
where
T: Send + Sync + 'static,
F: Fn() -> G + Copy,
G: Future<Output = Result<T, E>> + Send + 'static,
E: Into<CapturedError>,
{
assert_eq!(std::mem::size_of::<F>(), 0, "The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.");
#[cfg(feature = "server")]
{
let ptr: F = unsafe { std::mem::zeroed() };
let fut = ptr();
return std::thread::spawn(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(fut)
.map_err(|e| e.into())
})
.join()
.unwrap();
}
#[cfg(not(feature = "server"))]
unimplemented!("Lazy initialization is only supported with tokio and threads enabled.")
}