asynchronousrustwebassembly

How can I poll a Future from a Rust WASM app?


I am playing with WebXR and I would like to draw something that I fetch from the web.

The reqwest library is async and returns Futures. In my rendering loop I do not want to block until the Future is ready. I would rather poll it and skip it if it is not yet ready.

There is a poll(ctx) method on the Future trait, but I am not familiar enough with the async system to know what ctx to pass to it. There is a crate wasm-bindgen-futures used to convert between Rust Futures and js Promises, but I'm not sure if I can get a relevant ctx out of it.


Solution

  • If you want to, you can periodically poll a future until it completes — and in this case, you do not need to provide any specific Context, and can just use a noop waker. (A non-trivial waker is only needed if you are planning to not poll the future until woken, and Context currently has no stable properties besides the waker.) This can be a convenient integration with a game loop.

    use std::future::Future;
    use std::task;
    
    // ...
    
        let mut future = Box::pin(some_async_function());
    
    // ...
    
    fn step_my_game_loop(...) {
        // ...
        let mut cx = task::Context::from_waker(task::Waker::noop());
        match future.as_mut().poll(&mut cx) { 
            // ...
        }
        // ...
    }
    

    (Adapted from the documentation example.)

    A detail that I'm not including in this example code: once the future reports Ready, you must stop polling that particular future; the consequences of polling a future after it completes are unspecified (and usually a panic).

    However, polling inside your loop has a disadvantage: the future can only make progress (that is, perform its computation) during the time while you are polling it. (For a network request, this may be largely insignificant compared to the idle time waiting for a response, but there’s still some CPU time spent assembling the request and parsing the response.) It may be better to run the future as a separate task. In the wasm-bindgen environment, that would look like:

    use futures_channel::oneshot;
    use wasm_bindgen_futures::spawn_local;
    
    // ...
    
        let (sender, receiver) = oneshot::channel::<ReturnValueOfTheFunction>();
    
        spawn_local(async move {
            let output = some_async_function().await;
            // _ to ignore error in case the receiver was dropped
            let _ = sender.send(output);
        });
    
    // ...
    
    fn step_my_game_loop(...) {
        // ...
        match receiver.try_recv() { 
            // ...
        }
        // ...
    }
    

    When run this way, the async function will run separately on the browser's event loop, and your game loop will only see the final result. This means the future can make progress during the times your game loop is idling waiting for the next frame.