use bevy_app::{App, PreUpdate};
use bevy_derive::Deref;
use bevy_ecs::{prelude::*, system::Commands, world::CommandQueue};
use bevy_tasks::IoTaskPool;
use ehttp::{Request, Response};
use serde::Deserialize;
use std::marker::PhantomData;
use crate::{get_channel, HttpClientSetting, RequestTask};
pub trait HttpTypedRequestTrait {
fn register_request_type<T: for<'a> Deserialize<'a> + Send + Sync + 'static + Clone>(
&mut self,
) -> &mut Self;
}
impl HttpTypedRequestTrait for App {
fn register_request_type<T: for<'a> Deserialize<'a> + Send + Sync + 'static + Clone>(
&mut self,
) -> &mut Self {
self.add_message::<TypedRequest<T>>();
self.add_message::<TypedResponse<T>>();
self.add_message::<TypedResponseError<T>>();
self.add_systems(PreUpdate, handle_typed_request::<T>);
self
}
}
#[derive(Debug, Message, Event)]
pub struct TypedRequest<T>
where
T: for<'a> Deserialize<'a> + 'static + Send + Sync,
{
pub from_entity: Option<Entity>,
pub request: Request,
inner: PhantomData<T>,
}
impl<T: 'static + Send + Sync + for<'a> serde::Deserialize<'a>> TypedRequest<T> {
pub fn new(request: Request, from_entity: Option<Entity>) -> Self {
TypedRequest {
from_entity,
request,
inner: PhantomData,
}
}
}
#[derive(Debug, Deref, Message, Event)]
pub struct TypedResponse<T>
where
T: Send + Sync + 'static + for<'a> Deserialize<'a>,
{
#[deref]
inner: T,
}
impl<T: Send + Sync + for<'a> serde::Deserialize<'a>> TypedResponse<T> {
pub fn into_inner(self) -> T {
self.inner
}
pub fn inner(&self) -> &T {
&self.inner
}
}
#[derive(Message, Event, Debug, Clone, Deref)]
pub struct TypedResponseError<T>
where
T: Send + Sync + 'static,
{
#[deref]
pub err: String,
pub response: Option<Response>,
phantom: PhantomData<T>,
}
impl<T: Send + Sync + 'static> TypedResponseError<T> {
pub fn new(err: String) -> Self {
Self {
err,
response: None,
phantom: Default::default(),
}
}
pub fn response(mut self, response: Response) -> Self {
self.response = Some(response);
self
}
}
#[derive(Debug, EntityEvent)]
pub struct HttpObserved<T: Send + Sync + 'static> {
pub entity: Entity,
pub event: T,
}
impl<T: Send + Sync + 'static> HttpObserved<T> {
pub fn new(entity: Entity, event: T) -> Self {
HttpObserved { entity, event }
}
pub fn inner(&self) -> &T {
&self.event
}
pub fn into_inner(self) -> T {
self.event
}
}
fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + Clone + 'static>(
mut commands: Commands,
mut req_res: ResMut<HttpClientSetting>,
mut requests: MessageReader<TypedRequest<T>>,
q_tasks: Query<&RequestTask>,
) {
let thread_pool = IoTaskPool::get();
for request in requests.read() {
if req_res.is_available() {
let (entity, has_from_entity) = if let Some(entity) = request.from_entity {
(entity, true)
} else {
(commands.spawn_empty().id(), false)
};
let req = request.request.clone();
let tx = get_channel(&mut commands, q_tasks, entity);
thread_pool
.spawn(async move {
let mut command_queue = CommandQueue::default();
let response = ehttp::fetch_async(req).await;
command_queue.push(move |world: &mut World| {
match response {
Ok(response) => {
let result: Result<T, _> =
serde_json::from_slice(response.bytes.as_slice());
match result {
Ok(inner) => {
if let Some(mut events) =
world.get_resource_mut::<Messages<TypedResponse<T>>>()
{
events.write(TypedResponse {
inner: inner.clone(),
});
} else {
bevy_log::error!(
"TypedResponse events resource not found"
);
}
world.trigger(HttpObserved::new(
entity,
TypedResponse { inner },
));
}
Err(e) => {
if let Some(mut messages) = world
.get_resource_mut::<Messages<TypedResponseError<T>>>()
{
messages.write(
TypedResponseError::new(e.to_string())
.response(response.clone()),
);
} else {
bevy_log::error!(
"TypedResponseError events resource not found"
);
}
world.trigger(HttpObserved::new(
entity,
TypedResponseError::<T>::new(e.to_string())
.response(response),
));
}
}
}
Err(e) => {
if let Some(mut events) =
world.get_resource_mut::<Messages<TypedResponseError<T>>>()
{
events.write(TypedResponseError::new(e.to_string()));
} else {
bevy_log::error!(
"TypedResponseError events resource not found"
);
}
}
}
if !has_from_entity {
world.entity_mut(entity).despawn();
}
});
if let Err(e) = tx.send(command_queue) {
bevy_log::error!("Failed to send command queue: {}", e);
}
})
.detach();
req_res.current_clients += 1;
}
}
}