Custom async loop

Hello :slight_smile:

I have a very basic macro I made

#[proc_macro_attribute]
pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let ident = item.clone().into_iter().nth(2).unwrap();
    quote! {
        static mut MAIN: Option<::core::pin::Pin<::alloc::boxed::Box<dyn Future<Output = ()>>>> = None;
        static mut ENDED: bool = false;

        #[unsafe(no_mangle)]
        extern "C" fn resume() {
            unsafe {
                if ENDED { return }

                if MAIN.is_none() {
                    MAIN = Some(::alloc::boxed::Box::pin($ident()));
                }

                let wk = ::core::task::Waker::noop();
                let mut cx = ::core::task::Context::from_waker(wk);

                match MAIN.as_mut().unwrap_unchecked().as_mut().poll(&mut cx) {
                    ::core::task::Poll::Ready(_) => ENDED = true,
                    ::core::task::Poll::Pending => {},
                }
            }
        }

        $item
    }
}

Which is used like this

#[rt::main]
async fn main() {
    loop {
        rt::sleep().await
    }
}

Resume is called by requestAnimationFrame in the browser, so I want to avoid freezing the entire browser tab, but still have a normal looking main function as Rust is of course really unhappy with managing this global coroutine-like behavior manually

sleep just returns an empty future.

I think I got something very wrong though since the browser tab very much freezes :sweat_smile:

If you mean it's simply async fn sleep() {} then this will not suspend your future but instead immediately complete it, resulting in the main future also never suspending.

You'll want something like futures_lite::yield_now instead (it's very few lines of code you can copy-paste if you don't want to pull in another dependency)

Oh I see, I assumed futures were not evaluated until the call to poll, but this makes more sense, thank you :slight_smile:

They do! What I meant by "immediately complete it" is that the very first poll will be a Poll::Complete. Then the main future will continue executing, running another iteration of the loop, immediately completing another sleep, and so on. That is, the first poll call for the main future will never return.