use core::cell::Cell;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
pub const BUDGET: u8 = 128;
std::thread_local! {
static LEFT: Cell<Option<u8>> = const { Cell::new(None) };
}
pub(crate) fn enter() -> Option<u8> {
LEFT.try_with(|left| left.replace(Some(BUDGET)))
.ok()
.flatten()
}
pub(crate) fn leave(previous: Option<u8>) {
let _ = LEFT.try_with(|left| left.set(previous));
}
pub(crate) fn outside<T>(f: impl FnOnce() -> T) -> T {
let saved = LEFT.try_with(|left| left.replace(None)).ok().flatten();
let result = f();
let _ = LEFT.try_with(|left| left.set(saved));
result
}
pub(crate) fn poll_budget<T>(
cx: &mut Context<'_>,
poll: impl FnOnce(&mut Context<'_>) -> Poll<T>,
) -> Poll<T> {
let left = LEFT.try_with(Cell::get).ok().flatten();
match left {
Some(0) => {
cx.waker().wake_by_ref();
Poll::Pending
}
Some(n) => {
let _ = LEFT.try_with(|cell| cell.set(Some(n - 1)));
let result = poll(cx);
if result.is_pending() {
let _ = LEFT.try_with(|cell| cell.set(cell.get().map(|m| m.saturating_add(1))));
}
result
}
None => poll(cx),
}
}
pub fn has_budget_remaining() -> bool {
LEFT.try_with(Cell::get).ok().flatten() != Some(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) };
let previous = LEFT.try_with(|left| left.replace(None)).ok().flatten();
let result = future.poll(cx);
leave(previous);
result
}
}
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))
}
}