use anyhow::anyhow;
use futures::channel::oneshot;
use std::fmt::{Debug, Display};
use thiserror::Error;
use tracing::{error, warn};
pub trait ErrorBounds: Display + Send + Sync + 'static + Debug {}
impl<T: Display + Send + Sync + 'static + Debug> ErrorBounds for T {}
#[derive(Error, Debug)]
pub enum DataStateError<E: ErrorBounds> {
#[error("Request sender was dropped")]
SenderDropped(oneshot::Canceled),
#[error("Response received was an error: {0}")]
ErrorResponse(E),
#[error(transparent)]
FromE(E),
}
#[derive(Debug)]
pub enum CanMakeProgress {
AbleToMakeProgress,
UnableToMakeProgress,
}
#[derive(Debug)]
pub struct Awaiting<T, E: ErrorBounds>(pub oneshot::Receiver<Result<T, E>>);
impl<T, E: ErrorBounds> From<oneshot::Receiver<Result<T, E>>> for Awaiting<T, E> {
fn from(value: oneshot::Receiver<Result<T, E>>) -> Self {
Self(value)
}
}
#[derive(Debug, Default)]
pub enum DataState<T, E: ErrorBounds = anyhow::Error> {
#[default]
None,
AwaitingResponse(Awaiting<T, E>), Present(T),
Failed(DataStateError<E>),
}
impl<T, E: ErrorBounds> DataState<T, E> {
#[cfg(feature = "egui")]
pub fn egui_start_request<F, R>(&mut self, ui: &mut egui::Ui, fetch_fn: F) -> CanMakeProgress
where
F: FnOnce() -> R,
R: Into<Awaiting<T, E>>,
{
let result = self.start_request(fetch_fn);
if result.is_able_to_make_progress() {
ui.spinner();
}
result
}
#[must_use]
pub fn start_request<F, R>(&mut self, fetch_fn: F) -> CanMakeProgress
where
F: FnOnce() -> R,
R: Into<Awaiting<T, E>>,
{
if self.is_none() {
*self = DataState::AwaitingResponse(fetch_fn().into());
CanMakeProgress::AbleToMakeProgress
} else {
debug_assert!(
false,
"No known good reason this path should be hit other than logic error"
);
CanMakeProgress::UnableToMakeProgress
}
}
pub fn poll(&mut self) -> &mut Self {
if let DataState::AwaitingResponse(rx) = self {
if let Some(new_state) = Self::await_data(rx) {
*self = new_state;
}
}
self
}
#[cfg(feature = "egui")]
pub fn egui_poll_mut(
&mut self,
ui: &mut egui::Ui,
error_btn_text: Option<&str>,
) -> Option<&mut T> {
match self {
DataState::None => {}
DataState::AwaitingResponse(_) => {
ui.spinner();
self.poll();
}
DataState::Present(data) => {
return Some(data);
}
DataState::Failed(e) => {
ui.colored_label(ui.visuals().error_fg_color, e.to_string());
if ui
.button(error_btn_text.unwrap_or("Clear Error Status"))
.clicked()
{
*self = DataState::default();
}
}
}
None
}
#[cfg(feature = "egui")]
pub fn egui_poll(&mut self, ui: &mut egui::Ui, error_btn_text: Option<&str>) -> Option<&T> {
self.egui_poll_mut(ui, error_btn_text).map(|x| &*x)
}
pub fn await_data(rx: &mut Awaiting<T, E>) -> Option<Self> {
Some(match rx.0.try_recv() {
Ok(recv_opt) => match recv_opt {
Some(outcome_result) => match outcome_result {
Ok(data) => DataState::Present(data),
Err(err_msg) => {
warn!(?err_msg, "Error response received instead of the data");
DataState::Failed(DataStateError::ErrorResponse(err_msg))
}
},
None => {
return None;
}
},
Err(e) => {
error!("Error receiving on channel. Sender dropped.");
DataState::Failed(DataStateError::SenderDropped(e))
}
})
}
pub fn present(&self) -> Option<&T> {
if let Self::Present(data) = self {
Some(data)
} else {
None
}
}
pub fn present_mut(&mut self) -> Option<&mut T> {
if let Self::Present(data) = self {
Some(data)
} else {
None
}
}
#[must_use]
pub fn is_present(&self) -> bool {
matches!(self, Self::Present(..))
}
#[must_use]
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
impl<T, E: ErrorBounds> AsRef<DataState<T, E>> for DataState<T, E> {
fn as_ref(&self) -> &DataState<T, E> {
self
}
}
impl<T, E: ErrorBounds> AsMut<DataState<T, E>> for DataState<T, E> {
fn as_mut(&mut self) -> &mut DataState<T, E> {
self
}
}
impl<E: ErrorBounds> From<E> for DataStateError<E> {
fn from(value: E) -> Self {
Self::FromE(value)
}
}
impl From<&str> for DataStateError<anyhow::Error> {
fn from(value: &str) -> Self {
value.to_string().into()
}
}
impl From<String> for DataStateError<anyhow::Error> {
fn from(value: String) -> Self {
anyhow!(value).into()
}
}
impl CanMakeProgress {
#[must_use]
pub fn is_able_to_make_progress(&self) -> bool {
matches!(self, Self::AbleToMakeProgress)
}
#[must_use]
pub fn is_unable_to_make_progress(&self) -> bool {
matches!(self, Self::UnableToMakeProgress)
}
}