use super::{Ticks, Timer};
use crate::{
loom::{
cell::UnsafeCell,
sync::atomic::{AtomicBool, Ordering::*},
},
sync::wait_cell::WaitCell,
};
use cordyceps::{list, Linked};
use core::{
future::Future,
marker::PhantomPinned,
pin::Pin,
ptr::{self, NonNull},
task::{ready, Context, Poll},
time::Duration,
};
use mycelium_util::fmt;
use pin_project::{pin_project, pinned_drop};
#[pin_project(PinnedDrop)]
#[must_use = "futures do nothing unless `.await`ed or `poll`ed"]
pub struct Sleep<'timer> {
state: State,
timer: &'timer Timer,
#[pin]
entry: Entry,
}
#[derive(Debug)]
#[pin_project]
pub(super) struct Entry {
#[pin]
links: UnsafeCell<list::Links<Entry>>,
waker: WaitCell,
pub(super) deadline: Ticks,
pub(super) ticks: Ticks,
pub(super) linked: AtomicBool,
_pin: PhantomPinned,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum State {
Unregistered,
Registered,
Completed,
}
impl<'timer> Sleep<'timer> {
pub(super) fn new(timer: &'timer Timer, ticks: Ticks) -> Self {
let deadline = timer.core().now() + ticks;
Self {
state: State::Unregistered,
timer,
entry: Entry {
links: UnsafeCell::new(list::Links::new()),
waker: WaitCell::new(),
deadline,
ticks,
linked: AtomicBool::new(false),
_pin: PhantomPinned,
},
}
}
pub fn duration(&self) -> Duration {
super::ticks_to_dur(self.timer.tick_duration, self.entry.ticks)
}
}
impl Future for Sleep<'_> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
trace!(sleep.addr = ?format_args!("{:p}", this.entry), "Sleep::poll");
match test_dbg!(*this.state) {
State::Unregistered => {
let ptr =
unsafe { ptr::NonNull::from(Pin::into_inner_unchecked(this.entry.as_mut())) };
match test_dbg!(this.timer.core().register_sleep(ptr)) {
Poll::Ready(()) => {
*this.state = State::Completed;
return Poll::Ready(());
}
Poll::Pending => {
*this.state = State::Registered;
}
}
}
State::Registered => {}
State::Completed => return Poll::Ready(()),
}
let _poll = ready!(test_dbg!(this.entry.waker.poll_wait(cx)));
debug_assert!(
_poll.is_err(),
"a Sleep's WaitCell should only be woken by closing"
);
Poll::Ready(())
}
}
#[pinned_drop]
impl PinnedDrop for Sleep<'_> {
fn drop(mut self: Pin<&mut Self>) {
let this = self.project();
trace!(sleep.addr = ?format_args!("{:p}", this.entry), "Sleep::drop");
if test_dbg!(this.entry.linked.load(Acquire)) {
this.timer.core().cancel_sleep(this.entry);
}
}
}
impl fmt::Debug for Sleep<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
state,
entry,
timer,
} = self;
f.debug_struct("Sleep")
.field("duration", &self.duration())
.field("state", state)
.field("addr", &fmt::ptr(entry))
.field("timer", &fmt::ptr(*timer))
.finish()
}
}
unsafe impl Linked<list::Links<Entry>> for Entry {
type Handle = NonNull<Entry>;
fn into_ptr(r: Self::Handle) -> NonNull<Self> {
r
}
unsafe fn from_ptr(ptr: NonNull<Self>) -> Self::Handle {
ptr
}
unsafe fn links(target: NonNull<Self>) -> NonNull<list::Links<Entry>> {
let links = ptr::addr_of!((*target.as_ptr()).links);
(*links).with_mut(|links| {
NonNull::new_unchecked(links)
})
}
}
impl Entry {
pub(super) fn fire(&self) {
trace!(sleep.addr = ?fmt::ptr(self), "firing sleep");
self.waker.close();
let _was_linked = self.linked.compare_exchange(true, false, AcqRel, Acquire);
test_trace!(sleep.was_linked = _was_linked.is_ok());
}
}