1
0
forked from ROMEO/nexosim

Merge pull request #1 from asynchronics/feature-submodels

Add setup step
This commit is contained in:
Jauhien Piatlicki 2024-04-26 11:55:03 +02:00 committed by GitHub
commit 97b173a081
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 836 additions and 798 deletions

View File

@ -17,7 +17,7 @@ jobs:
matrix:
rust:
- stable
- 1.64.0
- 1.75.0
steps:
- name: Checkout sources
uses: actions/checkout@v3

View File

@ -9,7 +9,7 @@ name = "asynchronix"
authors = ["Serge Barral <serge.barral@asynchronics.com>"]
version = "0.2.2"
edition = "2021"
rust-version = "1.64"
rust-version = "1.75"
license = "MIT OR Apache-2.0"
repository = "https://github.com/asynchronics/asynchronix"
readme = "../README.md"

View File

@ -31,14 +31,12 @@
//! (-)
//! ```
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use asynchronix::model::{InitializedModel, Model};
use asynchronix::model::{Context, InitializedModel, Model};
use asynchronix::ports::{EventSlot, Output};
use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{ActionKey, MonotonicTime, Scheduler};
use asynchronix::simulation::{ActionKey, Mailbox, SimInit};
use asynchronix::time::MonotonicTime;
/// Water pump.
pub struct Pump {
@ -122,7 +120,7 @@ impl Controller {
}
/// Starts brewing or cancels the current brew -- input port.
pub async fn brew_cmd(&mut self, _: (), scheduler: &Scheduler<Self>) {
pub async fn brew_cmd(&mut self, _: (), context: &Context<Self>) {
// If a brew was ongoing, sending the brew command is interpreted as a
// request to cancel it.
if let Some(key) = self.stop_brew_key.take() {
@ -141,7 +139,7 @@ impl Controller {
// Schedule the `stop_brew()` method and turn on the pump.
self.stop_brew_key = Some(
scheduler
context
.schedule_keyed_event(self.brew_time, Self::stop_brew, ())
.unwrap(),
);
@ -190,7 +188,7 @@ impl Tank {
}
/// Water volume added [m³] -- input port.
pub async fn fill(&mut self, added_volume: f64, scheduler: &Scheduler<Self>) {
pub async fn fill(&mut self, added_volume: f64, context: &Context<Self>) {
// Ignore zero and negative values. We could also impose a maximum based
// on tank capacity.
if added_volume <= 0.0 {
@ -208,11 +206,11 @@ impl Tank {
state.set_empty_key.cancel();
// Update the volume, saturating at 0 in case of rounding errors.
let time = scheduler.time();
let time = context.time();
let elapsed_time = time.duration_since(state.last_volume_update).as_secs_f64();
self.volume = (self.volume - state.flow_rate * elapsed_time).max(0.0);
self.schedule_empty(state.flow_rate, time, scheduler).await;
self.schedule_empty(state.flow_rate, time, context).await;
// There is no need to broadcast the state of the water sense since
// it could not be previously `Empty` (otherwise the dynamic state
@ -230,10 +228,10 @@ impl Tank {
/// # Panics
///
/// This method will panic if the flow rate is negative.
pub async fn set_flow_rate(&mut self, flow_rate: f64, scheduler: &Scheduler<Self>) {
pub async fn set_flow_rate(&mut self, flow_rate: f64, context: &Context<Self>) {
assert!(flow_rate >= 0.0);
let time = scheduler.time();
let time = context.time();
// If the flow rate was non-zero up to now, update the volume.
if let Some(state) = self.dynamic_state.take() {
@ -245,7 +243,7 @@ impl Tank {
self.volume = (self.volume - state.flow_rate * elapsed_time).max(0.0);
}
self.schedule_empty(flow_rate, time, scheduler).await;
self.schedule_empty(flow_rate, time, context).await;
}
/// Schedules a callback for when the tank becomes empty.
@ -258,7 +256,7 @@ impl Tank {
&mut self,
flow_rate: f64,
time: MonotonicTime,
scheduler: &Scheduler<Self>,
context: &Context<Self>,
) {
// Determine when the tank will be empty at the current flow rate.
let duration_until_empty = if self.volume == 0.0 {
@ -275,7 +273,7 @@ impl Tank {
let duration_until_empty = Duration::from_secs_f64(duration_until_empty);
// Schedule the next update.
match scheduler.schedule_keyed_event(duration_until_empty, Self::set_empty, ()) {
match context.schedule_keyed_event(duration_until_empty, Self::set_empty, ()) {
Ok(set_empty_key) => {
let state = TankDynamicState {
last_volume_update: time,
@ -302,21 +300,16 @@ impl Tank {
impl Model for Tank {
/// Broadcasts the initial state of the water sense.
fn init(
mut self,
_scheduler: &Scheduler<Self>,
) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>> {
Box::pin(async move {
self.water_sense
.send(if self.volume == 0.0 {
WaterSenseState::Empty
} else {
WaterSenseState::NotEmpty
})
.await;
async fn init(mut self, _: &Context<Self>) -> InitializedModel<Self> {
self.water_sense
.send(if self.volume == 0.0 {
WaterSenseState::Empty
} else {
WaterSenseState::NotEmpty
})
.await;
self.into()
})
self.into()
}
}

View File

@ -24,7 +24,7 @@
//! │ Power │ ◀current │ │
//! │ supply │ └────────┘
//! │ ├───────────────────────────────▶ Total power
//! └──────────┘
//! └──────────┘
//! ```
use asynchronix::model::Model;
use asynchronix::ports::{EventSlot, Output, Requestor};

View File

@ -15,13 +15,12 @@
//! ```
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use asynchronix::model::{InitializedModel, Model};
use asynchronix::model::{Context, InitializedModel, Model};
use asynchronix::ports::{EventBuffer, Output};
use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{MonotonicTime, Scheduler};
use asynchronix::time::MonotonicTime;
/// Stepper motor.
pub struct Motor {
@ -88,15 +87,9 @@ impl Motor {
impl Model for Motor {
/// Broadcasts the initial position of the motor.
fn init(
mut self,
_scheduler: &Scheduler<Self>,
) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>> {
Box::pin(async move {
self.position.send(self.pos).await;
self.into()
})
async fn init(mut self, _: &Context<Self>) -> InitializedModel<Self> {
self.position.send(self.pos).await;
self.into()
}
}
@ -130,7 +123,7 @@ impl Driver {
}
/// Sets the pulse rate (sign = direction) [Hz] -- input port.
pub async fn pulse_rate(&mut self, pps: f64, scheduler: &Scheduler<Self>) {
pub async fn pulse_rate(&mut self, pps: f64, context: &Context<Self>) {
let pps = pps.signum() * pps.abs().clamp(Self::MIN_PPS, Self::MAX_PPS);
if pps == self.pps {
return;
@ -142,7 +135,7 @@ impl Driver {
// Trigger the rotation if the motor is currently idle. Otherwise the
// new value will be accounted for at the next pulse.
if is_idle {
self.send_pulse((), scheduler).await;
self.send_pulse((), context).await;
}
}
@ -153,7 +146,7 @@ impl Driver {
fn send_pulse<'a>(
&'a mut self,
_: (),
scheduler: &'a Scheduler<Self>,
context: &'a Context<Self>,
) -> impl Future<Output = ()> + Send + 'a {
async move {
let current_out = match self.next_phase {
@ -174,7 +167,7 @@ impl Driver {
let pulse_duration = Duration::from_secs_f64(1.0 / self.pps.abs());
// Schedule the next pulse.
scheduler
context
.schedule_event(pulse_duration, Self::send_pulse, ())
.unwrap();
}

View File

@ -18,8 +18,7 @@ use recycle_box::RecycleBox;
use queue::{PopError, PushError, Queue};
use recycle_box::coerce_box;
use crate::model::Model;
use crate::time::Scheduler;
use crate::model::{Context, Model};
/// Data shared between the receiver and the senders.
struct Inner<M> {
@ -45,7 +44,7 @@ impl<M: 'static> Inner<M> {
}
/// A receiver which can asynchronously execute `async` message that take an
/// argument of type `&mut M` and an optional `&Scheduler<M>` argument.
/// argument of type `&mut M` and an optional `&Context<M>` argument.
pub(crate) struct Receiver<M> {
/// Shared data.
inner: Arc<Inner<M>>,
@ -90,7 +89,7 @@ impl<M: Model> Receiver<M> {
pub(crate) async fn recv(
&mut self,
model: &mut M,
scheduler: &Scheduler<M>,
context: &Context<M>,
) -> Result<(), RecvError> {
let msg = unsafe {
self.inner
@ -106,7 +105,7 @@ impl<M: Model> Receiver<M> {
match msg {
Some(mut msg) => {
// Consume the message to obtain a boxed future.
let fut = msg.call_once(model, scheduler, self.future_box.take().unwrap());
let fut = msg.call_once(model, context, self.future_box.take().unwrap());
// Now that `msg` was consumed and its slot in the queue was
// freed, signal to one awaiting sender that one slot is
@ -188,7 +187,7 @@ impl<M: Model> Sender<M> {
where
F: for<'a> FnOnce(
&'a mut M,
&'a Scheduler<M>,
&'a Context<M>,
RecycleBox<()>,
) -> RecycleBox<dyn Future<Output = ()> + Send + 'a>
+ Send
@ -311,7 +310,7 @@ impl<M> fmt::Debug for Sender<M> {
}
/// A closure that can be called once to create a future boxed in a `RecycleBox`
/// from an `&mut M`, a `&Scheduler<M>` and an empty `RecycleBox`.
/// from an `&mut M`, a `&Context<M>` and an empty `RecycleBox`.
///
/// This is basically a workaround to emulate an `FnOnce` with the equivalent of
/// an `FnMut` so that it is possible to call it as a `dyn` trait stored in a
@ -327,7 +326,7 @@ trait MessageFn<M: Model>: Send {
fn call_once<'a>(
&mut self,
model: &'a mut M,
scheduler: &'a Scheduler<M>,
context: &'a Context<M>,
recycle_box: RecycleBox<()>,
) -> RecycleBox<dyn Future<Output = ()> + Send + 'a>;
}
@ -349,7 +348,7 @@ impl<F, M: Model> MessageFn<M> for MessageFnOnce<F, M>
where
F: for<'a> FnOnce(
&'a mut M,
&'a Scheduler<M>,
&'a Context<M>,
RecycleBox<()>,
) -> RecycleBox<dyn Future<Output = ()> + Send + 'a>
+ Send,
@ -357,12 +356,12 @@ where
fn call_once<'a>(
&mut self,
model: &'a mut M,
scheduler: &'a Scheduler<M>,
context: &'a Context<M>,
recycle_box: RecycleBox<()>,
) -> RecycleBox<dyn Future<Output = ()> + Send + 'a> {
let closure = self.msg_fn.take().unwrap();
(closure)(model, scheduler, recycle_box)
(closure)(model, context, recycle_box)
}
}

View File

@ -45,7 +45,7 @@
//! * _input ports_, which are synchronous or asynchronous methods that
//! implement the [`InputFn`](ports::InputFn) trait and take an `&mut self`
//! argument, a message argument, and an optional
//! [`&Scheduler`](time::Scheduler) argument,
//! [`&Context`](model::Context) argument,
//! * _replier ports_, which are similar to input ports but implement the
//! [`ReplierFn`](ports::ReplierFn) trait and return a reply.
//!
@ -54,12 +54,17 @@
//! are referred to as *requests* and *replies*.
//!
//! Models must implement the [`Model`](model::Model) trait. The main purpose of
//! this trait is to allow models to specify an `init()` method that is
//! guaranteed to run once and only once when the simulation is initialized,
//! _i.e._ after all models have been connected but before the simulation
//! starts. The `init()` method has a default implementation, so models that do
//! not require initialization can simply implement the trait with a one-liner
//! such as `impl Model for MyModel {}`.
//! this trait is to allow models to specify
//! * a `setup()` method that is called once during model addtion to simulation,
//! this method allows e.g. creation and interconnection of submodels inside
//! the model,
//! * an `init()` method that is guaranteed to run once and only once when the
//! simulation is initialized, _i.e._ after all models have been connected but
//! before the simulation starts.
//!
//! The `setup()` and `init()` methods have default implementations, so models
//! that do not require setup and initialization can simply implement the trait
//! with a one-liner such as `impl Model for MyModel {}`.
//!
//! #### A simple model
//!
@ -93,29 +98,28 @@
//! impl Model for Multiplier {}
//! ```
//!
//! #### A model using the local scheduler
//! #### A model using the local context
//!
//! Models frequently need to schedule actions at a future time or simply get
//! access to the current simulation time. To do so, input and replier methods
//! can take an optional argument that gives them access to a local scheduler.
//! can take an optional argument that gives them access to a local context.
//!
//! To show how the local scheduler can be used in practice, let us implement
//! To show how the local context can be used in practice, let us implement
//! `Delay`, a model which simply forwards its input unmodified after a 1s
//! delay:
//!
//! ```
//! use std::time::Duration;
//! use asynchronix::model::Model;
//! use asynchronix::model::{Context, Model};
//! use asynchronix::ports::Output;
//! use asynchronix::time::Scheduler;
//!
//! #[derive(Default)]
//! pub struct Delay {
//! pub output: Output<f64>,
//! }
//! impl Delay {
//! pub fn input(&mut self, value: f64, scheduler: &Scheduler<Self>) {
//! scheduler.schedule_event(Duration::from_secs(1), Self::send, value).unwrap();
//! pub fn input(&mut self, value: f64, context: &Context<Self>) {
//! context.schedule_event(Duration::from_secs(1), Self::send, value).unwrap();
//! }
//!
//! async fn send(&mut self, value: f64) {
@ -137,7 +141,7 @@
//! [`Address`](simulation::Mailbox)es pointing to that mailbox.
//!
//! Addresses are used among others to connect models: each output or requestor
//! ports has a `connect()` method that takes as argument a function pointer to
//! port has a `connect()` method that takes as argument a function pointer to
//! the corresponding input or replier port method and the address of the
//! targeted model.
//!
@ -168,9 +172,8 @@
//! ```
//! # mod models {
//! # use std::time::Duration;
//! # use asynchronix::model::Model;
//! # use asynchronix::model::{Context, Model};
//! # use asynchronix::ports::Output;
//! # use asynchronix::time::Scheduler;
//! # #[derive(Default)]
//! # pub struct Multiplier {
//! # pub output: Output<f64>,
@ -186,8 +189,8 @@
//! # pub output: Output<f64>,
//! # }
//! # impl Delay {
//! # pub fn input(&mut self, value: f64, scheduler: &Scheduler<Self>) {
//! # scheduler.schedule_event(Duration::from_secs(1), Self::send, value).unwrap();
//! # pub fn input(&mut self, value: f64, context: &Context<Self>) {
//! # context.schedule_event(Duration::from_secs(1), Self::send, value).unwrap();
//! # }
//! # async fn send(&mut self, value: f64) { // this method can be private
//! # self.output.send(value).await;
@ -268,9 +271,8 @@
//! ```
//! # mod models {
//! # use std::time::Duration;
//! # use asynchronix::model::Model;
//! # use asynchronix::model::{Context, Model};
//! # use asynchronix::ports::Output;
//! # use asynchronix::time::Scheduler;
//! # #[derive(Default)]
//! # pub struct Multiplier {
//! # pub output: Output<f64>,
@ -286,8 +288,8 @@
//! # pub output: Output<f64>,
//! # }
//! # impl Delay {
//! # pub fn input(&mut self, value: f64, scheduler: &Scheduler<Self>) {
//! # scheduler.schedule_event(Duration::from_secs(1), Self::send, value).unwrap();
//! # pub fn input(&mut self, value: f64, context: &Context<Self>) {
//! # context.schedule_event(Duration::from_secs(1), Self::send, value).unwrap();
//! # }
//! # async fn send(&mut self, value: f64) { // this method can be private
//! # self.output.send(value).await;
@ -395,15 +397,14 @@
//!
//! * the [`model`] module provides more details about the signatures of input
//! and replier port methods and discusses model initialization in the
//! documentation of [`model::Model`],
//! documentation of [`model::Model`] and self-scheduling methods as well as
//! scheduling cancellation in the documentation of [`model::Context`],
//! * the [`simulation`] module discusses how the capacity of mailboxes may
//! affect the simulation, how connections can be modified after the
//! simulation was instantiated, and which pathological situations can lead to
//! a deadlock,
//! * the [`time`] module discusses in particular self-scheduling methods and
//! scheduling cancellation in the documentation of [`time::Scheduler`] while
//! the monotonic timestamp format used for simulations is documented in
//! [`time::MonotonicTime`].
//! * the [`time`] module discusses in particular the monotonic timestamp format
//! used for simulations ([`time::MonotonicTime`]).
#![warn(missing_docs, missing_debug_implementations, unreachable_pub)]
pub(crate) mod channel;

View File

@ -2,16 +2,19 @@
//!
//! # Model trait
//!
//! Every model must implement the [`Model`] trait. This trait defines an
//! asynchronous initialization method, [`Model::init()`], which main purpose is
//! to enable models to perform specific actions only once all models have been
//! connected and migrated to the simulation, but before the simulation actually
//! starts.
//! Every model must implement the [`Model`] trait. This trait defines
//! * a setup method, [`Model::setup()`], which main purpose is to create,
//! connect and add to the simulation bench submodels and perform other setup
//! steps,
//! * an asynchronous initialization method, [`Model::init()`], which main
//! purpose is to enable models to perform specific actions only once all
//! models have been connected and migrated to the simulation, but before the
//! simulation actually starts.
//!
//! #### Examples
//!
//! A model that does not require initialization can simply use the default
//! implementation of the `Model` trait:
//! A model that does not require setup and initialization can simply use the
//! default implementation of the `Model` trait:
//!
//! ```
//! use asynchronix::model::Model;
@ -22,28 +25,31 @@
//! impl Model for MyModel {}
//! ```
//!
//! Otherwise, a custom `init()` method can be implemented:
//! Otherwise, custom `setup()` or `init()` methods can be implemented:
//!
//! ```
//! use std::future::Future;
//! use std::pin::Pin;
//!
//! use asynchronix::model::{InitializedModel, Model};
//! use asynchronix::time::Scheduler;
//! use asynchronix::model::{Context, InitializedModel, Model, SetupContext};
//!
//! pub struct MyModel {
//! // ...
//! }
//! impl Model for MyModel {
//! fn init(
//! mut self,
//! scheduler: &Scheduler<Self>
//! ) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>>{
//! Box::pin(async move {
//! println!("...initialization...");
//! fn setup(
//! &mut self,
//! setup_context: &SetupContext<Self>) {
//! println!("...setup...");
//! }
//!
//! self.into()
//! })
//! async fn init(
//! mut self,
//! context: &Context<Self>
//! ) -> InitializedModel<Self> {
//! println!("...initialization...");
//!
//! self.into()
//! }
//! }
//! ```
@ -103,17 +109,17 @@
//! ```ignore
//! fn(&mut self) // argument elided, implies `T=()`
//! fn(&mut self, T)
//! fn(&mut self, T, &Scheduler<Self>)
//! fn(&mut self, T, &Context<Self>)
//! async fn(&mut self) // argument elided, implies `T=()`
//! async fn(&mut self, T)
//! async fn(&mut self, T, &Scheduler<Self>)
//! async fn(&mut self, T, &Context<Self>)
//! where
//! Self: Model,
//! T: Clone + Send + 'static,
//! R: Send + 'static,
//! ```
//!
//! The scheduler argument is useful for methods that need access to the
//! The context argument is useful for methods that need access to the
//! simulation time or that need to schedule an action at a future date.
//!
//! A replier port for a request of type `T` with a reply of type `R` may in
@ -123,7 +129,7 @@
//! ```ignore
//! async fn(&mut self) -> R // argument elided, implies `T=()`
//! async fn(&mut self, T) -> R
//! async fn(&mut self, T, &Scheduler<Self>) -> R
//! async fn(&mut self, T, &Context<Self>) -> R
//! where
//! Self: Model,
//! T: Clone + Send + 'static,
@ -134,7 +140,7 @@
//! can be connected to input and requestor ports when assembling the simulation
//! bench. However, input ports may instead be defined as private methods if
//! they are only used by the model itself to schedule future actions (see the
//! [`Scheduler`] examples).
//! [`Context`] examples).
//!
//! Changing the signature of an input or replier port is not considered to
//! alter the public interface of a model provided that the event, request and
@ -143,17 +149,16 @@
//! #### Example
//!
//! ```
//! use asynchronix::model::Model;
//! use asynchronix::time::Scheduler;
//! use asynchronix::model::{Context, Model};
//!
//! pub struct MyModel {
//! // ...
//! }
//! impl MyModel {
//! pub fn my_input(&mut self, input: String, scheduler: &Scheduler<Self>) {
//! pub fn my_input(&mut self, input: String, context: &Context<Self>) {
//! // ...
//! }
//! pub async fn my_replier(&mut self, request: u32) -> bool { // scheduler argument elided
//! pub async fn my_replier(&mut self, request: u32) -> bool { // context argument elided
//! // ...
//! # unimplemented!()
//! }
@ -163,14 +168,19 @@
//!
use std::future::Future;
use std::pin::Pin;
use crate::time::Scheduler;
pub use context::{Context, SetupContext};
mod context;
/// Trait to be implemented by all models.
///
/// This trait enables models to perform specific actions in the
/// [`Model::init()`] method only once all models have been connected and
/// This trait enables models to perform specific actions during setup and
/// initialization. The [`Model::setup()`] method is run only once when models
/// are being added to the simulation bench. This method allows in particular
/// sub-models to be created, connected and added to the simulation.
///
/// The [`Model::init()`] method is run only once all models have been connected and
/// migrated to the simulation bench, but before the simulation actually starts.
/// A common use for `init` is to send messages to connected models at the
/// beginning of the simulation.
@ -179,6 +189,37 @@ use crate::time::Scheduler;
/// to prevent an already initialized model from being added to the simulation
/// bench.
pub trait Model: Sized + Send + 'static {
/// Performs model setup.
///
/// This method is executed exactly once for all models of the simulation
/// when the [`SimInit::add_model()`](crate::simulation::SimInit::add_model)
/// method is called.
///
/// The default implementation does nothing.
///
/// # Examples
///
/// ```
/// use std::future::Future;
/// use std::pin::Pin;
///
/// use asynchronix::model::{InitializedModel, Model, SetupContext};
///
/// pub struct MyModel {
/// // ...
/// }
///
/// impl Model for MyModel {
/// fn setup(
/// &mut self,
/// setup_context: &SetupContext<Self>
/// ) {
/// println!("...setup...");
/// }
/// }
/// ```
fn setup(&mut self, _: &SetupContext<Self>) {}
/// Performs asynchronous model initialization.
///
/// This asynchronous method is executed exactly once for all models of the
@ -188,47 +229,31 @@ pub trait Model: Sized + Send + 'static {
/// The default implementation simply converts the model to an
/// `InitializedModel` without any side effect.
///
/// *Note*: it is currently necessary to box the returned future; this
/// limitation will be lifted once Rust supports `async` methods in traits.
///
/// # Examples
///
/// ```
/// use std::future::Future;
/// use std::pin::Pin;
///
/// use asynchronix::model::{InitializedModel, Model};
/// use asynchronix::time::Scheduler;
/// use asynchronix::model::{Context, InitializedModel, Model};
///
/// pub struct MyModel {
/// // ...
/// }
///
/// impl Model for MyModel {
/// fn init(
/// async fn init(
/// self,
/// scheduler: &Scheduler<Self>
/// ) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>>{
/// Box::pin(async move {
/// println!("...initialization...");
/// context: &Context<Self>
/// ) -> InitializedModel<Self> {
/// println!("...initialization...");
///
/// self.into()
/// })
/// self.into()
/// }
/// }
/// ```
// Removing the boxing constraint requires the
// `return_position_impl_trait_in_trait` and `async_fn_in_trait` features.
// Tracking issue: <https://github.com/rust-lang/rust/issues/91611>.
fn init(
self,
scheduler: &Scheduler<Self>,
) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>> {
Box::pin(async move {
let _ = scheduler; // suppress the unused argument warning
self.into()
})
fn init(self, _: &Context<Self>) -> impl Future<Output = InitializedModel<Self>> + Send {
async { self.into() }
}
}

View File

@ -0,0 +1,485 @@
use std::fmt;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::channel::Sender;
use crate::executor::Executor;
use crate::ports::InputFn;
use crate::simulation::{
self, schedule_event_at_unchecked, schedule_keyed_event_at_unchecked,
schedule_periodic_event_at_unchecked, schedule_periodic_keyed_event_at_unchecked, ActionKey,
Deadline, Mailbox, SchedulerQueue, SchedulingError,
};
use crate::time::{MonotonicTime, TearableAtomicTime};
use crate::util::sync_cell::SyncCellReader;
use super::Model;
/// A local context for models.
///
/// A `Context` is a handle to the global context associated to a model
/// instance. It can be used by the model to retrieve the simulation time or
/// schedule delayed actions on itself.
///
/// ### Caveat: self-scheduling `async` methods
///
/// Due to a current rustc issue, `async` methods that schedule themselves will
/// not compile unless an explicit `Send` bound is added to the returned future.
/// This can be done by replacing the `async` signature with a partially
/// desugared signature such as:
///
/// ```ignore
/// fn self_scheduling_method<'a>(
/// &'a mut self,
/// arg: MyEventType,
/// context: &'a Context<Self>
/// ) -> impl Future<Output=()> + Send + 'a {
/// async move {
/// /* implementation */
/// }
/// }
/// ```
///
/// Self-scheduling methods which are not `async` are not affected by this
/// issue.
///
/// # Examples
///
/// A model that sends a greeting after some delay.
///
/// ```
/// use std::time::Duration;
/// use asynchronix::model::{Context, Model};
/// use asynchronix::ports::Output;
///
/// #[derive(Default)]
/// pub struct DelayedGreeter {
/// msg_out: Output<String>,
/// }
///
/// impl DelayedGreeter {
/// // Triggers a greeting on the output port after some delay [input port].
/// pub async fn greet_with_delay(&mut self, delay: Duration, context: &Context<Self>) {
/// let time = context.time();
/// let greeting = format!("Hello, this message was scheduled at: {:?}.", time);
///
/// if delay.is_zero() {
/// self.msg_out.send(greeting).await;
/// } else {
/// context.schedule_event(delay, Self::send_msg, greeting).unwrap();
/// }
/// }
///
/// // Sends a message to the output [private input port].
/// async fn send_msg(&mut self, msg: String) {
/// self.msg_out.send(msg).await;
/// }
/// }
/// impl Model for DelayedGreeter {}
/// ```
// The self-scheduling caveat seems related to this issue:
// https://github.com/rust-lang/rust/issues/78649
pub struct Context<M: Model> {
sender: Sender<M>,
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: SyncCellReader<TearableAtomicTime>,
}
impl<M: Model> Context<M> {
/// Creates a new local context.
pub(crate) fn new(
sender: Sender<M>,
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: SyncCellReader<TearableAtomicTime>,
) -> Self {
Self {
sender,
scheduler_queue,
time,
}
}
/// Returns the current simulation time.
///
/// # Examples
///
/// ```
/// use asynchronix::model::{Context, Model};
/// use asynchronix::time::MonotonicTime;
///
/// fn is_third_millenium<M: Model>(context: &Context<M>) -> bool {
/// let time = context.time();
/// time >= MonotonicTime::new(978307200, 0).unwrap()
/// && time < MonotonicTime::new(32535216000, 0).unwrap()
/// }
/// ```
pub fn time(&self) -> MonotonicTime {
self.time.try_read().expect("internal simulation error: could not perform a synchronized read of the simulation time")
}
/// Schedules an event at a future time.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use asynchronix::model::{Context, Model};
///
/// // A timer.
/// pub struct Timer {}
///
/// impl Timer {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: Duration, context: &Context<Self>) {
/// if context.schedule_event(setting, Self::ring, ()).is_err() {
/// println!("The alarm clock can only be set for a future time");
/// }
/// }
///
/// // Rings [private input port].
/// fn ring(&mut self) {
/// println!("Brringggg");
/// }
/// }
///
/// impl Model for Timer {}
/// ```
pub fn schedule_event<F, T, S>(
&self,
deadline: impl Deadline,
func: F,
arg: T,
) -> Result<(), SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S>,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
let sender = self.sender.clone();
schedule_event_at_unchecked(time, func, arg, sender, &self.scheduler_queue);
Ok(())
}
/// Schedules a cancellable event at a future time and returns an action
/// key.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time.
///
/// # Examples
///
/// ```
/// use asynchronix::model::{Context, Model};
/// use asynchronix::simulation::ActionKey;
/// use asynchronix::time::MonotonicTime;
///
/// // An alarm clock that can be cancelled.
/// #[derive(Default)]
/// pub struct CancellableAlarmClock {
/// event_key: Option<ActionKey>,
/// }
///
/// impl CancellableAlarmClock {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: MonotonicTime, context: &Context<Self>) {
/// self.cancel();
/// match context.schedule_keyed_event(setting, Self::ring, ()) {
/// Ok(event_key) => self.event_key = Some(event_key),
/// Err(_) => println!("The alarm clock can only be set for a future time"),
/// };
/// }
///
/// // Cancels the current alarm, if any [input port].
/// pub fn cancel(&mut self) {
/// self.event_key.take().map(|k| k.cancel());
/// }
///
/// // Rings the alarm [private input port].
/// fn ring(&mut self) {
/// println!("Brringggg!");
/// }
/// }
///
/// impl Model for CancellableAlarmClock {}
/// ```
pub fn schedule_keyed_event<F, T, S>(
&self,
deadline: impl Deadline,
func: F,
arg: T,
) -> Result<ActionKey, SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S>,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
let sender = self.sender.clone();
let event_key =
schedule_keyed_event_at_unchecked(time, func, arg, sender, &self.scheduler_queue);
Ok(event_key)
}
/// Schedules a periodically recurring event at a future time.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time or if the specified period is null.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use asynchronix::model::{Context, Model};
/// use asynchronix::time::MonotonicTime;
///
/// // An alarm clock beeping at 1Hz.
/// pub struct BeepingAlarmClock {}
///
/// impl BeepingAlarmClock {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: MonotonicTime, context: &Context<Self>) {
/// if context.schedule_periodic_event(
/// setting,
/// Duration::from_secs(1), // 1Hz = 1/1s
/// Self::beep,
/// ()
/// ).is_err() {
/// println!("The alarm clock can only be set for a future time");
/// }
/// }
///
/// // Emits a single beep [private input port].
/// fn beep(&mut self) {
/// println!("Beep!");
/// }
/// }
///
/// impl Model for BeepingAlarmClock {}
/// ```
pub fn schedule_periodic_event<F, T, S>(
&self,
deadline: impl Deadline,
period: Duration,
func: F,
arg: T,
) -> Result<(), SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S> + Clone,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
if period.is_zero() {
return Err(SchedulingError::NullRepetitionPeriod);
}
let sender = self.sender.clone();
schedule_periodic_event_at_unchecked(
time,
period,
func,
arg,
sender,
&self.scheduler_queue,
);
Ok(())
}
/// Schedules a cancellable, periodically recurring event at a future time
/// and returns an action key.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time or if the specified period is null.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use asynchronix::model::{Context, Model};
/// use asynchronix::simulation::ActionKey;
/// use asynchronix::time::MonotonicTime;
///
/// // An alarm clock beeping at 1Hz that can be cancelled before it sets off, or
/// // stopped after it sets off.
/// #[derive(Default)]
/// pub struct CancellableBeepingAlarmClock {
/// event_key: Option<ActionKey>,
/// }
///
/// impl CancellableBeepingAlarmClock {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: MonotonicTime, context: &Context<Self>) {
/// self.cancel();
/// match context.schedule_keyed_periodic_event(
/// setting,
/// Duration::from_secs(1), // 1Hz = 1/1s
/// Self::beep,
/// ()
/// ) {
/// Ok(event_key) => self.event_key = Some(event_key),
/// Err(_) => println!("The alarm clock can only be set for a future time"),
/// };
/// }
///
/// // Cancels or stops the alarm [input port].
/// pub fn cancel(&mut self) {
/// self.event_key.take().map(|k| k.cancel());
/// }
///
/// // Emits a single beep [private input port].
/// fn beep(&mut self) {
/// println!("Beep!");
/// }
/// }
///
/// impl Model for CancellableBeepingAlarmClock {}
/// ```
pub fn schedule_keyed_periodic_event<F, T, S>(
&self,
deadline: impl Deadline,
period: Duration,
func: F,
arg: T,
) -> Result<ActionKey, SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S> + Clone,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
if period.is_zero() {
return Err(SchedulingError::NullRepetitionPeriod);
}
let sender = self.sender.clone();
let event_key = schedule_periodic_keyed_event_at_unchecked(
time,
period,
func,
arg,
sender,
&self.scheduler_queue,
);
Ok(event_key)
}
}
impl<M: Model> fmt::Debug for Context<M> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Context").finish_non_exhaustive()
}
}
/// A setup context for models.
///
/// A `SetupContext` can be used by models during the setup stage to
/// create submodels and add them to the simulation bench.
///
/// # Examples
///
/// A model that contains two connected submodels.
///
/// ```
/// use std::time::Duration;
/// use asynchronix::model::{Model, SetupContext};
/// use asynchronix::ports::Output;
/// use asynchronix::simulation::Mailbox;
///
/// #[derive(Default)]
/// pub struct SubmodelA {
/// out: Output<u32>,
/// }
///
/// impl Model for SubmodelA {}
///
/// #[derive(Default)]
/// pub struct SubmodelB {}
///
/// impl SubmodelB {
/// pub async fn input(&mut self, value: u32) {
/// println!("Received {}", value);
/// }
/// }
///
/// impl Model for SubmodelB {}
///
/// #[derive(Default)]
/// pub struct Parent {}
///
/// impl Model for Parent {
/// fn setup(
/// &mut self,
/// setup_context: &SetupContext<Self>) {
/// let mut a = SubmodelA::default();
/// let b = SubmodelB::default();
/// let a_mbox = Mailbox::new();
/// let b_mbox = Mailbox::new();
///
/// a.out.connect(SubmodelB::input, &b_mbox);
///
/// setup_context.add_model(a, a_mbox);
/// setup_context.add_model(b, b_mbox);
/// }
/// }
///
/// ```
#[derive(Debug)]
pub struct SetupContext<'a, M: Model> {
/// Mailbox of the model.
pub mailbox: &'a Mailbox<M>,
context: &'a Context<M>,
executor: &'a Executor,
}
impl<'a, M: Model> SetupContext<'a, M> {
/// Creates a new local context.
pub(crate) fn new(
mailbox: &'a Mailbox<M>,
context: &'a Context<M>,
executor: &'a Executor,
) -> Self {
Self {
mailbox,
context,
executor,
}
}
/// Adds a new model and its mailbox to the simulation bench.
pub fn add_model<N: Model>(&self, model: N, mailbox: Mailbox<N>) {
simulation::add_model(
model,
mailbox,
self.context.scheduler_queue.clone(),
self.context.time.clone(),
self.executor,
);
}
}

View File

@ -6,14 +6,14 @@
pub struct WithoutArguments {}
/// Marker type for regular simulation model methods that take a mutable
/// reference to the model and a message, without scheduler argument.
/// reference to the model and a message, without context argument.
#[derive(Debug)]
pub struct WithoutScheduler {}
pub struct WithoutContext {}
/// Marker type for regular simulation model methods that take a mutable
/// reference to the model, a message and an explicit scheduler argument.
/// reference to the model, a message and an explicit context argument.
#[derive(Debug)]
pub struct WithScheduler {}
pub struct WithContext {}
/// Marker type for asynchronous simulation model methods that take a mutable
/// reference to the model, without any other argument.
@ -21,11 +21,11 @@ pub struct WithScheduler {}
pub struct AsyncWithoutArguments {}
/// Marker type for asynchronous simulation model methods that take a mutable
/// reference to the model and a message, without scheduler argument.
/// reference to the model and a message, without context argument.
#[derive(Debug)]
pub struct AsyncWithoutScheduler {}
pub struct AsyncWithoutContext {}
/// Marker type for asynchronous simulation model methods that take a mutable
/// reference to the model, a message and an explicit scheduler argument.
/// reference to the model, a message and an explicit context argument.
#[derive(Debug)]
pub struct AsyncWithScheduler {}
pub struct AsyncWithContext {}

View File

@ -2,8 +2,7 @@
use std::future::{ready, Future, Ready};
use crate::model::Model;
use crate::time::Scheduler;
use crate::model::{Context, Model};
use super::markers;
@ -15,9 +14,9 @@ use super::markers;
///
/// ```ignore
/// FnOnce(&mut M, T)
/// FnOnce(&mut M, T, &Scheduler<M>)
/// FnOnce(&mut M, T, &Context<M>)
/// async fn(&mut M, T)
/// async fn(&mut M, T, &Scheduler<M>)
/// async fn(&mut M, T, &Context<M>)
/// where
/// M: Model
/// ```
@ -35,7 +34,7 @@ pub trait InputFn<'a, M: Model, T, S>: Send + 'static {
type Future: Future<Output = ()> + Send + 'a;
/// Calls the method.
fn call(self, model: &'a mut M, arg: T, scheduler: &'a Scheduler<M>) -> Self::Future;
fn call(self, model: &'a mut M, arg: T, context: &'a Context<M>) -> Self::Future;
}
impl<'a, M, F> InputFn<'a, M, (), markers::WithoutArguments> for F
@ -45,36 +44,36 @@ where
{
type Future = Ready<()>;
fn call(self, model: &'a mut M, _arg: (), _scheduler: &'a Scheduler<M>) -> Self::Future {
fn call(self, model: &'a mut M, _arg: (), _context: &'a Context<M>) -> Self::Future {
self(model);
ready(())
}
}
impl<'a, M, T, F> InputFn<'a, M, T, markers::WithoutScheduler> for F
impl<'a, M, T, F> InputFn<'a, M, T, markers::WithoutContext> for F
where
M: Model,
F: FnOnce(&'a mut M, T) + Send + 'static,
{
type Future = Ready<()>;
fn call(self, model: &'a mut M, arg: T, _scheduler: &'a Scheduler<M>) -> Self::Future {
fn call(self, model: &'a mut M, arg: T, _context: &'a Context<M>) -> Self::Future {
self(model, arg);
ready(())
}
}
impl<'a, M, T, F> InputFn<'a, M, T, markers::WithScheduler> for F
impl<'a, M, T, F> InputFn<'a, M, T, markers::WithContext> for F
where
M: Model,
F: FnOnce(&'a mut M, T, &'a Scheduler<M>) + Send + 'static,
F: FnOnce(&'a mut M, T, &'a Context<M>) + Send + 'static,
{
type Future = Ready<()>;
fn call(self, model: &'a mut M, arg: T, scheduler: &'a Scheduler<M>) -> Self::Future {
self(model, arg, scheduler);
fn call(self, model: &'a mut M, arg: T, context: &'a Context<M>) -> Self::Future {
self(model, arg, context);
ready(())
}
@ -88,12 +87,12 @@ where
{
type Future = Fut;
fn call(self, model: &'a mut M, _arg: (), _scheduler: &'a Scheduler<M>) -> Self::Future {
fn call(self, model: &'a mut M, _arg: (), _context: &'a Context<M>) -> Self::Future {
self(model)
}
}
impl<'a, M, T, Fut, F> InputFn<'a, M, T, markers::AsyncWithoutScheduler> for F
impl<'a, M, T, Fut, F> InputFn<'a, M, T, markers::AsyncWithoutContext> for F
where
M: Model,
Fut: Future<Output = ()> + Send + 'a,
@ -101,21 +100,21 @@ where
{
type Future = Fut;
fn call(self, model: &'a mut M, arg: T, _scheduler: &'a Scheduler<M>) -> Self::Future {
fn call(self, model: &'a mut M, arg: T, _context: &'a Context<M>) -> Self::Future {
self(model, arg)
}
}
impl<'a, M, T, Fut, F> InputFn<'a, M, T, markers::AsyncWithScheduler> for F
impl<'a, M, T, Fut, F> InputFn<'a, M, T, markers::AsyncWithContext> for F
where
M: Model,
Fut: Future<Output = ()> + Send + 'a,
F: FnOnce(&'a mut M, T, &'a Scheduler<M>) -> Fut + Send + 'static,
F: FnOnce(&'a mut M, T, &'a Context<M>) -> Fut + Send + 'static,
{
type Future = Fut;
fn call(self, model: &'a mut M, arg: T, scheduler: &'a Scheduler<M>) -> Self::Future {
self(model, arg, scheduler)
fn call(self, model: &'a mut M, arg: T, context: &'a Context<M>) -> Self::Future {
self(model, arg, context)
}
}
@ -127,7 +126,7 @@ where
///
/// ```ignore
/// async fn(&mut M, T) -> R
/// async fn(&mut M, T, &Scheduler<M>) -> R
/// async fn(&mut M, T, &Context<M>) -> R
/// where
/// M: Model
/// ```
@ -144,7 +143,7 @@ pub trait ReplierFn<'a, M: Model, T, R, S>: Send + 'static {
type Future: Future<Output = R> + Send + 'a;
/// Calls the method.
fn call(self, model: &'a mut M, arg: T, scheduler: &'a Scheduler<M>) -> Self::Future;
fn call(self, model: &'a mut M, arg: T, context: &'a Context<M>) -> Self::Future;
}
impl<'a, M, R, Fut, F> ReplierFn<'a, M, (), R, markers::AsyncWithoutArguments> for F
@ -155,12 +154,12 @@ where
{
type Future = Fut;
fn call(self, model: &'a mut M, _arg: (), _scheduler: &'a Scheduler<M>) -> Self::Future {
fn call(self, model: &'a mut M, _arg: (), _context: &'a Context<M>) -> Self::Future {
self(model)
}
}
impl<'a, M, T, R, Fut, F> ReplierFn<'a, M, T, R, markers::AsyncWithoutScheduler> for F
impl<'a, M, T, R, Fut, F> ReplierFn<'a, M, T, R, markers::AsyncWithoutContext> for F
where
M: Model,
Fut: Future<Output = R> + Send + 'a,
@ -168,20 +167,20 @@ where
{
type Future = Fut;
fn call(self, model: &'a mut M, arg: T, _scheduler: &'a Scheduler<M>) -> Self::Future {
fn call(self, model: &'a mut M, arg: T, _context: &'a Context<M>) -> Self::Future {
self(model, arg)
}
}
impl<'a, M, T, R, Fut, F> ReplierFn<'a, M, T, R, markers::AsyncWithScheduler> for F
impl<'a, M, T, R, Fut, F> ReplierFn<'a, M, T, R, markers::AsyncWithContext> for F
where
M: Model,
Fut: Future<Output = R> + Send + 'a,
F: FnOnce(&'a mut M, T, &'a Scheduler<M>) -> Fut + Send + 'static,
F: FnOnce(&'a mut M, T, &'a Context<M>) -> Fut + Send + 'static,
{
type Future = Fut;
fn call(self, model: &'a mut M, arg: T, scheduler: &'a Scheduler<M>) -> Self::Future {
self(model, arg, scheduler)
fn call(self, model: &'a mut M, arg: T, context: &'a Context<M>) -> Self::Future {
self(model, arg, context)
}
}

View File

@ -505,7 +505,7 @@ mod tests {
use futures_executor::block_on;
use crate::channel::Receiver;
use crate::time::Scheduler;
use crate::model::Context;
use crate::time::{MonotonicTime, TearableAtomicTime};
use crate::util::priority_queue::PriorityQueue;
use crate::util::sync_cell::SyncCell;
@ -563,9 +563,9 @@ mod tests {
let dummy_priority_queue = Arc::new(Mutex::new(PriorityQueue::new()));
let dummy_time =
SyncCell::new(TearableAtomicTime::new(MonotonicTime::EPOCH)).reader();
let dummy_scheduler =
Scheduler::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_scheduler)).unwrap();
let dummy_context =
Context::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_context)).unwrap();
}
})
})
@ -614,9 +614,9 @@ mod tests {
let dummy_priority_queue = Arc::new(Mutex::new(PriorityQueue::new()));
let dummy_time =
SyncCell::new(TearableAtomicTime::new(MonotonicTime::EPOCH)).reader();
let dummy_scheduler =
Scheduler::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_scheduler)).unwrap();
let dummy_context =
Context::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_context)).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
})

View File

@ -8,9 +8,8 @@ use std::time::Duration;
use crate::model::Model;
use crate::ports::InputFn;
use crate::ports::{LineError, LineId};
use crate::simulation::Address;
use crate::time::{
Action, ActionKey, KeyedOnceAction, KeyedPeriodicAction, OnceAction, PeriodicAction,
use crate::simulation::{
Action, ActionKey, Address, KeyedOnceAction, KeyedPeriodicAction, OnceAction, PeriodicAction,
};
use crate::util::slot;

View File

@ -430,7 +430,7 @@ mod tests {
use futures_executor::block_on;
use crate::channel::Receiver;
use crate::time::Scheduler;
use crate::model::Context;
use crate::time::{MonotonicTime, TearableAtomicTime};
use crate::util::priority_queue::PriorityQueue;
use crate::util::sync_cell::SyncCell;
@ -488,9 +488,9 @@ mod tests {
let dummy_priority_queue = Arc::new(Mutex::new(PriorityQueue::new()));
let dummy_time =
SyncCell::new(TearableAtomicTime::new(MonotonicTime::EPOCH)).reader();
let dummy_scheduler =
Scheduler::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_scheduler)).unwrap();
let dummy_context =
Context::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_context)).unwrap();
}
})
})
@ -539,9 +539,9 @@ mod tests {
let dummy_priority_queue = Arc::new(Mutex::new(PriorityQueue::new()));
let dummy_time =
SyncCell::new(TearableAtomicTime::new(MonotonicTime::EPOCH)).reader();
let dummy_scheduler =
Scheduler::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_scheduler)).unwrap();
let dummy_context =
Context::new(dummy_address, dummy_priority_queue, dummy_time);
block_on(mailbox.recv(&mut counter, &dummy_context)).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
})

View File

@ -11,7 +11,10 @@ pub struct ServerError {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyRequest {
/// Expects exactly 1 variant.
#[prost(oneof = "any_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")]
#[prost(
oneof = "any_request::Request",
tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11"
)]
pub request: ::core::option::Option<any_request::Request>,
}
/// Nested message and enum types in `AnyRequest`.
@ -48,7 +51,10 @@ pub mod any_request {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyReply {
/// Contains exactly 1 variant.
#[prost(oneof = "any_reply::Reply", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 100")]
#[prost(
oneof = "any_reply::Reply",
tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 100"
)]
pub reply: ::core::option::Option<any_reply::Reply>,
}
/// Nested message and enum types in `AnyReply`.

View File

@ -411,31 +411,19 @@ pub mod simulation_server {
async fn schedule_event(
&self,
request: tonic::Request<super::ScheduleEventRequest>,
) -> std::result::Result<
tonic::Response<super::ScheduleEventReply>,
tonic::Status,
>;
) -> std::result::Result<tonic::Response<super::ScheduleEventReply>, tonic::Status>;
async fn cancel_event(
&self,
request: tonic::Request<super::CancelEventRequest>,
) -> std::result::Result<
tonic::Response<super::CancelEventReply>,
tonic::Status,
>;
) -> std::result::Result<tonic::Response<super::CancelEventReply>, tonic::Status>;
async fn process_event(
&self,
request: tonic::Request<super::ProcessEventRequest>,
) -> std::result::Result<
tonic::Response<super::ProcessEventReply>,
tonic::Status,
>;
) -> std::result::Result<tonic::Response<super::ProcessEventReply>, tonic::Status>;
async fn process_query(
&self,
request: tonic::Request<super::ProcessQueryRequest>,
) -> std::result::Result<
tonic::Response<super::ProcessQueryReply>,
tonic::Status,
>;
) -> std::result::Result<tonic::Response<super::ProcessQueryReply>, tonic::Status>;
async fn read_events(
&self,
request: tonic::Request<super::ReadEventsRequest>,
@ -472,10 +460,7 @@ pub mod simulation_server {
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
@ -531,21 +516,15 @@ pub mod simulation_server {
"/simulation.Simulation/Init" => {
#[allow(non_camel_case_types)]
struct InitSvc<T: Simulation>(pub Arc<T>);
impl<T: Simulation> tonic::server::UnaryService<super::InitRequest>
for InitSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::InitRequest> for InitSvc<T> {
type Response = super::InitReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::InitRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Simulation>::init(&inner, request).await
};
let fut = async move { <T as Simulation>::init(&inner, request).await };
Box::pin(fut)
}
}
@ -575,21 +554,15 @@ pub mod simulation_server {
"/simulation.Simulation/Time" => {
#[allow(non_camel_case_types)]
struct TimeSvc<T: Simulation>(pub Arc<T>);
impl<T: Simulation> tonic::server::UnaryService<super::TimeRequest>
for TimeSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::TimeRequest> for TimeSvc<T> {
type Response = super::TimeReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::TimeRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Simulation>::time(&inner, request).await
};
let fut = async move { <T as Simulation>::time(&inner, request).await };
Box::pin(fut)
}
}
@ -619,21 +592,15 @@ pub mod simulation_server {
"/simulation.Simulation/Step" => {
#[allow(non_camel_case_types)]
struct StepSvc<T: Simulation>(pub Arc<T>);
impl<T: Simulation> tonic::server::UnaryService<super::StepRequest>
for StepSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::StepRequest> for StepSvc<T> {
type Response = super::StepReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::StepRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Simulation>::step(&inner, request).await
};
let fut = async move { <T as Simulation>::step(&inner, request).await };
Box::pin(fut)
}
}
@ -663,23 +630,16 @@ pub mod simulation_server {
"/simulation.Simulation/StepUntil" => {
#[allow(non_camel_case_types)]
struct StepUntilSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::StepUntilRequest>
for StepUntilSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::StepUntilRequest> for StepUntilSvc<T> {
type Response = super::StepUntilReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::StepUntilRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Simulation>::step_until(&inner, request).await
};
let fut =
async move { <T as Simulation>::step_until(&inner, request).await };
Box::pin(fut)
}
}
@ -709,15 +669,11 @@ pub mod simulation_server {
"/simulation.Simulation/ScheduleEvent" => {
#[allow(non_camel_case_types)]
struct ScheduleEventSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::ScheduleEventRequest>
for ScheduleEventSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::ScheduleEventRequest>
for ScheduleEventSvc<T>
{
type Response = super::ScheduleEventReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::ScheduleEventRequest>,
@ -755,15 +711,9 @@ pub mod simulation_server {
"/simulation.Simulation/CancelEvent" => {
#[allow(non_camel_case_types)]
struct CancelEventSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::CancelEventRequest>
for CancelEventSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::CancelEventRequest> for CancelEventSvc<T> {
type Response = super::CancelEventReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::CancelEventRequest>,
@ -801,15 +751,9 @@ pub mod simulation_server {
"/simulation.Simulation/ProcessEvent" => {
#[allow(non_camel_case_types)]
struct ProcessEventSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::ProcessEventRequest>
for ProcessEventSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::ProcessEventRequest> for ProcessEventSvc<T> {
type Response = super::ProcessEventReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::ProcessEventRequest>,
@ -847,15 +791,9 @@ pub mod simulation_server {
"/simulation.Simulation/ProcessQuery" => {
#[allow(non_camel_case_types)]
struct ProcessQuerySvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::ProcessQueryRequest>
for ProcessQuerySvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::ProcessQueryRequest> for ProcessQuerySvc<T> {
type Response = super::ProcessQueryReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::ProcessQueryRequest>,
@ -893,15 +831,9 @@ pub mod simulation_server {
"/simulation.Simulation/ReadEvents" => {
#[allow(non_camel_case_types)]
struct ReadEventsSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::ReadEventsRequest>
for ReadEventsSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::ReadEventsRequest> for ReadEventsSvc<T> {
type Response = super::ReadEventsReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::ReadEventsRequest>,
@ -939,23 +871,16 @@ pub mod simulation_server {
"/simulation.Simulation/OpenSink" => {
#[allow(non_camel_case_types)]
struct OpenSinkSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::OpenSinkRequest>
for OpenSinkSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::OpenSinkRequest> for OpenSinkSvc<T> {
type Response = super::OpenSinkReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::OpenSinkRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Simulation>::open_sink(&inner, request).await
};
let fut =
async move { <T as Simulation>::open_sink(&inner, request).await };
Box::pin(fut)
}
}
@ -985,23 +910,16 @@ pub mod simulation_server {
"/simulation.Simulation/CloseSink" => {
#[allow(non_camel_case_types)]
struct CloseSinkSvc<T: Simulation>(pub Arc<T>);
impl<
T: Simulation,
> tonic::server::UnaryService<super::CloseSinkRequest>
for CloseSinkSvc<T> {
impl<T: Simulation> tonic::server::UnaryService<super::CloseSinkRequest> for CloseSinkSvc<T> {
type Response = super::CloseSinkReply;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(
&mut self,
request: tonic::Request<super::CloseSinkRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Simulation>::close_sink(&inner, request).await
};
let fut =
async move { <T as Simulation>::close_sink(&inner, request).await };
Box::pin(fut)
}
}
@ -1028,18 +946,14 @@ pub mod simulation_server {
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
Ok(
http::Response::builder()
.status(200)
.header("grpc-status", "12")
.header("content-type", "application/grpc")
.body(empty_body())
.unwrap(),
)
})
}
_ => Box::pin(async move {
Ok(http::Response::builder()
.status(200)
.header("grpc-status", "12")
.header("content-type", "application/grpc")
.body(empty_body())
.unwrap())
}),
}
}
}

View File

@ -98,9 +98,9 @@
//! connects or disconnects a port, such as:
//!
//! ```
//! # use asynchronix::model::Model;
//! # use asynchronix::model::{Context, Model};
//! # use asynchronix::ports::Output;
//! # use asynchronix::time::{MonotonicTime, Scheduler};
//! # use asynchronix::time::MonotonicTime;
//! # use asynchronix::simulation::{Mailbox, SimInit};
//! # pub struct ModelA {
//! # pub output: Output<i32>,
@ -123,9 +123,16 @@
//! );
//! ```
mod mailbox;
mod scheduler;
mod sim_init;
pub use mailbox::{Address, Mailbox};
pub(crate) use scheduler::{
schedule_event_at_unchecked, schedule_keyed_event_at_unchecked,
schedule_periodic_event_at_unchecked, schedule_periodic_keyed_event_at_unchecked,
KeyedOnceAction, KeyedPeriodicAction, OnceAction, PeriodicAction, SchedulerQueue,
};
pub use scheduler::{Action, ActionKey, Deadline, SchedulingError};
pub use sim_init::SimInit;
use std::error::Error;
@ -137,15 +144,12 @@ use std::time::Duration;
use recycle_box::{coerce_box, RecycleBox};
use crate::executor::Executor;
use crate::model::Model;
use crate::model::{Context, Model, SetupContext};
use crate::ports::{InputFn, ReplierFn};
use crate::time::{
self, Action, ActionKey, Clock, Deadline, MonotonicTime, SchedulerQueue, SchedulingError,
TearableAtomicTime,
};
use crate::time::{Clock, MonotonicTime, TearableAtomicTime};
use crate::util::seq_futures::SeqFuture;
use crate::util::slot;
use crate::util::sync_cell::SyncCell;
use crate::util::sync_cell::{SyncCell, SyncCellReader};
/// Simulation environment.
///
@ -157,9 +161,9 @@ use crate::util::sync_cell::SyncCell;
/// A [`Simulation`] object also manages an event scheduling queue and
/// simulation time. The scheduling queue can be accessed from the simulation
/// itself, but also from models via the optional
/// [`&Scheduler`](time::Scheduler) argument of input and replier port methods.
/// [`&Context`](crate::model::Context) argument of input and replier port methods.
/// Likewise, simulation time can be accessed with the [`Simulation::time()`]
/// method, or from models with the [`Scheduler::time()`](time::Scheduler::time)
/// method, or from models with the [`Context::time()`](crate::model::Context::time)
/// method.
///
/// Events and queries can be scheduled immediately, *i.e.* for the current
@ -177,7 +181,7 @@ use crate::util::sync_cell::SyncCell;
///
/// 1. increment simulation time until that of the next scheduled event in
/// chronological order, then
/// 2. call [`Clock::synchronize()`](time::Clock::synchronize) which, unless the
/// 2. call [`Clock::synchronize()`](crate::time::Clock::synchronize) which, unless the
/// simulation is configured to run as fast as possible, blocks until the
/// desired wall clock time, and finally
/// 3. run all computations scheduled for the new simulation time.
@ -217,7 +221,7 @@ impl Simulation {
/// that event as well as all other event scheduled for the same time.
///
/// Processing is gated by a (possibly blocking) call to
/// [`Clock::synchronize()`](time::Clock::synchronize) on the configured
/// [`Clock::synchronize()`](crate::time::Clock::synchronize) on the configured
/// simulation clock. This method blocks until all newly processed events
/// have completed.
pub fn step(&mut self) {
@ -292,7 +296,7 @@ impl Simulation {
/// Events scheduled for the same time and targeting the same model are
/// guaranteed to be processed according to the scheduling order.
///
/// See also: [`time::Scheduler::schedule_event`].
/// See also: [`Context::schedule_event`](crate::model::Context::schedule_event).
pub fn schedule_event<M, F, T, S>(
&mut self,
deadline: impl Deadline,
@ -311,8 +315,7 @@ impl Simulation {
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
time::schedule_event_at_unchecked(time, func, arg, address.into().0, &self.scheduler_queue);
schedule_event_at_unchecked(time, func, arg, address.into().0, &self.scheduler_queue);
Ok(())
}
@ -325,7 +328,7 @@ impl Simulation {
/// Events scheduled for the same time and targeting the same model are
/// guaranteed to be processed according to the scheduling order.
///
/// See also: [`time::Scheduler::schedule_keyed_event`].
/// See also: [`Context::schedule_keyed_event`](crate::model::Context::schedule_keyed_event).
pub fn schedule_keyed_event<M, F, T, S>(
&mut self,
deadline: impl Deadline,
@ -344,7 +347,7 @@ impl Simulation {
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
let event_key = time::schedule_keyed_event_at_unchecked(
let event_key = schedule_keyed_event_at_unchecked(
time,
func,
arg,
@ -363,7 +366,7 @@ impl Simulation {
/// Events scheduled for the same time and targeting the same model are
/// guaranteed to be processed according to the scheduling order.
///
/// See also: [`time::Scheduler::schedule_periodic_event`].
/// See also: [`Context::schedule_periodic_event`](crate::model::Context::schedule_periodic_event).
pub fn schedule_periodic_event<M, F, T, S>(
&mut self,
deadline: impl Deadline,
@ -386,7 +389,7 @@ impl Simulation {
if period.is_zero() {
return Err(SchedulingError::NullRepetitionPeriod);
}
time::schedule_periodic_event_at_unchecked(
schedule_periodic_event_at_unchecked(
time,
period,
func,
@ -407,7 +410,7 @@ impl Simulation {
/// Events scheduled for the same time and targeting the same model are
/// guaranteed to be processed according to the scheduling order.
///
/// See also: [`time::Scheduler::schedule_keyed_periodic_event`].
/// See also: [`Context::schedule_keyed_periodic_event`](crate::model::Context::schedule_keyed_periodic_event).
pub fn schedule_keyed_periodic_event<M, F, T, S>(
&mut self,
deadline: impl Deadline,
@ -430,7 +433,7 @@ impl Simulation {
if period.is_zero() {
return Err(SchedulingError::NullRepetitionPeriod);
}
let event_key = time::schedule_periodic_keyed_event_at_unchecked(
let event_key = schedule_periodic_keyed_event_at_unchecked(
time,
period,
func,
@ -658,3 +661,25 @@ impl fmt::Display for QueryError {
}
impl Error for QueryError {}
/// Adds a model and its mailbox to the simulation bench.
pub(crate) fn add_model<M: Model>(
mut model: M,
mailbox: Mailbox<M>,
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: SyncCellReader<TearableAtomicTime>,
executor: &Executor,
) {
let sender = mailbox.0.sender();
let context = Context::new(sender, scheduler_queue, time);
let setup_context = SetupContext::new(&mailbox, &context, executor);
model.setup(&setup_context);
let mut receiver = mailbox.0;
executor.spawn_and_forget(async move {
let mut model = model.init(&context).await.0;
while receiver.recv(&mut model, &context).await.is_ok() {}
});
}

View File

@ -17,9 +17,8 @@ use crate::channel::Sender;
use crate::executor::Executor;
use crate::model::Model;
use crate::ports::InputFn;
use crate::time::{MonotonicTime, TearableAtomicTime};
use crate::time::MonotonicTime;
use crate::util::priority_queue::PriorityQueue;
use crate::util::sync_cell::SyncCellReader;
/// Shorthand for the scheduler queue type.
@ -55,387 +54,6 @@ impl Deadline for MonotonicTime {
}
}
/// A local scheduler for models.
///
/// A `Scheduler` is a handle to the global scheduler associated to a model
/// instance. It can be used by the model to retrieve the simulation time or
/// schedule delayed actions on itself.
///
/// ### Caveat: self-scheduling `async` methods
///
/// Due to a current rustc issue, `async` methods that schedule themselves will
/// not compile unless an explicit `Send` bound is added to the returned future.
/// This can be done by replacing the `async` signature with a partially
/// desugared signature such as:
///
/// ```ignore
/// fn self_scheduling_method<'a>(
/// &'a mut self,
/// arg: MyEventType,
/// scheduler: &'a Scheduler<Self>
/// ) -> impl Future<Output=()> + Send + 'a {
/// async move {
/// /* implementation */
/// }
/// }
/// ```
///
/// Self-scheduling methods which are not `async` are not affected by this
/// issue.
///
/// # Examples
///
/// A model that sends a greeting after some delay.
///
/// ```
/// use std::time::Duration;
/// use asynchronix::model::Model;
/// use asynchronix::ports::Output;
/// use asynchronix::time::Scheduler;
///
/// #[derive(Default)]
/// pub struct DelayedGreeter {
/// msg_out: Output<String>,
/// }
///
/// impl DelayedGreeter {
/// // Triggers a greeting on the output port after some delay [input port].
/// pub async fn greet_with_delay(&mut self, delay: Duration, scheduler: &Scheduler<Self>) {
/// let time = scheduler.time();
/// let greeting = format!("Hello, this message was scheduled at: {:?}.", time);
///
/// if delay.is_zero() {
/// self.msg_out.send(greeting).await;
/// } else {
/// scheduler.schedule_event(delay, Self::send_msg, greeting).unwrap();
/// }
/// }
///
/// // Sends a message to the output [private input port].
/// async fn send_msg(&mut self, msg: String) {
/// self.msg_out.send(msg).await;
/// }
/// }
/// impl Model for DelayedGreeter {}
/// ```
// The self-scheduling caveat seems related to this issue:
// https://github.com/rust-lang/rust/issues/78649
pub struct Scheduler<M: Model> {
sender: Sender<M>,
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: SyncCellReader<TearableAtomicTime>,
}
impl<M: Model> Scheduler<M> {
/// Creates a new local scheduler.
pub(crate) fn new(
sender: Sender<M>,
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: SyncCellReader<TearableAtomicTime>,
) -> Self {
Self {
sender,
scheduler_queue,
time,
}
}
/// Returns the current simulation time.
///
/// # Examples
///
/// ```
/// use asynchronix::model::Model;
/// use asynchronix::time::{MonotonicTime, Scheduler};
///
/// fn is_third_millenium<M: Model>(scheduler: &Scheduler<M>) -> bool {
/// let time = scheduler.time();
/// time >= MonotonicTime::new(978307200, 0).unwrap()
/// && time < MonotonicTime::new(32535216000, 0).unwrap()
/// }
/// ```
pub fn time(&self) -> MonotonicTime {
self.time.try_read().expect("internal simulation error: could not perform a synchronized read of the simulation time")
}
/// Schedules an event at a future time.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use asynchronix::model::Model;
/// use asynchronix::time::Scheduler;
///
/// // A timer.
/// pub struct Timer {}
///
/// impl Timer {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: Duration, scheduler: &Scheduler<Self>) {
/// if scheduler.schedule_event(setting, Self::ring, ()).is_err() {
/// println!("The alarm clock can only be set for a future time");
/// }
/// }
///
/// // Rings [private input port].
/// fn ring(&mut self) {
/// println!("Brringggg");
/// }
/// }
///
/// impl Model for Timer {}
/// ```
pub fn schedule_event<F, T, S>(
&self,
deadline: impl Deadline,
func: F,
arg: T,
) -> Result<(), SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S>,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
let sender = self.sender.clone();
schedule_event_at_unchecked(time, func, arg, sender, &self.scheduler_queue);
Ok(())
}
/// Schedules a cancellable event at a future time and returns an action
/// key.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time.
///
/// # Examples
///
/// ```
/// use asynchronix::model::Model;
/// use asynchronix::time::{ActionKey, MonotonicTime, Scheduler};
///
/// // An alarm clock that can be cancelled.
/// #[derive(Default)]
/// pub struct CancellableAlarmClock {
/// event_key: Option<ActionKey>,
/// }
///
/// impl CancellableAlarmClock {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: MonotonicTime, scheduler: &Scheduler<Self>) {
/// self.cancel();
/// match scheduler.schedule_keyed_event(setting, Self::ring, ()) {
/// Ok(event_key) => self.event_key = Some(event_key),
/// Err(_) => println!("The alarm clock can only be set for a future time"),
/// };
/// }
///
/// // Cancels the current alarm, if any [input port].
/// pub fn cancel(&mut self) {
/// self.event_key.take().map(|k| k.cancel());
/// }
///
/// // Rings the alarm [private input port].
/// fn ring(&mut self) {
/// println!("Brringggg!");
/// }
/// }
///
/// impl Model for CancellableAlarmClock {}
/// ```
pub fn schedule_keyed_event<F, T, S>(
&self,
deadline: impl Deadline,
func: F,
arg: T,
) -> Result<ActionKey, SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S>,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
let sender = self.sender.clone();
let event_key =
schedule_keyed_event_at_unchecked(time, func, arg, sender, &self.scheduler_queue);
Ok(event_key)
}
/// Schedules a periodically recurring event at a future time.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time or if the specified period is null.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use asynchronix::model::Model;
/// use asynchronix::time::{MonotonicTime, Scheduler};
///
/// // An alarm clock beeping at 1Hz.
/// pub struct BeepingAlarmClock {}
///
/// impl BeepingAlarmClock {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: MonotonicTime, scheduler: &Scheduler<Self>) {
/// if scheduler.schedule_periodic_event(
/// setting,
/// Duration::from_secs(1), // 1Hz = 1/1s
/// Self::beep,
/// ()
/// ).is_err() {
/// println!("The alarm clock can only be set for a future time");
/// }
/// }
///
/// // Emits a single beep [private input port].
/// fn beep(&mut self) {
/// println!("Beep!");
/// }
/// }
///
/// impl Model for BeepingAlarmClock {}
/// ```
pub fn schedule_periodic_event<F, T, S>(
&self,
deadline: impl Deadline,
period: Duration,
func: F,
arg: T,
) -> Result<(), SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S> + Clone,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
if period.is_zero() {
return Err(SchedulingError::NullRepetitionPeriod);
}
let sender = self.sender.clone();
schedule_periodic_event_at_unchecked(
time,
period,
func,
arg,
sender,
&self.scheduler_queue,
);
Ok(())
}
/// Schedules a cancellable, periodically recurring event at a future time
/// and returns an action key.
///
/// An error is returned if the specified deadline is not in the future of
/// the current simulation time or if the specified period is null.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use asynchronix::model::Model;
/// use asynchronix::time::{ActionKey, MonotonicTime, Scheduler};
///
/// // An alarm clock beeping at 1Hz that can be cancelled before it sets off, or
/// // stopped after it sets off.
/// #[derive(Default)]
/// pub struct CancellableBeepingAlarmClock {
/// event_key: Option<ActionKey>,
/// }
///
/// impl CancellableBeepingAlarmClock {
/// // Sets an alarm [input port].
/// pub fn set(&mut self, setting: MonotonicTime, scheduler: &Scheduler<Self>) {
/// self.cancel();
/// match scheduler.schedule_keyed_periodic_event(
/// setting,
/// Duration::from_secs(1), // 1Hz = 1/1s
/// Self::beep,
/// ()
/// ) {
/// Ok(event_key) => self.event_key = Some(event_key),
/// Err(_) => println!("The alarm clock can only be set for a future time"),
/// };
/// }
///
/// // Cancels or stops the alarm [input port].
/// pub fn cancel(&mut self) {
/// self.event_key.take().map(|k| k.cancel());
/// }
///
/// // Emits a single beep [private input port].
/// fn beep(&mut self) {
/// println!("Beep!");
/// }
/// }
///
/// impl Model for CancellableBeepingAlarmClock {}
/// ```
pub fn schedule_keyed_periodic_event<F, T, S>(
&self,
deadline: impl Deadline,
period: Duration,
func: F,
arg: T,
) -> Result<ActionKey, SchedulingError>
where
F: for<'a> InputFn<'a, M, T, S> + Clone,
T: Send + Clone + 'static,
S: Send + 'static,
{
let now = self.time();
let time = deadline.into_time(now);
if now >= time {
return Err(SchedulingError::InvalidScheduledTime);
}
if period.is_zero() {
return Err(SchedulingError::NullRepetitionPeriod);
}
let sender = self.sender.clone();
let event_key = schedule_periodic_keyed_event_at_unchecked(
time,
period,
func,
arg,
sender,
&self.scheduler_queue,
);
Ok(event_key)
}
}
impl<M: Model> fmt::Debug for Scheduler<M> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Scheduler").finish_non_exhaustive()
}
}
/// Handle to a scheduled action.
///
/// An `ActionKey` can be used to cancel a scheduled action.
@ -468,7 +86,7 @@ impl PartialEq for ActionKey {
/// Implements equality by considering clones to be equivalent, rather than
/// keys with the same `is_cancelled` value.
fn eq(&self, other: &Self) -> bool {
ptr::addr_eq(&*self.is_cancelled, &*other.is_cancelled)
ptr::eq(&*self.is_cancelled, &*other.is_cancelled)
}
}

View File

@ -3,12 +3,12 @@ use std::sync::{Arc, Mutex};
use crate::executor::Executor;
use crate::model::Model;
use crate::time::{Clock, NoClock, Scheduler};
use crate::time::{MonotonicTime, SchedulerQueue, TearableAtomicTime};
use crate::time::{Clock, NoClock};
use crate::time::{MonotonicTime, TearableAtomicTime};
use crate::util::priority_queue::PriorityQueue;
use crate::util::sync_cell::SyncCell;
use super::{Mailbox, Simulation};
use super::{add_model, Mailbox, SchedulerQueue, Simulation};
/// Builder for a multi-threaded, discrete-event simulation.
pub struct SimInit {
@ -44,15 +44,8 @@ impl SimInit {
pub fn add_model<M: Model>(self, model: M, mailbox: Mailbox<M>) -> Self {
let scheduler_queue = self.scheduler_queue.clone();
let time = self.time.reader();
let mut receiver = mailbox.0;
self.executor.spawn_and_forget(async move {
let sender = receiver.sender();
let scheduler = Scheduler::new(sender, scheduler_queue, time);
let mut model = model.init(&scheduler).await.0;
while receiver.recv(&mut model, &scheduler).await.is_ok() {}
});
add_model(model, mailbox, scheduler_queue, time, &self.executor);
self
}

View File

@ -4,9 +4,7 @@
//!
//! * [`MonotonicTime`]: a monotonic timestamp based on the [TAI] time standard,
//! * [`Clock`]: a trait for types that can synchronize a simulation,
//! implemented for instance by [`SystemClock`] and [`AutoSystemClock`],
//! * [`Scheduler`]: a model-local handle to the global scheduler that can be
//! used by models to schedule future actions onto themselves.
//! implemented for instance by [`SystemClock`] and [`AutoSystemClock`].
//!
//! [TAI]: https://en.wikipedia.org/wiki/International_Atomic_Time
//!
@ -17,8 +15,8 @@
//! the specified timestamp.
//!
//! ```
//! use asynchronix::model::Model;
//! use asynchronix::time::{MonotonicTime, Scheduler};
//! use asynchronix::model::{Context, Model};
//! use asynchronix::time::MonotonicTime;
//!
//! // An alarm clock model.
//! pub struct AlarmClock {
@ -32,8 +30,8 @@
//! }
//!
//! // Sets an alarm [input port].
//! pub fn set(&mut self, setting: MonotonicTime, scheduler: &Scheduler<Self>) {
//! if scheduler.schedule_event(setting, Self::ring, ()).is_err() {
//! pub fn set(&mut self, setting: MonotonicTime, context: &Context<Self>) {
//! if context.schedule_event(setting, Self::ring, ()).is_err() {
//! println!("The alarm clock can only be set for a future time");
//! }
//! }
@ -49,15 +47,8 @@
mod clock;
mod monotonic_time;
mod scheduler;
pub use tai_time::MonotonicTime;
pub use clock::{AutoSystemClock, Clock, NoClock, SyncStatus, SystemClock};
pub(crate) use monotonic_time::TearableAtomicTime;
pub(crate) use scheduler::{
schedule_event_at_unchecked, schedule_keyed_event_at_unchecked,
schedule_periodic_event_at_unchecked, schedule_periodic_keyed_event_at_unchecked,
KeyedOnceAction, KeyedPeriodicAction, OnceAction, PeriodicAction, SchedulerQueue,
};
pub use scheduler::{Action, ActionKey, Deadline, Scheduler, SchedulingError};

View File

@ -143,7 +143,6 @@ impl<T: TearableAtomic> SyncCell<T> {
/// A handle to a `SyncCell` that enables synchronized reads from multiple
/// threads.
#[derive(Clone)]
pub(crate) struct SyncCellReader<T: TearableAtomic> {
inner: Arc<Inner<T>>,
}
@ -186,6 +185,14 @@ impl<T: TearableAtomic> SyncCellReader<T> {
}
}
impl<T: TearableAtomic> Clone for SyncCellReader<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
/// An error returned when attempting to perform a read operation concurrently
/// with a write operation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View File

@ -2,10 +2,10 @@
use std::time::Duration;
use asynchronix::model::Model;
use asynchronix::model::{Context, Model};
use asynchronix::ports::{EventBuffer, Output};
use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{ActionKey, MonotonicTime, Scheduler};
use asynchronix::simulation::{ActionKey, Mailbox, SimInit};
use asynchronix::time::MonotonicTime;
#[test]
fn model_schedule_event() {
@ -14,9 +14,9 @@ fn model_schedule_event() {
output: Output<()>,
}
impl TestModel {
fn trigger(&mut self, _: (), scheduler: &Scheduler<Self>) {
scheduler
.schedule_event(scheduler.time() + Duration::from_secs(2), Self::action, ())
fn trigger(&mut self, _: (), context: &Context<Self>) {
context
.schedule_event(context.time() + Duration::from_secs(2), Self::action, ())
.unwrap();
}
async fn action(&mut self) {
@ -51,12 +51,12 @@ fn model_cancel_future_keyed_event() {
key: Option<ActionKey>,
}
impl TestModel {
fn trigger(&mut self, _: (), scheduler: &Scheduler<Self>) {
scheduler
.schedule_event(scheduler.time() + Duration::from_secs(1), Self::action1, ())
fn trigger(&mut self, _: (), context: &Context<Self>) {
context
.schedule_event(context.time() + Duration::from_secs(1), Self::action1, ())
.unwrap();
self.key = scheduler
.schedule_keyed_event(scheduler.time() + Duration::from_secs(2), Self::action2, ())
self.key = context
.schedule_keyed_event(context.time() + Duration::from_secs(2), Self::action2, ())
.ok();
}
async fn action1(&mut self) {
@ -97,12 +97,12 @@ fn model_cancel_same_time_keyed_event() {
key: Option<ActionKey>,
}
impl TestModel {
fn trigger(&mut self, _: (), scheduler: &Scheduler<Self>) {
scheduler
.schedule_event(scheduler.time() + Duration::from_secs(2), Self::action1, ())
fn trigger(&mut self, _: (), context: &Context<Self>) {
context
.schedule_event(context.time() + Duration::from_secs(2), Self::action1, ())
.unwrap();
self.key = scheduler
.schedule_keyed_event(scheduler.time() + Duration::from_secs(2), Self::action2, ())
self.key = context
.schedule_keyed_event(context.time() + Duration::from_secs(2), Self::action2, ())
.ok();
}
async fn action1(&mut self) {
@ -142,10 +142,10 @@ fn model_schedule_periodic_event() {
output: Output<i32>,
}
impl TestModel {
fn trigger(&mut self, _: (), scheduler: &Scheduler<Self>) {
scheduler
fn trigger(&mut self, _: (), context: &Context<Self>) {
context
.schedule_periodic_event(
scheduler.time() + Duration::from_secs(2),
context.time() + Duration::from_secs(2),
Duration::from_secs(3),
Self::action,
42,
@ -190,10 +190,10 @@ fn model_cancel_periodic_event() {
key: Option<ActionKey>,
}
impl TestModel {
fn trigger(&mut self, _: (), scheduler: &Scheduler<Self>) {
self.key = scheduler
fn trigger(&mut self, _: (), context: &Context<Self>) {
self.key = context
.schedule_keyed_periodic_event(
scheduler.time() + Duration::from_secs(2),
context.time() + Duration::from_secs(2),
Duration::from_secs(3),
Self::action,
(),

View File

@ -2,6 +2,8 @@
use std::time::Duration;
#[cfg(not(miri))]
use asynchronix::model::Context;
use asynchronix::model::Model;
use asynchronix::ports::{EventBuffer, Output};
use asynchronix::simulation::{Address, Mailbox, SimInit, Simulation};
@ -219,21 +221,9 @@ impl TimestampModel {
}
#[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()
})
async fn init(mut self, _: &Context<Self>) -> asynchronix::model::InitializedModel<Self> {
self.stamp.send((Instant::now(), SystemTime::now())).await;
self.into()
}
}
@ -267,7 +257,7 @@ fn timestamp_bench(
#[test]
fn simulation_system_clock_from_instant() {
let t0 = MonotonicTime::EPOCH;
const TOLERANCE: f64 = 0.0005; // [s]
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