Skip to main content

main_loop_async/
data_state.rs

1//! Helpers for handling pending data.
2
3use anyhow::anyhow;
4use futures::channel::oneshot;
5use std::fmt::{Debug, Display};
6use thiserror::Error;
7use tracing::{error, warn};
8
9/// Provides a common way to specify the bounds errors are expected to meet
10pub trait ErrorBounds: Display + Send + Sync + 'static + Debug {}
11impl<T: Display + Send + Sync + 'static + Debug> ErrorBounds for T {}
12
13#[derive(Error, Debug)]
14/// Represents the types of errors that can occur while using [`DataState`]
15pub enum DataStateError<E: ErrorBounds> {
16    /// Sender was dropped, task cancelled
17    #[error("Task sender was dropped")]
18    SenderDropped(oneshot::Canceled),
19
20    /// The response received from the task was an error
21    #[error("Response received was an error: {0}")]
22    ErrorResponse(E),
23
24    /// This variant is supplied for use by application code
25    #[error(transparent)]
26    FromE(E),
27}
28
29#[derive(Debug)]
30/// Provides a way to ensure the calling code knows if it is calling a function
31/// that cannot do anything useful anymore
32pub enum CanMakeProgress {
33    /// Used to indicate that it is still possible for progress to be made
34    AbleToMakeProgress,
35
36    /// Used to indicate that further calls are not useful as no progress can be
37    /// made in current state
38    UnableToMakeProgress,
39}
40
41/// Used to represent data that is pending being available
42#[derive(Debug)]
43pub struct Awaiting<T, E: ErrorBounds>(pub oneshot::Receiver<Result<T, E>>);
44impl<T, E: ErrorBounds> From<oneshot::Receiver<Result<T, E>>> for Awaiting<T, E> {
45    fn from(value: oneshot::Receiver<Result<T, E>>) -> Self {
46        Self(value)
47    }
48}
49
50/// Used to store a type that is not always available and we need to keep
51/// polling it to get it ready
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[derive(Debug, Default)]
54pub enum DataState<T, E: ErrorBounds = anyhow::Error> {
55    /// Represent no data present and not pending
56    #[default]
57    None,
58
59    #[cfg_attr(feature = "serde", serde(skip))]
60    /// Represents data has been requested and awaiting it being available
61    AwaitingResponse(Awaiting<T, E>), // TODO 4: Add support for a timeout on waiting
62
63    /// Represents data that is available for use
64    Present(T),
65
66    #[cfg_attr(feature = "serde", serde(skip))]
67    /// Represents an error that Occurred
68    Failed(DataStateError<E>),
69}
70
71impl<T, E: ErrorBounds> DataState<T, E> {
72    #[cfg(feature = "egui")]
73    /// Calls [`Self::start_task`] and adds a spinner if progress can be made
74    pub fn egui_start_task<F, R>(&mut self, ui: &mut egui::Ui, f: F) -> CanMakeProgress
75    where
76        F: FnOnce() -> R,
77        R: Into<Awaiting<T, E>>,
78    {
79        let result = self.start_task(f);
80        if result.is_able_to_make_progress() {
81            ui.spinner();
82        }
83        result
84    }
85
86    /// Starts a new task. Only intended to be on [`Self::None`] and if state
87    /// is any other value it returns [`CanMakeProgress::UnableToMakeProgress`]
88    #[must_use]
89    pub fn start_task<F, R>(&mut self, f: F) -> CanMakeProgress
90    where
91        F: FnOnce() -> R,
92        R: Into<Awaiting<T, E>>,
93    {
94        if self.is_none() {
95            *self = Self::AwaitingResponse(f().into());
96            CanMakeProgress::AbleToMakeProgress
97        } else {
98            debug_assert!(
99                false,
100                "No known good reason this path should be hit other than logic error"
101            );
102            CanMakeProgress::UnableToMakeProgress
103        }
104    }
105
106    /// Convenience method that will try to make progress if in
107    /// [`Self::AwaitingResponse`] and does nothing otherwise. Returns a
108    /// reference to self for chaining
109    pub fn poll(&mut self) -> &mut Self {
110        if let Self::AwaitingResponse(rx) = self
111            && let Some(new_state) = Self::await_data(rx)
112        {
113            *self = new_state;
114        }
115        self
116    }
117
118    #[cfg(feature = "egui")]
119    /// Meant to be a simple method to just provide the data if it's ready or
120    /// help with UI and polling to get it ready if it's not.
121    ///
122    /// WARNING: Does nothing if `self` is [`Self::None`]
123    ///
124    /// If a `error_btn_text` is provided then it overrides the default
125    pub fn egui_poll_mut(
126        &mut self,
127        ui: &mut egui::Ui,
128        error_btn_text: Option<&str>,
129    ) -> Option<&mut T> {
130        match self {
131            Self::None => {}
132            Self::AwaitingResponse(_) => {
133                ui.spinner();
134                self.poll();
135            }
136            Self::Present(data) => {
137                return Some(data);
138            }
139            Self::Failed(e) => {
140                ui.colored_label(ui.visuals().error_fg_color, e.to_string());
141                if ui
142                    .button(error_btn_text.unwrap_or("Clear Error Status"))
143                    .clicked()
144                {
145                    *self = Self::default();
146                }
147            }
148        }
149        None
150    }
151
152    #[cfg(feature = "egui")]
153    /// Wraps [`Self::egui_poll_mut`] and returns an immutable reference
154    pub fn egui_poll(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<&T> {
155        self.egui_poll_mut(ui, error_btn_text).map(|x| &*x)
156    }
157
158    /// Checks to see if the data is ready and if it is returns a new [`Self`]
159    /// otherwise None.
160    pub fn await_data(rx: &mut Awaiting<T, E>) -> Option<Self> {
161        Some(match rx.0.try_recv() {
162            Ok(recv_opt) => match recv_opt {
163                Some(outcome_result) => match outcome_result {
164                    Ok(data) => Self::Present(data),
165                    Err(err_msg) => {
166                        warn!(?err_msg, "Error response received instead of the data");
167                        Self::Failed(DataStateError::ErrorResponse(err_msg))
168                    }
169                },
170                None => {
171                    return None;
172                }
173            },
174            Err(e) => {
175                error!("Error receiving on channel. Sender dropped.");
176                Self::Failed(DataStateError::SenderDropped(e))
177            }
178        })
179    }
180
181    /// Returns a reference to the inner data if available otherwise None.
182    ///
183    /// NOTE: This function does not poll to get the data ready if the state is
184    /// still awaiting
185    pub fn present(&self) -> Option<&T> {
186        if let Self::Present(data) = self {
187            Some(data)
188        } else {
189            None
190        }
191    }
192
193    /// Returns a mutable reference to the inner data if available otherwise
194    /// None
195    ///
196    /// NOTE: This function does not poll to get the data ready if the state is
197    /// still awaiting
198    pub fn present_mut(&mut self) -> Option<&mut T> {
199        if let Self::Present(data) = self {
200            Some(data)
201        } else {
202            None
203        }
204    }
205
206    /// Returns `true` if the data state is [`Present`].
207    ///
208    /// [`Present`]: DataState::Present
209    #[must_use]
210    pub fn is_present(&self) -> bool {
211        matches!(self, Self::Present(..))
212    }
213
214    /// Returns `true` if the data state is [`None`].
215    ///
216    /// [`None`]: DataState::None
217    #[must_use]
218    pub fn is_none(&self) -> bool {
219        matches!(self, Self::None)
220    }
221
222    /// Returns `true` if the data state is [`AwaitingResponse`].
223    ///
224    /// [`AwaitingResponse`]: DataState::AwaitingResponse
225    #[must_use]
226    pub fn is_awaiting_response(&self) -> bool {
227        matches!(self, Self::AwaitingResponse(..))
228    }
229}
230
231impl<T, E: ErrorBounds> AsRef<Self> for DataState<T, E> {
232    fn as_ref(&self) -> &Self {
233        self
234    }
235}
236
237impl<T, E: ErrorBounds> AsMut<Self> for DataState<T, E> {
238    fn as_mut(&mut self) -> &mut Self {
239        self
240    }
241}
242
243impl<E: ErrorBounds> From<E> for DataStateError<E> {
244    fn from(value: E) -> Self {
245        Self::FromE(value)
246    }
247}
248
249impl From<&str> for DataStateError<anyhow::Error> {
250    fn from(value: &str) -> Self {
251        value.to_owned().into()
252    }
253}
254
255impl From<String> for DataStateError<anyhow::Error> {
256    fn from(value: String) -> Self {
257        anyhow!(value).into()
258    }
259}
260
261impl CanMakeProgress {
262    /// Returns `true` if the can make progress is [`AbleToMakeProgress`].
263    ///
264    /// [`AbleToMakeProgress`]: CanMakeProgress::AbleToMakeProgress
265    #[must_use]
266    pub fn is_able_to_make_progress(&self) -> bool {
267        matches!(self, Self::AbleToMakeProgress)
268    }
269
270    /// Returns `true` if the can make progress is [`UnableToMakeProgress`].
271    ///
272    /// [`UnableToMakeProgress`]: CanMakeProgress::UnableToMakeProgress
273    #[must_use]
274    pub fn is_unable_to_make_progress(&self) -> bool {
275        matches!(self, Self::UnableToMakeProgress)
276    }
277}