shakesbeare
shakesbeare

Reputation: 11

Request data from webserver at runtime Rust/Bevy/Wasm

I have a small game on my website and im trying to implement a leaderboard. For this to work, I need to fetch the high scores to compare the current score to and then post back the updated list when I’m done.

The gist of the issue is the lack of async options for wasm. I’m aware of crates like wasm-bindgen-futures, but am not sure how to actually utilize them in practice.

fn get_scores() -> … {
    let fut = reqwest::get(url);
    let res = bevy::tasks::block_on(response); // crashes at runtime
    let text = res.text(); // more async love needed here, omitted for brevity
    let highscore = serde_json::from_str(text);
    return highscore;
}

The problem is that Rust’s type system requires that the values be present. Now, wasm-binden-futures has spawn_local, but

  1. the async block which it executes can’t return a value so it isn’t particularly useful for fetching data from the server
  2. you can’t capture stack variables inside of it, which would be necessary for updating the high score resource within the game, given the first limitation

There’s also the option of using wasm-bindgen-futures to create a JavaScript promise, but I wasn’t sure how well that would interact with the wasm code.

future_to_promise seems like it might be able to coordinate information in the way that I need, but it seems like I’m still running into the issue of “Rust expects there to be a value here but there isn’t.”

I’ve toyed with the idea of handling the requests of data completely in JavaScript, but I’m not sure how to communicate that data back into the Bevy wasm app — again, with no guarantee that it’ll be ready by the time I need it.

It seems like a really solvable problem: I need some data from the server and I only need it later once the player has finished their game. I don’t necessarily need to block (which is impossible, anyway, I’ve learned) since I don’t need the data immediately. Except that the type system is demanding the value be there. It’s also perfectly acceptable for my program to pause execution without blocking and let the browser do other work while it waits, but I don’t know how to do that without an async runtime.

I wonder if there’s a way I can use Bevy’s event loop to repeatedly poll the futures myself. I’m just not fluent enough (read: not at all) with async Rust to be able to approach a problem like that.

Upvotes: 1

Views: 562

Answers (2)

Kristoffer
Kristoffer

Reputation: 486

If you are using bevy, I wrote https://crates.io/crates/bevy_mod_reqwest for exactly this purpose

This is the example on how to use it:

use std::time::Duration;

use bevy::{log::LogPlugin, prelude::*, time::common_conditions::on_timer};
use bevy_mod_reqwest::*;

fn send_requests(mut client: BevyReqwest) {
    let url = "https://bored-api.appbrewery.com/random";

    // use regular reqwest http calls, then poll them to completion.
    let reqwest_request = client.get(url).build().unwrap();

    client
        // Sends the created http request
        .send(reqwest_request)
        // The response from the http request can be reached using an observersystem
        .on_response(|trigger: Trigger<ReqwestResponseEvent>| {
            let response = trigger.event();
            let data = response.as_str();
            let status = response.status();
            // let headers = req.response_headers();
            bevy::log::info!("code: {status}, data: {data:?}");
        })
        // In case of request error, it can be reached using an observersystem
        .on_error(|trigger: Trigger<ReqwestErrorEvent>| {
            let e = &trigger.event().0;
            bevy::log::info!("error: {e:?}");
        });
}

fn main() {
    App::new()
        .add_plugins(MinimalPlugins)
        .add_plugins(LogPlugin::default())
        .add_plugins(ReqwestPlugin::default())
        .add_systems(
            Update,
            send_requests.run_if(on_timer(Duration::from_secs(5))),
        )
        .run();
}

Upvotes: 0

trust_nickol
trust_nickol

Reputation: 2114

If you want to run wasm in the browser, you can use wasm-bindgen/web-sys. This is suggested by reqwest; only if you don't target the browser, there is limited support of reqwest for wasm runtime.

So it would be easier to just use the fetch API provided by the browser. Here is an example for this.

To execute a async task with bevy, you can use the AsyncComputeTaskPool.

Upvotes: 1

Related Questions