///|
/// Errors raised by deterministic network simulation.
pub suberror SimulationError {
TimeWentBackwards
InvalidPeriod
EventLimitReached
} derive(Debug)
///|
/// A frame scheduled for a simulated bus time.
pub struct ScheduledFrame {
timestamp_us : UInt64
frame : Frame
source : String
}
///|
/// A periodic publisher registered with a simulation.
pub struct PeriodicPublisher {
source : String
frame : Frame
period_us : UInt64
mut remaining : Int
}
///|
/// Counters produced by a deterministic simulation run.
pub struct SimulationReport {
start_us : UInt64
end_us : UInt64
scheduled : Int
delivered : Int
dropped : Int
arbitration_bits : UInt64
}
///|
/// A deterministic, single-threaded CAN network model.
pub struct Simulation {
mut now_us : UInt64
bus : VirtualBus
events : Array[ScheduledFrame]
publishers : Array[PeriodicPublisher]
trace : Trace
mut scheduled : Int
mut delivered : Int
mut dropped : Int
mut arbitration_bits : UInt64
bitrate_kbps : UInt
}
///|
/// Create a simulation with a bounded or unbounded receive queue.
pub fn new_simulation(capacity : Int, bitrate_kbps : UInt) -> Simulation {
let bus = new_bus(capacity)
let filters = new_filter_bank()
filters.add(mask_filter(0, 0))
bus.set_filters(filters)
{
now_us: 0,
bus,
events: [],
publishers: [],
trace: new_trace(),
scheduled: 0,
delivered: 0,
dropped: 0,
arbitration_bits: 0,
bitrate_kbps,
}
}
///|
pub fn Simulation::now(self : Simulation) -> UInt64 {
self.now_us
}
///|
pub fn Simulation::pending_events(self : Simulation) -> Int {
self.events.length()
}
///|
pub fn Simulation::bus(self : Simulation) -> VirtualBus {
self.bus
}
///|
/// Schedule a frame at an absolute microsecond timestamp.
pub fn Simulation::schedule(
self : Simulation,
timestamp_us : UInt64,
frame : Frame,
source? : String = "anonymous",
) -> Unit raise SimulationError {
if timestamp_us < self.now_us {
raise TimeWentBackwards
}
self.events.push({ timestamp_us, frame, source })
self.events.sort_by((left, right) => compare_events(left, right))
self.scheduled += 1
}
///|
/// Register a periodic publisher and schedule its first event.
pub fn Simulation::add_periodic(
self : Simulation,
source : String,
frame : Frame,
first_timestamp_us : UInt64,
period_us : UInt64,
count : Int,
) -> Unit raise SimulationError {
if period_us == 0 || count < 0 {
raise InvalidPeriod
}
self.publishers.push({ source, frame, period_us, remaining: count })
if count > 0 {
self.schedule(first_timestamp_us, frame, source~)
}
}
///|
/// Execute the next event and deliver it through the virtual bus.
pub fn Simulation::step(self : Simulation) -> ScheduledFrame? {
if self.events.is_empty() {
return None
}
let event = self.events.remove(0)
self.now_us = event.timestamp_us
self.trace.record(event.timestamp_us, event.frame)
self.arbitration_bits += frame_wire_bits(event.frame).to_uint64()
if self.bus.publish(event.frame) {
self.delivered += 1
} else {
self.dropped += 1
}
self.reschedule_periodic(event)
Some(event)
}
///|
/// Execute events up to an inclusive timestamp.
pub fn Simulation::run_until(
self : Simulation,
end_us : UInt64,
) -> Int raise SimulationError {
if end_us < self.now_us {
raise TimeWentBackwards
}
let mut count = 0
while self.events.length() > 0 {
match self.events.get(0) {
Some(event) => {
if event.timestamp_us > end_us {
break
}
ignore(self.step())
count += 1
}
None => break
}
}
self.now_us = end_us
count
}
///|
/// Execute at most `limit` events.
pub fn Simulation::run_steps(
self : Simulation,
limit : Int,
) -> Int raise SimulationError {
if limit < 0 {
raise EventLimitReached
}
let mut count = 0
while count < limit && self.events.length() > 0 {
ignore(self.step())
count += 1
}
count
}
///|
/// Drain the highest-priority frame ready at the current time.
pub fn Simulation::receive(self : Simulation) -> Frame? {
self.bus.receive()
}
///|
/// Return the captured trace.
pub fn Simulation::trace(self : Simulation) -> Trace {
self.trace
}
///|
/// Return a summary of the run so far.
pub fn Simulation::report(
self : Simulation,
start_us : UInt64,
) -> SimulationReport {
{
start_us,
end_us: self.now_us,
scheduled: self.scheduled,
delivered: self.delivered,
dropped: self.dropped,
arbitration_bits: self.arbitration_bits,
}
}
///|
pub fn SimulationReport::scheduled(self : SimulationReport) -> Int {
self.scheduled
}
///|
pub fn SimulationReport::delivered(self : SimulationReport) -> Int {
self.delivered
}
///|
pub fn SimulationReport::dropped(self : SimulationReport) -> Int {
self.dropped
}
///|
pub fn SimulationReport::duration_us(self : SimulationReport) -> UInt64 {
self.end_us - self.start_us
}
///|
pub fn SimulationReport::arbitration_bits(self : SimulationReport) -> UInt64 {
self.arbitration_bits
}
///|
pub fn ScheduledFrame::timestamp(self : ScheduledFrame) -> UInt64 {
self.timestamp_us
}
///|
pub fn ScheduledFrame::frame(self : ScheduledFrame) -> Frame {
self.frame
}
///|
pub fn ScheduledFrame::source(self : ScheduledFrame) -> String {
self.source
}
///|
fn compare_events(left : ScheduledFrame, right : ScheduledFrame) -> Int {
if left.timestamp_us < right.timestamp_us {
-1
} else if left.timestamp_us > right.timestamp_us {
1
} else {
compare_frames(left.frame, right.frame)
}
}
///|
fn Simulation::reschedule_periodic(
self : Simulation,
event : ScheduledFrame,
) -> Unit {
for index in 0.. 0 {
self.publishers[index].remaining -= 1
if self.publishers[index].remaining > 0 {
let next_time = event.timestamp_us + publisher.period_us
self.events.push({
timestamp_us: next_time,
frame: publisher.frame,
source: publisher.source,
})
self.events.sort_by((left, right) => compare_events(left, right))
self.scheduled += 1
}
}
}
}