use std::cell::Cell;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::NonNull;
use o3::marker::ThreadBound;
use crate::task::{Context, Waker};
type WaiterPtr = NonNull<Waiter<'static>>;
pub struct WaitQueue {
head: Cell<Option<WaiterPtr>>,
tail: Cell<Option<WaiterPtr>>,
len: Cell<usize>,
capacity: usize,
_pin: PhantomPinned,
_thread: ThreadBound,
}
pub struct Waiter<'d> {
queue: Cell<Option<NonNull<WaitQueue>>>,
previous: Cell<Option<WaiterPtr>>,
next: Cell<Option<WaiterPtr>>,
wake: Cell<Option<Waker<'d>>>,
_pin: PhantomPinned,
_thread: ThreadBound,
}
impl WaitQueue {
pub const fn with_capacity(capacity: usize) -> Self {
Self {
head: Cell::new(None),
tail: Cell::new(None),
len: Cell::new(0),
capacity,
_pin: PhantomPinned,
_thread: ThreadBound::NEW,
}
}
pub fn get_pinned(queues: Pin<&[Self]>, index: usize) -> Option<Pin<&Self>> {
let queue = queues.get_ref().get(index)?;
Some(unsafe { Pin::new_unchecked(queue) })
}
fn contains<'d>(self: Pin<&Self>, waiter: Pin<&Waiter<'d>>) -> bool {
waiter.queue.get() == Some(NonNull::from(self.get_ref()))
}
pub fn can_register<'d>(self: Pin<&Self>, waiter: Pin<&Waiter<'d>>) -> bool {
self.contains(waiter) || self.len.get() < self.capacity
}
#[must_use]
pub fn try_register<'d>(
self: Pin<&Self>,
waiter: Pin<&Waiter<'d>>,
context: Pin<&Context<'_, 'd>>,
) -> bool {
self.try_register_waker(waiter, unsafe { context.waker_unchecked() })
}
#[doc(hidden)]
pub fn try_register_waker<'d>(
self: Pin<&Self>,
waiter: Pin<&Waiter<'d>>,
waker: Waker<'d>,
) -> bool {
if self.contains(waiter) {
waiter.wake.set(Some(waker));
return true;
}
if self.len.get() == self.capacity {
return false;
}
waiter.unregister();
debug_assert!(waiter.previous.get().is_none());
debug_assert!(waiter.next.get().is_none());
let queue = NonNull::from(self.get_ref());
let node = NonNull::from(waiter.get_ref()).cast::<Waiter<'static>>();
let previous = self.tail.get();
waiter.queue.set(Some(queue));
waiter.previous.set(previous);
waiter.wake.set(Some(waker));
if let Some(previous) = previous {
unsafe { previous.as_ref() }.next.set(Some(node));
} else {
self.head.set(Some(node));
}
self.tail.set(Some(node));
self.len.set(self.len.get() + 1);
true
}
fn unlink<'d>(self: Pin<&Self>, waiter: NonNull<Waiter<'d>>) -> Option<Waker<'d>> {
let waiter = unsafe { waiter.as_ref() };
if waiter.queue.get() != Some(NonNull::from(self.get_ref())) {
return None;
}
let previous = waiter.previous.take();
let next = waiter.next.take();
if let Some(previous) = previous {
unsafe { previous.as_ref() }.next.set(next);
} else {
self.head.set(next);
}
if let Some(next) = next {
unsafe { next.as_ref() }.previous.set(previous);
} else {
self.tail.set(previous);
}
waiter.queue.set(None);
self.len.set(self.len.get() - 1);
waiter.wake.take()
}
fn pop_next(self: Pin<&Self>, wake: bool) -> bool {
let Some(node) = self.head.get() else {
return false;
};
let waiter = node.cast::<Waiter<'_>>();
let waker = self
.unlink(waiter)
.expect("dope-fiber: linked waiter missing its queue");
if wake {
waker.wake();
}
true
}
pub fn wake(self: Pin<&Self>) {
while self.pop_next(true) {}
}
pub fn wake_one(self: Pin<&Self>) {
self.pop_next(true);
}
pub fn len(&self) -> usize {
self.len.get()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Drop for WaitQueue {
fn drop(&mut self) {
let queue = unsafe { Pin::new_unchecked(&*self) };
while queue.pop_next(false) {}
}
}
impl<'d> Waiter<'d> {
pub const fn new() -> Self {
Self {
queue: Cell::new(None),
previous: Cell::new(None),
next: Cell::new(None),
wake: Cell::new(None),
_pin: PhantomPinned,
_thread: ThreadBound::NEW,
}
}
pub fn unregister(self: Pin<&Self>) -> bool {
let Some(queue) = self.queue.get() else {
return false;
};
unsafe { Pin::new_unchecked(queue.as_ref()) }
.unlink(NonNull::from(self.get_ref()))
.is_some()
}
pub fn is_registered(&self) -> bool {
self.queue.get().is_some()
}
}
impl Default for Waiter<'_> {
fn default() -> Self {
Self::new()
}
}
impl Drop for Waiter<'_> {
fn drop(&mut self) {
let Some(queue) = self.queue.get() else {
return;
};
let queue = unsafe { Pin::new_unchecked(queue.as_ref()) };
let _ = queue.unlink(NonNull::from(&*self));
}
}