forked from ROMEO/nexosim
Add support for custom/real-time clocks
This commit is contained in:
@ -6,7 +6,7 @@ use asynchronix::model::{Model, Output};
|
||||
use asynchronix::simulation::{Address, EventStream, Mailbox, SimInit, Simulation};
|
||||
use asynchronix::time::MonotonicTime;
|
||||
|
||||
// Simple input-to-output pass-through model.
|
||||
// Input-to-output pass-through model.
|
||||
struct PassThroughModel<T: Clone + Send + 'static> {
|
||||
pub output: Output<T>,
|
||||
}
|
||||
@ -23,13 +23,10 @@ impl<T: Clone + Send + 'static> PassThroughModel<T> {
|
||||
impl<T: Clone + Send + 'static> Model for PassThroughModel<T> {}
|
||||
|
||||
/// A simple bench containing a single pass-through model (input forwarded to
|
||||
/// output).
|
||||
fn simple_bench<T: Clone + Send + 'static>() -> (
|
||||
Simulation,
|
||||
MonotonicTime,
|
||||
Address<PassThroughModel<T>>,
|
||||
EventStream<T>,
|
||||
) {
|
||||
/// 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();
|
||||
@ -37,16 +34,15 @@ fn simple_bench<T: Clone + Send + 'static>() -> (
|
||||
let out_stream = model.output.connect_stream().0;
|
||||
let addr = mbox.address();
|
||||
|
||||
let t0 = MonotonicTime::EPOCH;
|
||||
|
||||
let simu = SimInit::new().add_model(model, mbox).init(t0);
|
||||
|
||||
(simu, t0, addr, out_stream)
|
||||
(simu, addr, out_stream)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simulation_schedule_events() {
|
||||
let (mut simu, t0, addr, mut output) = simple_bench();
|
||||
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)
|
||||
@ -82,7 +78,8 @@ fn simulation_schedule_events() {
|
||||
|
||||
#[test]
|
||||
fn simulation_schedule_keyed_events() {
|
||||
let (mut simu, t0, addr, mut output) = simple_bench();
|
||||
let t0 = MonotonicTime::EPOCH;
|
||||
let (mut simu, addr, mut output) = passthrough_bench(t0);
|
||||
|
||||
let event_t1 = simu
|
||||
.schedule_keyed_event(
|
||||
@ -120,7 +117,8 @@ fn simulation_schedule_keyed_events() {
|
||||
|
||||
#[test]
|
||||
fn simulation_schedule_periodic_events() {
|
||||
let (mut simu, t0, addr, mut output) = simple_bench();
|
||||
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(
|
||||
@ -155,7 +153,8 @@ fn simulation_schedule_periodic_events() {
|
||||
|
||||
#[test]
|
||||
fn simulation_schedule_periodic_keyed_events() {
|
||||
let (mut simu, t0, addr, mut output) = simple_bench();
|
||||
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(
|
||||
@ -197,3 +196,222 @@ fn simulation_schedule_periodic_keyed_events() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user