//! A good example where a synchronous but separate thread of execution is useful. //! here, we have two separate "threads": one for the display logic, and one for the //! dialog. //! //! We could theoretically do this with other schedulers (ex. tokio), but doing so would //! cause lots of dependency and runtime resource bloat for such a simple task. use futures_channel::{ mpsc, oneshot, }; use futures_util::{ future::FutureExt, pin_mut, sink::SinkExt, }; use std::{ io::{ self, BufRead, Write, }, thread::sleep, time::Duration, }; use sync_async_runner; /// Our dialog. /// /// Dialog changes over time, and can interact with input, taking different branches, etc. /// As such, it makes sense to model it as a thread of execution, akin to a coroutine. async fn hello_dialog(mut d: DialogSender) { d.text("Hello there!").await; d.text("I'm going to ask you a question.").await; d.text("What's your favorite color?").await; let choice = d.choices(vec!["Red", "Green", "Blue"]).await; match choice { 0 => d.text("Just like a rose! How pretty!").await, 1 => d.text("Green is not a creative color...").await, 2 => d.text("That reminds me, I am thirsty").await, _ => unreachable!(), }; d.text("Thank you for talking with me.").await; } /// A convenience struct, wrapping a channel, to send dialog actions to the main routine. struct DialogSender(mpsc::Sender); impl DialogSender { /// Sends some text to display pub async fn text(&mut self, text: &'static str) { self.0.send(DialogAction::Text(text)).await.unwrap(); } /// Ask the user to pick one option from the supplied set of choices. /// /// Completes with the index of the choice. pub async fn choices(&mut self, choices: Vec<&'static str>) -> usize { // Make a oneshot channel to get the response. let (sender, receiver) = oneshot::channel(); self.0 .send(DialogAction::Choice { choices, response: sender, }) .await .unwrap(); receiver.await.unwrap() } } /// Object sent over the channel from the dialog routine to the main routine. #[derive(Debug)] enum DialogAction { Text(&'static str), Choice { choices: Vec<&'static str>, response: oneshot::Sender, }, } fn main() { // Set up needed objects let stdin = io::stdin(); let stdout = io::stdout(); let (dialog_sender, mut dialog_receiver) = mpsc::channel(5); let dialog_sender = DialogSender(dialog_sender); // Create the dialog routine. // // Add fuse to prevent a panic when calling poll on the future after it exits let dialog_coroutine = sync_async_runner::runner(hello_dialog(dialog_sender).fuse()); pin_mut!(dialog_coroutine); // Run the dialog routine and receive the actions it sends us loop { // Continue the coroutine. // // Don't actually care if its finished or not; the stream closing will tell us when // (in fact, the future finishes as soon as it has pushed its last bit of dialog!) let _ = dialog_coroutine.as_mut().poll(); // Pop a command off the queue and process it let command = dialog_receiver.try_next().ok().flatten(); match command { Some(DialogAction::Text(t)) => { // Just print the text println!("{}", t); sleep(Duration::from_millis(1000)); } Some(DialogAction::Choice { choices, response }) => { // Present the choices, parse user input, then send the // response over the oneshot channel for (i, choice) in choices.iter().enumerate() { println!("{}. {}", i, choice); } loop { print!("Choose by number: "); stdout.lock().flush().unwrap(); let mut input = String::new(); stdin.lock().read_line(&mut input).unwrap(); if let Ok(v) = input.trim().parse() { response.send(v).unwrap(); break; } } } None => { // All done, exit main loop break; } } } }