forked from ROMEO/nexosim
Merge pull request #81 from asynchronics/feature/release_cleanups
Improve documentation, fix README example
This commit is contained in:
commit
2a4b389977
35
README.md
35
README.md
@ -90,20 +90,21 @@ asynchronix = "0.2.4"
|
||||
// Input ●─────►│ multiplier 1 ├─────►│ multiplier 2 ├─────► Output
|
||||
// │ │ │ │
|
||||
// └──────────────┘ └──────────────┘
|
||||
use nexosim::model::{Model, Output};
|
||||
use nexosim::simulation::{Mailbox, SimInit};
|
||||
use nexosim::time::{MonotonicTime, Scheduler};
|
||||
use std::time::Duration;
|
||||
|
||||
use nexosim::model::{Context, Model};
|
||||
use nexosim::ports::{EventSlot, Output};
|
||||
use nexosim::simulation::{Mailbox, SimInit};
|
||||
use nexosim::time::MonotonicTime;
|
||||
|
||||
// A model that doubles its input and forwards it with a 1s delay.
|
||||
#[derive(Default)]
|
||||
pub struct DelayedMultiplier {
|
||||
pub output: Output<f64>,
|
||||
}
|
||||
impl DelayedMultiplier {
|
||||
pub fn input(&mut self, value: f64, scheduler: &Scheduler<Self>) {
|
||||
scheduler
|
||||
.schedule_event(Duration::from_secs(1), Self::send, 2.0 * value)
|
||||
pub fn input(&mut self, value: f64, ctx: &mut Context<Self>) {
|
||||
ctx.schedule_event(Duration::from_secs(1), Self::send, 2.0 * value)
|
||||
.unwrap();
|
||||
}
|
||||
async fn send(&mut self, value: f64) {
|
||||
@ -124,28 +125,32 @@ multiplier1
|
||||
.connect(DelayedMultiplier::input, &multiplier2_mbox);
|
||||
|
||||
// Keep handles to the main input and output.
|
||||
let mut output_slot = multiplier2.output.connect_slot().0;
|
||||
let mut output_slot = EventSlot::new();
|
||||
multiplier2.output.connect_sink(&output_slot);
|
||||
let input_address = multiplier1_mbox.address();
|
||||
|
||||
// Instantiate the simulator
|
||||
let t0 = MonotonicTime::EPOCH; // arbitrary start time
|
||||
let mut simu = SimInit::new()
|
||||
.add_model(multiplier1, multiplier1_mbox)
|
||||
.add_model(multiplier2, multiplier2_mbox)
|
||||
.init(t0);
|
||||
.add_model(multiplier1, multiplier1_mbox, "multiplier 1")
|
||||
.add_model(multiplier2, multiplier2_mbox, "multiplier 2")
|
||||
.init(t0)?
|
||||
.0;
|
||||
|
||||
// Send a value to the first multiplier.
|
||||
simu.send_event(DelayedMultiplier::input, 3.5, &input_address);
|
||||
simu.process_event(DelayedMultiplier::input, 3.5, &input_address)?;
|
||||
|
||||
// Advance time to the next event.
|
||||
simu.step();
|
||||
simu.step()?;
|
||||
assert_eq!(simu.time(), t0 + Duration::from_secs(1));
|
||||
assert_eq!(output_slot.take(), None);
|
||||
assert_eq!(output_slot.next(), None);
|
||||
|
||||
// Advance time to the next event.
|
||||
simu.step();
|
||||
simu.step()?;
|
||||
assert_eq!(simu.time(), t0 + Duration::from_secs(2));
|
||||
assert_eq!(output_slot.take(), Some(14.0));
|
||||
assert_eq!(output_slot.next(), Some(14.0));
|
||||
|
||||
Ok(())
|
||||
```
|
||||
|
||||
# Implementation notes
|
||||
|
@ -29,7 +29,6 @@ keywords = [
|
||||
]
|
||||
|
||||
[features]
|
||||
# gRPC service.
|
||||
server = [
|
||||
"dep:bytes",
|
||||
"dep:ciborium",
|
||||
|
@ -79,8 +79,8 @@ impl Executor {
|
||||
|
||||
/// Spawns a task which output will never be retrieved.
|
||||
///
|
||||
/// Note that spawned tasks are not executed until [`run()`](Executor::run)
|
||||
/// is called.
|
||||
/// Note that spawned tasks are not executed until [`run`](Executor::run) is
|
||||
/// called.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn spawn<T>(&self, future: T) -> Promise<T::Output>
|
||||
where
|
||||
@ -95,8 +95,8 @@ impl Executor {
|
||||
|
||||
/// Spawns a task which output will never be retrieved.
|
||||
///
|
||||
/// Note that spawned tasks are not executed until [`run()`](Executor::run)
|
||||
/// is called.
|
||||
/// Note that spawned tasks are not executed until [`run`](Executor::run) is
|
||||
/// called.
|
||||
pub(crate) fn spawn_and_forget<T>(&self, future: T)
|
||||
where
|
||||
T: Future + Send + 'static,
|
||||
|
@ -186,8 +186,8 @@ impl Executor {
|
||||
/// Spawns a task and returns a promise that can be polled to retrieve the
|
||||
/// task's output.
|
||||
///
|
||||
/// Note that spawned tasks are not executed until [`run()`](Executor::run)
|
||||
/// is called.
|
||||
/// Note that spawned tasks are not executed until [`run`](Executor::run) is
|
||||
/// called.
|
||||
pub(crate) fn spawn<T>(&self, future: T) -> Promise<T::Output>
|
||||
where
|
||||
T: Future + Send + 'static,
|
||||
@ -215,8 +215,8 @@ impl Executor {
|
||||
/// This is mostly useful to avoid undue reference counting for futures that
|
||||
/// return a `()` type.
|
||||
///
|
||||
/// Note that spawned tasks are not executed until [`run()`](Executor::run)
|
||||
/// is called.
|
||||
/// Note that spawned tasks are not executed until [`run`](Executor::run) is
|
||||
/// called.
|
||||
pub(crate) fn spawn_and_forget<T>(&self, future: T)
|
||||
where
|
||||
T: Future + Send + 'static,
|
||||
|
@ -59,7 +59,7 @@ impl Executor {
|
||||
/// Spawns a task and returns a promise that can be polled to retrieve the
|
||||
/// task's output.
|
||||
///
|
||||
/// Note that spawned tasks are not executed until [`run()`](Executor::run)
|
||||
/// Note that spawned tasks are not executed until [`run`](Executor::run)
|
||||
/// is called.
|
||||
pub(crate) fn spawn<T>(&self, future: T) -> Promise<T::Output>
|
||||
where
|
||||
@ -91,7 +91,7 @@ impl Executor {
|
||||
/// This is mostly useful to avoid undue reference counting for futures that
|
||||
/// return a `()` type.
|
||||
///
|
||||
/// Note that spawned tasks are not executed until [`run()`](Executor::run)
|
||||
/// Note that spawned tasks are not executed until [`run`](Executor::run)
|
||||
/// is called.
|
||||
pub(crate) fn spawn_and_forget<T>(&self, future: T)
|
||||
where
|
||||
|
@ -39,9 +39,9 @@
|
||||
//! * _output ports_, which are instances of the [`Output`](ports::Output) type
|
||||
//! and can be used to broadcast a message,
|
||||
//! * _requestor ports_, which are instances of the
|
||||
//! [`Requestor`](ports::Requestor) type and can be used to broadcast a
|
||||
//! message and receive an iterator yielding the replies from all connected
|
||||
//! replier ports,
|
||||
//! [`Requestor`](ports::Requestor) or [`UniRequestor`](ports::UniRequestor)
|
||||
//! types and can be used to broadcast a message and receive an iterator
|
||||
//! yielding the replies from all connected replier ports,
|
||||
//! * _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
|
||||
@ -54,19 +54,27 @@
|
||||
//! 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
|
||||
//! * 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.
|
||||
//! this trait is to allow models to specify a
|
||||
//! [`Model::init`](model::Model::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 {}`.
|
||||
//! The [`Model::init`](model::Model::init) methods has a 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
|
||||
//! More complex models can be built with the [`ProtoModel`](model::ProtoModel)
|
||||
//! trait. The [`ProtoModel::build`](model::ProtoModel::build) method makes it
|
||||
//! possible to:
|
||||
//!
|
||||
//! * build the final [`Model`](model::Model) from a builder (the *model prototype*),
|
||||
//! * perform possibly blocking actions when the model is added to the
|
||||
//! simulation rather than when the simulation starts, such as establishing a
|
||||
//! network connection or configuring hardware devices,
|
||||
//! * connect submodels and add them to the simulation.
|
||||
//!
|
||||
//! ### A simple model
|
||||
//!
|
||||
//! Let us consider for illustration a simple model that forwards its input
|
||||
//! after multiplying it by 2. This model has only one input and one output
|
||||
@ -98,7 +106,7 @@
|
||||
//! impl Model for Multiplier {}
|
||||
//! ```
|
||||
//!
|
||||
//! #### A model using the local context
|
||||
//! ### 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
|
||||
@ -141,7 +149,7 @@
|
||||
//! [`Address`](simulation::Mailbox)es pointing to that mailbox.
|
||||
//!
|
||||
//! Addresses are used among others to connect models: each output or requestor
|
||||
//! port 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.
|
||||
//!
|
||||
@ -230,7 +238,7 @@
|
||||
//!
|
||||
//! // Pick an arbitrary simulation start time and build the simulation.
|
||||
//! let t0 = MonotonicTime::EPOCH;
|
||||
//! let mut simu = SimInit::new()
|
||||
//! let (mut simu, scheduler) = SimInit::new()
|
||||
//! .add_model(multiplier1, multiplier1_mbox, "multiplier1")
|
||||
//! .add_model(multiplier2, multiplier2_mbox, "multiplier2")
|
||||
//! .add_model(delay1, delay1_mbox, "delay1")
|
||||
@ -245,27 +253,27 @@
|
||||
//! The simulation can be controlled in several ways:
|
||||
//!
|
||||
//! 1. by advancing time, either until the next scheduled event with
|
||||
//! [`Simulation::step()`](simulation::Simulation::step), or until a specific
|
||||
//! [`Simulation::step`](simulation::Simulation::step), until a specific
|
||||
//! deadline with
|
||||
//! [`Simulation::step_until()`](simulation::Simulation::step_until).
|
||||
//! [`Simulation::step_until`](simulation::Simulation::step_until), or
|
||||
//! until there are no more scheduled events with
|
||||
//! [`Simulation::step_unbounded`](simulation::Simulation::step_unbounded).
|
||||
//! 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),
|
||||
//! 3. by scheduling events, using for instance
|
||||
//! [`Scheduler::schedule_event()`](simulation::Scheduler::schedule_event).
|
||||
//! [`Simulation::process_event`](simulation::Simulation::process_event) or
|
||||
//! [`Simulation::send_query`](simulation::Simulation::process_query),
|
||||
//! 3. by scheduling events with a [`Scheduler`](simulation::Scheduler).
|
||||
//!
|
||||
//! When initialized with the default clock, the simulation will run as fast as
|
||||
//! possible, without regard for the actual wall clock time. Alternatively, the
|
||||
//! simulation time can be synchronized to the wall clock time using
|
||||
//! [`SimInit::set_clock()`](simulation::SimInit::set_clock) and providing a
|
||||
//! [`SimInit::set_clock`](simulation::SimInit::set_clock) and providing a
|
||||
//! custom [`Clock`](time::Clock) type or a readily-available real-time clock
|
||||
//! such as [`AutoSystemClock`](time::AutoSystemClock).
|
||||
//!
|
||||
//! Simulation outputs can be monitored using [`EventSlot`](ports::EventSlot)s
|
||||
//! and [`EventBuffer`](ports::EventBuffer)s, which can be connected to any
|
||||
//! model's output port. While an event slot only gives access to the last value
|
||||
//! sent from a port, an event stream is an iterator that yields all events that
|
||||
//! were sent in first-in-first-out order.
|
||||
//! Simulation outputs can be monitored using [`EventSlot`](ports::EventSlot)s,
|
||||
//! [`EventBuffer`](ports::EventBuffer)s, or any implementer of the
|
||||
//! [`EventSink`](ports::EventSink) trait, connected to one or several model
|
||||
//! output ports.
|
||||
//!
|
||||
//! This is an example of simulation that could be performed using the above
|
||||
//! bench assembly:
|
||||
@ -373,20 +381,20 @@
|
||||
//! processed in any order by `B` and `C`, it is guaranteed that `B` will
|
||||
//! process `M1` before `M3`.
|
||||
//!
|
||||
//! The first guarantee (and only the first) also extends to events scheduled
|
||||
//! from a simulation with a
|
||||
//! [`Scheduler::schedule_*()`](simulation::Scheduler::schedule_event) method:
|
||||
//! if the scheduler contains several events to be delivered at the same time to
|
||||
//! the same model, these events will always be processed in the order in which
|
||||
//! they were scheduled.
|
||||
//! Both guarantees also extend to same-time events scheduled from the global
|
||||
//! [`Scheduler`](simulation::Scheduler), *i.e.* the relative ordering of events
|
||||
//! scheduled for the same time is preserved and warranties 1 and 2 above
|
||||
//! accordingly hold (assuming model `A` stands for the scheduler). Likewise,
|
||||
//! the relative order of same-time events self-scheduled by a model using its
|
||||
//! [`Context`](model::Context) is preserved.
|
||||
//!
|
||||
//! [actor_model]: https://en.wikipedia.org/wiki/Actor_model
|
||||
//! [pony]: https://www.ponylang.io/
|
||||
//!
|
||||
//!
|
||||
//! # Feature flags
|
||||
//! # Cargo feature flags
|
||||
//!
|
||||
//! ## Tracing support
|
||||
//! ## Tracing
|
||||
//!
|
||||
//! The `tracing` feature flag provides support for the
|
||||
//! [`tracing`](https://docs.rs/tracing/latest/tracing/) crate and can be
|
||||
@ -409,32 +417,50 @@
|
||||
//! nexosim = { version = "0.3.0-beta.0", features = ["server"] }
|
||||
//! ```
|
||||
//!
|
||||
//! See the [`registry`] and [`server`] modules for more information.
|
||||
//!
|
||||
//! Front-end usage documentation will be added upon release of the NeXosim
|
||||
//! Python client.
|
||||
//!
|
||||
//!
|
||||
//! # Other resources
|
||||
//!
|
||||
//! ## Other examples
|
||||
//!
|
||||
//! The [`examples`][gh_examples] directory in the main repository contains more
|
||||
//! fleshed out examples that demonstrate various capabilities of the simulation
|
||||
//! Several [`examples`][gh_examples] are available that contain more fleshed
|
||||
//! out examples and demonstrate various capabilities of the simulation
|
||||
//! framework.
|
||||
//!
|
||||
//! [gh_examples]:
|
||||
//! https://github.com/asynchronics/nexosim/tree/main/nexosim/examples
|
||||
//!
|
||||
//! ## Modules documentation
|
||||
//!
|
||||
//! While the above overview does cover the basic concepts, more information is
|
||||
//! available in the documentation of the different modules:
|
||||
//! ## Other features and advanced topics
|
||||
//!
|
||||
//! While the above overview does cover most basic concepts, more information is
|
||||
//! available in the modules' documentation:
|
||||
//!
|
||||
//! * the [`model`] module provides more details about models, **model
|
||||
//! prototypes** and **hierarchical models**; be sure to check as well the
|
||||
//! documentation of [`model::Context`] for topics such as **self-scheduling**
|
||||
//! methods and **event cancellation**,
|
||||
//! * the [`ports`] module discusses in more details model ports and simulation
|
||||
//! endpoints, as well as the ability to **modify and filter messages**
|
||||
//! exchanged between ports; it also provides
|
||||
//! [`EventSource`](ports::EventSource) and
|
||||
//! [`QuerySource`](ports::QuerySource) objects which can be connected to
|
||||
//! models just like [`Output`](ports::Output) and
|
||||
//! [`Requestor`](ports::Requestor) ports, but for use as simulation
|
||||
//! endpoints.
|
||||
//! * the [`registry`] and [`server`] modules make it possible to manage and
|
||||
//! monitor a simulation locally or remotely from a NeXosim Python client,
|
||||
//! * the [`simulation`] module discusses **mailbox capacity** and pathological
|
||||
//! situations that may lead to a **deadlock**,
|
||||
//! * the [`time`] module introduces the [`time::MonotonicTime`] monotonic
|
||||
//! timestamp object and **simulation clocks**.
|
||||
//! * the [`tracing`] module discusses time-stamping and filtering of `tracing`
|
||||
//! events.
|
||||
//!
|
||||
//! * 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`] 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 the monotonic timestamp format
|
||||
//! used for simulations ([`time::MonotonicTime`]).
|
||||
#![warn(missing_docs, missing_debug_implementations, unreachable_pub)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide))]
|
||||
#![cfg_attr(docsrs, doc(cfg_hide(feature = "dev-hooks")))]
|
||||
@ -458,4 +484,5 @@ pub mod server;
|
||||
pub mod tracing;
|
||||
|
||||
#[cfg(feature = "dev-hooks")]
|
||||
#[doc(hidden)]
|
||||
pub mod dev_hooks;
|
||||
|
@ -5,30 +5,30 @@
|
||||
//! 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 when the simulation starts,
|
||||
//! i.e. after all models have been connected and added to the simulation.
|
||||
//! *i.e.* after all models have been connected and added to the simulation.
|
||||
//!
|
||||
//! It is frequently convenient to expose to users a model builder type—called a
|
||||
//! *model prototype*—rather than the final model. This can be done by
|
||||
//! implementing the [`ProtoModel`] trait, which defines the associated model
|
||||
//! type and a [`ProtoModel::build`] method invoked when a model is added the
|
||||
//! the simulation and returning the actual model instance.
|
||||
//! simulation.
|
||||
//!
|
||||
//! Prototype models can be used whenever the Rust builder pattern is helpful,
|
||||
//! for instance to set optional parameters. One of the use-cases that may
|
||||
//! benefit from the use of prototype models is hierarchical model building.
|
||||
//! When a parent model contains submodels, these submodels are often an
|
||||
//! implementation detail that needs not be exposed to the user. One may then
|
||||
//! define a prototype model that contains all outputs and requestors ports.
|
||||
//! Upon invocation of [`ProtoModel::build`], the ports are moved to the
|
||||
//! appropriate submodels and those submodels are added to the simulation.
|
||||
//! define a prototype model that contains all outputs and requestors ports,
|
||||
//! while the model itself contains the input and replier ports. Upon invocation
|
||||
//! of [`ProtoModel::build`], the exit ports are moved to the model or its
|
||||
//! submodels, and those submodels are added to the simulation.
|
||||
//!
|
||||
//! Note that a trivial [`ProtoModel`] implementation is generated by default
|
||||
//! for any object implementing the [`Model`] trait, where the associated
|
||||
//! [`ProtoModel::Model`] type is the model type itself and where
|
||||
//! [`ProtoModel::build`] simply returns the model instance. This is what makes
|
||||
//! it possible to use either an explicitly-defined [`ProtoModel`] as argument
|
||||
//! to the [`SimInit::add_model`](crate::simulation::SimInit::add_model) method,
|
||||
//! or a plain [`Model`] type.
|
||||
//! [`ProtoModel::Model`] type is the model type itself. This is what makes it
|
||||
//! possible to use either an explicitly-defined [`ProtoModel`] as argument to
|
||||
//! the [`SimInit::add_model`](crate::simulation::SimInit::add_model) method, or
|
||||
//! a plain [`Model`] type.
|
||||
//!
|
||||
//! #### Examples
|
||||
//!
|
||||
@ -66,7 +66,9 @@
|
||||
//! ```
|
||||
//!
|
||||
//! Finally, if a model builder is required, the [`ProtoModel`] trait can be
|
||||
//! explicitly implemented:
|
||||
//! explicitly implemented. Note that the [`ProtoModel`] contains all output and
|
||||
//! requestor ports, while the associated [`Model`] contains all input and
|
||||
//! replier methods.
|
||||
//!
|
||||
//! ```
|
||||
//! use nexosim::model::{BuildContext, InitializedModel, Model, ProtoModel};
|
||||
@ -117,120 +119,81 @@
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Hierarchical models
|
||||
//!
|
||||
//! # Events and queries
|
||||
//! Hierarchical models are models build from a prototype, which prototype adds
|
||||
//! submodels to the simulation within its [`ProtoModel::build`] method. From a
|
||||
//! formal point of view, however, hierarchical models are just regular models
|
||||
//! implementing the [`Model`] trait, as are their submodels.
|
||||
//!
|
||||
//! Models can exchange data via *events* and *queries*.
|
||||
//!
|
||||
//! Events are send-and-forget messages that can be broadcast from an *output
|
||||
//! port* to an arbitrary number of *input ports* with a matching event type.
|
||||
//!
|
||||
//! Queries actually involve two messages: a *request* that can be broadcast
|
||||
//! from a *requestor port* to an arbitrary number of *replier ports* with a
|
||||
//! matching request type, and a *reply* sent in response to such request. The
|
||||
//! response received by a requestor port is an iterator that yields as many
|
||||
//! items (replies) as there are connected replier ports.
|
||||
//!
|
||||
//!
|
||||
//! ### Output and requestor ports
|
||||
//!
|
||||
//! Output and requestor ports can be added to a model using composition, adding
|
||||
//! [`Output`](crate::ports::Output) and [`Requestor`](crate::ports::Requestor)
|
||||
//! objects as members. They are parametrized by the event, request and reply
|
||||
//! types.
|
||||
//!
|
||||
//! Models are expected to expose their output and requestor ports as public
|
||||
//! members so they can be connected to input and replier ports when assembling
|
||||
//! the simulation bench.
|
||||
//!
|
||||
//! #### Example
|
||||
//!
|
||||
//! ```
|
||||
//! use nexosim::model::Model;
|
||||
//! use nexosim::ports::{Output, Requestor};
|
||||
//! This example demonstrates a child model inside a parent model, where the
|
||||
//! parent model simply forwards input data to the child and the child in turn
|
||||
//! sends the data to the output exposed by the parent's prototype.
|
||||
//!
|
||||
//! pub struct MyModel {
|
||||
//! pub my_output: Output<String>,
|
||||
//! pub my_requestor: Requestor<u32, bool>,
|
||||
//! }
|
||||
//! impl MyModel {
|
||||
//! // ...
|
||||
//! }
|
||||
//! impl Model for MyModel {}
|
||||
//! ```
|
||||
//! For a more comprehensive example demonstrating hierarchical model
|
||||
//! assemblies, see the [`assembly`][assembly] example.
|
||||
//!
|
||||
//!
|
||||
//! ### Input and replier ports
|
||||
//!
|
||||
//! Input ports and replier ports are methods that implement the
|
||||
//! [`InputFn`](crate::ports::InputFn) or [`ReplierFn`](crate::ports::ReplierFn)
|
||||
//! traits with appropriate bounds on their argument and return types.
|
||||
//!
|
||||
//! In practice, an input port method for an event of type `T` may have any of
|
||||
//! the following signatures, where the futures returned by the `async` variants
|
||||
//! must implement `Send`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! fn(&mut self) // argument elided, implies `T=()`
|
||||
//! fn(&mut self, T)
|
||||
//! fn(&mut self, T, &mut Context<Self>)
|
||||
//! async fn(&mut self) // argument elided, implies `T=()`
|
||||
//! async fn(&mut self, T)
|
||||
//! async fn(&mut self, T, &mut Context<Self>)
|
||||
//! where
|
||||
//! Self: Model,
|
||||
//! T: Clone + Send + 'static,
|
||||
//! R: Send + 'static,
|
||||
//! ```
|
||||
//!
|
||||
//! 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
|
||||
//! turn have any of the following signatures, where the futures must implement
|
||||
//! `Send`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! async fn(&mut self) -> R // argument elided, implies `T=()`
|
||||
//! async fn(&mut self, T) -> R
|
||||
//! async fn(&mut self, T, &mut Context<Self>) -> R
|
||||
//! where
|
||||
//! Self: Model,
|
||||
//! T: Clone + Send + 'static,
|
||||
//! R: Send + 'static,
|
||||
//! ```
|
||||
//!
|
||||
//! Output and replier ports will normally be exposed as public methods so they
|
||||
//! 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
|
||||
//! [`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
|
||||
//! reply types remain the same.
|
||||
//!
|
||||
//! #### Example
|
||||
//! [assembly]:
|
||||
//! https://github.com/asynchronics/nexosim/tree/main/nexosim/examples/assembly.rs
|
||||
//!
|
||||
//! ```
|
||||
//! use nexosim::model::{Context, Model};
|
||||
//! use nexosim::model::{BuildContext, Model, ProtoModel};
|
||||
//! use nexosim::ports::Output;
|
||||
//! use nexosim::simulation::Mailbox;
|
||||
//!
|
||||
//! pub struct MyModel {
|
||||
//! // ...
|
||||
//! pub struct ParentModel {
|
||||
//! // Private internal port connected to the submodel.
|
||||
//! to_child: Output<u64>,
|
||||
//! }
|
||||
//! impl MyModel {
|
||||
//! pub fn my_input(&mut self, input: String, cx: &mut Context<Self>) {
|
||||
//! // ...
|
||||
//! }
|
||||
//! pub async fn my_replier(&mut self, request: u32) -> bool { // context argument elided
|
||||
//! // ...
|
||||
//! # unimplemented!()
|
||||
//! impl ParentModel {
|
||||
//! async fn input(&mut self, my_data: u64) {
|
||||
//! // Forward to the submodel.
|
||||
//! self.to_child.send(my_data).await;
|
||||
//! }
|
||||
//! }
|
||||
//! impl Model for MyModel {}
|
||||
//! impl Model for ParentModel {}
|
||||
//!
|
||||
//! pub struct ProtoParentModel {
|
||||
//! pub output: Output<u64>,
|
||||
//! }
|
||||
//! impl ProtoModel for ProtoParentModel {
|
||||
//! type Model = ParentModel;
|
||||
//!
|
||||
//! fn build(self, cx: &mut BuildContext<Self>) -> ParentModel {
|
||||
//! // Move the output to the child model.
|
||||
//! let child = ChildModel { output: self.output };
|
||||
//! let mut parent = ParentModel {
|
||||
//! to_child: Output::default(),
|
||||
//! };
|
||||
//!
|
||||
//! let child_mailbox = Mailbox::new();
|
||||
//!
|
||||
//! // Establish an internal Parent -> Child connection.
|
||||
//! parent
|
||||
//! .to_child
|
||||
//! .connect(ChildModel::input, child_mailbox.address());
|
||||
//!
|
||||
//! // Add the child model to the simulation.
|
||||
//! cx.add_submodel(child, child_mailbox, "child");
|
||||
//!
|
||||
//! parent
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! struct ChildModel {
|
||||
//! output: Output<u64>,
|
||||
//! }
|
||||
//! impl ChildModel {
|
||||
//! async fn input(&mut self, my_data: u64) {
|
||||
//! self.output.send(my_data).await;
|
||||
//! }
|
||||
//! }
|
||||
//! impl Model for ChildModel {}
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
pub use context::{BuildContext, Context};
|
||||
@ -240,7 +203,7 @@ mod context;
|
||||
/// Trait to be implemented by simulation models.
|
||||
///
|
||||
/// This trait enables models to perform specific actions during initialization.
|
||||
/// The [`Model::init()`] method is run only once all models have been connected
|
||||
/// 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.
|
||||
@ -253,7 +216,7 @@ pub trait Model: Sized + Send + 'static {
|
||||
///
|
||||
/// This asynchronous method is executed exactly once for all models of the
|
||||
/// simulation when the
|
||||
/// [`SimInit::init()`](crate::simulation::SimInit::init) method is called.
|
||||
/// [`SimInit::init`](crate::simulation::SimInit::init) method is called.
|
||||
///
|
||||
/// The default implementation simply converts the model to an
|
||||
/// `InitializedModel` without any side effect.
|
||||
@ -290,7 +253,7 @@ pub trait Model: Sized + Send + 'static {
|
||||
///
|
||||
/// A model can be converted to an `InitializedModel` using the `Into`/`From`
|
||||
/// traits. The implementation of the simulation guarantees that the
|
||||
/// [`Model::init()`] method will never be called on a model after conversion to
|
||||
/// [`Model::init`] method will never be called on a model after conversion to
|
||||
/// an `InitializedModel`.
|
||||
#[derive(Debug)]
|
||||
pub struct InitializedModel<M: Model>(pub(crate) M);
|
||||
@ -301,18 +264,16 @@ impl<M: Model> From<M> for InitializedModel<M> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to be implemented by model prototypes.
|
||||
/// Trait to be implemented by simulation model prototypes.
|
||||
///
|
||||
/// This trait makes it possible to build the final model from a builder type
|
||||
/// when it is added to the simulation.
|
||||
///
|
||||
/// The [`ProtoModel::build()`] method consumes the prototype. It is
|
||||
/// The [`ProtoModel::build`] method consumes the prototype. It is
|
||||
/// automatically called when a model or submodel prototype is added to the
|
||||
/// simulation using
|
||||
/// [`Simulation::add_model()`](crate::simulation::SimInit::add_model) or
|
||||
/// [`Simulation::add_model`](crate::simulation::SimInit::add_model) or
|
||||
/// [`BuildContext::add_submodel`].
|
||||
///
|
||||
/// The
|
||||
pub trait ProtoModel: Sized {
|
||||
/// Type of the model to be built.
|
||||
type Model: Model;
|
||||
@ -320,8 +281,8 @@ pub trait ProtoModel: Sized {
|
||||
/// Builds the model.
|
||||
///
|
||||
/// This method is invoked when the
|
||||
/// [`SimInit::add_model()`](crate::simulation::SimInit::add_model) or
|
||||
/// [`BuildContext::add_submodel`] method is called.
|
||||
/// [`SimInit::add_model`](crate::simulation::SimInit::add_model) or
|
||||
/// [`BuildContext::add_submodel`] method are called.
|
||||
fn build(self, cx: &mut BuildContext<Self>) -> Self::Model;
|
||||
}
|
||||
|
||||
|
@ -1,63 +1,182 @@
|
||||
//! Model ports for event and query broadcasting.
|
||||
//! Ports for event and query broadcasting.
|
||||
//!
|
||||
//! Models typically contain [`Output`] and/or [`Requestor`] ports, exposed as
|
||||
//! public member variables. Output ports broadcast events to all connected
|
||||
//! input ports, while requestor ports broadcast queries to, and retrieve
|
||||
//! replies from, all connected replier ports.
|
||||
//!
|
||||
//! # Events and queries
|
||||
//!
|
||||
//! Models can exchange data via *events* and *queries*.
|
||||
//!
|
||||
//! Events are send-and-forget messages that can be broadcast from an [`Output`]
|
||||
//! port or [`EventSource`] to an arbitrary number of *input ports* or
|
||||
//! [`EventSink`]s with a matching event type.
|
||||
//!
|
||||
//! Queries actually involve two messages: a *request* that can be broadcast
|
||||
//! from a [`Requestor`] port, a [`UniRequestor`] port or a [`QuerySource`] to
|
||||
//! an arbitrary number of *replier ports* with a matching request type, and a
|
||||
//! *reply* sent in response to such request. The response received by a
|
||||
//! [`Requestor`] port is an iterator that yields as many items (replies) as
|
||||
//! there are connected replier ports, while a [`UniRequestor`] received exactly
|
||||
//! one reply.
|
||||
//!
|
||||
//! # Model ports
|
||||
//!
|
||||
//! ## Input and replier ports
|
||||
//!
|
||||
//! Input ports and replier ports are methods that implement the [`InputFn`] or
|
||||
//! [`ReplierFn`] traits.
|
||||
//!
|
||||
//! In practice, an input port method for an event of type `T` may have any of
|
||||
//! the following signatures, where the futures returned by the `async` variants
|
||||
//! must implement `Send`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! fn(&mut self) // argument elided, implies `T=()`
|
||||
//! fn(&mut self, T)
|
||||
//! fn(&mut self, T, &mut Context<Self>)
|
||||
//! async fn(&mut self) // argument elided, implies `T=()`
|
||||
//! async fn(&mut self, T)
|
||||
//! async fn(&mut self, T, &mut Context<Self>)
|
||||
//! where
|
||||
//! Self: Model,
|
||||
//! T: Clone + Send + 'static,
|
||||
//! R: Send + 'static,
|
||||
//! ```
|
||||
//!
|
||||
//! 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
|
||||
//! turn have any of the following signatures, where the futures must implement
|
||||
//! `Send`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! async fn(&mut self) -> R // argument elided, implies `T=()`
|
||||
//! async fn(&mut self, T) -> R
|
||||
//! async fn(&mut self, T, &mut Context<Self>) -> R
|
||||
//! where
|
||||
//! Self: Model,
|
||||
//! T: Clone + Send + 'static,
|
||||
//! R: Send + 'static,
|
||||
//! ```
|
||||
//!
|
||||
//! Note that, due to type resolution ambiguities, non-async methods are not
|
||||
//! allowed for replier ports.
|
||||
//!
|
||||
//! Input and replier ports will normally be exposed as public methods by a
|
||||
//! [`Model`](crate::model::Model) so they can be connected to output and
|
||||
//! requestor ports when assembling the simulation bench. However, input ports
|
||||
//! may (and should) be defined as private methods if they are only used
|
||||
//! internally by the model, for instance to schedule future actions on itself.
|
||||
//!
|
||||
//! Changing the signature of a public input or replier port is not considered
|
||||
//! to alter the public interface of a model provided that the event, request
|
||||
//! and reply types remain the same. In particular, adding a context argument or
|
||||
//! changing a regular method to an `async` method will not cause idiomatic user
|
||||
//! code to miscompile.
|
||||
//!
|
||||
//! #### Basic example
|
||||
//!
|
||||
//! ```
|
||||
//! use nexosim::model::{Context, Model};
|
||||
//!
|
||||
//! pub struct MyModel {
|
||||
//! // ...
|
||||
//! }
|
||||
//! impl MyModel {
|
||||
//! pub fn my_input(&mut self, input: String, cx: &mut Context<Self>) {
|
||||
//! // ...
|
||||
//! }
|
||||
//! pub async fn my_replier(&mut self, request: u32) -> bool { // context argument elided
|
||||
//! // ...
|
||||
//! # unimplemented!()
|
||||
//! }
|
||||
//! }
|
||||
//! impl Model for MyModel {}
|
||||
//! ```
|
||||
//!
|
||||
//! ## Output and requestor ports
|
||||
//!
|
||||
//! Output and requestor ports can be added to a model using composition, adding
|
||||
//! [`Output`], [`Requestor`] or [`UniRequestor`] objects as members. They are
|
||||
//! parametrized by the event type, or by the request and reply types.
|
||||
//!
|
||||
//! Output ports broadcast events to all connected input ports, while requestor
|
||||
//! ports broadcast queries to, and retrieve replies from, all connected replier
|
||||
//! ports.
|
||||
//!
|
||||
//! On the surface, output and requestor ports only differ in that sending a
|
||||
//! query from a requestor port also returns an iterator over the replies from
|
||||
//! all connected ports. Sending a query is more costly, however, because of the
|
||||
//! need to wait until all connected models have processed the query. In
|
||||
//! contrast, since events are buffered in the mailbox of the target model,
|
||||
//! sending an event is a fire-and-forget operation. For this reason, output
|
||||
//! ports should generally be preferred over requestor ports when possible.
|
||||
//! all connected ports (or a single reply in the case of [`UniRequestor`]).
|
||||
//! Sending a query is more costly, however, because of the need to wait until
|
||||
//! the connected model(s) have processed the query. In contrast, since events
|
||||
//! are buffered in the mailbox of the target model, sending an event is a
|
||||
//! fire-and-forget operation. For this reason, output ports should generally be
|
||||
//! preferred over requestor ports when possible.
|
||||
//!
|
||||
//! `Output` and `Requestor` ports are clonable. Their clones are shallow
|
||||
//! copies, meaning that any modification of the ports connected to one clone is
|
||||
//! immediately reflected in other clones.
|
||||
//! Models (or model prototypes, as appropriate) are expected to expose their
|
||||
//! output and requestor ports as public members so they can be connected to
|
||||
//! input and replier ports when assembling the simulation bench. Internal ports
|
||||
//! used by hierarchical models to communicate with submodels are an exception
|
||||
//! to this rule and are typically private.
|
||||
//!
|
||||
//! #### Example
|
||||
//! #### Basic example
|
||||
//!
|
||||
//! This example demonstrates two submodels inside a parent model. The output of
|
||||
//! the submodel and of the main model are clones and remain therefore always
|
||||
//! connected to the same inputs.
|
||||
//! ```
|
||||
//! use nexosim::model::Model;
|
||||
//! use nexosim::ports::{Output, Requestor};
|
||||
//!
|
||||
//! For a more comprehensive example demonstrating hierarchical model
|
||||
//! assemblies, see the [`assembly example`][assembly].
|
||||
//! pub struct MyModel {
|
||||
//! pub my_output: Output<String>,
|
||||
//! pub my_requestor: Requestor<u32, bool>,
|
||||
//! }
|
||||
//! impl MyModel {
|
||||
//! // ...
|
||||
//! }
|
||||
//! impl Model for MyModel {}
|
||||
//! ```
|
||||
//!
|
||||
//! [assembly]:
|
||||
//! https://github.com/asynchronics/nexosim/tree/main/nexosim/examples/assembly.rs
|
||||
//! #### Example with cloned ports
|
||||
//!
|
||||
//! [`Output`] and [`Requestor`] ports are clonable. The clones are shallow
|
||||
//! copies, meaning that any modification of the ports connected to one instance
|
||||
//! is immediately reflected by its clones.
|
||||
//!
|
||||
//! Clones of output and requestor ports should be used with care: even though
|
||||
//! they uphold the usual [ordering
|
||||
//! guaranties](crate#message-ordering-guarantees), their use can lead to
|
||||
//! somewhat surprising message orderings.
|
||||
//!
|
||||
//! For instance, in the below [hierarchical
|
||||
//! model](crate::model#hierarchical-models), while message `M0` is guaranteed
|
||||
//! to reach the cloned output first, the relative arrival order of messages
|
||||
//! `M1` and `M2` forwarded by the submodels to the cloned output is not
|
||||
//! guaranteed.
|
||||
//!
|
||||
//! ```
|
||||
//! use nexosim::model::{BuildContext, Model, ProtoModel};
|
||||
//! use nexosim::ports::Output;
|
||||
//! use nexosim::simulation::Mailbox;
|
||||
//!
|
||||
//! pub struct ChildModel {
|
||||
//! pub output: Output<u64>,
|
||||
//! }
|
||||
//!
|
||||
//! impl ChildModel {
|
||||
//! pub fn new(output: Output<u64>) -> Self {
|
||||
//! Self {
|
||||
//! output,
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl Model for ChildModel {}
|
||||
//!
|
||||
//! pub struct ParentModel {
|
||||
//! output: Output<u64>,
|
||||
//! output: Output<String>,
|
||||
//! to_child1: Output<String>,
|
||||
//! to_child2: Output<String>,
|
||||
//! }
|
||||
//! impl ParentModel {
|
||||
//! pub async fn trigger(&mut self) {
|
||||
//! // M0 is guaranteed to reach the cloned output first.
|
||||
//! self.output.send("M0".to_string()).await;
|
||||
//!
|
||||
//! // M1 and M2 are forwarded by `Child1` and `Child2` to the cloned output
|
||||
//! // but may reach it in any relative order.
|
||||
//! self.to_child1.send("M1".to_string()).await;
|
||||
//! self.to_child2.send("M2".to_string()).await;
|
||||
//! }
|
||||
//! }
|
||||
//! impl Model for ParentModel {}
|
||||
//!
|
||||
//! pub struct ProtoParentModel {
|
||||
//! pub output: Output<u64>,
|
||||
//! pub output: Output<String>,
|
||||
//! }
|
||||
//!
|
||||
//! impl ProtoParentModel {
|
||||
//! pub fn new() -> Self {
|
||||
//! Self {
|
||||
@ -65,20 +184,86 @@
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl ProtoModel for ProtoParentModel {
|
||||
//! type Model = ParentModel;
|
||||
//!
|
||||
//! fn build(self, cx: &mut BuildContext<Self>) -> ParentModel {
|
||||
//! let mut child = ChildModel::new(self.output.clone());
|
||||
//! let child1 = ChildModel { output: self.output.clone() };
|
||||
//! let child2 = ChildModel { output: self.output.clone() };
|
||||
//! let mut parent = ParentModel {
|
||||
//! output: self.output,
|
||||
//! to_child1: Output::default(),
|
||||
//! to_child2: Output::default(),
|
||||
//! };
|
||||
//!
|
||||
//! cx.add_submodel(child, Mailbox::new(), "child");
|
||||
//! let child1_mailbox = Mailbox::new();
|
||||
//! let child2_mailbox = Mailbox::new();
|
||||
//! parent
|
||||
//! .to_child1
|
||||
//! .connect(ChildModel::input, child1_mailbox.address());
|
||||
//! parent
|
||||
//! .to_child2
|
||||
//! .connect(ChildModel::input, child2_mailbox.address());
|
||||
//!
|
||||
//! ParentModel { output: self.output }
|
||||
//! cx.add_submodel(child1, child1_mailbox, "child1");
|
||||
//! cx.add_submodel(child2, child2_mailbox, "child2");
|
||||
//!
|
||||
//! parent
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! pub struct ChildModel {
|
||||
//! pub output: Output<String>,
|
||||
//! }
|
||||
//! impl ChildModel {
|
||||
//! async fn input(&mut self, msg: String) {
|
||||
//! self.output.send(msg).await;
|
||||
//! }
|
||||
//! }
|
||||
//! impl Model for ChildModel {}
|
||||
//! ```
|
||||
|
||||
//!
|
||||
//! # Simulation endpoints
|
||||
//!
|
||||
//! Simulation endpoints can be seen as entry and exit ports for a simulation
|
||||
//! bench.
|
||||
//!
|
||||
//! [`EventSource`] and [`QuerySource`] objects are similar to [`Output`] and
|
||||
//! [`Requestor`] ports, respectively. They can be connected to models and can
|
||||
//! be used to send events or queries to such models via
|
||||
//! [`Action`](crate::simulation::Action)s.
|
||||
//!
|
||||
//! Objects implementing the [`EventSink`] trait, such as [`EventSlot`] and
|
||||
//! [`EventBuffer`], are in turn similar to input ports. They can be connected
|
||||
//! to model outputs and collect events sent by such models.
|
||||
//!
|
||||
//!
|
||||
//! # Connections
|
||||
//!
|
||||
//! Model ports can be connected to other model ports and to simulation
|
||||
//! endpoints using the `*connect` family of methods exposed by [`Output`],
|
||||
//! [`Requestor`], [`UniRequestor`], [`EventSource`] and [`QuerySource`].
|
||||
//!
|
||||
//! Regular connections between two ports are made with the appropriate
|
||||
//! `connect` method (for instance [`Output::connect`]). Such connections
|
||||
//! broadcast events or requests from a sender port to one or several receiver
|
||||
//! ports, cloning the event or request if necessary.
|
||||
//!
|
||||
//! Sometimes, however, it may be necessary to also map the event or request to
|
||||
//! another type so it can be understood by the receiving port. While this
|
||||
//! translation could be done by a model placed between the two ports, the
|
||||
//! `map_connect` methods (for instance [`Output::map_connect`]) provide a
|
||||
//! lightweight and computationally efficient alternative. These methods take a
|
||||
//! mapping closure as argument which maps outgoing messages, and in the case of
|
||||
//! requestors, a mapping closure which maps replies.
|
||||
//!
|
||||
//! Finally, it is sometime necessary to only forward messages or requests that
|
||||
//! satisfy specific criteria. For instance, a model of a data bus may be
|
||||
//! connected to many models (the "peripherals"), but its messages are usually
|
||||
//! only addressed to selected models. The `filter_map_connect` methods (for
|
||||
//! instance [`Output::filter_map_connect`]) enable this use-case by accepting a
|
||||
//! closure that inspects the messages and determines whether they should be
|
||||
//! forwarded, possibly after being mapped to another type.
|
||||
//!
|
||||
mod input;
|
||||
mod output;
|
||||
mod sink;
|
||||
|
@ -9,25 +9,20 @@ use super::markers;
|
||||
/// A function, method or closures that can be used as an *input port*.
|
||||
///
|
||||
/// This trait is in particular implemented for any function or method with the
|
||||
/// following signature, where it is implicitly assumed that the function
|
||||
/// implements `Send + 'static`:
|
||||
/// following signature, where the futures returned by the `async` variants must
|
||||
/// implement `Send`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// FnOnce(&mut M, T)
|
||||
/// FnOnce(&mut M, T, &mut Context<M>)
|
||||
/// fn(&mut M) // argument elided, implies `T=()`
|
||||
/// fn(&mut M, T)
|
||||
/// fn(&mut M, T, &mut Context<M>)
|
||||
/// async fn(&mut M) // argument elided, implies `T=()`
|
||||
/// async fn(&mut M, T)
|
||||
/// async fn(&mut M, T, &mut Context<M>)
|
||||
/// where
|
||||
/// M: Model
|
||||
/// ```
|
||||
///
|
||||
/// It is also implemented for the following signatures when `T=()`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// FnOnce(&mut M)
|
||||
/// async fn(&mut M)
|
||||
/// where
|
||||
/// M: Model
|
||||
/// M: Model,
|
||||
/// T: Clone + Send + 'static,
|
||||
/// R: Send + 'static,
|
||||
/// ```
|
||||
pub trait InputFn<'a, M: Model, T, S>: Send + 'static {
|
||||
/// The `Future` returned by the asynchronous method.
|
||||
@ -121,22 +116,16 @@ where
|
||||
/// A function, method or closure that can be used as a *replier port*.
|
||||
///
|
||||
/// This trait is in particular implemented for any function or method with the
|
||||
/// following signature, where it is implicitly assumed that the function
|
||||
/// implements `Send + 'static`:
|
||||
/// following signature, where the returned futures must implement `Send`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn(&mut M) -> R // argument elided, implies `T=()`
|
||||
/// async fn(&mut M, T) -> R
|
||||
/// async fn(&mut M, T, &mut Context<M>) -> R
|
||||
/// where
|
||||
/// M: Model
|
||||
/// ```
|
||||
///
|
||||
/// It is also implemented for the following signatures when `T=()`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn(&mut M) -> R
|
||||
/// where
|
||||
/// M: Model
|
||||
/// M: Model,
|
||||
/// T: Clone + Send + 'static,
|
||||
/// R: Send + 'static,
|
||||
/// ```
|
||||
pub trait ReplierFn<'a, M: Model, T, R, S>: Send + 'static {
|
||||
/// The `Future` returned by the asynchronous method.
|
||||
|
@ -22,7 +22,7 @@ pub trait EventSinkWriter<T>: Clone + Send + Sync + 'static {
|
||||
/// An iterator over collected events with the ability to pause and resume event
|
||||
/// collection.
|
||||
///
|
||||
/// An `EventSinkStream` will typically be implemented on an `EventSink` for
|
||||
/// An `EventSinkStream` will typically be implemented on an [`EventSink`] for
|
||||
/// which it will constitute a draining iterator.
|
||||
pub trait EventSinkStream: Iterator {
|
||||
/// Starts or resumes the collection of new events.
|
||||
|
@ -12,7 +12,8 @@ struct Inner<T> {
|
||||
buffer: Mutex<VecDeque<T>>,
|
||||
}
|
||||
|
||||
/// An [`EventSink`] and [`EventSinkStream`] with a bounded size.
|
||||
/// An iterator implementing [`EventSink`] and [`EventSinkStream`], backed by a
|
||||
/// fixed-capacity buffer.
|
||||
///
|
||||
/// If the maximum capacity is exceeded, older events are overwritten. Events
|
||||
/// are returned in first-in-first-out order. Note that even if the iterator
|
||||
|
@ -10,7 +10,8 @@ struct Inner<T> {
|
||||
slot: Mutex<Option<T>>,
|
||||
}
|
||||
|
||||
/// An [`EventSink`] and [`EventSinkStream`] that only keeps the last event.
|
||||
/// An iterator implementing [`EventSink`] and [`EventSinkStream`] that only
|
||||
/// keeps the last event.
|
||||
///
|
||||
/// Once the value is read, the iterator will return `None` until a new value is
|
||||
/// received. If the slot contains a value when a new value is received, the
|
||||
|
@ -188,13 +188,13 @@ impl<T: Clone + Send + 'static> fmt::Debug for EventSource<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A request source port.
|
||||
/// A query source port.
|
||||
///
|
||||
/// The `QuerySource` port is similar to an
|
||||
/// [`Requestor`](crate::ports::Requestor) port in that it can send events to
|
||||
/// connected input ports. It is not meant, however, to be instantiated as a
|
||||
/// member of a model, but rather as a simulation monitoring endpoint
|
||||
/// instantiated during bench assembly.
|
||||
/// [`Requestor`](crate::ports::Requestor) port in that it can send requests to
|
||||
/// connected replier ports and receive replies. It is not meant, however, to be
|
||||
/// instantiated as a member of a model, but rather as a simulation monitoring
|
||||
/// endpoint instantiated during bench assembly.
|
||||
pub struct QuerySource<T: Clone + Send + 'static, R: Send + 'static> {
|
||||
broadcaster: QueryBroadcaster<T, R>,
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
//! Registry for sinks and sources.
|
||||
//!
|
||||
//! This module provides the `EndpointRegistry` object which associates each
|
||||
//! event sink, event source and query source to a unique name.
|
||||
//! event sink, event source and query source in a simulation bench to a unique
|
||||
//! name.
|
||||
|
||||
mod event_sink_registry;
|
||||
mod event_source_registry;
|
||||
@ -15,8 +16,7 @@ pub(crate) use event_sink_registry::EventSinkRegistry;
|
||||
pub(crate) use event_source_registry::EventSourceRegistry;
|
||||
pub(crate) use query_source_registry::QuerySourceRegistry;
|
||||
|
||||
/// A registry that holds all sources and sinks meant to be accessed through
|
||||
/// bindings or remote procedure calls.
|
||||
/// A registry that holds the sources and sinks of a simulation bench.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct EndpointRegistry {
|
||||
pub(crate) event_sink_registry: EventSinkRegistry,
|
||||
|
@ -50,7 +50,7 @@ impl fmt::Debug for EventSinkRegistry {
|
||||
/// A type-erased `EventSinkStream`.
|
||||
pub(crate) trait EventSinkStreamAny: Send + 'static {
|
||||
/// Human-readable name of the event type, as returned by
|
||||
/// `any::type_name()`.
|
||||
/// `any::type_name`.
|
||||
fn event_type_name(&self) -> &'static str;
|
||||
|
||||
/// Starts or resumes the collection of new events.
|
||||
|
@ -92,7 +92,7 @@ pub(crate) trait EventSourceAny: Send + Sync + 'static {
|
||||
) -> Result<(Action, ActionKey), DeserializationError>;
|
||||
|
||||
/// Human-readable name of the event type, as returned by
|
||||
/// `any::type_name()`.
|
||||
/// `any::type_name`.
|
||||
fn event_type_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
|
@ -68,11 +68,11 @@ pub(crate) trait QuerySourceAny: Send + Sync + 'static {
|
||||
) -> Result<(Action, Box<dyn ReplyReceiverAny>), DeserializationError>;
|
||||
|
||||
/// Human-readable name of the request type, as returned by
|
||||
/// `any::type_name()`.
|
||||
/// `any::type_name`.
|
||||
fn request_type_name(&self) -> &'static str;
|
||||
|
||||
/// Human-readable name of the reply type, as returned by
|
||||
/// `any::type_name()`.
|
||||
/// `any::type_name`.
|
||||
fn reply_type_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl ControllerService {
|
||||
/// that event as well as all other events scheduled for the same time.
|
||||
///
|
||||
/// Processing is gated by a (possibly blocking) call to
|
||||
/// [`Clock::synchronize()`](crate::time::Clock::synchronize) on the
|
||||
/// [`Clock::synchronize`](crate::time::Clock::synchronize) on the
|
||||
/// configured simulation clock. This method blocks until all newly
|
||||
/// processed events have completed.
|
||||
pub(crate) fn step(&mut self, _request: StepRequest) -> StepReply {
|
||||
@ -59,7 +59,7 @@ impl ControllerService {
|
||||
|
||||
/// Iteratively advances the simulation time until the specified deadline,
|
||||
/// as if by calling
|
||||
/// [`Simulation::step()`](crate::simulation::Simulation::step) repeatedly.
|
||||
/// [`Simulation::step`](crate::simulation::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
|
||||
|
@ -13,10 +13,10 @@
|
||||
//! 2. connection of the models' output/requestor ports to input/replier ports
|
||||
//! using the [`Address`]es of the target models,
|
||||
//! 3. instantiation of a [`SimInit`] simulation builder and migration of all
|
||||
//! models and mailboxes to the builder with [`SimInit::add_model()`],
|
||||
//! 4. initialization of a [`Simulation`] instance with [`SimInit::init()`],
|
||||
//! models and mailboxes to the builder with [`SimInit::add_model`],
|
||||
//! 4. initialization of a [`Simulation`] instance with [`SimInit::init`],
|
||||
//! possibly preceded by the setup of a custom clock with
|
||||
//! [`SimInit::set_clock()`],
|
||||
//! [`SimInit::set_clock`],
|
||||
//! 5. discrete-time simulation, which typically involves scheduling events and
|
||||
//! incrementing simulation time while observing the models outputs.
|
||||
//!
|
||||
@ -42,8 +42,8 @@
|
||||
//!
|
||||
//! The default capacity should prove a reasonable trade-off in most cases, but
|
||||
//! for situations where it is not appropriate, it is possible to instantiate
|
||||
//! mailboxes with a custom capacity by using [`Mailbox::with_capacity()`]
|
||||
//! instead of [`Mailbox::new()`].
|
||||
//! mailboxes with a custom capacity by using [`Mailbox::with_capacity`] instead
|
||||
//! of [`Mailbox::new`].
|
||||
//!
|
||||
//! ## Avoiding deadlocks
|
||||
//!
|
||||
@ -68,14 +68,13 @@
|
||||
//!
|
||||
//! The second scenario is rare in well-behaving models and if it occurs, it is
|
||||
//! most typically at the very beginning of a simulation when models
|
||||
//! simultaneously and mutually send events during the call to
|
||||
//! [`Model::init()`](crate::model::Model::init). If such a large amount of
|
||||
//! events is deemed normal behavior, the issue can be remedied by increasing
|
||||
//! the capacity of the saturated mailboxes.
|
||||
//! simultaneously and mutually send events during the call to [`Model::init`].
|
||||
//! If such a large amount of events is deemed normal behavior, the issue can be
|
||||
//! remedied by increasing the capacity of the saturated mailboxes.
|
||||
//!
|
||||
//! Any deadlocks will be reported as an [`ExecutionError::Deadlock`] error,
|
||||
//! which identifies all involved models and the amount of unprocessed messages
|
||||
//! (events or requests) in their mailboxes.
|
||||
//! Deadlocks are reported as [`ExecutionError::Deadlock`] errors, which
|
||||
//! identify all involved models and the count of unprocessed messages (events
|
||||
//! or requests) in their mailboxes.
|
||||
mod mailbox;
|
||||
mod scheduler;
|
||||
mod sim_init;
|
||||
@ -118,7 +117,7 @@ thread_local! { pub(crate) static CURRENT_MODEL_ID: Cell<ModelId> = const { Cell
|
||||
/// Simulation environment.
|
||||
///
|
||||
/// A `Simulation` is created by calling
|
||||
/// [`SimInit::init()`](crate::simulation::SimInit::init) on a simulation
|
||||
/// [`SimInit::init`](crate::simulation::SimInit::init) on a simulation
|
||||
/// initializer. It contains an asynchronous executor that runs all simulation
|
||||
/// models added beforehand to [`SimInit`].
|
||||
///
|
||||
@ -126,31 +125,31 @@ thread_local! { pub(crate) static CURRENT_MODEL_ID: Cell<ModelId> = const { Cell
|
||||
/// simulation time. The scheduling queue can be accessed from the simulation
|
||||
/// itself, but also from models via the optional [`&mut
|
||||
/// Context`](crate::model::Context) argument of input and replier port methods.
|
||||
/// Likewise, simulation time can be accessed with the [`Simulation::time()`]
|
||||
/// Likewise, simulation time can be accessed with the [`Simulation::time`]
|
||||
/// method, or from models with the
|
||||
/// [`Context::time()`](crate::simulation::Context::time) method.
|
||||
/// [`Context::time`](crate::simulation::Context::time) method.
|
||||
///
|
||||
/// Events and queries can be scheduled immediately, *i.e.* for the current
|
||||
/// simulation time, using [`process_event()`](Simulation::process_event) and
|
||||
/// [`send_query()`](Simulation::process_query). Calling these methods will
|
||||
/// block until all computations triggered by such event or query have
|
||||
/// completed. In the case of queries, the response is returned.
|
||||
/// simulation time, using [`process_event`](Simulation::process_event) and
|
||||
/// [`send_query`](Simulation::process_query). Calling these methods will block
|
||||
/// until all computations triggered by such event or query have completed. In
|
||||
/// the case of queries, the response is returned.
|
||||
///
|
||||
/// Events can also be scheduled at a future simulation time using one of the
|
||||
/// [`schedule_*()`](Scheduler::schedule_event) method. These methods queue an
|
||||
/// [`schedule_*`](Scheduler::schedule_event) method. These methods queue an
|
||||
/// event without blocking.
|
||||
///
|
||||
/// Finally, the [`Simulation`] instance manages simulation time. A call to
|
||||
/// [`step()`](Simulation::step) will:
|
||||
/// [`step`](Simulation::step) will:
|
||||
///
|
||||
/// 1. increment simulation time until that of the next scheduled event in
|
||||
/// chronological order, then
|
||||
/// 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
|
||||
/// 2. call [`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.
|
||||
///
|
||||
/// The [`step_until()`](Simulation::step_until) method operates similarly but
|
||||
/// The [`step_until`](Simulation::step_until) method operates similarly but
|
||||
/// iterates until the target simulation time has been reached.
|
||||
pub struct Simulation {
|
||||
executor: Executor,
|
||||
@ -216,15 +215,14 @@ impl Simulation {
|
||||
/// that event as well as all other events scheduled for the same time.
|
||||
///
|
||||
/// Processing is gated by a (possibly blocking) call to
|
||||
/// [`Clock::synchronize()`](crate::time::Clock::synchronize) on the configured
|
||||
/// simulation clock. This method blocks until all newly processed events
|
||||
/// have completed.
|
||||
/// [`Clock::synchronize`] on the configured simulation clock. This method
|
||||
/// blocks until all newly processed events have completed.
|
||||
pub fn step(&mut self) -> Result<(), ExecutionError> {
|
||||
self.step_to_next(None).map(|_| ())
|
||||
}
|
||||
|
||||
/// Iteratively advances the simulation time until the specified deadline,
|
||||
/// as if by calling [`Simulation::step()`] repeatedly.
|
||||
/// 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
|
||||
@ -240,7 +238,7 @@ impl Simulation {
|
||||
}
|
||||
|
||||
/// Iteratively advances the simulation time, as if by calling
|
||||
/// [`Simulation::step()`] repeatedly.
|
||||
/// [`Simulation::step`] repeatedly.
|
||||
///
|
||||
/// This method blocks until all events scheduled have completed.
|
||||
pub fn step_unbounded(&mut self) -> Result<(), ExecutionError> {
|
||||
|
@ -8,7 +8,7 @@ use crate::model::Model;
|
||||
/// A mailbox is an entity associated to a model instance that collects all
|
||||
/// messages sent to that model. The size of its internal buffer can be
|
||||
/// optionally specified at construction time using
|
||||
/// [`with_capacity()`](Mailbox::with_capacity).
|
||||
/// [`with_capacity`](Mailbox::with_capacity).
|
||||
pub struct Mailbox<M: Model>(pub(crate) Receiver<M>);
|
||||
|
||||
impl<M: Model> Mailbox<M> {
|
||||
@ -58,7 +58,7 @@ impl<M: Model> fmt::Debug for Mailbox<M> {
|
||||
/// For the sake of convenience, methods that require an address by value will
|
||||
/// typically also accept an `&Address` or an `&Mailbox` since these references
|
||||
/// implement the `Into<Address>` trait, automatically invoking
|
||||
/// `Address::clone()` or `Mailbox::address()` as appropriate.
|
||||
/// `Address::clone` or `Mailbox::address` as appropriate.
|
||||
pub struct Address<M: Model>(pub(crate) Sender<M>);
|
||||
|
||||
impl<M: Model> Clone for Address<M> {
|
||||
@ -80,8 +80,7 @@ impl<M: Model> From<&Address<M>> for Address<M> {
|
||||
impl<M: Model> From<&Mailbox<M>> for Address<M> {
|
||||
/// Converts a [Mailbox] reference into an [`Address`].
|
||||
///
|
||||
/// This calls [`Mailbox::address()`] on the mailbox and returns the
|
||||
/// address.
|
||||
/// This calls [`Mailbox::address`] on the mailbox and returns the address.
|
||||
#[inline]
|
||||
fn from(s: &Mailbox<M>) -> Address<M> {
|
||||
s.address()
|
||||
|
@ -25,7 +25,9 @@ use crate::{time::TearableAtomicTime, util::sync_cell::SyncCell};
|
||||
|
||||
const GLOBAL_SCHEDULER_ORIGIN_ID: usize = 0;
|
||||
|
||||
/// A global scheduler.
|
||||
/// A global simulation scheduler.
|
||||
///
|
||||
/// A `Scheduler` can be `Clone`d and sent to other threads.
|
||||
#[derive(Clone)]
|
||||
pub struct Scheduler(GlobalScheduler);
|
||||
|
||||
@ -40,9 +42,6 @@ impl Scheduler {
|
||||
|
||||
/// Returns the current simulation time.
|
||||
///
|
||||
/// Beware that, if the scheduler runs in a separate thread as the
|
||||
/// simulation, the time may change concurrently.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
@ -200,9 +199,9 @@ impl fmt::Debug for Scheduler {
|
||||
/// Managed handle to a scheduled action.
|
||||
///
|
||||
/// An `AutoActionKey` is a managed handle to a scheduled action that cancels
|
||||
/// action on drop.
|
||||
/// its associated action on drop.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "managed action key shall be used"]
|
||||
#[must_use = "dropping this key immediately cancels the associated action"]
|
||||
pub struct AutoActionKey {
|
||||
is_cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
@ -295,6 +294,12 @@ impl Error for SchedulingError {}
|
||||
|
||||
/// A possibly periodic, possibly cancellable action that can be scheduled or
|
||||
/// processed immediately.
|
||||
///
|
||||
/// `Actions` can be created from an [`EventSource`](crate::ports::EventSource)
|
||||
/// or [`QuerySource`](crate::ports::QuerySource). They can be used to schedule
|
||||
/// events and requests with [`Scheduler::schedule`], or to process events and
|
||||
/// requests immediately with
|
||||
/// [`Simulation::process`](crate::simulation::Simulation::process).
|
||||
pub struct Action {
|
||||
inner: Box<dyn ActionInner>,
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ impl SimInit {
|
||||
}
|
||||
|
||||
/// Builds a simulation initialized at the specified simulation time,
|
||||
/// executing the [`Model::init()`](crate::model::Model::init) method on all
|
||||
/// executing the [`Model::init`](crate::model::Model::init) method on all
|
||||
/// model initializers.
|
||||
///
|
||||
/// The simulation object and its associated scheduler are returned upon
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Simulation time and scheduling.
|
||||
//! Simulation time and clocks.
|
||||
//!
|
||||
//! This module provides most notably:
|
||||
//!
|
||||
|
@ -10,7 +10,7 @@ use crate::time::MonotonicTime;
|
||||
/// as-fast-as-possible and real-time clocks.
|
||||
///
|
||||
/// A clock can be associated to a simulation prior to initialization by calling
|
||||
/// [`SimInit::set_clock()`](crate::simulation::SimInit::set_clock).
|
||||
/// [`SimInit::set_clock`](crate::simulation::SimInit::set_clock).
|
||||
pub trait Clock: Send {
|
||||
/// Blocks until the deadline.
|
||||
fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus;
|
||||
@ -107,7 +107,7 @@ impl SystemClock {
|
||||
/// The provided reference time may lie in the past or in the future.
|
||||
///
|
||||
/// Note that, even though the wall clock reference is specified with the
|
||||
/// (non-monotonic) system clock, the [`synchronize()`](Clock::synchronize)
|
||||
/// (non-monotonic) system clock, the [`synchronize`](Clock::synchronize)
|
||||
/// method will still use the system's _monotonic_ clock. This constructor
|
||||
/// makes a best-effort attempt at synchronizing the monotonic clock with
|
||||
/// the non-monotonic system clock _at construction time_, but this
|
||||
@ -164,8 +164,8 @@ impl Clock for SystemClock {
|
||||
/// monotonic clock.
|
||||
///
|
||||
/// This clock is similar to [`SystemClock`] except that the first call to
|
||||
/// [`synchronize()`](Clock::synchronize) never blocks and implicitly defines
|
||||
/// the reference time. In other words, the clock starts running on its first
|
||||
/// [`synchronize`](Clock::synchronize) never blocks and implicitly defines the
|
||||
/// reference time. In other words, the clock starts running on its first
|
||||
/// invocation.
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct AutoSystemClock {
|
||||
|
@ -69,7 +69,7 @@
|
||||
//! 2024-09-10T14:39:24.670921Z INFO my_simulation: something happened outside the simulation
|
||||
//! ```
|
||||
//!
|
||||
//! Alternatively, `SimulationTime::with_system_timer_always()` can be used to
|
||||
//! Alternatively, `SimulationTime::with_system_timer_always` can be used to
|
||||
//! always prepend the system time even for simulation events:
|
||||
//!
|
||||
//! ```text
|
||||
|
Loading…
x
Reference in New Issue
Block a user