Skip to content

Commit 504b129

Browse files
committed
feat: change api to make more use cases easier
1 parent 1540df2 commit 504b129

File tree

3 files changed

+126
-126
lines changed

3 files changed

+126
-126
lines changed

examples/loop_yield_data_state.rs

+26-24
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// DataState type.
44

55
use anyhow::Context;
6-
use reqwest_cross::{fetch_plus, reqwest, Awaiting, DataState};
6+
use reqwest_cross::{fetch_plus, oneshot, reqwest, DataState};
77

88
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tokio"))]
99
#[tokio::main]
@@ -22,10 +22,6 @@ fn main() {
2222
}
2323

2424
async fn common_code() -> Result<(), Box<dyn std::error::Error>> {
25-
// Allows for one iteration where we see no progress but next loop should go
26-
// into first branch
27-
let mut seen_no_progress = false;
28-
2925
let client = reqwest::Client::new();
3026
let mut state = DataState::None;
3127

@@ -34,34 +30,40 @@ async fn common_code() -> Result<(), Box<dyn std::error::Error>> {
3430
// This loop would normally be a game loop, or the executor of an immediate mode
3531
// GUI.
3632
loop {
37-
if let DataState::Present(status_code) = state.as_ref() {
33+
if state.is_none() {
34+
let client = client.clone();
35+
let can_make_progress =
36+
state.start_request(|| make_request(client, "https://httpbin.org/get"));
37+
assert!(can_make_progress.is_able_to_make_progress());
38+
}
39+
if let Some(status_code) = state.poll().present() {
3840
println!("Response received");
3941
assert_eq!(status_code, &200);
4042
break;
41-
} else {
42-
let outcome = state.get(|| {
43-
let req = client.get("https://httpbin.org/get");
44-
let response_handler = |resp: reqwest::Result<reqwest::Response>| async {
45-
resp.map(|resp| resp.status())
46-
.context("Request failed, got an error back")
47-
};
48-
let ui_notify = || {
49-
println!("Request Completed, this is where you would wake up your UI thread");
50-
};
51-
Awaiting(fetch_plus(req, response_handler, ui_notify))
52-
});
53-
assert!(!seen_no_progress);
54-
if outcome.is_unable_to_make_progress() {
55-
// We should never get into this branch again
56-
seen_no_progress = true;
57-
}
58-
reqwest_cross::yield_now().await;
5943
}
44+
reqwest_cross::yield_now().await;
6045
}
6146
println!("Exited loop");
6247
Ok(())
6348
}
6449

50+
fn make_request(
51+
client: reqwest::Client,
52+
url: impl reqwest::IntoUrl,
53+
) -> oneshot::Receiver<anyhow::Result<reqwest::StatusCode>> {
54+
let req = client.get(url);
55+
let response_handler = |resp: reqwest::Result<reqwest::Response>| async {
56+
resp.map(|resp| resp.status())
57+
.context("Request failed, got an error back")
58+
};
59+
let ui_notify = || {
60+
println!("Request Completed, this is where you would wake up your UI thread.
61+
If using egui version of the functions the associated methods add spinners which will keep the loop going so no wake up is needed.
62+
Passing an empty closure would suffice.");
63+
};
64+
fetch_plus(req, response_handler, ui_notify)
65+
}
66+
6567
#[cfg(all(test, not(target_arch = "wasm32")))]
6668
mod tests {
6769

src/data_state.rs

+75-64
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ pub enum CanMakeProgress {
3737
/// Used to represent data that is pending being available
3838
#[derive(Debug)]
3939
pub struct Awaiting<T, E: ErrorBounds>(pub oneshot::Receiver<Result<T, E>>);
40+
impl<T, E: ErrorBounds> From<oneshot::Receiver<Result<T, E>>> for Awaiting<T, E> {
41+
fn from(value: oneshot::Receiver<Result<T, E>>) -> Self {
42+
Self(value)
43+
}
44+
}
4045

4146
/// Used to store a type that is not always available and we need to keep
4247
/// polling it to get it ready
@@ -57,9 +62,10 @@ impl<T, E: ErrorBounds> DataState<T, E> {
5762
#[cfg(feature = "egui")]
5863
/// Calls [Self::start_request] and adds a spinner if progress can be made
5964
#[must_use]
60-
pub fn egui_start_request<F>(&mut self, ui: &mut egui::Ui, fetch_fn: F) -> CanMakeProgress
65+
pub fn egui_start_request<F, R>(&mut self, ui: &mut egui::Ui, fetch_fn: F) -> CanMakeProgress
6166
where
62-
F: FnOnce() -> Awaiting<T, E>,
67+
F: FnOnce() -> R,
68+
R: Into<Awaiting<T, E>>,
6369
{
6470
let result = self.start_request(fetch_fn);
6571
if result.is_able_to_make_progress() {
@@ -69,97 +75,77 @@ impl<T, E: ErrorBounds> DataState<T, E> {
6975
}
7076

7177
/// Starts a new request. Only intended to be on [Self::None] and if state
72-
/// is any other value it returns
73-
/// [CanMakeProgress::UnableToMakeProgress]
78+
/// is any other value it returns [CanMakeProgress::UnableToMakeProgress]
7479
#[must_use]
75-
pub fn start_request<F>(&mut self, fetch_fn: F) -> CanMakeProgress
80+
pub fn start_request<F, R>(&mut self, fetch_fn: F) -> CanMakeProgress
7681
where
77-
F: FnOnce() -> Awaiting<T, E>,
82+
F: FnOnce() -> R,
83+
R: Into<Awaiting<T, E>>,
7884
{
7985
if self.is_none() {
80-
let result = self.get(fetch_fn);
81-
assert!(result.is_able_to_make_progress());
82-
result
86+
*self = DataState::AwaitingResponse(fetch_fn().into());
87+
CanMakeProgress::AbleToMakeProgress
8388
} else {
89+
debug_assert!(
90+
false,
91+
"No known good reason this path should be hit other than logic error"
92+
);
8493
CanMakeProgress::UnableToMakeProgress
8594
}
8695
}
8796

97+
/// Convenience method that will try to make progress if in
98+
/// [Self::AwaitingResponse] and does nothing otherwise. Returns a reference
99+
/// to self for chaining
100+
pub fn poll(&mut self) -> &mut Self {
101+
if let DataState::AwaitingResponse(rx) = self {
102+
if let Some(new_state) = Self::await_data(rx) {
103+
*self = new_state;
104+
}
105+
}
106+
self
107+
}
108+
88109
#[cfg(feature = "egui")]
89-
/// Attempts to load the data and displays appropriate UI if applicable.
90-
/// Some branches lead to no UI being displayed, in particular when the data
91-
/// or an error is received (On the expectation it will show next frame).
92-
/// When in an error state the error messages will show as applicable.
93-
/// If called an already has data present this function does nothing and
94-
/// returns [CanMakeProgress::UnableToMakeProgress]
110+
/// Meant to be a simple method to just provide the data if it's ready or
111+
/// help with UI and polling to get it ready if it's not.
95112
///
96-
/// If a `retry_msg` is provided then it overrides the default
113+
/// WARNING: Does nothing if `self` is [Self::None]
97114
///
98-
/// Note see [`Self::get`] for more info.
115+
/// If a `error_btn_text` is provided then it overrides the default
99116
#[must_use]
100-
pub fn egui_get<F>(
117+
pub fn egui_poll_mut(
101118
&mut self,
102119
ui: &mut egui::Ui,
103-
retry_msg: Option<&str>,
104-
fetch_fn: F,
105-
) -> CanMakeProgress
106-
where
107-
F: FnOnce() -> Awaiting<T, E>,
108-
{
120+
error_btn_text: Option<&str>,
121+
) -> Option<&mut T> {
109122
match self {
110-
DataState::None => {
111-
ui.spinner();
112-
self.get(fetch_fn)
113-
}
123+
DataState::None => {}
114124
DataState::AwaitingResponse(_) => {
115125
ui.spinner();
116-
self.get(fetch_fn)
126+
self.poll();
117127
}
118-
DataState::Present(_data) => {
119-
// Does nothing as data is already present
120-
CanMakeProgress::UnableToMakeProgress
128+
DataState::Present(data) => {
129+
return Some(data);
121130
}
122131
DataState::Failed(e) => {
123132
ui.colored_label(ui.visuals().error_fg_color, e.to_string());
124-
if ui.button(retry_msg.unwrap_or("Retry Request")).clicked() {
133+
if ui
134+
.button(error_btn_text.unwrap_or("Clear Error Status"))
135+
.clicked()
136+
{
125137
*self = DataState::default();
126138
}
127-
CanMakeProgress::AbleToMakeProgress
128139
}
129140
}
141+
None
130142
}
131143

132-
/// Attempts to load the data and returns if it is able to make progress.
133-
///
134-
/// Note: F needs to return `AwaitingType<T>` and not T because it needs to
135-
/// be able to be pending if T is not ready.
144+
#[cfg(feature = "egui")]
145+
/// Wraps [Self::egui_poll_mut] and returns an immutable reference
136146
#[must_use]
137-
pub fn get<F>(&mut self, fetch_fn: F) -> CanMakeProgress
138-
where
139-
F: FnOnce() -> Awaiting<T, E>,
140-
{
141-
match self {
142-
DataState::None => {
143-
let rx = fetch_fn();
144-
*self = DataState::AwaitingResponse(rx);
145-
CanMakeProgress::AbleToMakeProgress
146-
}
147-
DataState::AwaitingResponse(rx) => {
148-
if let Some(new_state) = Self::await_data(rx) {
149-
*self = new_state;
150-
}
151-
CanMakeProgress::AbleToMakeProgress
152-
}
153-
DataState::Present(_data) => {
154-
// Does nothing data is already present
155-
CanMakeProgress::UnableToMakeProgress
156-
}
157-
DataState::Failed(_e) => {
158-
// Have no way to let the user know there is an error nothing we
159-
// can do here
160-
CanMakeProgress::UnableToMakeProgress
161-
}
162-
}
147+
pub fn egui_poll(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<&T> {
148+
self.egui_poll_mut(ui, error_btn_text).map(|x| &*x)
163149
}
164150

165151
/// Checks to see if the data is ready and if it is returns a new [`Self`]
@@ -185,6 +171,31 @@ impl<T, E: ErrorBounds> DataState<T, E> {
185171
})
186172
}
187173

174+
/// Returns a reference to the inner data if available otherwise None.
175+
///
176+
/// NOTE: This function does not poll to get the data ready if the state is
177+
/// still awaiting
178+
pub fn present(&self) -> Option<&T> {
179+
if let Self::Present(data) = self {
180+
Some(data)
181+
} else {
182+
None
183+
}
184+
}
185+
186+
/// Returns a mutable reference to the inner data if available otherwise
187+
/// None
188+
///
189+
/// NOTE: This function does not poll to get the data ready if the state is
190+
/// still awaiting
191+
pub fn present_mut(&mut self) -> Option<&mut T> {
192+
if let Self::Present(data) = self {
193+
Some(data)
194+
} else {
195+
None
196+
}
197+
}
198+
188199
/// Returns `true` if the data state is [`Present`].
189200
///
190201
/// [`Present`]: DataState::Present

0 commit comments

Comments
 (0)