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)]
73#[allow(dead_code)]
74mod tests {
75 use super::*;
76 use crate::effect::Effect;
77
78 #[test]
79 fn batch_flattens_and_map_preserves_structure() {
80 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
81 enum Msg {
82 A,
83 B,
84 }
85
86 let cmd = Cmd::batch([
87 Cmd::single(Effect::<Msg>::none()),
88 Cmd::none(),
89 Cmd::single(Effect::<Msg>::none()),
90 ]);
91 assert_eq!(cmd.into_effects().len(), 2);
92
93 let mapped = Cmd::<Msg>::single(Effect::<Msg>::none()).map(|m| match m {
94 Msg::A => 1,
95 Msg::B => 2,
96 });
97 assert!(mapped.is_none());
98 }
99}