1
0
forked from ROMEO/nexosim
nexosim/asynchronix/tests/simulation_scheduling.rs
Serge Barral 1be2f48a00 Revert "Merge pull request #12 from asynchronics/feature/event-sinks"
This reverts commit 7e881afb638ccc0dbcfc7b539fc152dc923d63e1, reversing
changes made to 9d78e4f72a4c6ff459fc386b2f25beae40b94429.
2024-03-06 16:16:55 +01:00

418 lines
12 KiB
Rust

//! Event scheduling from a `Simulation` instance.
use std::time::Duration;
use asynchronix::model::{Model, Output};
use asynchronix::simulation::{Address, EventStream, Mailbox, SimInit, Simulation};
use asynchronix::time::MonotonicTime;
// Input-to-output pass-through model.
struct PassThroughModel<T: Clone + Send + 'static> {
pub output: Output<T>,
}
impl<T: Clone + Send + 'static> PassThroughModel<T> {
pub fn new() -> Self {
Self {
output: Output::default(),
}
}
pub async fn input(&mut self, arg: T) {
self.output.send(arg).await;
}
}
impl<T: Clone + Send + 'static> Model for PassThroughModel<T> {}
/// A simple bench containing a single pass-through model (input forwarded to
/// output) running as fast as possible.
fn passthrough_bench<T: Clone + Send + 'static>(
t0: MonotonicTime,
) -> (Simulation, Address<PassThroughModel<T>>, EventStream<T>) {
// Bench assembly.
let mut model = PassThroughModel::new();
let mbox = Mailbox::new();
let out_stream = model.output.connect_stream().0;
let addr = mbox.address();
let simu = SimInit::new().add_model(model, mbox).init(t0);
(simu, addr, out_stream)
}
#[test]
fn simulation_schedule_events() {
let t0 = MonotonicTime::EPOCH;
let (mut simu, addr, mut output) = passthrough_bench(t0);
// Queue 2 events at t0+3s and t0+2s, in reverse order.
simu.schedule_event(Duration::from_secs(3), PassThroughModel::input, (), &addr)
.unwrap();
simu.schedule_event(
t0 + Duration::from_secs(2),
PassThroughModel::input,
(),
&addr,
)
.unwrap();
// Move to the 1st event at t0+2s.
simu.step();
assert_eq!(simu.time(), t0 + Duration::from_secs(2));
assert!(output.next().is_some());
// Schedule another event in 4s (at t0+6s).
simu.schedule_event(Duration::from_secs(4), PassThroughModel::input, (), &addr)
.unwrap();
// Move to the 2nd event at t0+3s.
simu.step();
assert_eq!(simu.time(), t0 + Duration::from_secs(3));
assert!(output.next().is_some());
// Move to the 3rd event at t0+6s.
simu.step();
assert_eq!(simu.time(), t0 + Duration::from_secs(6));
assert!(output.next().is_some());
assert!(output.next().is_none());
}
#[test]
fn simulation_schedule_keyed_events() {
let t0 = MonotonicTime::EPOCH;
let (mut simu, addr, mut output) = passthrough_bench(t0);
let event_t1 = simu
.schedule_keyed_event(
t0 + Duration::from_secs(1),
PassThroughModel::input,
1,
&addr,
)
.unwrap();
let event_t2_1 = simu
.schedule_keyed_event(Duration::from_secs(2), PassThroughModel::input, 21, &addr)
.unwrap();
simu.schedule_event(Duration::from_secs(2), PassThroughModel::input, 22, &addr)
.unwrap();
// Move to the 1st event at t0+1.
simu.step();
// Try to cancel the 1st event after it has already taken place and check
// that the cancellation had no effect.
event_t1.cancel();
assert_eq!(simu.time(), t0 + Duration::from_secs(1));
assert_eq!(output.next(), Some(1));
// Cancel the second event (t0+2) before it is meant to takes place and
// check that we move directly to the 3rd event.
event_t2_1.cancel();
simu.step();
assert_eq!(simu.time(), t0 + Duration::from_secs(2));
assert_eq!(output.next(), Some(22));
assert!(output.next().is_none());
}
#[test]
fn simulation_schedule_periodic_events() {
let t0 = MonotonicTime::EPOCH;
let (mut simu, addr, mut output) = passthrough_bench(t0);
// Queue 2 periodic events at t0 + 3s + k*2s.
simu.schedule_periodic_event(
Duration::from_secs(3),
Duration::from_secs(2),
PassThroughModel::input,
1,
&addr,
)
.unwrap();
simu.schedule_periodic_event(
t0 + Duration::from_secs(3),
Duration::from_secs(2),
PassThroughModel::input,
2,
&addr,
)
.unwrap();
// Move to the next events at t0 + 3s + k*2s.
for k in 0..10 {
simu.step();
assert_eq!(
simu.time(),
t0 + Duration::from_secs(3) + k * Duration::from_secs(2)
);
assert_eq!(output.next(), Some(1));
assert_eq!(output.next(), Some(2));
assert!(output.next().is_none());
}
}
#[test]
fn simulation_schedule_periodic_keyed_events() {
let t0 = MonotonicTime::EPOCH;
let (mut simu, addr, mut output) = passthrough_bench(t0);
// Queue 2 periodic events at t0 + 3s + k*2s.
simu.schedule_periodic_event(
Duration::from_secs(3),
Duration::from_secs(2),
PassThroughModel::input,
1,
&addr,
)
.unwrap();
let event2_key = simu
.schedule_keyed_periodic_event(
t0 + Duration::from_secs(3),
Duration::from_secs(2),
PassThroughModel::input,
2,
&addr,
)
.unwrap();
// Move to the next event at t0+3s.
simu.step();
assert_eq!(simu.time(), t0 + Duration::from_secs(3));
assert_eq!(output.next(), Some(1));
assert_eq!(output.next(), Some(2));
assert!(output.next().is_none());
// Cancel the second event.
event2_key.cancel();
// Move to the next events at t0 + 3s + k*2s.
for k in 1..10 {
simu.step();
assert_eq!(
simu.time(),
t0 + Duration::from_secs(3) + k * Duration::from_secs(2)
);
assert_eq!(output.next(), Some(1));
assert!(output.next().is_none());
}
}
#[cfg(not(miri))]
use std::time::{Instant, SystemTime};
#[cfg(not(miri))]
use asynchronix::time::{AutoSystemClock, Clock, SystemClock};
// Model that outputs timestamps at init and each time its input is triggered.
#[cfg(not(miri))]
#[derive(Default)]
struct TimestampModel {
pub stamp: Output<(Instant, SystemTime)>,
}
#[cfg(not(miri))]
impl TimestampModel {
pub async fn trigger(&mut self) {
self.stamp.send((Instant::now(), SystemTime::now())).await;
}
}
#[cfg(not(miri))]
impl Model for TimestampModel {
fn init(
mut self,
_scheduler: &asynchronix::time::Scheduler<Self>,
) -> std::pin::Pin<
Box<
dyn futures_util::Future<Output = asynchronix::model::InitializedModel<Self>>
+ Send
+ '_,
>,
> {
Box::pin(async {
self.stamp.send((Instant::now(), SystemTime::now())).await;
self.into()
})
}
}
/// A simple bench containing a single timestamping model with a custom clock.
#[cfg(not(miri))]
fn timestamp_bench(
t0: MonotonicTime,
clock: impl Clock + 'static,
) -> (
Simulation,
Address<TimestampModel>,
EventStream<(Instant, SystemTime)>,
) {
// Bench assembly.
let mut model = TimestampModel::default();
let mbox = Mailbox::new();
let stamp_stream = model.stamp.connect_stream().0;
let addr = mbox.address();
let simu = SimInit::new()
.add_model(model, mbox)
.init_with_clock(t0, clock);
(simu, addr, stamp_stream)
}
#[cfg(not(miri))]
#[test]
fn simulation_system_clock_from_instant() {
let t0 = MonotonicTime::EPOCH;
const TOLERANCE: f64 = 0.0005; // [s]
// The reference simulation time is set in the past of t0 so that the
// simulation starts in the future when the reference wall clock time is
// close to the wall clock time when the simulation in initialized.
let simulation_ref_offset = 0.3; // [s] must be greater than any `instant_offset`.
let simulation_ref = t0 - Duration::from_secs_f64(simulation_ref_offset);
// Test reference wall clock times in the near past and near future.
for wall_clock_offset in [-0.1, 0.1] {
// The clock reference is the current time offset by `instant_offset`.
let wall_clock_init = Instant::now();
let wall_clock_ref = if wall_clock_offset >= 0.0 {
wall_clock_init + Duration::from_secs_f64(wall_clock_offset)
} else {
wall_clock_init - Duration::from_secs_f64(-wall_clock_offset)
};
let clock = SystemClock::from_instant(simulation_ref, wall_clock_ref);
let (mut simu, addr, mut stamp) = timestamp_bench(t0, clock);
// Queue a single event at t0 + 0.1s.
simu.schedule_event(
Duration::from_secs_f64(0.1),
TimestampModel::trigger,
(),
&addr,
)
.unwrap();
// Check the stamps.
for expected_time in [
simulation_ref_offset + wall_clock_offset,
simulation_ref_offset + wall_clock_offset + 0.1,
] {
let measured_time = (stamp.next().unwrap().0 - wall_clock_init).as_secs_f64();
assert!(
(expected_time - measured_time).abs() <= TOLERANCE,
"Expected t = {:.6}s +/- {:.6}s, measured t = {:.6}s",
expected_time,
TOLERANCE,
measured_time,
);
simu.step();
}
}
}
#[cfg(not(miri))]
#[test]
fn simulation_system_clock_from_system_time() {
let t0 = MonotonicTime::EPOCH;
const TOLERANCE: f64 = 0.005; // [s]
// The reference simulation time is set in the past of t0 so that the
// simulation starts in the future when the reference wall clock time is
// close to the wall clock time when the simulation in initialized.
let simulation_ref_offset = 0.3; // [s] must be greater than any `instant_offset`.
let simulation_ref = t0 - Duration::from_secs_f64(simulation_ref_offset);
// Test reference wall clock times in the near past and near future.
for wall_clock_offset in [-0.1, 0.1] {
// The clock reference is the current time offset by `instant_offset`.
let wall_clock_init = SystemTime::now();
let wall_clock_ref = if wall_clock_offset >= 0.0 {
wall_clock_init + Duration::from_secs_f64(wall_clock_offset)
} else {
wall_clock_init - Duration::from_secs_f64(-wall_clock_offset)
};
let clock = SystemClock::from_system_time(simulation_ref, wall_clock_ref);
let (mut simu, addr, mut stamp) = timestamp_bench(t0, clock);
// Queue a single event at t0 + 0.1s.
simu.schedule_event(
Duration::from_secs_f64(0.1),
TimestampModel::trigger,
(),
&addr,
)
.unwrap();
// Check the stamps.
for expected_time in [
simulation_ref_offset + wall_clock_offset,
simulation_ref_offset + wall_clock_offset + 0.1,
] {
let measured_time = stamp
.next()
.unwrap()
.1
.duration_since(wall_clock_init)
.unwrap()
.as_secs_f64();
assert!(
(expected_time - measured_time).abs() <= TOLERANCE,
"Expected t = {:.6}s +/- {:.6}s, measured t = {:.6}s",
expected_time,
TOLERANCE,
measured_time,
);
simu.step();
}
}
}
#[cfg(not(miri))]
#[test]
fn simulation_auto_system_clock() {
let t0 = MonotonicTime::EPOCH;
const TOLERANCE: f64 = 0.005; // [s]
let (mut simu, addr, mut stamp) = timestamp_bench(t0, AutoSystemClock::new());
let instant_t0 = Instant::now();
// Queue a periodic event at t0 + 0.2s + k*0.2s.
simu.schedule_periodic_event(
Duration::from_secs_f64(0.2),
Duration::from_secs_f64(0.2),
TimestampModel::trigger,
(),
&addr,
)
.unwrap();
// Queue a single event at t0 + 0.3s.
simu.schedule_event(
Duration::from_secs_f64(0.3),
TimestampModel::trigger,
(),
&addr,
)
.unwrap();
// Check the stamps.
for expected_time in [0.0, 0.2, 0.3, 0.4, 0.6] {
let measured_time = (stamp.next().unwrap().0 - instant_t0).as_secs_f64();
assert!(
(expected_time - measured_time).abs() <= TOLERANCE,
"Expected t = {:.6}s +/- {:.6}s, measured t = {:.6}s",
expected_time,
TOLERANCE,
measured_time,
);
simu.step();
}
}