#![allow(dead_code)] use std::sync::atomic::{ self, AtomicBool, AtomicUsize, }; use std::mem::{self, MaybeUninit}; use std::cell::UnsafeCell; use std::ops::Drop; pub mod iter; use iter::*; mod private { pub(crate) trait Sealed{} } /// A parallel, atomic populator of items pub struct Populator { values: UnsafeCell]>>, populates: Box<[AtomicBool]>, // populated: AtomicUsize, // number of populated items } impl Populator { #[inline(always)] fn values_mut(&mut self) -> &mut [MaybeUninit] { self.values.get_mut() } #[inline(always)] fn values_ref(&self) -> &[MaybeUninit] { let ptr = self.values.get() as *const Box<[_]>; unsafe { &(*ptr)[..] } } #[inline(always)] fn get_mut_ptr(&self, idx: usize) -> *mut MaybeUninit { let ptr = self.values.get(); unsafe { &mut (*ptr)[idx] as *mut _ } } } impl Drop for Populator { fn drop(&mut self) { if mem::needs_drop::() { let len = self.values_ref().len(); if *self.populated.get_mut() == len { // Fully populated, drop whole slice in place unsafe { std::ptr::drop_in_place( self.values_mut() as *mut [MaybeUninit] as *mut [T]) } } else if len > 0 { // If values is 0, then that means `[try_]complete()` has been called. // Partially populated, drop individual parts for value in self.values.get_mut().iter_mut() .zip(self.populates.iter() .map(|x| x.load(atomic::Ordering::Acquire))) .filter_map(|(v, prod)| prod.then(move || v.as_mut_ptr())) { unsafe { std::ptr::drop_in_place(value) } } } } // Both boxes will be dealloced after this, the values are dropped. } } unsafe impl Send for Populator where Box: Send {} unsafe impl Sync for Populator{} // Populator is always sync impl Populator { /// How many items are populated pub fn populated(&self) -> usize { self.populated.load(atomic::Ordering::Acquire) } /// Is the populator full? pub fn is_full(&self) -> bool { self.populated() == self.len() } /// Number of items held by the populator #[inline] pub fn len(&self) -> usize { self.values_ref().len() } /// Create a new, empty populator with this size pub fn new(size: usize) -> Self { Self { // SAFETY: MaybeUninit is not Copy, so instead we allocate the space for uninitialised memory and then .set_len(). values: UnsafeCell::new(unsafe { let mut uninit = Vec::with_capacity(size); uninit.set_len(size); uninit }.into_boxed_slice()), populates: std::iter::repeat_with(|| false.into()).take(size).collect(), populated: 0usize.into(), } } /// Try to insert `value` at `idx`. /// /// If `idx` already has a value, then `Err(value)` is returned, otherwise, `value` is inserted into the table and the number of items now populated is returned. pub fn try_insert(&self, idx: usize, value: T) -> Result { //TODO: XXX: Should we use SeqCst -> Acquire, or Acquire -> Relaxed? if let Ok(false) = self.populates[idx].compare_exchange(false, true, atomic::Ordering::SeqCst, atomic::Ordering::Acquire) { // The value at idx hasn't been set if cfg!(debug_assertions) { match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let ptr = self.get_mut_ptr(idx); //self.values[idx].get(); unsafe { *ptr = MaybeUninit::new(value); } })) { Err(p) => std::panic::resume_unwind(p), Ok(_) => (), } } else { // SAFETY: This operation will never panic, since `values` and `populates` are always the same size // SAFETY: We have already ensured that `values[idx]` does not contain a value. unsafe { *self.get_mut_ptr(idx) = MaybeUninit::new(value); } } // Value is inserted, increment `populated` Ok(self.populated.fetch_add(1, atomic::Ordering::SeqCst) + 1) } else { Err(value) } } /// Insert `value` into `idx`. /// /// # Panics /// If `idx` already has a value inserted. pub fn insert(&self, idx: usize, value: T) -> usize { #[inline(never)] #[cold] fn panic_inserted(i: usize) -> ! { panic!("There is already a value at {}", i) } match self.try_insert(idx, value) { Ok(v) => v, Err(_) => panic_inserted(idx), } } /// Faster fullness check for when this instance has no other references #[inline] pub fn is_full_exclusive(&mut self) -> bool { *self.populated.get_mut() == self.len() } #[inline(always)] fn take_all(&mut self) -> (Box<[MaybeUninit]>, Box<[AtomicBool]>) { let inner = self.values.get_mut(); (mem::replace(inner, vec![].into_boxed_slice()), mem::replace(&mut self.populates, vec![].into_boxed_slice())) } #[inline(always)] fn take_values(&mut self) -> Box<[MaybeUninit]> { let inner = self.values.get_mut(); mem::replace(inner, vec![].into_boxed_slice()) } /// If all values are populated, then convert it into a boxed slice and return it. pub fn try_complete(mut self) -> Result, Self> { if *self.populated.get_mut() == self.len() { //let ptr = Box::into_raw(std::mem::replace(&mut self.values, UnsafeCell::new(vec![].into_boxed_slice())).into_inner()); let ptr = { let inner = self.values.get_mut(); Box::into_raw(mem::replace(inner, vec![].into_boxed_slice())) }; Ok(unsafe { Box::from_raw(ptr as *mut [T]) }) } else { Err(self) } } /// Returns the completed population. /// /// # Panics /// If the collection is not fully populated. pub fn complete(self) -> Box<[T]> { #[inline(never)] #[cold] fn panic_uncomplete() -> ! { panic!("Not all values had been populated") } match self.try_complete() { Ok(v) => v, Err(_) => panic_uncomplete(), } } } impl IntoIterator for Populator // FUCK why do we need to make this 'static???? fuck this... dyn dispatch in rust is so jank. why can't we use 'a!!! { type Item = T; type IntoIter = iter::IntoIter<'static, T>; #[inline] fn into_iter(self) -> Self::IntoIter { iter::IntoIter::create_from(self) } } #[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } }