use core::{
error::Error,
fmt,
future::Future,
pin::Pin,
task::{Context, Poll},
};
type FixedFuture<'a> = Pin<&'a mut (dyn Future<Output = ()> + 'a)>;
type FixedSendFuture<'a> = Pin<&'a mut (dyn Future<Output = ()> + Send + 'a)>;
fn erase_future<'a, F>(future: Pin<&'a mut F>) -> FixedFuture<'a>
where
F: Future<Output = ()> + 'a,
{
future
}
fn erase_send_future<'a, F>(future: Pin<&'a mut F>) -> FixedSendFuture<'a>
where
F: Future<Output = ()> + Send + 'a,
{
future
}
#[must_use]
pub struct CapacityError<T> {
task: T,
}
impl<T> CapacityError<T> {
#[inline]
pub fn into_inner(self) -> T {
self.task
}
#[inline]
pub const fn task(&self) -> &T {
&self.task
}
}
impl<T> fmt::Debug for CapacityError<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CapacityError")
.finish_non_exhaustive()
}
}
impl<T> fmt::Display for CapacityError<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("the fixed async scope is at capacity")
}
}
impl<T> Error for CapacityError<T> {}
trait CleanupFuture {
fn poll_cleanup(&mut self, context: &mut Context<'_>) -> Poll<()>;
}
impl<F> CleanupFuture for Pin<&mut F>
where
F: Future<Output = ()> + ?Sized,
{
fn poll_cleanup(&mut self, context: &mut Context<'_>) -> Poll<()> {
self.as_mut().poll(context)
}
}
struct Registry<T, const N: usize> {
tasks: [Option<T>; N],
len: usize,
}
impl<T, const N: usize> Registry<T, N> {
const fn new() -> Self {
Self {
tasks: [const { None }; N],
len: 0,
}
}
const fn len(&self) -> usize {
self.len
}
const fn is_empty(&self) -> bool {
self.len == 0
}
const fn is_full(&self) -> bool {
self.len == N
}
fn push(&mut self, task: T) {
debug_assert!(!self.is_full());
self.tasks[self.len] = Some(task);
self.len += 1;
}
fn clear(&mut self) {
while self.len > 0 {
self.len -= 1;
self.tasks[self.len] = None;
}
}
fn poll_all(&mut self, context: &mut Context<'_>) -> Poll<()>
where
T: CleanupFuture,
{
loop {
let Some(index) = self.len.checked_sub(1) else {
return Poll::Ready(());
};
let mut future = self.tasks[index]
.take()
.expect("an occupied fixed scope slot contains a future");
self.len = index;
match future.poll_cleanup(context) {
Poll::Pending => {
self.tasks[index] = Some(future);
self.len = index + 1;
return Poll::Pending;
}
Poll::Ready(()) => {}
}
}
}
}
fn try_store<T, U, const N: usize>(
registry: &mut Registry<T, N>,
task: U,
erase: impl FnOnce(U) -> T,
) -> Result<(), CapacityError<U>> {
if registry.is_full() {
return Err(CapacityError { task });
}
registry.push(erase(task));
Ok(())
}
#[must_use = "call run().await or finish().await to execute registered cleanup futures"]
pub struct FixedAsyncScope<'a, const N: usize> {
registry: Registry<FixedFuture<'a>, N>,
}
#[must_use = "futures do nothing unless polled or awaited"]
pub struct Run<'scope, 'task, const N: usize> {
scope: &'scope mut FixedAsyncScope<'task, N>,
}
impl<const N: usize> Future for Run<'_, '_, N> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
self.scope.registry.poll_all(context)
}
}
impl<'a, const N: usize> FixedAsyncScope<'a, N> {
#[inline]
pub const fn new() -> Self {
Self {
registry: Registry::new(),
}
}
#[inline]
pub fn try_defer<F>(
&mut self,
future: Pin<&'a mut F>,
) -> Result<(), CapacityError<Pin<&'a mut F>>>
where
F: Future<Output = ()> + 'a,
{
try_store(&mut self.registry, future, erase_future::<F>)
}
#[inline]
pub const fn len(&self) -> usize {
self.registry.len()
}
#[inline]
pub const fn capacity(&self) -> usize {
N
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.registry.is_empty()
}
#[inline]
pub const fn is_full(&self) -> bool {
self.registry.is_full()
}
pub fn clear(&mut self) {
self.registry.clear();
}
#[inline]
pub fn run(&mut self) -> Run<'_, 'a, N> {
Run { scope: self }
}
#[inline]
pub async fn finish(mut self) {
self.run().await;
}
}
impl<const N: usize> Default for FixedAsyncScope<'_, N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> fmt::Debug for FixedAsyncScope<'_, N> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("FixedAsyncScope")
.field("pending", &self.len())
.field("capacity", &N)
.finish()
}
}
#[must_use = "call run().await or finish().await to execute registered cleanup futures"]
pub struct FixedSendAsyncScope<'a, const N: usize> {
registry: Registry<FixedSendFuture<'a>, N>,
}
#[must_use = "futures do nothing unless polled or awaited"]
pub struct FixedSendRun<'scope, 'task, const N: usize> {
scope: &'scope mut FixedSendAsyncScope<'task, N>,
}
impl<const N: usize> Future for FixedSendRun<'_, '_, N> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
self.scope.registry.poll_all(context)
}
}
impl<'a, const N: usize> FixedSendAsyncScope<'a, N> {
#[inline]
pub const fn new() -> Self {
Self {
registry: Registry::new(),
}
}
#[inline]
pub fn try_defer<F>(
&mut self,
future: Pin<&'a mut F>,
) -> Result<(), CapacityError<Pin<&'a mut F>>>
where
F: Future<Output = ()> + Send + 'a,
{
try_store(&mut self.registry, future, erase_send_future::<F>)
}
#[inline]
pub const fn len(&self) -> usize {
self.registry.len()
}
#[inline]
pub const fn capacity(&self) -> usize {
N
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.registry.is_empty()
}
#[inline]
pub const fn is_full(&self) -> bool {
self.registry.is_full()
}
pub fn clear(&mut self) {
self.registry.clear();
}
#[inline]
pub fn run(&mut self) -> FixedSendRun<'_, 'a, N> {
FixedSendRun { scope: self }
}
#[inline]
pub async fn finish(mut self) {
self.run().await;
}
}
impl<const N: usize> Default for FixedSendAsyncScope<'_, N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> fmt::Debug for FixedSendAsyncScope<'_, N> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("FixedSendAsyncScope")
.field("pending", &self.len())
.field("capacity", &N)
.finish()
}
}