Build the Mutt-style TUI shell and action model
This commit is contained in:
65
apps/tui/src/runtime.rs
Normal file
65
apps/tui/src/runtime.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! Small in-process executor for storage calls. It never launches a process.
|
||||
|
||||
use std::{
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
thread,
|
||||
};
|
||||
|
||||
use crate::app::{AsyncPayload, AsyncResult, RequestToken};
|
||||
|
||||
pub struct AsyncExecutor {
|
||||
sender: Sender<AsyncResult>,
|
||||
receiver: Receiver<AsyncResult>,
|
||||
}
|
||||
|
||||
impl Default for AsyncExecutor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncExecutor {
|
||||
pub fn new() -> Self {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
Self { sender, receiver }
|
||||
}
|
||||
|
||||
pub fn submit<F>(&self, token: RequestToken, work: F)
|
||||
where
|
||||
F: FnOnce() -> Result<AsyncPayload, String> + Send + 'static,
|
||||
{
|
||||
let sender = self.sender.clone();
|
||||
thread::spawn(move || {
|
||||
let _ignored = sender.send(AsyncResult {
|
||||
token,
|
||||
payload: work(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn drain(&self) -> impl Iterator<Item = AsyncResult> + '_ {
|
||||
self.receiver.try_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use super::*;
|
||||
use crate::app::{App, ResultDisposition};
|
||||
|
||||
#[test]
|
||||
fn work_completes_off_the_input_thread() {
|
||||
let executor = AsyncExecutor::new();
|
||||
let mut app = App::new();
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, || Ok(AsyncPayload::Refreshed));
|
||||
|
||||
let result = executor
|
||||
.receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("worker should return a typed result");
|
||||
assert_eq!(app.apply_result(result), ResultDisposition::Applied);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user