From b5aea810ae8508f8bb9e031839eea12d7fca3ef3 Mon Sep 17 00:00:00 2001 From: Serge Barral Date: Thu, 14 Nov 2024 12:45:00 +0100 Subject: [PATCH] Merge step_by and step_until into a unique method Now that `step_by` returns an error anyway (it was unfaillible before), there is no more incentive to keep it as a separate method. The `step_until` method now accepts an `impl Deadline`, which covers both cases (`Duration` and `MonotonicTime`). --- asynchronix-util/examples/observables.rs | 6 ++-- asynchronix/examples/assembly.rs | 2 +- asynchronix/examples/external_input.rs | 2 +- asynchronix/examples/stepper_motor.rs | 6 ++-- .../src/grpc/services/controller_service.rs | 4 ++- asynchronix/src/lib.rs | 4 +-- asynchronix/src/simulation.rs | 30 +++++-------------- asynchronix/src/simulation/scheduler.rs | 26 +--------------- asynchronix/src/time.rs | 24 +++++++++++++++ .../integration/simulation_clock_sync.rs | 4 +-- 10 files changed, 48 insertions(+), 60 deletions(-) diff --git a/asynchronix-util/examples/observables.rs b/asynchronix-util/examples/observables.rs index 1949b98..6403d18 100644 --- a/asynchronix-util/examples/observables.rs +++ b/asynchronix-util/examples/observables.rs @@ -230,7 +230,7 @@ fn main() -> Result<(), SimulationError> { ); // All data processed. - simu.step_by(Duration::from_millis(101))?; + simu.step_until(Duration::from_millis(101))?; expect( &mut mode, Some(ModeId::Idle), @@ -266,7 +266,7 @@ fn main() -> Result<(), SimulationError> { ); // Wait for short processing to finish, check results. - simu.step_by(Duration::from_millis(11))?; + simu.step_until(Duration::from_millis(11))?; expect( &mut mode, Some(ModeId::Idle), @@ -279,7 +279,7 @@ fn main() -> Result<(), SimulationError> { // Wait long enough, no state change as the long processing has been // cancelled. - simu.step_by(Duration::from_millis(100))?; + simu.step_until(Duration::from_millis(100))?; assert_eq!(mode.next(), None); assert_eq!(value.next(), None); assert_eq!(hk.next(), None); diff --git a/asynchronix/examples/assembly.rs b/asynchronix/examples/assembly.rs index b3c42a0..00b0dbb 100644 --- a/asynchronix/examples/assembly.rs +++ b/asynchronix/examples/assembly.rs @@ -175,7 +175,7 @@ fn main() -> Result<(), SimulationError> { // Advance simulation time by 0.9s, which with a 10Hz PPS should correspond to // 9 position increments. - simu.step_by(Duration::new(0, 900_000_000))?; + simu.step_until(Duration::new(0, 900_000_000))?; t += Duration::new(0, 900_000_000); assert_eq!(simu.time(), t); for _ in 0..9 { diff --git a/asynchronix/examples/external_input.rs b/asynchronix/examples/external_input.rs index e5b48f2..b72bd7b 100644 --- a/asynchronix/examples/external_input.rs +++ b/asynchronix/examples/external_input.rs @@ -236,7 +236,7 @@ fn main() -> Result<(), SimulationError> { }); // Advance simulation, external messages will be collected. - simu.step_by(Duration::from_secs(2))?; + simu.step_until(Duration::from_secs(2))?; // Shut down the server. let socket = sender_handle.join().unwrap(); diff --git a/asynchronix/examples/stepper_motor.rs b/asynchronix/examples/stepper_motor.rs index 52af7d7..470af09 100644 --- a/asynchronix/examples/stepper_motor.rs +++ b/asynchronix/examples/stepper_motor.rs @@ -251,7 +251,7 @@ fn main() -> Result<(), asynchronix::simulation::SimulationError> { // Advance simulation time by 0.9s, which with a 10Hz PPS should correspond to // 9 position increments. - simu.step_by(Duration::new(0, 900_000_000))?; + simu.step_until(Duration::new(0, 900_000_000))?; t += Duration::new(0, 900_000_000); assert_eq!(simu.time(), t); for _ in 0..9 { @@ -290,7 +290,7 @@ fn main() -> Result<(), asynchronix::simulation::SimulationError> { // Advance simulation time by 0.7s, which with a 10Hz PPS should correspond to // 7 position increments. - simu.step_by(Duration::new(0, 700_000_000))?; + simu.step_until(Duration::new(0, 700_000_000))?; t += Duration::new(0, 700_000_000); assert_eq!(simu.time(), t); for _ in 0..7 { @@ -310,7 +310,7 @@ fn main() -> Result<(), asynchronix::simulation::SimulationError> { // Advance simulation time by 1.9s, which with a -10Hz PPS should correspond // to 19 position decrements. - simu.step_by(Duration::new(1, 900_000_000))?; + simu.step_until(Duration::new(1, 900_000_000))?; t += Duration::new(1, 900_000_000); assert_eq!(simu.time(), t); pos = (pos + Motor::STEPS_PER_REV - 19) % Motor::STEPS_PER_REV; diff --git a/asynchronix/src/grpc/services/controller_service.rs b/asynchronix/src/grpc/services/controller_service.rs index c8dd1d4..c3f4c83 100644 --- a/asynchronix/src/grpc/services/controller_service.rs +++ b/asynchronix/src/grpc/services/controller_service.rs @@ -118,7 +118,9 @@ impl ControllerService { "the specified deadline lies in the past", ))?; - simulation.step_by(duration).map_err(map_execution_error)?; + simulation + .step_until(duration) + .map_err(map_execution_error)?; } }; diff --git a/asynchronix/src/lib.rs b/asynchronix/src/lib.rs index 142f7b1..115c70e 100644 --- a/asynchronix/src/lib.rs +++ b/asynchronix/src/lib.rs @@ -246,8 +246,8 @@ //! //! 1. by advancing time, either until the next scheduled event with //! [`Simulation::step()`](simulation::Simulation::step), or until a specific -//! deadline using for instance -//! [`Simulation::step_by()`](simulation::Simulation::step_by). +//! deadline with +//! [`Simulation::step_until()`](simulation::Simulation::step_until). //! 2. by sending events or queries without advancing simulation time, using //! [`Simulation::process_event()`](simulation::Simulation::process_event) or //! [`Simulation::send_query()`](simulation::Simulation::process_query), diff --git a/asynchronix/src/simulation.rs b/asynchronix/src/simulation.rs index 60ef222..f4a00b3 100644 --- a/asynchronix/src/simulation.rs +++ b/asynchronix/src/simulation.rs @@ -122,9 +122,7 @@ mod scheduler; mod sim_init; pub use mailbox::{Address, Mailbox}; -pub use scheduler::{ - Action, ActionKey, AutoActionKey, Deadline, LocalScheduler, Scheduler, SchedulingError, -}; +pub use scheduler::{Action, ActionKey, AutoActionKey, LocalScheduler, Scheduler, SchedulingError}; pub(crate) use scheduler::{ KeyedOnceAction, KeyedPeriodicAction, OnceAction, PeriodicAction, SchedulerQueue, }; @@ -148,7 +146,7 @@ use crate::channel::ChannelObserver; use crate::executor::{Executor, ExecutorError, Signal}; use crate::model::{BuildContext, Context, Model, ProtoModel}; use crate::ports::{InputFn, ReplierFn}; -use crate::time::{AtomicTime, Clock, MonotonicTime, SyncStatus}; +use crate::time::{AtomicTime, Clock, Deadline, MonotonicTime, SyncStatus}; use crate::util::seq_futures::SeqFuture; use crate::util::slot; @@ -189,9 +187,8 @@ thread_local! { pub(crate) static CURRENT_MODEL_ID: Cell = const { Cell /// desired wall clock time, and finally /// 3. run all computations scheduled for the new simulation time. /// -/// The [`step_by()`](Simulation::step_by) and -/// [`step_until()`](Simulation::step_until) methods operate similarly but -/// iterate until the target simulation time has been reached. +/// The [`step_until()`](Simulation::step_until) method operates similarly but +/// iterates until the target simulation time has been reached. pub struct Simulation { executor: Executor, scheduler_queue: Arc>, @@ -260,19 +257,6 @@ impl Simulation { self.step_to_next_bounded(MonotonicTime::MAX).map(|_| ()) } - /// Iteratively advances the simulation time by the specified duration, as - /// if by calling [`Simulation::step()`] repeatedly. - /// - /// This method blocks until all events scheduled up to the specified target - /// time have completed. The simulation time upon completion is equal to the - /// initial simulation time incremented by the specified duration, whether - /// or not an event was scheduled for that time. - pub fn step_by(&mut self, duration: Duration) -> Result<(), ExecutionError> { - let target_time = self.time.read() + duration; - - self.step_until_unchecked(target_time) - } - /// Iteratively advances the simulation time until the specified deadline, /// as if by calling [`Simulation::step()`] repeatedly. /// @@ -280,8 +264,10 @@ impl Simulation { /// time have completed. The simulation time upon completion is equal to the /// specified target time, whether or not an event was scheduled for that /// time. - pub fn step_until(&mut self, target_time: MonotonicTime) -> Result<(), ExecutionError> { - if self.time.read() >= target_time { + pub fn step_until(&mut self, deadline: impl Deadline) -> Result<(), ExecutionError> { + let now = self.time.read(); + let target_time = deadline.into_time(now); + if target_time < now { return Err(ExecutionError::InvalidDeadline(target_time)); } self.step_until_unchecked(target_time) diff --git a/asynchronix/src/simulation/scheduler.rs b/asynchronix/src/simulation/scheduler.rs index 79aeead..1cfc394 100644 --- a/asynchronix/src/simulation/scheduler.rs +++ b/asynchronix/src/simulation/scheduler.rs @@ -18,7 +18,7 @@ use crate::executor::Executor; use crate::model::Model; use crate::ports::InputFn; use crate::simulation::Address; -use crate::time::{AtomicTimeReader, MonotonicTime}; +use crate::time::{AtomicTimeReader, Deadline, MonotonicTime}; use crate::util::priority_queue::PriorityQueue; /// Scheduler. @@ -557,30 +557,6 @@ impl fmt::Debug for LocalScheduler { // control their relative order of execution. pub(crate) type SchedulerQueue = PriorityQueue<(MonotonicTime, usize), Action>; -/// Trait abstracting over time-absolute and time-relative deadlines. -/// -/// This trait is implemented by [`std::time::Duration`] and -/// [`MonotonicTime`]. -pub trait Deadline { - /// Make this deadline into an absolute timestamp, using the provided - /// current time as a reference. - fn into_time(self, now: MonotonicTime) -> MonotonicTime; -} - -impl Deadline for Duration { - #[inline(always)] - fn into_time(self, now: MonotonicTime) -> MonotonicTime { - now + self - } -} - -impl Deadline for MonotonicTime { - #[inline(always)] - fn into_time(self, _: MonotonicTime) -> MonotonicTime { - self - } -} - /// Managed handle to a scheduled action. /// /// An `AutoActionKey` is a managed handle to a scheduled action that cancels diff --git a/asynchronix/src/time.rs b/asynchronix/src/time.rs index e31ac58..ff91963 100644 --- a/asynchronix/src/time.rs +++ b/asynchronix/src/time.rs @@ -55,3 +55,27 @@ pub(crate) use monotonic_time::TearableAtomicTime; pub(crate) type AtomicTime = crate::util::sync_cell::SyncCell; pub(crate) type AtomicTimeReader = crate::util::sync_cell::SyncCellReader; + +/// Trait abstracting over time-absolute and time-relative deadlines. +/// +/// This trait is implemented by [`std::time::Duration`] and +/// [`MonotonicTime`]. +pub trait Deadline { + /// Make this deadline into an absolute timestamp, using the provided + /// current time as a reference. + fn into_time(self, now: MonotonicTime) -> MonotonicTime; +} + +impl Deadline for std::time::Duration { + #[inline(always)] + fn into_time(self, now: MonotonicTime) -> MonotonicTime { + now + self + } +} + +impl Deadline for MonotonicTime { + #[inline(always)] + fn into_time(self, _: MonotonicTime) -> MonotonicTime { + self + } +} diff --git a/asynchronix/tests/integration/simulation_clock_sync.rs b/asynchronix/tests/integration/simulation_clock_sync.rs index 0bbd7c7..1fbe9cc 100644 --- a/asynchronix/tests/integration/simulation_clock_sync.rs +++ b/asynchronix/tests/integration/simulation_clock_sync.rs @@ -23,7 +23,7 @@ impl Model for TestModel {} // synchronization tolerance. // // Returns the last simulation tick at completion or when the error occurred, and -// the result of `Simulation::step_by`. +// the result of `Simulation::step_until`. fn clock_sync( num_threads: usize, block_time_ms: u64, @@ -58,7 +58,7 @@ fn clock_sync( .unwrap(); } - let res = simu.step_by(delta); + let res = simu.step_until(delta); let last_tick = simu.time().duration_since(t0); (last_tick, res)