I am trying to build a pausable pomodoro timer, but can't figure out how my threads should be set up. Specifically I am having a hard time on finding a way to pause/quit the timer thread. Here's what I am currently trying to do:
I have an input thread collecting crossterm input events and sending them via an mpsc-channel to my main thread
My main thread runs the timer (via thread::sleep
) and receives the input messages from the other thread
However because sleep is a blocking operation, the main thread is often unable to react to my key-events.
Any advice how I could be going about this?
Here is a minimum reproducable example:
pub fn run() {
enable_raw_mode().expect("Can run in raw mode");
let (input_worker_tx, input_worker_rx): (Sender<CliEvent<Event>>, Receiver<CliEvent<Event>>) =
mpsc::channel();
poll_input_thread(input_worker_tx);
main_loop(input_worker_rx);
}
fn main_loop(input_worker_rx: Receiver<CliEvent<Event>>) {
let stdout = std::io::stdout();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).expect("Terminal could be created");
terminal.clear().expect("Terminal could be cleared");
let mut remaining_time = 20;
while remaining_time > 0 {
if let InputEvent::Quit = handle_input(&input_worker_rx) {
break;
};
remaining_time -= 1;
let time = util::seconds_to_time(remaining_time);
view::render(&mut terminal, &time);
sleep(time::Duration::new(1, 0));
}
quit(&mut terminal, Some("Cya!"));
}
pub fn poll_input_thread(input_worker_tx: Sender<CliEvent<Event>>) {
let tick_rate = Duration::from_millis(200);
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).expect("poll works") {
let crossterm_event = event::read().expect("can read events");
input_worker_tx
.send(CliEvent::Input(crossterm_event))
.expect("can send events");
}
if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
last_tick = Instant::now();
}
}
});
}
pub fn handle_input(input_worker_rx: &Receiver<CliEvent<Event>>) -> InputEvent {
match input_worker_rx.try_recv() {
Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
KeyEvent {
code: KeyCode::Char('q'),
..
}
| KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
} => {
return InputEvent::Quit;
}
_ => {}
},
Ok(CliEvent::Tick) => {}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => eject("Input handler disconnected"),
_ => {}
}
InputEvent::Continue
}
CodePudding user response:
Don't use sleep
and don't use try_recv
. Instead use recv_timeout
with a timeout corresponding to your sleep
duration:
pub fn run() {
enable_raw_mode().expect("Can run in raw mode");
let (input_worker_tx, input_worker_rx): (Sender<CliEvent<Event>>, Receiver<CliEvent<Event>>) =
mpsc::channel();
poll_input_thread(input_worker_tx);
main_loop(input_worker_rx);
}
fn main_loop(input_worker_rx: Receiver<CliEvent<Event>>) {
let stdout = std::io::stdout();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).expect("Terminal could be created");
terminal.clear().expect("Terminal could be cleared");
let target_time = Instant::now() Duration::from_secs(20);
while target_time > Instant::now() {
if let InputEvent::Quit = handle_input(&input_worker_rx, Duration::from_secs (1)) {
break;
};
let time = (target_time - Instant::now()).as_secs();
view::render(&mut terminal, &time);
}
quit(&mut terminal, Some("Cya!"));
}
pub fn poll_input_thread(input_worker_tx: Sender<CliEvent<Event>>) {
let tick_rate = Duration::from_millis(200);
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).expect("poll works") {
let crossterm_event = event::read().expect("can read events");
input_worker_tx
.send(CliEvent::Input(crossterm_event))
.expect("can send events");
}
if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
last_tick = Instant::now();
}
}
});
}
pub fn handle_input(input_worker_rx: &Receiver<CliEvent<Event>>, timeout: Duration) -> InputEvent {
match input_worker_rx.recv_timeout(timeout) {
Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
KeyEvent {
code: KeyCode::Char('q'),
..
}
| KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
} => {
return InputEvent::Quit;
}
_ => {}
},
Ok(CliEvent::Tick) => {}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => eject("Input handler disconnected"),
_ => {}
}
InputEvent::Continue
}