use core::mem::{ManuallyDrop, MaybeUninit};
use alloc::sync::Arc;
use crate::{
Cleanups, SimpleBatch,
loomish::cell::Cell,
qsbr,
stack::{self, Stack},
};
pub struct Guard<B = SimpleBatch> {
cleanups: Arc<Cleanups<B>>,
raw: ManuallyDrop<RawGuard<B>>,
}
impl<B> Guard<B> {
pub fn new(cleanups: Arc<Cleanups<B>>) -> Self {
Self {
raw: ManuallyDrop::new(RawGuard::new(&cleanups)),
cleanups,
}
}
pub fn refresh(&mut self) -> Option<impl IntoIterator<Item = B> + use<B>> {
unsafe { self.raw.refresh(&self.cleanups) }
}
pub fn drop_and_take(self) -> Option<impl IntoIterator<Item = B> + use<B>> {
let (cleanups, raw) = self.into_raw_parts();
unsafe { raw.drop_and_take(&cleanups) }
}
pub fn into_raw_parts(self) -> (Arc<Cleanups<B>>, RawGuard<B>) {
let this = MaybeUninit::new(self);
let cleanups = unsafe { (&raw const (*this.as_ptr()).cleanups).read() };
let raw = unsafe { (&raw const (*this.as_ptr()).raw).read() };
(cleanups, ManuallyDrop::into_inner(raw))
}
pub const unsafe fn from_raw_parts(cleanups: Arc<Cleanups<B>>, raw: RawGuard<B>) -> Self {
Self {
cleanups,
raw: ManuallyDrop::new(raw),
}
}
}
impl<B> Drop for Guard<B> {
fn drop(&mut self) {
let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
unsafe { raw.drop(&self.cleanups) };
}
}
impl Guard<SimpleBatch> {
pub fn defer<F: FnOnce() + Send + 'static>(&self, f: F) {
unsafe { self.defer_unchecked(f) }
}
pub unsafe fn defer_unchecked<F: FnOnce()>(&self, f: F) {
if let Some(mut batch) = self.take_batch() {
if !batch.is_full() {
unsafe { batch.add_unchecked(f) }
.unwrap_or_else(|_| unreachable!("the batch is not full"));
self.store_batch(batch)
.unwrap_or_else(|_| unreachable!("no user-defined code was called"));
return;
} else {
self.send_batch(batch);
}
}
if let Some(mut batch) = self.reuse_batch() {
batch.execute();
if let Some(mut other) = self.take_batch() {
if !other.is_full() {
unsafe { other.add_unchecked(f) }
.unwrap_or_else(|_| unreachable!("the batch is not full"));
self.store_batch(other)
.unwrap_or_else(|_| unreachable!("no user-defined code was called"));
return;
} else {
self.send_batch(other);
}
}
unsafe { batch.add_unchecked(f) }
.unwrap_or_else(|_| unreachable!("the batch has been cleared"));
self.store_batch(batch)
.unwrap_or_else(|_| unreachable!("no user-defined code was called"));
return;
}
let mut batch = SimpleBatch::new();
unsafe { batch.add_unchecked(f) }
.unwrap_or_else(|_| unreachable!("the batch was just created"));
self.store_batch(batch)
.unwrap_or_else(|_| unreachable!("no user-defined code was called"));
}
}
impl<B> Guard<B> {
#[inline]
pub fn store_batch(&self, batch: B) -> Result<(), B> {
self.raw.store_batch(batch)
}
#[inline]
pub fn take_batch(&self) -> Option<B> {
self.raw.take_batch()
}
pub fn send_batch(&self, batch: B) {
unsafe { self.raw.send_batch(batch, &self.cleanups) }
}
pub fn reuse_batch(&self) -> Option<B> {
unsafe { self.raw.reuse_batch(&self.cleanups) }
}
}
pub struct RawGuard<B> {
user: ManuallyDrop<qsbr::User>,
batch: Cell<Option<B>>,
}
impl<B> RawGuard<B> {
pub fn new(cleanups: &Cleanups<B>) -> Self {
let user = cleanups.schedule.register();
Self {
user: ManuallyDrop::new(user),
batch: Cell::new(None),
}
}
pub unsafe fn refresh(
&mut self,
cleanups: &Cleanups<B>,
) -> Option<impl IntoIterator<Item = B> + use<B>> {
if let Some(batch) = self.take_batch() {
unsafe { self.send_batch(batch, cleanups) };
}
if let Some(leaving) = self.user.progress(&cleanups.schedule)
&& let Some(_last_leaving) = leaving.leave_last()
{
let deferred = &cleanups.deferred[self.user.index()];
let reused = &cleanups.reused[self.user.index()];
let slots = &cleanups.slots[self.user.index()];
let deferred = unsafe { deferred.swap(Stack::new()) };
let reused = unsafe { reused.swap(deferred) };
let _ = unsafe { slots.swap(Stack::new()) };
Some(reused)
} else {
None
}
}
pub unsafe fn drop(self, cleanups: &Cleanups<B>) {
if let Some(batch) = self.take_batch() {
unsafe { self.send_batch(batch, cleanups) };
}
let index = self.user.index();
let user = ManuallyDrop::into_inner(self.user);
let leaving = user.deregister(&cleanups.schedule);
if let Some(_last_leaving) = leaving.leave_last() {
let deferred = &cleanups.deferred[index];
let reused = &cleanups.reused[index];
let slots = &cleanups.slots[index];
while let Some(node) = unsafe { reused.pop() } {
unsafe { deferred.push(node) };
}
let deferred = unsafe { deferred.swap(Stack::new()) };
let _ = unsafe { reused.swap(deferred) };
let _ = unsafe { slots.swap(Stack::new()) };
}
}
pub unsafe fn drop_and_take(
self,
cleanups: &Cleanups<B>,
) -> Option<impl IntoIterator<Item = B> + use<B>> {
if let Some(batch) = self.take_batch() {
unsafe { self.send_batch(batch, cleanups) };
}
let index = self.user.index();
let user = ManuallyDrop::into_inner(self.user);
let leaving = user.deregister(&cleanups.schedule);
if let Some(_last_leaving) = leaving.leave_last() {
let deferred = &cleanups.deferred[index];
let reused = &cleanups.reused[index];
let slots = &cleanups.slots[index];
let deferred = unsafe { deferred.swap(Stack::new()) };
let reused = unsafe { reused.swap(deferred) };
let _ = unsafe { slots.swap(Stack::new()) };
Some(reused)
} else {
None
}
}
}
impl<B> RawGuard<B> {
pub fn store_batch(&self, batch: B) -> Result<(), B> {
match self.batch.take() {
None => {
self.batch.set(Some(batch));
Ok(())
}
Some(existing) => {
self.batch.set(Some(existing));
Err(batch)
}
}
}
pub fn take_batch(&self) -> Option<B> {
self.batch.replace(None)
}
pub unsafe fn send_batch(&self, batch: B, cleanups: &Cleanups<B>) {
let slot = unsafe {
cleanups.slots[self.user.index()]
.pop()
.unwrap_or_else(|| stack::Node::new(MaybeUninit::uninit()))
};
unsafe { (*slot.as_ptr()).item.write(batch) };
let slot = slot.cast::<stack::Node<B>>();
unsafe { cleanups.deferred[self.user.index()].push(slot) };
}
pub unsafe fn reuse_batch(&self, cleanups: &Cleanups<B>) -> Option<B> {
unsafe { cleanups.reused[self.user.index()].pop() }.map(|node| {
let slot = node.cast::<stack::Node<MaybeUninit<B>>>();
let batch = unsafe { (*slot.as_ptr()).item.assume_init_read() };
unsafe { cleanups.slots[self.user.index()].push(slot) };
batch
})
}
}