Skip to main content

acex_sim/
node.rs

1// region: Imports
2
3use crate::bus::SimBus;
4use crate::clock::{Duration, Instant};
5use crate::io::NodeAddress;
6use acex_core::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 Vec<(NodeAddress, 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
100#[cfg_attr(all(feature = "defmt", not(feature = "alloc")), derive(defmt::Format))]
101pub struct SimRunner<const N: usize, const Q: usize> {
102    bus: SimBus<N, Q>,
103}
104
105impl<const N: usize, const Q: usize> SimRunner<N, Q> {
106    pub fn new(bus: SimBus<N, Q>) -> Self {
107        Self { bus }
108    }
109
110    /// Ticks the simulation by `duration` microseconds, routing all delivered messages to their
111    /// destination nodes and collecting their responses back onto the bus.
112    ///
113    /// Returns the number of messages delivered in this tick.
114    pub fn tick(
115        &mut self,
116        nodes: &mut [&mut dyn SimNodeErased<N, Q>],
117        duration: Duration,
118    ) -> usize {
119        let delivered = self.bus.tick(duration);
120        let now = self.bus.now();
121        let mut count = 0;
122
123        for envelope in &delivered {
124            for node in nodes.iter_mut() {
125                if *node.address() == envelope.dst {
126                    let _ = node.handle(&envelope.src, &envelope.data, now);
127                    count += 1;
128                }
129            }
130        }
131
132        for node in nodes.iter_mut() {
133            let _ = node.tick(now);
134        }
135
136        let mut outbox = Vec::new();
137
138        for node in nodes.iter_mut() {
139            outbox.clear();
140            node.drain_outbox(&mut outbox);
141
142            let src = node.address().clone();
143            for (dst, data) in outbox.iter() {
144                self.bus.send(src.clone(), dst.clone(), data);
145            }
146        }
147
148        count
149    }
150
151    pub fn bus(&mut self) -> &mut SimBus<N, Q> {
152        &mut self.bus
153    }
154}
155
156// endregion: SimRunner