bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.1k stars 3.45k forks source link

Async TaskPool with Http Request Example Wanted #6903

Open miketwenty1 opened 1 year ago

miketwenty1 commented 1 year ago

How can Bevy's documentation be improved?

I'm suggesting something that may be incorrect! But it seems that an async IoTaskPool example would be ideal for helping teach people how to bring in/send out external data to/from a webserver. This would be in the case where using sockets would be overkill.

As far as I understand the main example documentation around IoTaskPool is the test within this lib.rs. (which for myself, a bevy and rust noob, was too sparse). https://github.com/bevyengine/bevy/blob/main/crates/bevy_core/src/lib.rs#L145

Also the AsyncComputeTaskPool example is too specific to be useful for understanding channels/http requests. https://github.com/bevyengine/bevy/blob/main/examples/async_tasks/async_compute.rs

It would be nice to see an example where the bevy app calls out to a webserver using some sort of user input, and then the webserver returns data that is rendered in the bevy game. Using something like reqwest (if applicable). I believe channels would also be needed for this example? This would really paint the full picture on how to currently handle these sort of network io tasks for a bevy game. I'm guessing many more people interested in WASM bevy development would be able to utilize this example.

Ohh did I mention the motivation for this example is for WASM targets? :)

Example

I have managed to create a PoC for this, but it's quite barbaric and I'm guessing someone could make a really slick example of this idea and put it up as an official example. https://gist.github.com/miketwenty1/baa1634fe558186e606c02932b8f37c8

Also note, this seems to work fine with either IoTaskPool or AsyncComputeTaskPool So I'm sort of just guessing IoTaskPool is more appropriate in this case.

mockersf commented 1 year ago

It would be nice to see an example where the bevy app calls out to a webserver

There are two "difficulties" with an http request example, though I agree having this example would be great:

As an extra, having it work in both wasm and native could make it harder

miketwenty1 commented 1 year ago

We avoid dependencies just for tests that are not present in the rest of the engine, that takes out all dependencies that could perfom an http query

So this means Bevy wouldn't post an example using reqwest? If I understand this correctly? Would it be acceptable to use a more primitive-y method via https://rustwasm.github.io/wasm-bindgen/examples/fetch.html ?

We would need a stable external api that we're sure enough won't change without a notice, won't fail due to server error,

I think a good example resource to pull would be data from like from crates.io / the bevy website / github.com/bevyengine or something similar.. just so it's in the spirit of a web resource related to bevy.

As an extra, having it work in both wasm and native could make it harder

Agreed, but the main concern I have with this statement isn't the hardness of it.. it's whether or not the tokio runtime would be acceptable for the non wasm build.. but that kinda goes back to your first point. that's an external dependency. Not sure if bevy already has something.

james7132 commented 1 year ago

It should be noted that reqwest depends on hyper which depends on tokio which is incompatible with our current async executor since tokio does not work on WASM.

miketwenty1 commented 1 year ago

@james7132 the example gist on the original post above works with target WASM. test for yourself.

Edit: added Cargo.toml to the gist to help people run if they doubt the code. you need to run cargo run --target wasm32-unknown-unknown will need to point to a valid api endpoint though.

Will need

[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"

in .cargo/config.toml as well.

mockersf commented 1 year ago

So this means Bevy wouldn't post an example using reqwest?

Yeah, reqwest is everything but light...

Would it be acceptable to use a more primitive-y method via https://rustwasm.github.io/wasm-bindgen/examples/fetch.html ?

Most of those are already dependencies in Bevy, but then that would work only in wasm.

it's whether or not the tokio runtime would be acceptable for the non wasm build..

No. Aside from the dependency issue, having tokio means having two tasks ecosystem handling their threads at the same time, not the best to show in an example.

miketwenty1 commented 1 year ago

@mockersf would there be consideration for wasm only target examples? or does every bevy example HAVE to support multiple targets?

mockersf commented 1 year ago

I would rather avoid having wasm only examples... I'm not a fan of https://crates.io/crates/ehttp API but it's the crate with the smallest dependency set (that I know of) that handles both native and WASM. Maybe an example with it could be added...

dlight commented 1 year ago

@mockersf ehttp uses ureq on native, which has blocking I/O underneath and requires spawning a new thread for each request. Which isn't usually a problem but it's about the same overhead as a single threaded Tokio executor (but, okay, with a smaller dependency footprint).

I think that, in general, having an example showing integration with Tokio or other executor would be very nice, even if running things on Tokio shouldn't be the first option. For example, relm4 (a GUI framework based on GTK4) features its own executor (which runs tasks on GTK's event loop) but has examples showing that integration with Tokio is possible.

Hopefully the async ecosystem in Rust will evolve to be more executor agnostic. As of today, the only executor agnostic http client I can find is isahc which besides not working in wasm, also spawns a thread

mockersf commented 1 year ago

Yeah ehhtp/ureq is not perfect either...

having an example showing integration with Tokio or other executor would be very nice

Yes! but... maybe not in the main Bevy repo. Maybe in the cheat book? @inodentry would that be something interesting for you? Or somewhere else where we could host more complex examples.

Why not in the main Bevy repo?

miketwenty1 commented 1 year ago

@mockersf would a feature for bevy_http or bevy_fetch make more sense.. to not increase the build time?

mockersf commented 1 year ago

I don't think so, the example would still need to have it enabled.

You could make a third party plugin to handle that nicely though (maybe with a less generic name)

miketwenty1 commented 1 year ago

@mockersf I don't understand this response

I don't think so, the example would still need to have it enabled.

What would be the issue with a bevy example utilizing a feature flag?

mockersf commented 1 year ago

If the example is requiring a feature flag and we don't enable it, it will not be updated with breaking changes and quickly fall out of sync.

miketwenty1 commented 1 year ago

I'm still not following. All examples are subject to falling out of sync with bevy updates, correct?

If a feature isn't maintained for some reason, I imagine the examples with that feature should be be removed. What is the thinking around a specific feature example not being maintained with the rest bevy examples?

james7132 commented 1 year ago

All examples are subject to falling out of sync with bevy updates, correct?

In our CI, we enforce that examples compile (and run) properly so that they're always in sync with potential breaking changes to the public API.

miketwenty1 commented 1 year ago

I'm guessing you would need multiple CI runs with different features enabled against the various example sets.

wanderrful commented 1 year ago

FYI @miketwenty1 I did an example of this back in October using the ehttp crate with Bevy: https://github.com/wanderrful/yet-another-bevy-project/blob/experiment-6/src/services/reqres.rs

In the /ui/experiment-6 folder you can see how it's used to call the API and use the response to populate a UI (the concerns of the UI are separated into different module files, so it's not all-in-one as most examples usually are).

jkb0o commented 1 year ago

There is promises implementation for bevy: https://github.com/jkb0o/pecs It supports http requests via ehttp out of the box:

commands.add(
  Promise::start(asyn!(_ => {
    info!("How large is is the Bevy main web page?");
      asyn::http::get("https://bevyengine.org")
    }))
    .then(asyn!(_, result => {
      match result {
        Ok(response) => info!("It is {} bytes!", response.bytes.len()),
        Err(err) => info!("Ahhh... something goes wrong: {err}")
      }
      Promise::pass()
    }))
);

Output:

41.627  INFO simple: How large is is the Bevy main web page?
41.715  INFO simple: It is 17759 bytes!

Doesn't solve the issue itself, but looks related to me.

Vrixyz commented 1 year ago

ehttp merged support for cross platform async, I think it's a strong candidate (https://github.com/emilk/ehttp/pull/25)

dimvoly commented 1 year ago

I struggled with this initially, so have created as simple of an example as I could. See gist linked below:

https://gist.github.com/dimvoly/3b41e864b44b4e2af3938f9f608a0665

Is uses the blocking variety of reqwest but at least in my use case I'm sending a very limited amount of requests so there's no need for me to initialize a runtime.

If you search around you can find that people have setup tokio runtimes/contexts for the non-blocking version if that's what you need.

I don't believe this example would work on WASM, but should get most people started with basic http stuff for non-WASM.

Hopefully helps someone.

miketwenty1 commented 1 year ago

@dimvoly the example in the original post has a working example with wasm

dimvoly commented 1 year ago

@miketwenty1 yes the original example is super useful in that regard

dlight commented 1 year ago

@dimvoly thanks for your Bevy 0.11 example!

It's a bit frustrating that one would write async move { .. } but then use reqwest::blocking inside that async block. Didn't reqwest::get("https://httpbin.org/get").await work?

dimvoly commented 1 year ago

@dlight Bit beyond my current understanding how to use the non-blocking variety (reqwest::get("https://httpbin.org/get").await) inside an IoTaskPool task. You'd need a tokio context which you'd have to spawn manually in your app start up somewhere. However, I suspect that isn't strictly compatible with IoTaskPool at present - see this issue for further discussion on .await inside TaskPool.

So from my understanding, for the example in my gist, the thread is blocked, but it's the thread in IoTaskPool which is not the main thread so your app doesn't freeze.

Leinnan commented 7 months ago

Is there any chance that in the near future we will have at least basic web request support in Bevy without using third party plugins? For now, I feel if I would use 3 plugins each one will implement request on their own leading to multiple possible source of bugs.

There is a nice and small third party plugin which implements requests using ehttp mentioned before: https://github.com/foxzool/bevy_http_client I even update it a little and make it possible to have TypedResponse which make it easy to parse results into structs, example here: https://github.com/Leinnan/bevy_http_client/blob/master/examples/typed.rs