use crate::utils::lock::{read_or_recover, write_or_recover};
use std::fmt;
use std::sync::{Arc, RwLock};
use std::thread;
use super::{signal, Signal};
#[derive(Clone, Debug, Default, PartialEq)]
pub enum AsyncState<T> {
#[default]
Idle,
Loading,
Ready(T),
Error(String),
}
impl<T> AsyncState<T> {
pub fn is_idle(&self) -> bool {
matches!(self, AsyncState::Idle)
}
pub fn is_loading(&self) -> bool {
matches!(self, AsyncState::Loading)
}
pub fn is_ready(&self) -> bool {
matches!(self, AsyncState::Ready(_))
}
pub fn is_error(&self) -> bool {
matches!(self, AsyncState::Error(_))
}
pub fn value(&self) -> Option<&T> {
match self {
AsyncState::Ready(v) => Some(v),
_ => None,
}
}
pub fn error(&self) -> Option<&str> {
match self {
AsyncState::Error(e) => Some(e),
_ => None,
}
}
pub fn map<U, F: FnOnce(&T) -> U>(&self, f: F) -> AsyncState<U>
where
U: Clone,
{
match self {
AsyncState::Idle => AsyncState::Idle,
AsyncState::Loading => AsyncState::Loading,
AsyncState::Ready(v) => AsyncState::Ready(f(v)),
AsyncState::Error(e) => AsyncState::Error(e.clone()),
}
}
pub fn unwrap_or(&self, default: T) -> T
where
T: Clone,
{
match self {
AsyncState::Ready(v) => v.clone(),
_ => default,
}
}
pub fn unwrap_or_else<F: FnOnce() -> T>(&self, f: F) -> T
where
T: Clone,
{
match self {
AsyncState::Ready(v) => v.clone(),
_ => f(),
}
}
}
impl<T: fmt::Display> fmt::Display for AsyncState<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsyncState::Idle => write!(f, "Idle"),
AsyncState::Loading => write!(f, "Loading"),
AsyncState::Ready(v) => write!(f, "Ready({})", v),
AsyncState::Error(e) => write!(f, "Error({})", e),
}
}
}
pub type AsyncResult<T> = Result<T, String>;
pub fn use_async<T, F>(f: F) -> (Signal<AsyncState<T>>, impl Fn() + Clone)
where
T: Clone + Send + Sync + 'static,
F: Fn() -> AsyncResult<T> + Send + Sync + Clone + 'static,
{
let state: Signal<AsyncState<T>> = signal(AsyncState::Idle);
let state_clone = state.clone();
let trigger = move || {
state_clone.set(AsyncState::Loading);
let f_clone = f.clone();
let state_for_thread = state_clone.clone();
thread::spawn(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f_clone));
let result = match result {
Ok(r) => r,
Err(_) => Err("Task panicked".to_string()),
};
match result {
Ok(value) => state_for_thread.set(AsyncState::Ready(value)),
Err(e) => state_for_thread.set(AsyncState::Error(e)),
}
});
};
(state, trigger)
}
#[derive(Clone)]
enum PollState<T> {
Idle,
Running,
Done(AsyncResult<T>),
}
pub fn use_async_poll<T, F>(
f: F,
) -> (
Signal<AsyncState<T>>,
impl Fn() + Clone,
impl Fn() -> bool + Clone,
)
where
T: Clone + Send + Sync + 'static,
F: Fn() -> AsyncResult<T> + Send + Sync + Clone + 'static,
{
let state: Signal<AsyncState<T>> = signal(AsyncState::Idle);
let poll_state: Arc<RwLock<PollState<T>>> = Arc::new(RwLock::new(PollState::Idle));
let poll_state_start = poll_state.clone();
let state_start = state.clone();
let start = move || {
state_start.set(AsyncState::Loading);
*write_or_recover(&poll_state_start) = PollState::Running;
let f_clone = f.clone();
let poll_state_thread = poll_state_start.clone();
thread::spawn(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f_clone));
let result = match result {
Ok(r) => r,
Err(_) => Err("Task panicked".to_string()),
};
*write_or_recover(&poll_state_thread) = PollState::Done(result);
});
};
let poll_state_poll = poll_state.clone();
let state_poll = state.clone();
let poll = move || -> bool {
let is_done = matches!(*read_or_recover(&poll_state_poll), PollState::Done(_));
if is_done {
let mut guard = write_or_recover(&poll_state_poll);
if let PollState::Done(result) = std::mem::replace(&mut *guard, PollState::Idle) {
match result {
Ok(value) => state_poll.set(AsyncState::Ready(value)),
Err(e) => state_poll.set(AsyncState::Error(e)),
}
return true; }
}
false };
(state, start, poll)
}
pub fn use_async_immediate<T, F>(f: F) -> Signal<AsyncState<T>>
where
T: Clone + Send + Sync + 'static,
F: FnOnce() -> AsyncResult<T> + Send + 'static,
{
let state: Signal<AsyncState<T>> = signal(AsyncState::Loading);
let state_for_thread = state.clone();
thread::spawn(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
let result = match result {
Ok(r) => r,
Err(_) => Err("Task panicked".to_string()),
};
match result {
Ok(value) => state_for_thread.set(AsyncState::Ready(value)),
Err(e) => state_for_thread.set(AsyncState::Error(e)),
}
});
state
}
pub struct AsyncResource<T, F>
where
T: Clone + Send + Sync + 'static,
F: Fn() -> AsyncResult<T> + Send + Sync + Clone + 'static,
{
fetch: F,
_phantom: std::marker::PhantomData<T>,
}
impl<T, F> AsyncResource<T, F>
where
T: Clone + Send + Sync + 'static,
F: Fn() -> AsyncResult<T> + Send + Sync + Clone + 'static,
{
pub fn new(fetch: F) -> Self {
Self {
fetch,
_phantom: std::marker::PhantomData,
}
}
pub fn build(self) -> (Signal<AsyncState<T>>, impl Fn() + Clone) {
use_async(self.fetch)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[test]
fn test_async_state_default() {
let state: AsyncState<i32> = AsyncState::default();
assert!(state.is_idle());
}
#[test]
fn test_async_state_idle() {
let state = AsyncState::<i32>::Idle;
assert!(state.is_idle());
assert!(!state.is_loading());
assert!(!state.is_ready());
assert!(!state.is_error());
}
#[test]
fn test_async_state_loading() {
let state = AsyncState::<i32>::Loading;
assert!(!state.is_idle());
assert!(state.is_loading());
assert!(!state.is_ready());
assert!(!state.is_error());
}
#[test]
fn test_async_state_ready() {
let state = AsyncState::Ready(42);
assert!(!state.is_idle());
assert!(!state.is_loading());
assert!(state.is_ready());
assert!(!state.is_error());
}
#[test]
fn test_async_state_error() {
let state = AsyncState::<i32>::Error("test error".to_string());
assert!(!state.is_idle());
assert!(!state.is_loading());
assert!(!state.is_ready());
assert!(state.is_error());
}
#[test]
fn test_async_state_value() {
let state = AsyncState::Ready(42);
assert_eq!(state.value(), Some(&42));
let state = AsyncState::<i32>::Loading;
assert_eq!(state.value(), None);
}
#[test]
fn test_async_state_error_message() {
let state = AsyncState::<i32>::Error("test error".to_string());
assert_eq!(state.error(), Some("test error"));
let state = AsyncState::<i32>::Loading;
assert_eq!(state.error(), None);
}
#[test]
fn test_async_state_map() {
let state = AsyncState::Ready(42);
let mapped = state.map(|v| v * 2);
assert_eq!(mapped, AsyncState::Ready(84));
}
#[test]
fn test_async_state_map_idle() {
let state = AsyncState::<i32>::Idle;
let mapped = state.map(|v| v * 2);
assert_eq!(mapped, AsyncState::<i32>::Idle);
}
#[test]
fn test_async_state_map_loading() {
let state = AsyncState::<i32>::Loading;
let mapped = state.map(|v| v * 2);
assert_eq!(mapped, AsyncState::<i32>::Loading);
}
#[test]
fn test_async_state_map_error() {
let state = AsyncState::<i32>::Error("error".to_string());
let mapped = state.map(|v| v * 2);
assert_eq!(mapped, AsyncState::Error("error".to_string()));
}
#[test]
fn test_async_state_unwrap_or() {
let state = AsyncState::Ready(42);
assert_eq!(state.unwrap_or(0), 42);
let state = AsyncState::<i32>::Idle;
assert_eq!(state.unwrap_or(0), 0);
}
#[test]
fn test_async_state_unwrap_or_else() {
let state = AsyncState::Ready(42);
assert_eq!(state.unwrap_or_else(|| 0), 42);
let state = AsyncState::<i32>::Idle;
assert_eq!(state.unwrap_or_else(|| 99), 99);
}
#[test]
fn test_async_state_display_idle() {
let state = AsyncState::<i32>::Idle;
assert_eq!(format!("{}", state), "Idle");
}
#[test]
fn test_async_state_display_loading() {
let state = AsyncState::<i32>::Loading;
assert_eq!(format!("{}", state), "Loading");
}
#[test]
fn test_async_state_display_ready() {
let state = AsyncState::Ready(42);
assert_eq!(format!("{}", state), "Ready(42)");
}
#[test]
fn test_async_state_display_error() {
let state = AsyncState::<i32>::Error("error".to_string());
assert_eq!(format!("{}", state), "Error(error)");
}
#[test]
fn test_async_state_clone_ready() {
let state1 = AsyncState::Ready(42);
let state2 = state1.clone();
assert_eq!(state2, AsyncState::Ready(42));
}
#[test]
fn test_async_state_clone_error() {
let state1: AsyncState<i32> = AsyncState::Error("error".to_string());
let state2 = state1.clone();
assert_eq!(state2, AsyncState::Error("error".to_string()));
}
#[test]
fn test_async_state_partial_eq() {
let state1 = AsyncState::Ready(42);
let state2 = AsyncState::Ready(42);
assert_eq!(state1, state2);
}
#[test]
fn test_use_async() {
let (state, trigger) = use_async(|| {
std::thread::sleep(Duration::from_millis(50));
Ok::<i32, String>(42)
});
assert!(state.get().is_idle());
trigger();
std::thread::sleep(Duration::from_millis(200));
assert!(state.get().is_ready());
assert_eq!(state.get().value(), Some(&42));
}
#[test]
fn test_use_async_with_error() {
let (state, trigger) = use_async(|| Err::<i32, String>("error".to_string()));
trigger();
std::thread::sleep(Duration::from_millis(100));
assert!(state.get().is_error());
}
#[test]
fn test_use_async_trigger_multiple() {
let (state, trigger) = use_async(|| Ok::<i32, String>(42));
trigger();
trigger();
std::thread::sleep(Duration::from_millis(100));
assert!(state.get().is_ready() || state.get().is_loading());
}
#[test]
fn test_use_async_poll() {
let (state, start, poll) = use_async_poll(|| Ok::<i32, String>(42));
assert!(state.get().is_idle());
start();
assert!(state.get().is_loading());
for _ in 0..10 {
if poll() {
break;
}
std::thread::sleep(Duration::from_millis(10));
}
assert!(state.get().is_ready());
}
#[test]
fn test_use_async_poll_returns_false_when_not_done() {
let (_state, start, poll) = use_async_poll(|| {
std::thread::sleep(Duration::from_millis(100));
Ok::<i32, String>(42)
});
start();
assert!(!poll());
}
#[test]
fn test_use_async_poll_with_error() {
let (state, start, poll) = use_async_poll(|| Err::<i32, String>("error".to_string()));
start();
for _ in 0..10 {
if poll() {
break;
}
std::thread::sleep(Duration::from_millis(10));
}
assert!(state.get().is_error());
}
#[test]
fn test_use_async_immediate() {
let state = use_async_immediate(|| Ok::<i32, String>(42));
std::thread::sleep(Duration::from_millis(100));
assert!(state.get().is_ready());
assert_eq!(state.get().value(), Some(&42));
}
#[test]
fn test_use_async_immediate_with_error() {
let state = use_async_immediate(|| Err::<i32, String>("error".to_string()));
std::thread::sleep(Duration::from_millis(100));
assert!(state.get().is_error());
}
#[test]
fn test_async_resource_new() {
let _resource = AsyncResource::new(|| Ok::<i32, String>(42));
}
#[test]
fn test_async_resource_build() {
let resource = AsyncResource::new(|| Ok::<i32, String>(42));
let (state, trigger) = resource.build();
assert!(state.get().is_idle());
trigger();
std::thread::sleep(Duration::from_millis(100));
assert!(state.get().is_ready());
}
#[test]
fn test_async_state_with_string() {
let state = AsyncState::Ready("hello".to_string());
assert!(state.is_ready());
assert_eq!(state.value(), Some(&"hello".to_string()));
}
#[test]
fn test_async_state_with_vec() {
let state = AsyncState::Ready(vec![1, 2, 3]);
assert!(state.is_ready());
assert_eq!(state.value(), Some(&vec![1, 2, 3]));
}
#[test]
fn test_async_state_map_string() {
let state = AsyncState::Ready("hello".to_string());
let mapped = state.map(|s| s.len());
assert_eq!(mapped, AsyncState::Ready(5));
}
#[test]
fn test_use_async_with_closure() {
let value = Arc::new(AtomicI32::new(10));
let value_clone = value.clone();
let (state, trigger) = use_async(move || {
std::thread::sleep(Duration::from_millis(50));
value_clone.fetch_add(1, Ordering::SeqCst);
Ok::<i32, String>(value_clone.load(Ordering::SeqCst))
});
trigger();
std::thread::sleep(Duration::from_millis(200));
assert!(state.get().is_ready());
assert_eq!(state.get().value(), Some(&11));
}
}