cog_task/action/core/
switch.rs

1use crate::action::{Action, ActionSignal, Props, StatefulAction, DEFAULT, INFINITE, VISUAL};
2use crate::comm::{QWriter, Signal, SignalId};
3use crate::resource::{IoManager, ResourceAddr, ResourceManager};
4use crate::server::{AsyncSignal, Config, State, SyncSignal};
5use crate::util::approx_eq;
6use eframe::egui;
7use eyre::{eyre, Result};
8use itertools::Itertools;
9use serde::{Deserialize, Serialize};
10use serde_cbor::Value;
11use std::collections::BTreeSet;
12
13#[derive(Debug, Deserialize, Serialize)]
14pub struct Switch {
15    #[serde(default)]
16    default: bool,
17    #[serde(alias = "if")]
18    if_true: Box<dyn Action>,
19    #[serde(alias = "else")]
20    if_false: Box<dyn Action>,
21    in_control: SignalId,
22}
23
24enum Decision {
25    Temporary(bool),
26    Final(bool),
27}
28
29stateful!(Switch {
30    if_true: Box<dyn StatefulAction>,
31    if_false: Box<dyn StatefulAction>,
32    in_control: SignalId,
33    decision: Decision,
34});
35
36impl Action for Switch {
37    #[inline]
38    fn in_signals(&self) -> BTreeSet<SignalId> {
39        let mut signals = BTreeSet::from([self.in_control]);
40        signals.extend(self.if_true.in_signals());
41        signals.extend(self.if_false.in_signals());
42        signals
43    }
44
45    #[inline]
46    fn out_signals(&self) -> BTreeSet<SignalId> {
47        let mut signals = BTreeSet::new();
48        signals.extend(self.if_true.out_signals());
49        signals.extend(self.if_false.out_signals());
50        signals
51    }
52
53    #[inline]
54    fn resources(&self, config: &Config) -> Vec<ResourceAddr> {
55        [&self.if_true, &self.if_false]
56            .iter()
57            .flat_map(|c| c.resources(config))
58            .unique()
59            .collect()
60    }
61
62    fn stateful(
63        &self,
64        io: &IoManager,
65        res: &ResourceManager,
66        config: &Config,
67        sync_writer: &QWriter<SyncSignal>,
68        async_writer: &QWriter<AsyncSignal>,
69    ) -> Result<Box<dyn StatefulAction>> {
70        Ok(Box::new(StatefulSwitch {
71            done: false,
72            if_true: self
73                .if_true
74                .stateful(io, res, config, sync_writer, async_writer)?,
75            if_false: self
76                .if_false
77                .stateful(io, res, config, sync_writer, async_writer)?,
78            in_control: self.in_control,
79            decision: Decision::Temporary(self.default),
80        }))
81    }
82}
83
84impl StatefulAction for StatefulSwitch {
85    impl_stateful!();
86
87    #[inline]
88    fn props(&self) -> Props {
89        if let Decision::Final(true) = self.decision {
90            self.if_true.props()
91        } else if let Decision::Final(false) = self.decision {
92            self.if_false.props()
93        } else {
94            [&self.if_true, &self.if_false]
95                .iter()
96                .fold(DEFAULT, |mut state, c| {
97                    let c = c.props();
98                    if c.visual() {
99                        state |= VISUAL;
100                    }
101                    if c.infinite() {
102                        state |= INFINITE;
103                    }
104                    state
105                })
106                .into()
107        }
108    }
109
110    #[inline]
111    fn start(
112        &mut self,
113        sync_writer: &mut QWriter<SyncSignal>,
114        async_writer: &mut QWriter<AsyncSignal>,
115        state: &State,
116    ) -> Result<Signal> {
117        if matches!(self.decision, Decision::Final(_)) {
118            return Err(eyre!("Tried to restart a switch."));
119        }
120
121        let decision = match state.get(&self.in_control) {
122            Some(Value::Bool(c)) => *c,
123            Some(Value::Integer(1)) => true,
124            Some(Value::Integer(0)) => false,
125            Some(Value::Float(x)) if approx_eq(*x, 1.0) => true,
126            Some(Value::Float(x)) if approx_eq(*x, 0.0) => false,
127            Some(v) => return Err(eyre!("Failed to interpret value ({v:?}) as boolean.")),
128            None => return Err(eyre!("Control state is missing.")),
129        };
130
131        self.decision = Decision::Final(decision);
132        if decision {
133            self.if_true.start(sync_writer, async_writer, state)
134        } else {
135            self.if_false.start(sync_writer, async_writer, state)
136        }
137    }
138
139    #[inline]
140    fn update(
141        &mut self,
142        signal: &ActionSignal,
143        sync_writer: &mut QWriter<SyncSignal>,
144        async_writer: &mut QWriter<AsyncSignal>,
145        state: &State,
146    ) -> Result<Signal> {
147        match self.decision {
148            Decision::Final(true) => {
149                let news = self
150                    .if_true
151                    .update(signal, sync_writer, async_writer, state)?;
152                if self.if_true.is_over()? {
153                    self.done = true;
154                }
155                Ok(news)
156            }
157            Decision::Final(false) => {
158                let news = self
159                    .if_false
160                    .update(signal, sync_writer, async_writer, state)?;
161                if self.if_false.is_over()? {
162                    self.done = true;
163                }
164                Ok(news)
165            }
166            _ => Ok(Signal::none()),
167        }
168    }
169
170    fn show(
171        &mut self,
172        ui: &mut egui::Ui,
173        sync_writer: &mut QWriter<SyncSignal>,
174        async_writer: &mut QWriter<AsyncSignal>,
175        state: &State,
176    ) -> Result<()> {
177        match self.decision {
178            Decision::Final(true) => self.if_true.show(ui, sync_writer, async_writer, state),
179            Decision::Final(false) => self.if_false.show(ui, sync_writer, async_writer, state),
180            Decision::Temporary(_) => Ok(()),
181        }
182    }
183
184    #[inline]
185    fn stop(
186        &mut self,
187        sync_writer: &mut QWriter<SyncSignal>,
188        async_writer: &mut QWriter<AsyncSignal>,
189        state: &State,
190    ) -> Result<Signal> {
191        match self.decision {
192            Decision::Final(true) => self.if_true.stop(sync_writer, async_writer, state),
193            Decision::Final(false) => self.if_false.stop(sync_writer, async_writer, state),
194            Decision::Temporary(_) => Ok(Signal::none()),
195        }
196    }
197}