use std::sync::mpsc::{channel, Receiver, TryRecvError};
use std::sync::{Arc, Mutex};
use eframe::egui;
#[derive(Clone, Default)]
pub struct Progress(Arc<Mutex<String>>);
impl Progress {
pub fn say(&self, words: impl Into<String>) {
if let Ok(mut held) = self.0.lock() {
*held = words.into();
}
}
pub fn said(&self) -> String {
self.0.lock().map(|held| held.clone()).unwrap_or_default()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Answer<T> {
Running,
Answered(T),
Died,
}
pub struct Job<T> {
rx: Receiver<T>,
progress: Progress,
}
impl<T> Job<T> {
pub fn poll(&self) -> Answer<T> {
match self.rx.try_recv() {
Ok(answer) => Answer::Answered(answer),
Err(TryRecvError::Empty) => Answer::Running,
Err(TryRecvError::Disconnected) => Answer::Died,
}
}
pub fn progress(&self) -> String {
self.progress.said()
}
#[cfg(test)]
pub fn wait(&self) -> Answer<T> {
match self.rx.recv() {
Ok(answer) => Answer::Answered(answer),
Err(_) => Answer::Died,
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run<T: Send + 'static>(
ctx: &egui::Context,
work: impl FnOnce(&Progress) -> T + Send + 'static,
) -> Job<T> {
let (tx, rx) = channel();
let progress = Progress::default();
let reported = progress.clone();
let ctx = ctx.clone();
std::thread::spawn(move || {
let _ = tx.send(work(&reported));
ctx.request_repaint();
});
Job { rx, progress }
}
#[cfg(target_arch = "wasm32")]
pub fn run<T: Send + 'static>(
_ctx: &egui::Context,
work: impl FnOnce(&Progress) -> T + Send + 'static,
) -> Job<T> {
let (tx, rx) = channel();
let progress = Progress::default();
let _ = tx.send(work(&progress));
Job { rx, progress }
}
#[cfg(test)]
mod tests {
use super::*;
fn settled<T>(job: &Job<T>) -> Answer<T> {
loop {
match job.poll() {
Answer::Running => std::thread::yield_now(),
answer => return answer,
}
}
}
#[test]
fn a_job_answers_once() {
let job = run(&egui::Context::default(), |_| 7);
assert_eq!(settled(&job), Answer::Answered(7));
assert_eq!(settled(&job), Answer::Died, "an answer is taken once");
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn a_worker_that_panics_is_reported_as_dead() {
let job: Job<u32> = run(&egui::Context::default(), |_| panic!("the work gave up"));
assert_eq!(settled(&job), Answer::Died);
}
#[test]
fn progress_reads_back_the_last_thing_said() {
let progress = Progress::default();
assert_eq!(progress.said(), "");
progress.say("resampling 1 of 3");
progress.say("coding 3 strokes");
assert_eq!(progress.said(), "coding 3 strokes");
}
}