use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::thread::{self, JoinHandle};
pub struct AsyncTask<T> {
rx: Receiver<T>,
handle: Option<JoinHandle<()>>,
}
impl<T: Send + 'static> AsyncTask<T> {
pub fn spawn<F>(f: F) -> Self
where
F: FnOnce() -> T + Send + 'static,
{
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let result = f();
let _ = tx.send(result);
});
Self {
rx,
handle: Some(handle),
}
}
pub fn try_recv(&mut self) -> Option<T> {
match self.rx.try_recv() {
Ok(result) => Some(result),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => None,
}
}
pub fn is_running(&self) -> bool {
matches!(self.rx.try_recv(), Err(TryRecvError::Empty))
}
pub fn wait(self) -> Option<T> {
self.rx.recv().ok()
}
pub fn cancel(mut self) {
drop(self.rx);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
pub const SPINNER_FRAMES: &[&str] = &["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
pub fn spinner_char(frame: usize) -> &'static str {
SPINNER_FRAMES[frame % SPINNER_FRAMES.len()]
}
pub fn spawn_task<T, F>(f: F) -> (Receiver<T>, JoinHandle<()>)
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let result = f();
let _ = tx.send(result);
});
(rx, handle)
}
pub fn spawn_with_sender<T, F>(f: F) -> Receiver<T>
where
T: Send + 'static,
F: FnOnce(Sender<T>) + Send + 'static,
{
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
f(tx);
});
rx
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_async_task_spawn_and_wait() {
let task = AsyncTask::spawn(|| 42);
assert_eq!(task.wait(), Some(42));
}
#[test]
fn test_async_task_spawn_string() {
let task = AsyncTask::spawn(|| "hello".to_string());
assert_eq!(task.wait(), Some("hello".to_string()));
}
#[test]
fn test_async_task_spawn_vec() {
let task = AsyncTask::spawn(|| vec![1, 2, 3]);
assert_eq!(task.wait(), Some(vec![1, 2, 3]));
}
#[test]
fn test_async_task_try_recv_completed() {
let mut task = AsyncTask::spawn(|| 42);
thread::sleep(Duration::from_millis(10));
let _ = task.try_recv();
}
#[test]
fn test_async_task_cancel() {
let task = AsyncTask::spawn(|| {
thread::sleep(Duration::from_millis(1000));
42
});
task.cancel();
}
#[test]
fn test_async_task_timing() {
let task = AsyncTask::spawn(|| {
thread::sleep(Duration::from_millis(5));
42
});
let mut task = task;
let mut result = None;
for _ in 0..100 {
if let Some(r) = task.try_recv() {
result = Some(r);
break;
}
thread::sleep(Duration::from_millis(10));
}
assert_eq!(result, Some(42));
}
#[test]
fn test_async_task_is_running() {
use std::sync::{Arc, Barrier};
let started = Arc::new(Barrier::new(2));
let finish = Arc::new(Barrier::new(2));
let started_clone = started.clone();
let finish_clone = finish.clone();
let task = AsyncTask::spawn(move || {
started_clone.wait(); finish_clone.wait(); 42
});
started.wait();
assert!(task.is_running(), "Task should be running while blocked");
finish.wait();
let result = task.wait();
assert_eq!(result, Some(42));
}
#[test]
fn test_spinner_frames_count() {
assert_eq!(SPINNER_FRAMES.len(), 8);
}
#[test]
fn test_spinner_char_first() {
assert_eq!(spinner_char(0), "⣾");
}
#[test]
fn test_spinner_char_second() {
assert_eq!(spinner_char(1), "⣽");
}
#[test]
fn test_spinner_char_all_frames() {
assert_eq!(spinner_char(0), "⣾");
assert_eq!(spinner_char(1), "⣽");
assert_eq!(spinner_char(2), "⣻");
assert_eq!(spinner_char(3), "⢿");
assert_eq!(spinner_char(4), "â¡¿");
assert_eq!(spinner_char(5), "⣟");
assert_eq!(spinner_char(6), "⣯");
assert_eq!(spinner_char(7), "⣷");
}
#[test]
fn test_spinner_char_wraps() {
assert_eq!(spinner_char(8), "⣾"); assert_eq!(spinner_char(9), "⣽"); assert_eq!(spinner_char(16), "⣾"); }
#[test]
fn test_spinner_char_large_number() {
let _ = spinner_char(1000);
let _ = spinner_char(usize::MAX);
}
#[test]
fn test_spawn_task_int() {
let (rx, handle) = spawn_task(|| 42);
assert_eq!(rx.recv().unwrap(), 42);
handle.join().unwrap();
}
#[test]
fn test_spawn_task_string() {
let (rx, handle) = spawn_task(|| "result".to_string());
assert_eq!(rx.recv().unwrap(), "result");
handle.join().unwrap();
}
#[test]
fn test_spawn_task_result() {
let (rx, handle) = spawn_task(|| -> Result<i32, &str> { Ok(42) });
assert_eq!(rx.recv().unwrap(), Ok(42));
handle.join().unwrap();
}
#[test]
fn test_spawn_task_computation() {
let (rx, _) = spawn_task(|| {
let mut sum = 0;
for i in 0..100 {
sum += i;
}
sum
});
assert_eq!(rx.recv().unwrap(), 4950);
}
#[test]
fn test_spawn_with_sender_single() {
let rx = spawn_with_sender(|tx| {
tx.send(42).unwrap();
});
assert_eq!(rx.recv().unwrap(), 42);
}
#[test]
fn test_spawn_with_sender_multiple() {
let rx = spawn_with_sender(|tx| {
tx.send(1).unwrap();
tx.send(2).unwrap();
tx.send(3).unwrap();
});
assert_eq!(rx.recv().unwrap(), 1);
assert_eq!(rx.recv().unwrap(), 2);
assert_eq!(rx.recv().unwrap(), 3);
}
#[test]
fn test_spawn_with_sender_collect() {
let rx = spawn_with_sender(|tx| {
for i in 0..5 {
tx.send(i).unwrap();
}
});
let results: Vec<i32> = rx.iter().collect();
assert_eq!(results, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_spawn_with_sender_strings() {
let rx = spawn_with_sender(|tx| {
tx.send("hello".to_string()).unwrap();
tx.send("world".to_string()).unwrap();
});
assert_eq!(rx.recv().unwrap(), "hello");
assert_eq!(rx.recv().unwrap(), "world");
}
#[test]
fn test_spawn_with_sender_channel_closes() {
let rx = spawn_with_sender(|tx| {
tx.send(1).unwrap();
});
assert_eq!(rx.recv().unwrap(), 1);
assert!(rx.recv().is_err());
}
}