Skip to content

Commit 7d800dd

Browse files
committed
release 0.8.1
1 parent b2e0bd7 commit 7d800dd

File tree

6 files changed

+125
-23
lines changed

6 files changed

+125
-23
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [0.8.1]
4+
5+
- add component observe
6+
37
## [0.8.0]
48

59
- bump bevy version to 0.16.0

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "bevy_http_client"
33
description = "A simple HTTP client for Bevy"
4-
version = "0.8.0"
4+
version = "0.8.1"
55
edition = "2024"
66
readme = "README.md"
77
homepage = "https://crates.io/crates/bevy_http_client"

examples/observer.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use bevy::{prelude::*, time::common_conditions::on_timer};
2+
3+
use bevy_http_client::prelude::*;
4+
5+
fn main() {
6+
App::new()
7+
.add_plugins((MinimalPlugins, HttpClientPlugin))
8+
.add_systems(Startup, init_request)
9+
.add_systems(
10+
Update,
11+
send_request.run_if(on_timer(std::time::Duration::from_secs(1))),
12+
)
13+
.run();
14+
}
15+
16+
#[derive(Component)]
17+
struct IpRequestMarker;
18+
19+
fn init_request(mut commands: Commands) {
20+
let entity = commands.spawn(IpRequestMarker).id();
21+
let request = HttpClient::new_with_entity(entity).get("https://api.ipify.org");
22+
commands
23+
.entity(entity)
24+
.insert(request)
25+
.observe(handle_response)
26+
.observe(handle_error);
27+
}
28+
29+
fn send_request(
30+
clients: Query<&HttpClient, With<IpRequestMarker>>,
31+
mut ev_request: EventWriter<HttpRequest>,
32+
) {
33+
let requests = clients
34+
.iter()
35+
.map(|c| c.clone().build())
36+
.collect::<Vec<_>>();
37+
38+
ev_request.write_batch(requests);
39+
}
40+
41+
fn handle_response(response: Trigger<HttpResponse>) {
42+
println!("response: {:?}", response.text());
43+
}
44+
45+
fn handle_error(error: Trigger<HttpResponseError>) {
46+
println!("Error retrieving IP: {}", error.err);
47+
}

examples/typed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn main() {
2020
}
2121

2222
fn send_request(mut ev_request: EventWriter<TypedRequest<IpInfo>>) {
23-
ev_request.send(
23+
ev_request.write(
2424
HttpClient::new()
2525
.get("https://api.ipify.org?format=json")
2626
.with_type::<IpInfo>(),

src/lib.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bevy_app::{App, Plugin, Update};
44
use bevy_derive::Deref;
55
use bevy_ecs::{prelude::*, world::CommandQueue};
66
use bevy_tasks::IoTaskPool;
7-
use crossbeam_channel::Receiver;
7+
use crossbeam_channel::{Receiver, Sender};
88
use ehttp::{Headers, Request, Response};
99

1010
use crate::prelude::TypedRequest;
@@ -130,6 +130,27 @@ impl HttpClient {
130130
Self::default()
131131
}
132132

133+
/// his method is used to create a new `HttpClient` instance with `Entity`.
134+
///
135+
/// # Arguments
136+
///
137+
/// * `entity`: Target Entity
138+
///
139+
/// returns: HttpClient
140+
///
141+
/// # Examples
142+
///
143+
/// ```
144+
/// let e = commands.spawn().id();
145+
/// let http_client = HttpClient::new_with_entity(e)
146+
/// ```
147+
pub fn new_with_entity(entity: Entity) -> Self {
148+
Self {
149+
from_entity: Some(entity),
150+
..Default::default()
151+
}
152+
}
153+
133154
/// This method is used to create a `GET` HTTP request.
134155
///
135156
/// # Arguments
@@ -489,12 +510,16 @@ impl HttpResponseError {
489510

490511
/// task for ehttp response result
491512
#[derive(Component, Debug)]
492-
pub struct RequestTask(pub Receiver<CommandQueue>);
513+
pub struct RequestTask {
514+
tx: Sender<CommandQueue>,
515+
rx: Receiver<CommandQueue>,
516+
}
493517

494518
fn handle_request(
495519
mut commands: Commands,
496520
mut req_res: ResMut<HttpClientSetting>,
497521
mut requests: EventReader<HttpRequest>,
522+
q_tasks: Query<&RequestTask>,
498523
) {
499524
let thread_pool = IoTaskPool::get();
500525
for request in requests.read() {
@@ -505,7 +530,8 @@ fn handle_request(
505530
} else {
506531
(commands.spawn_empty().id(), false)
507532
};
508-
let (tx, rx) = crossbeam_channel::bounded(1);
533+
534+
let tx = get_channel(&mut commands, q_tasks, entity);
509535

510536
thread_pool
511537
.spawn(async move {
@@ -518,19 +544,20 @@ fn handle_request(
518544
world
519545
.get_resource_mut::<Events<HttpResponse>>()
520546
.unwrap()
521-
.send(HttpResponse(res));
547+
.send(HttpResponse(res.clone()));
548+
world.trigger_targets(HttpResponse(res), entity);
522549
}
523550
Err(e) => {
524551
world
525552
.get_resource_mut::<Events<HttpResponseError>>()
526553
.unwrap()
527554
.send(HttpResponseError::new(e.to_string()));
555+
world
556+
.trigger_targets(HttpResponseError::new(e.to_string()), entity);
528557
}
529558
}
530559

531-
if has_from_entity {
532-
world.entity_mut(entity).remove::<RequestTask>();
533-
} else {
560+
if !has_from_entity {
534561
world.entity_mut(entity).despawn();
535562
}
536563
});
@@ -539,19 +566,37 @@ fn handle_request(
539566
})
540567
.detach();
541568

542-
commands.entity(entity).insert(RequestTask(rx));
543569
req_res.current_clients += 1;
544570
}
545571
}
546572
}
547573

574+
fn get_channel(
575+
commands: &mut Commands,
576+
q_tasks: Query<&RequestTask>,
577+
entity: Entity,
578+
) -> Sender<CommandQueue> {
579+
if let Ok(task) = q_tasks.get(entity) {
580+
task.tx.clone()
581+
} else {
582+
let (tx, rx) = crossbeam_channel::bounded(5);
583+
584+
commands.entity(entity).insert(RequestTask {
585+
tx: tx.clone(),
586+
rx: rx.clone(),
587+
});
588+
589+
tx
590+
}
591+
}
592+
548593
fn handle_tasks(
549594
mut commands: Commands,
550595
mut req_res: ResMut<HttpClientSetting>,
551596
mut request_tasks: Query<&RequestTask>,
552597
) {
553598
for task in request_tasks.iter_mut() {
554-
if let Ok(mut command_queue) = task.0.try_recv() {
599+
if let Ok(mut command_queue) = task.rx.try_recv() {
555600
commands.append(&mut command_queue);
556601
req_res.current_clients -= 1;
557602
}

src/typed.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use ehttp::{Request, Response};
66
use serde::Deserialize;
77
use std::marker::PhantomData;
88

9-
use crate::{HttpClientSetting, RequestTask};
9+
use crate::{HttpClientSetting, RequestTask, get_channel};
1010

1111
pub trait HttpTypedRequestTrait {
1212
/// Registers a new request type `T` to the application.
@@ -29,13 +29,13 @@ pub trait HttpTypedRequestTrait {
2929
/// ```
3030
/// app.register_request_type::<MyRequestType>();
3131
/// ```
32-
fn register_request_type<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
32+
fn register_request_type<T: for<'a> Deserialize<'a> + Send + Sync + 'static + Clone>(
3333
&mut self,
3434
) -> &mut Self;
3535
}
3636

3737
impl HttpTypedRequestTrait for App {
38-
fn register_request_type<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
38+
fn register_request_type<T: for<'a> Deserialize<'a> + Send + Sync + 'static + Clone>(
3939
&mut self,
4040
) -> &mut Self {
4141
self.add_event::<TypedRequest<T>>();
@@ -148,10 +148,11 @@ impl<T> TypedResponseError<T> {
148148
}
149149

150150
/// A system that handles typed HTTP requests.
151-
fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
151+
fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + Clone + 'static>(
152152
mut commands: Commands,
153153
mut req_res: ResMut<HttpClientSetting>,
154154
mut requests: EventReader<TypedRequest<T>>,
155+
q_tasks: Query<&RequestTask>,
155156
) {
156157
let thread_pool = IoTaskPool::get();
157158
for request in requests.read() {
@@ -162,8 +163,7 @@ fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
162163
(commands.spawn_empty().id(), false)
163164
};
164165
let req = request.request.clone();
165-
let (tx, rx) = crossbeam_channel::bounded(1);
166-
166+
let tx = get_channel(&mut commands, q_tasks, entity);
167167
thread_pool
168168
.spawn(async move {
169169
let mut command_queue = CommandQueue::default();
@@ -181,7 +181,10 @@ fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
181181
world
182182
.get_resource_mut::<Events<TypedResponse<T>>>()
183183
.unwrap()
184-
.send(TypedResponse { inner });
184+
.send(TypedResponse {
185+
inner: inner.clone(),
186+
});
187+
world.trigger_targets(TypedResponse { inner }, entity);
185188
}
186189
// deserialize error, send error + response
187190
Err(e) => {
@@ -190,8 +193,14 @@ fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
190193
.unwrap()
191194
.send(
192195
TypedResponseError::new(e.to_string())
193-
.response(response),
196+
.response(response.clone()),
194197
);
198+
199+
world.trigger_targets(
200+
TypedResponseError::<T>::new(e.to_string())
201+
.response(response),
202+
entity,
203+
);
195204
}
196205
}
197206
}
@@ -203,9 +212,7 @@ fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
203212
}
204213
}
205214

206-
if has_from_entity {
207-
world.entity_mut(entity).remove::<RequestTask>();
208-
} else {
215+
if !has_from_entity {
209216
world.entity_mut(entity).despawn();
210217
}
211218
});
@@ -214,7 +221,6 @@ fn handle_typed_request<T: for<'a> Deserialize<'a> + Send + Sync + 'static>(
214221
})
215222
.detach();
216223

217-
commands.entity(entity).insert(RequestTask(rx));
218224
req_res.current_clients += 1;
219225
}
220226
}

0 commit comments

Comments
 (0)