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    /// if state may not be [`Self::None`] you can chain after
75    /// [`Self::set_none`] to ensure `self` is [`Self::None`] before calling as
76    /// `self` should be [`Self::None`] before calling
77    pub fn egui_start_task<F, R>(&mut self, ui: &mut egui::Ui, f: F) -> CanMakeProgress
78    where
79        F: FnOnce() -> R,
80        R: Into<Awaiting<T, E>>,
81    {
82        let result = self.start_task(f);
83        if result.is_able_to_make_progress() {
84            ui.spinner();
85        }
86        result
87    }
88
89    /// Starts a new task. Only intended to be on [`Self::None`] and if state
90    /// is any other value it returns [`CanMakeProgress::UnableToMakeProgress`]
91    /// if state may not be [`Self::None`] you can chain after
92    /// [`Self::set_none`] to ensure `self` is [`Self::None`] before calling
93    #[must_use]
94    pub fn start_task<F, R>(&mut self, f: F) -> CanMakeProgress
95    where
96        F: FnOnce() -> R,
97        R: Into<Awaiting<T, E>>,
98    {
99        if self.is_none() {
100            *self = Self::AwaitingResponse(f().into());
101            CanMakeProgress::AbleToMakeProgress
102        } else {
103            debug_assert!(
104                false,
105                "No known good reason this path should be hit other than logic error"
106            );
107            CanMakeProgress::UnableToMakeProgress
108        }
109    }
110
111    /// Resets `self` to [`Self::None`] and returns `&mut self` for chaining
112    pub fn set_none(&mut self) -> &mut Self {
113        *self = Self::None;
114        self
115    }
116
117    /// Convenience method that will try to make progress if in
118    /// [`Self::AwaitingResponse`] and does nothing otherwise. Returns a
119    /// reference to self for chaining
120    pub fn poll(&mut self) -> &mut Self {
121        if let Self::AwaitingResponse(rx) = self
122            && let Some(new_state) = Self::await_data(rx)
123        {
124            *self = new_state;
125        }
126        self
127    }
128
129    /// Will poll and if in [`Self::Present`] takes the value out and returns
130    /// the owned value, leaving `self` reset to [`Self::None`].
131    pub fn poll_take(&mut self) -> Option<T> {
132        self.poll();
133        if self.is_present() {
134            // This is safe and there is no race condition because we have mutable access
135            let mut result = Self::None;
136            std::mem::swap(self, &mut result);
137            let non_present_state = match result {
138                Self::Present(inner) => return Some(inner),
139                Self::None => "None",
140                Self::AwaitingResponse(_) => "Awaiting",
141                Self::Failed(_) => "Error",
142            };
143            unreachable!(
144                "we checked that self.is_present() was true then we tried to extract the value but got: {non_present_state:?} State"
145            )
146        } else {
147            None
148        }
149    }
150
151    #[cfg(feature = "egui")]
152    /// Meant to be a simple method to just provide the data if it's ready or
153    /// help with UI and polling to get it ready if it's not.
154    ///
155    /// WARNING: Does nothing if `self` is [`Self::None`]
156    ///
157    /// If a `error_btn_text` is provided then it overrides the default
158    pub fn egui_poll_mut(
159        &mut self,
160        ui: &mut egui::Ui,
161        error_btn_text: Option<&str>,
162    ) -> Option<&mut T> {
163        match self {
164            Self::None => {}
165            Self::AwaitingResponse(_) => {
166                ui.spinner();
167                self.poll();
168            }
169            Self::Present(data) => {
170                return Some(data);
171            }
172            Self::Failed(e) => {
173                ui.colored_label(ui.visuals().error_fg_color, e.to_string());
174                if ui
175                    .button(error_btn_text.unwrap_or("Clear Error Status"))
176                    .clicked()
177                {
178                    *self = Self::default();
179                }
180            }
181        }
182        None
183    }
184
185    #[cfg(feature = "egui")]
186    /// Wraps [`Self::egui_poll_mut`] and returns an immutable reference
187    pub fn egui_poll(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<&T> {
188        self.egui_poll_mut(ui, error_btn_text).map(|x| &*x)
189    }
190
191    #[cfg(feature = "egui")]
192    /// Wraps [`Self::egui_poll_mut`] for the UI but instead returns the output
193    /// from [`Self::poll_take`] which will reset `self` to [`Self::None`] and
194    /// return the owned value if present
195    #[must_use]
196    pub fn egui_poll_take(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<T> {
197        self.egui_poll_mut(ui, error_btn_text);
198        self.poll_take()
199    }
200
201    /// Checks to see if the data is ready and if it is returns a new [`Self`]
202    /// otherwise None.
203    pub fn await_data(rx: &mut Awaiting<T, E>) -> Option<Self> {
204        Some(match rx.0.try_recv() {
205            Ok(recv_opt) => match recv_opt {
206                Some(outcome_result) => match outcome_result {
207                    Ok(data) => Self::Present(data),
208                    Err(err_msg) => {
209                        warn!(?err_msg, "Error response received instead of the data");
210                        Self::Failed(DataStateError::ErrorResponse(err_msg))
211                    }
212                },
213                None => {
214                    return None;
215                }
216            },
217            Err(e) => {
218                error!("Error receiving on channel. Sender dropped.");
219                Self::Failed(DataStateError::SenderDropped(e))
220            }
221        })
222    }
223
224    /// Returns a reference to the inner data if available otherwise None.
225    ///
226    /// NOTE: This function does not poll to get the data ready if the state is
227    /// still awaiting
228    pub fn present(&self) -> Option<&T> {
229        if let Self::Present(data) = self {
230            Some(data)
231        } else {
232            None
233        }
234    }
235
236    /// Returns a mutable reference to the inner data if available otherwise
237    /// None
238    ///
239    /// NOTE: This function does not poll to get the data ready if the state is
240    /// still awaiting
241    pub fn present_mut(&mut self) -> Option<&mut T> {
242        if let Self::Present(data) = self {
243            Some(data)
244        } else {
245            None
246        }
247    }
248
249    /// Returns `true` if the data state is [`Present`].
250    ///
251    /// [`Present`]: DataState::Present
252    #[must_use]
253    pub fn is_present(&self) -> bool {
254        matches!(self, Self::Present(..))
255    }
256
257    /// Returns `true` if the data state is [`None`].
258    ///
259    /// [`None`]: DataState::None
260    #[must_use]
261    pub fn is_none(&self) -> bool {
262        matches!(self, Self::None)
263    }
264
265    /// Returns `true` if the data state is [`AwaitingResponse`].
266    ///
267    /// [`AwaitingResponse`]: DataState::AwaitingResponse
268    #[must_use]
269    pub fn is_awaiting_response(&self) -> bool {
270        matches!(self, Self::AwaitingResponse(..))
271    }
272}
273
274impl<T, E: ErrorBounds> AsRef<Self> for DataState<T, E> {
275    fn as_ref(&self) -> &Self {
276        self
277    }
278}
279
280impl<T, E: ErrorBounds> AsMut<Self> for DataState<T, E> {
281    fn as_mut(&mut self) -> &mut Self {
282        self
283    }
284}
285
286impl<E: ErrorBounds> From<E> for DataStateError<E> {
287    fn from(value: E) -> Self {
288        Self::FromE(value)
289    }
290}
291
292impl From<&str> for DataStateError<anyhow::Error> {
293    fn from(value: &str) -> Self {
294        value.to_owned().into()
295    }
296}
297
298impl From<String> for DataStateError<anyhow::Error> {
299    fn from(value: String) -> Self {
300        anyhow!(value).into()
301    }
302}
303
304impl CanMakeProgress {
305    /// Returns `true` if the can make progress is [`AbleToMakeProgress`].
306    ///
307    /// [`AbleToMakeProgress`]: CanMakeProgress::AbleToMakeProgress
308    #[must_use]
309    pub fn is_able_to_make_progress(&self) -> bool {
310        matches!(self, Self::AbleToMakeProgress)
311    }
312
313    /// Returns `true` if the can make progress is [`UnableToMakeProgress`].
314    ///
315    /// [`UnableToMakeProgress`]: CanMakeProgress::UnableToMakeProgress
316    #[must_use]
317    pub fn is_unable_to_make_progress(&self) -> bool {
318        matches!(self, Self::UnableToMakeProgress)
319    }
320}