use bevy::prelude::*;
use bevy::tasks::AsyncComputeTaskPool;
pub use reqwest;
#[cfg(target_family = "wasm")]
use crossbeam_channel::{bounded, Receiver};
#[cfg(not(target_family = "wasm"))]
use {bevy::tasks::Task, futures_lite::future};
#[derive(Resource)]
pub struct ReqwestClient(pub reqwest::Client);
impl Default for ReqwestClient {
fn default() -> Self {
Self(reqwest::Client::new())
}
}
#[derive(Component, Deref)]
pub struct ReqwestRequest(pub Option<reqwest::Request>);
impl Into<ReqwestRequest> for reqwest::Request {
fn into(self) -> ReqwestRequest {
ReqwestRequest(Some(self))
}
}
#[cfg(target_family = "wasm")]
#[derive(Component, Deref)]
struct ReqwestInflight(pub Receiver<reqwest::Result<bytes::Bytes>>);
#[cfg(not(target_family = "wasm"))]
#[derive(Component, Deref)]
struct ReqwestInflight(pub Task<reqwest::Result<bytes::Bytes>>);
#[derive(Component, Deref)]
pub struct ReqwestBytesResult(pub reqwest::Result<bytes::Bytes>);
impl ReqwestBytesResult {
pub fn as_str(&self) -> Option<&str> {
match &self.0 {
Ok(string) => Some(std::str::from_utf8(&string).ok()?),
Err(_) => None,
}
}
pub fn as_string(&mut self) -> Option<String> {
Some(self.as_str()?.into())
}
pub fn deserialize_json<'de, T: serde::Deserialize<'de>>(&'de mut self) -> Option<T> {
serde_json::from_str(self.as_str()?).ok()
}
}
pub struct ReqwestPlugin;
impl Plugin for ReqwestPlugin {
fn build(&self, app: &mut App) {
if !app.world.contains_resource::<ReqwestClient>() {
app.init_resource::<ReqwestClient>();
}
app.add_system(Self::start_handling_requests);
app.add_system(Self::poll_inflight_requests_to_bytes);
}
}
impl ReqwestPlugin {
fn start_handling_requests(
mut commands: Commands,
http_client: ResMut<ReqwestClient>,
mut requests: Query<(Entity, &mut ReqwestRequest), Added<ReqwestRequest>>,
) {
let thread_pool = AsyncComputeTaskPool::get();
for (entity, mut request) in requests.iter_mut() {
bevy::log::debug!("Creating: {entity:?}");
if let Some(request) = request.0.take() {
let client = http_client.0.clone();
#[cfg(target_family = "wasm")]
let (tx, task) = bounded(1);
#[cfg(target_family = "wasm")]
thread_pool
.spawn(async move {
let r = client.execute(request).await;
let r = match r {
Ok(res) => res.bytes().await,
Err(r) => Err(r),
};
tx.send(r).ok();
})
.detach();
#[cfg(not(target_family = "wasm"))]
let task = {
thread_pool.spawn(async move {
#[cfg(not(target_family = "wasm"))]
let r = async_compat::Compat::new(async {
client.execute(request).await?.bytes().await
})
.await;
r
})
};
commands.entity(entity).insert(ReqwestInflight(task));
commands.entity(entity).remove::<ReqwestRequest>();
}
}
}
fn poll_inflight_requests_to_bytes(
mut commands: Commands,
mut requests: Query<(Entity, &mut ReqwestInflight), Without<ReqwestBytesResult>>,
) {
for (entity, mut request) in requests.iter_mut() {
bevy::log::debug!("polling: {entity:?}");
#[cfg(target_family = "wasm")]
if let Ok(result) = request.0.try_recv() {
commands
.entity(entity)
.insert(ReqwestBytesResult(result))
.remove::<ReqwestRequest>();
}
#[cfg(not(target_family = "wasm"))]
if let Some(result) = future::block_on(future::poll_once(&mut request.0)) {
commands
.entity(entity)
.insert(ReqwestBytesResult(result))
.remove::<ReqwestRequest>();
}
}
}
}