Skip to main content

ace_sim/
node.rs

1// region: Imports
2
3use crate::bus::SimBus;
4use crate::clock::{Duration, Instant};
5use crate::io::NodeAddress;
6use heapless::Vec;
7
8// endregion: Imports
9
10// region: SimNode Trait
11
12/// A participant in the simulation - either a sever (ECU) or client (tester).
13///
14/// Each node is a pure state machine. It receives messages via `handle`, produces outbound
15/// messages via `drain_outbox`, and can be ticket for time-based state transitions.
16///
17/// `N` - max message payload bytes
18/// `Q` - max messages in outbox simultaneously
19pub trait SimNode<const N: usize, const Q: usize> {
20    type Error: core::fmt::Debug;
21
22    /// Returns this node's address on the simulation bus.
23    fn address(&self) -> &NodeAddress;
24
25    /// Delivers an inbound message to the node.
26    fn handle(&mut self, src: &NodeAddress, data: &[u8], now: Instant) -> Result<(), Self::Error>;
27
28    /// Advances the node's internal state to the given time.
29    ///
30    /// Used to trigger timeouts and retries. Called by the simulation bus on every tick even if no
31    /// messages were delivered.
32    fn tick(&mut self, now: Instant) -> Result<(), Self::Error>;
33
34    /// Drains all pending outbound messages from the node's outbox.
35    ///
36    /// Return an iterator of `(dst, data)` pairs. The bus calls this after every `handle` and
37    /// `tick` call to collect and route output.
38    fn drain_outbox(
39        &mut self,
40        out: &mut heapless::Vec<(NodeAddress, heapless::Vec<u8, N>), Q>,
41    ) -> usize;
42}
43
44// endregion: SimNode Trait
45
46// region: SimNodeErased Trait
47
48pub trait SimNodeErased<const N: usize, const Q: usize> {
49    fn address(&self) -> &NodeAddress;
50    fn handle(&mut self, src: &NodeAddress, data: &[u8], now: Instant);
51    fn tick(&mut self, now: Instant);
52    fn drain_outbox(&mut self, out: &mut Vec<(NodeAddress, Vec<u8, N>), Q>) -> usize;
53}
54
55/// Blanket impl - any SimNode becomes a SimNodeErased by discarding errors.
56impl<const N: usize, const Q: usize, T> SimNodeErased<N, Q> for T
57where
58    T: SimNode<N, Q>,
59    T::Error: core::fmt::Debug,
60{
61    fn address(&self) -> &NodeAddress {
62        SimNode::address(self)
63    }
64
65    fn handle(&mut self, src: &NodeAddress, data: &[u8], now: Instant) {
66        if let Err(e) = SimNode::handle(self, src, data, now) {
67            // In no_std we cannot print but the error is available for
68            // inspection via a debugger or a custom hook. Errors are
69            // intentionally swallowed here - the simulation continues.
70            let _ = e;
71        }
72    }
73
74    fn tick(&mut self, now: Instant) {
75        if let Err(e) = SimNode::tick(self, now) {
76            let _ = e;
77        }
78    }
79
80    fn drain_outbox(&mut self, out: &mut Vec<(NodeAddress, Vec<u8, N>), Q>) -> usize {
81        SimNode::drain_outbox(self, out)
82    }
83}
84
85// endregion: SimNodeErased Trait
86
87// region: SimRunner
88
89/// Drives a collection of [`SimNode`]s connected via a [`SimBus`].
90///
91/// `N` - max message payload bytes
92/// `Q` - max messages in-flight on the bus
93/// `S` - max nodes in the simulation
94pub struct SimRunner<const N: usize, const Q: usize> {
95    bus: SimBus<N, Q>,
96}
97
98impl<const N: usize, const Q: usize> SimRunner<N, Q> {
99    pub fn new(bus: SimBus<N, Q>) -> Self {
100        Self { bus }
101    }
102
103    /// Ticks the simulation by `duration` microseconds, routing all delivered messages to their
104    /// destination nodes and collecting their responses back onto the bus.
105    ///
106    /// Returns the number of messages delivered in this tick.
107    pub fn tick(
108        &mut self,
109        nodes: &mut [&mut dyn SimNodeErased<N, Q>],
110        duration: Duration,
111    ) -> usize {
112        let delivered = self.bus.tick(duration);
113        let now = self.bus.now();
114        let mut count = 0;
115
116        for envelope in &delivered {
117            for node in nodes.iter_mut() {
118                if *node.address() == envelope.dst {
119                    let _ = node.handle(&envelope.src, &envelope.data, now);
120                    count += 1;
121                }
122            }
123        }
124
125        for node in nodes.iter_mut() {
126            let _ = node.tick(now);
127        }
128
129        let mut outbox = heapless::Vec::new();
130
131        for node in nodes.iter_mut() {
132            outbox.clear();
133            node.drain_outbox(&mut outbox);
134
135            let src = node.address().clone();
136            for (dst, data) in outbox.iter() {
137                self.bus.send(src.clone(), dst.clone(), data);
138            }
139        }
140
141        count
142    }
143
144    pub fn bus(&mut self) -> &mut SimBus<N, Q> {
145        &mut self.bus
146    }
147}
148
149// endregion: SimRunner