use std::{
cell::UnsafeCell,
mem::MaybeUninit,
sync::atomic::{AtomicBool, Ordering},
};
pub(crate) struct ReduceSlots<T> {
slots: Box<[ReduceSlot<T>]>,
}
struct ReduceSlot<T> {
value: UnsafeCell<MaybeUninit<T>>,
initialized: AtomicBool,
}
unsafe impl<T: Send> Sync for ReduceSlot<T> {}
impl<T> ReduceSlots<T> {
pub(crate) fn new(len: usize) -> Self {
Self {
slots: (0..len)
.map(|_| ReduceSlot::new())
.collect::<Vec<_>>()
.into_boxed_slice(),
}
}
pub(crate) fn write(&self, index: usize, value: T) {
self.slots[index].write(value);
}
pub(crate) fn reduce<F>(&self, identity: T, reduce: F) -> T
where
F: Fn(T, T) -> T,
{
self.slots.iter().fold(identity, |accumulator, slot| {
if let Some(value) = slot.take() {
reduce(accumulator, value)
} else {
accumulator
}
})
}
}
impl<T> Drop for ReduceSlots<T> {
fn drop(&mut self) {
for slot in &self.slots {
let _ = slot.take();
}
}
}
impl<T> ReduceSlot<T> {
fn new() -> Self {
Self {
value: UnsafeCell::new(MaybeUninit::uninit()),
initialized: AtomicBool::new(false),
}
}
fn write(&self, value: T) {
unsafe {
(*self.value.get()).write(value);
}
self.initialized.store(true, Ordering::Release);
}
fn take(&self) -> Option<T> {
if self.initialized.swap(false, Ordering::AcqRel) {
Some(unsafe { (*self.value.get()).assume_init_read() })
} else {
None
}
}
}