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