use alloc::{boxed::Box, vec::Vec};
use core::{
any::Any,
future::Future,
pin::Pin,
task::{
Context,
Poll::{self, Pending, Ready},
Waker,
},
};
use crate::{
portal, run,
sys::{self, Command},
tls::Local,
};
struct State {
queue: Vec<Command>,
pending: Vec<Option<Waker>>,
drops: Vec<Box<dyn Any>>,
}
static STATE: Local<State> = Local::new(State {
queue: Vec::new(),
pending: Vec::new(),
drops: Vec::new(),
});
#[inline(always)]
fn add_waker() -> usize {
STATE.with(|state| {
let waker = run::new_waker();
if let Some(index) = state.pending.iter().position(|w| w.is_none()) {
state.pending[index] = Some(waker);
index
} else {
let index = state.pending.len();
state.pending.push(Some(waker));
index
}
})
}
#[inline(never)]
pub fn defer(mut item: Box<dyn Any>) -> *mut () {
let ptr: *mut _ = &mut *item;
STATE.with(|state| state.drops.push(item));
ptr.cast()
}
#[inline(never)]
pub unsafe fn queue(command: Command) {
STATE.with(|state| state.queue.push(command));
}
#[inline(never)]
pub fn flush() {
STATE.with(|state| {
unsafe {
portal::ready_list(
sys::ar(state.queue.len(), state.queue.as_ptr()),
|ready_list| {
for ready in ready_list {
if *ready == usize::MAX {
continue;
}
if let Some(waker) = state.pending[*ready].take() {
waker.wake();
}
}
state.queue.clear();
state.drops.clear();
},
)
}
});
}
pub unsafe fn until(command: Command) {
queue(command);
flush();
}
#[inline(always)]
pub unsafe fn execute<T>(channel: u32, data: &T) -> impl Future<Output = ()> {
let data: *const T = data;
let data = data.cast();
let size = core::mem::size_of::<T>();
execute_erased(channel, size, data)
}
#[inline(never)]
unsafe fn execute_erased(
channel: u32,
size: usize,
data: *const (),
) -> impl Future<Output = ()> {
let ready = add_waker();
until(Command {
ready,
channel,
size,
data,
});
Request(ready)
}
struct Request(usize);
impl Future for Request {
type Output = ();
#[inline(never)]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
STATE.with(|state| {
if let Some(ref mut waker) = state.pending[self.0] {
*waker = cx.waker().clone();
Pending
} else {
Ready(())
}
})
}
}