1use crate::effect::Effect;
2
3#[derive(Debug, Clone, PartialEq, Default)]
5pub enum Cmd<M> {
6 #[default]
7 None,
8 Single(Effect<M>),
9 Batch(Vec<Cmd<M>>),
10}
11
12impl<M> Cmd<M> {
13 pub fn none() -> Self {
14 Self::None
15 }
16
17 pub fn single(effect: Effect<M>) -> Self {
18 Self::Single(effect)
19 }
20
21 pub fn batch(cmds: impl IntoIterator<Item = Cmd<M>>) -> Self {
22 let mut cmds: Vec<_> = cmds.into_iter().collect();
23 match cmds.len() {
24 0 => Self::None,
25 1 => cmds.pop().map_or(Self::None, |single| single),
26 _ => Self::Batch(cmds),
27 }
28 }
29
30 pub fn map<N>(self, f: fn(M) -> N) -> Cmd<N>
31 where
32 M: Send + 'static,
33 N: Send + 'static,
34 {
35 match self {
36 Self::None => Cmd::None,
37 Self::Single(e) => {
38 let mapped = e.map(f);
39 if matches!(mapped, Effect::None) {
40 Cmd::None
41 } else {
42 Cmd::Single(mapped)
43 }
44 }
45 Self::Batch(cmds) => Cmd::Batch(cmds.into_iter().map(|c| c.map(f)).collect()),
46 }
47 }
48
49 pub fn into_effects(self) -> Vec<Effect<M>> {
50 match self {
51 Self::None => vec![],
52 Self::Single(e) => vec![e],
53 Self::Batch(cmds) => cmds.into_iter().flat_map(Cmd::into_effects).collect(),
54 }
55 }
56
57 pub fn is_none(&self) -> bool {
58 matches!(self, Self::None)
59 }
60
61 pub fn effect_count(&self) -> usize {
62 match self {
63 Self::None => 0,
64 Self::Single(_) => 1,
65 Self::Batch(cmds) => cmds.iter().map(|c| c.effect_count()).sum(),
66 }
67 }
68}
69
70#[cfg(test)]
71#[allow(dead_code)]
72mod tests {
73 use super::*;
74 use crate::effect::Effect;
75
76 #[test]
77 fn batch_flattens_and_map_preserves_structure() {
78 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
79 enum Msg {
80 A,
81 B,
82 }
83
84 let cmd = Cmd::batch([
85 Cmd::single(Effect::<Msg>::none()),
86 Cmd::none(),
87 Cmd::single(Effect::<Msg>::none()),
88 ]);
89 assert_eq!(cmd.into_effects().len(), 2);
90
91 let mapped = Cmd::<Msg>::single(Effect::<Msg>::none()).map(|m| match m {
92 Msg::A => 1,
93 Msg::B => 2,
94 });
95 assert!(mapped.is_none());
96 }
97}