use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
pub type Cmd<M> = Pin<Box<dyn Future<Output = CmdResult<M>> + Send>>;
#[doc(hidden)]
pub enum CmdResult<M> {
Msg(M),
Batch(Vec<Cmd<M>>),
Quit,
None,
}
pub fn cmd<M, F, Fut>(f: F) -> Cmd<M>
where
M: Send + 'static,
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = M> + Send + 'static,
{
Box::pin(async move { CmdResult::Msg(f().await) })
}
pub fn msg<M: Send + 'static>(m: M) -> Cmd<M> {
Box::pin(async move { CmdResult::Msg(m) })
}
pub fn batch<M: Send + 'static>(cmds: Vec<Cmd<M>>) -> Cmd<M> {
Box::pin(async move { CmdResult::Batch(cmds) })
}
pub fn sequence<M: Send + 'static>(cmds: Vec<Cmd<M>>) -> Cmd<M> {
Box::pin(async move { CmdResult::Batch(cmds) })
}
pub fn tick<M: Send + 'static>(duration: Duration, m: M) -> Cmd<M> {
Box::pin(async move {
tokio::time::sleep(duration).await;
CmdResult::Msg(m)
})
}
pub fn delay<M: Send + 'static>(duration: Duration, m: M) -> Cmd<M> {
tick(duration, m)
}
pub fn perform<M, F, Fut>(f: F) -> Cmd<M>
where
M: Send + 'static,
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = M> + Send + 'static,
{
Box::pin(async move { CmdResult::Msg(f().await) })
}
pub fn quit<M: Send + 'static>() -> Cmd<M> {
Box::pin(async move { CmdResult::Quit })
}
pub fn none<M: Send + 'static>() -> Cmd<M> {
Box::pin(async move { CmdResult::None })
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn msg_produces_message() {
let cmd = msg::<String>("hello".to_string());
match cmd.await {
CmdResult::Msg(m) => assert_eq!(m, "hello"),
_ => panic!("expected Msg"),
}
}
#[tokio::test]
async fn quit_produces_quit() {
let cmd = quit::<()>();
assert!(matches!(cmd.await, CmdResult::Quit));
}
#[tokio::test]
async fn none_produces_none() {
let cmd = none::<()>();
assert!(matches!(cmd.await, CmdResult::None));
}
#[tokio::test]
async fn tick_delays_then_produces() {
let start = std::time::Instant::now();
let cmd = tick(Duration::from_millis(10), 42u32);
match cmd.await {
CmdResult::Msg(m) => {
assert_eq!(m, 42);
assert!(start.elapsed() >= Duration::from_millis(10));
}
_ => panic!("expected Msg"),
}
}
#[tokio::test]
async fn perform_runs_async() {
let cmd = perform(|| async { 1 + 2 });
match cmd.await {
CmdResult::Msg(m) => assert_eq!(m, 3),
_ => panic!("expected Msg"),
}
}
#[tokio::test]
async fn batch_produces_batch() {
let cmds = vec![msg(1), msg(2)];
let cmd = batch(cmds);
match cmd.await {
CmdResult::Batch(b) => assert_eq!(b.len(), 2),
_ => panic!("expected Batch"),
}
}
#[tokio::test]
async fn cmd_from_closure() {
let c = cmd(|| async { "result".to_string() });
match c.await {
CmdResult::Msg(m) => assert_eq!(m, "result"),
_ => panic!("expected Msg"),
}
}
}