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 MAX_FRAME: usize, const MAX_OUTBOX: 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, MAX_FRAME>), MAX_OUTBOX>,
41    ) -> usize;
42}
43
44// endregion: SimNode Trait
45
46// region: SimNodeErased Trait
47
48pub trait SimNodeErased<const MAX_FRAME: usize, const MAX_OUTBOX: 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(
53        &mut self,
54        out: &mut Vec<(NodeAddress, Vec<u8, MAX_FRAME>), MAX_OUTBOX>,
55    ) -> usize;
56}
57
58/// Blanket impl - any SimNode becomes a SimNodeErased by discarding errors.
59impl<const MAX_FRAME: usize, const MAX_OUTBOX: usize, T> SimNodeErased<MAX_FRAME, MAX_OUTBOX> for T
60where
61    T: SimNode<MAX_FRAME, MAX_OUTBOX>,
62    T::Error: core::fmt::Debug,
63{
64    fn address(&self) -> &NodeAddress {
65        SimNode::address(self)
66    }
67
68    fn handle(&mut self, src: &NodeAddress, data: &[u8], now: Instant) {
69        if let Err(e) = SimNode::handle(self, src, data, now) {
70            // In no_std we cannot print but the error is available for
71            // inspection via a debugger or a custom hook. Errors are
72            // intentionally swallowed here - the simulation continues.
73            let _ = e;
74        }
75    }
76
77    fn tick(&mut self, now: Instant) {
78        if let Err(e) = SimNode::tick(self, now) {
79            let _ = e;
80        }
81    }
82
83    fn drain_outbox(
84        &mut self,
85        out: &mut Vec<(NodeAddress, Vec<u8, MAX_FRAME>), MAX_OUTBOX>,
86    ) -> usize {
87        SimNode::drain_outbox(self, out)
88    }
89}
90
91// endregion: SimNodeErased Trait
92
93// region: SimRunner
94
95/// Drives a collection of [`SimNode`]s connected via a [`SimBus`].
96///
97/// `N` - max message payload bytes
98/// `Q` - max messages in-flight on the bus
99/// `S` - max nodes in the simulation
100pub struct SimRunner<const N: usize, const Q: usize> {
101    bus: SimBus<N, Q>,
102}
103
104impl<const N: usize, const Q: usize> SimRunner<N, Q> {
105    pub fn new(bus: SimBus<N, Q>) -> Self {
106        Self { bus }
107    }
108
109    /// Ticks the simulation by `duration` microseconds, routing all delivered messages to their
110    /// destination nodes and collecting their responses back onto the bus.
111    ///
112    /// Returns the number of messages delivered in this tick.
113    pub fn tick(
114        &mut self,
115        nodes: &mut [&mut dyn SimNodeErased<N, Q>],
116        duration: Duration,
117    ) -> usize {
118        let delivered = self.bus.tick(duration);
119        let now = self.bus.now();
120        let mut count = 0;
121
122        for envelope in &delivered {
123            for node in nodes.iter_mut() {
124                if *node.address() == envelope.dst {
125                    let _ = node.handle(&envelope.src, &envelope.data, now);
126                    count += 1;
127                }
128            }
129        }
130
131        for node in nodes.iter_mut() {
132            let _ = node.tick(now);
133        }
134
135        let mut outbox = heapless::Vec::new();
136
137        for node in nodes.iter_mut() {
138            outbox.clear();
139            node.drain_outbox(&mut outbox);
140
141            let src = node.address().clone();
142            for (dst, data) in outbox.iter() {
143                self.bus.send(src.clone(), dst.clone(), data);
144            }
145        }
146
147        count
148    }
149
150    pub fn bus(&mut self) -> &mut SimBus<N, Q> {
151        &mut self.bus
152    }
153}
154
155// endregion: SimRunner