use core::cell::Cell;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
use crate::rt::task::Id;
pub const BUDGET: u8 = 128;
pub(crate) const UNBUDGETED: u64 = u64::MAX;
#[inline(always)]
fn left() -> &'static Cell<u64> {
&crate::rt::context::local().budget
}
#[derive(Clone, Copy)]
pub(crate) struct Entered {
left: u64,
task: Option<Id>,
}
#[inline(always)]
pub(crate) fn enter_task(id: Id) -> Entered {
let here = crate::rt::context::local();
Entered {
left: here.budget.replace(u64::from(BUDGET)),
task: here.task.replace(Some(id)),
}
}
#[inline(always)]
pub(crate) fn leave_task(entered: Entered) {
let here = crate::rt::context::local();
here.budget.set(entered.left);
here.task.set(entered.task);
}
#[inline]
pub(crate) fn current_task() -> Option<Id> {
crate::rt::context::local().task.get()
}
#[inline]
pub(crate) fn enter() -> u64 {
left().replace(u64::from(BUDGET))
}
#[inline]
pub(crate) fn leave(previous: u64) {
left().set(previous);
}
#[inline]
pub(crate) fn outside<T>(f: impl FnOnce() -> T) -> T {
let left = left();
let saved = left.replace(UNBUDGETED);
let result = f();
left.set(saved);
result
}
#[inline(always)]
pub(crate) fn try_spend() -> bool {
try_spend_in(left())
}
#[inline(always)]
pub(crate) fn try_spend_in(left: &Cell<u64>) -> bool {
let (rest, spent) = left.get().overflowing_sub(1);
left.set(rest);
if spent {
was_spent();
}
!spent
}
#[cold]
#[inline(never)]
fn was_spent() {
left().set(0);
}
#[inline(always)]
pub(crate) fn spend(waker: &Waker) -> bool {
try_spend() || refused(waker)
}
#[inline(always)]
pub(crate) fn spend_in(left: &Cell<u64>, waker: &Waker) -> bool {
try_spend_in(left) || refused(waker)
}
#[cold]
#[inline(never)]
pub(crate) fn refused(waker: &Waker) -> bool {
crate::rt::counters::budget_yield();
waker.wake_by_ref();
false
}
pub(crate) async fn yield_spent() {
crate::rt::counters::budget_yield();
crate::task::yield_now().await;
}
#[inline]
pub(crate) fn exhausted(waker: &Waker) -> bool {
left().get() == 0 && !refused(waker)
}
#[inline(always)]
pub(crate) fn spent_one() {
let left = left();
let rest = left.get();
debug_assert_ne!(rest, 0, "spent without a look at the budget");
left.set(rest.wrapping_sub(1));
}
#[inline]
fn refund() {
let left = left();
left.set(left.get().saturating_add(1));
}
#[inline(always)]
pub(crate) fn poll_budget<T>(
cx: &mut Context<'_>,
poll: impl FnOnce(&mut Context<'_>) -> Poll<T>,
) -> Poll<T> {
if !try_spend() && !refused(cx.waker()) {
return Poll::Pending;
}
let result = poll(cx);
if result.is_pending() {
refund();
}
result
}
pub fn has_budget_remaining() -> bool {
left().get() != 0
}
pub async fn consume_budget() {
core::future::poll_fn(|cx| poll_budget(cx, |_| Poll::Ready(()))).await
}
pub fn unconstrained<F: Future>(future: F) -> Unconstrained<F> {
Unconstrained { future }
}
#[derive(Debug)]
#[must_use = "futures do nothing unless polled"]
pub struct Unconstrained<F> {
future: F,
}
impl<F: Future> Future for Unconstrained<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
let future = unsafe { self.map_unchecked_mut(|this| &mut this.future) };
outside(|| future.poll(cx))
}
}
pub fn cooperative<F: Future>(future: F) -> Cooperative<F> {
Cooperative { future }
}
#[derive(Debug)]
#[must_use = "futures do nothing unless polled"]
pub struct Cooperative<F> {
future: F,
}
impl<F: Future> Future for Cooperative<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
let future = unsafe { self.map_unchecked_mut(|this| &mut this.future) };
poll_budget(cx, |cx| future.poll(cx))
}
}