use std::{
alloc::{handle_alloc_error, GlobalAlloc, Layout},
ptr,
sync::atomic::{AtomicPtr, Ordering},
};
#[doc(hidden)]
pub struct LazyAtomicCell<A, T>
where
A: 'static + GlobalAlloc,
{
#[doc(hidden)]
pub ptr: AtomicPtr<T>,
#[doc(hidden)]
pub allocator: &'static A,
}
impl<A, T> Drop for LazyAtomicCell<A, T>
where
A: 'static + GlobalAlloc,
{
fn drop(&mut self) {
let p = self.ptr.swap(ptr::null_mut(), Ordering::SeqCst);
if p.is_null() {
return;
}
unsafe {
ptr::drop_in_place(p);
self.allocator.dealloc(p.cast(), Layout::new::<T>());
}
}
}
impl<A, T> LazyAtomicCell<A, T>
where
A: 'static + GlobalAlloc,
{
pub fn new(allocator: &'static A) -> Self {
LazyAtomicCell {
ptr: AtomicPtr::new(ptr::null_mut()),
allocator,
}
}
pub fn get_or_create(&self, init: impl FnOnce() -> T) -> &T {
let ptr = self.ptr.load(Ordering::SeqCst);
if !ptr.is_null() {
return unsafe { &*ptr };
}
let layout = Layout::new::<T>();
let new_ptr = unsafe { self.allocator.alloc(layout).cast::<T>() };
if new_ptr.is_null() {
handle_alloc_error(layout);
}
unsafe {
ptr::write(new_ptr, init());
}
let existing_ptr = self
.ptr
.compare_and_swap(ptr::null_mut(), new_ptr, Ordering::SeqCst);
if existing_ptr.is_null() {
unsafe { &*new_ptr }
} else {
unsafe {
ptr::drop_in_place(new_ptr);
self.allocator.dealloc(new_ptr.cast(), layout);
&*existing_ptr
}
}
}
}