1
0
forked from ROMEO/nexosim

Improve documentation, fix README example

This commit is contained in:
Serge Barral 2025-01-21 02:11:23 +01:00
parent a71231f398
commit fa8b5cf034
25 changed files with 505 additions and 335 deletions

View File

@ -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

View File

@ -29,7 +29,6 @@ keywords = [
]
[features]
# gRPC service.
server = [
"dep:bytes",
"dep:ciborium",

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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>,
//! For a more comprehensive example demonstrating hierarchical model
//! assemblies, see the [`assembly`][assembly] example.
//!
//! [assembly]:
//! https://github.com/asynchronics/nexosim/tree/main/nexosim/examples/assembly.rs
//!
//! ```
//! use nexosim::model::{BuildContext, Model, ProtoModel};
//! use nexosim::ports::Output;
//! use nexosim::simulation::Mailbox;
//!
//! pub struct ParentModel {
//! // Private internal port connected to the submodel.
//! to_child: Output<u64>,
//! }
//! impl MyModel {
//! // ...
//! }
//! impl Model for MyModel {}
//! ```
//!
//!
//! ### 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
//!
//! ```
//! 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 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;
}

View File

@ -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>,
//! pub struct ParentModel {
//! 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;
//!
//! impl ChildModel {
//! pub fn new(output: Output<u64>) -> Self {
//! Self {
//! output,
//! }
//! // 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 ChildModel {}
//!
//! pub struct ParentModel {
//! output: Output<u64>,
//! }
//!
//! 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;

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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>,
}

View File

@ -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,

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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> {

View File

@ -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()

View File

@ -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>,
}

View File

@ -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

View File

@ -1,4 +1,4 @@
//! Simulation time and scheduling.
//! Simulation time and clocks.
//!
//! This module provides most notably:
//!

View File

@ -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 {

View File

@ -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