forked from ROMEO/nexosim
Introduce ProtoModel trait, remove Model::setup
The external_input example has been as well adapted and (at least temporarily) simplifiedi/modified to remove the dependencies on `atomic_wait` and `mio`.
This commit is contained in:
parent
8f7057689c
commit
039fefad47
@ -58,10 +58,8 @@ tracing = { version= "0.1.40", default-features = false, features=["std"], optio
|
|||||||
tracing-subscriber = { version= "0.3.18", optional = true }
|
tracing-subscriber = { version= "0.3.18", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
atomic-wait = "1.1"
|
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
mio = { version = "1.0", features = ["os-poll", "net"] }
|
|
||||||
tracing-subscriber = { version= "0.3.18", features=["env-filter"] }
|
tracing-subscriber = { version= "0.3.18", features=["env-filter"] }
|
||||||
|
|
||||||
[target.'cfg(asynchronix_loom)'.dev-dependencies]
|
[target.'cfg(asynchronix_loom)'.dev-dependencies]
|
||||||
|
@ -3,27 +3,29 @@
|
|||||||
//!
|
//!
|
||||||
//! This example demonstrates in particular:
|
//! This example demonstrates in particular:
|
||||||
//!
|
//!
|
||||||
|
//! * model prototypes,
|
||||||
//! * submodels,
|
//! * submodels,
|
||||||
//! * outputs cloning,
|
|
||||||
//! * self-scheduling methods,
|
//! * self-scheduling methods,
|
||||||
//! * model setup,
|
|
||||||
//! * model initialization,
|
//! * model initialization,
|
||||||
//! * simulation monitoring with event streams.
|
//! * simulation monitoring with buffered event sinks.
|
||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! ┌──────────────────────────────────────────────┐
|
//! ┌────────────────────────────────────────────┐
|
||||||
//! │ Assembly │
|
//! │ Assembly │
|
||||||
//! │ ┌──────────┐ ┌──────────┐ │
|
//! │ ┌──────────┐ │
|
||||||
//! PPS │ │ │ coil currents │ │ │position
|
//! PPS │ │ │ coil currents ┌─────────┐ │
|
||||||
//! Pulse rate ●───────►│──►│ Driver ├───────────────►│ Motor ├──►│─────────►
|
//! Pulse rate ●──────────┼──►│ Driver ├───────────────►│ │ │
|
||||||
//! (±freq)│ │ │ (IA, IB) │ │ │(0:199)
|
//! (±freq) │ │ │ (IA, IB) │ │ │ position
|
||||||
//! │ └──────────┘ └──────────┘ │
|
//! │ └──────────┘ │ Motor ├──┼──────────►
|
||||||
//! └──────────────────────────────────────────────┘
|
//! torque │ │ │ │ (0:199)
|
||||||
|
//! Load ●──────────┼──────────────────────────────►│ │ │
|
||||||
|
//! │ └─────────┘ │
|
||||||
|
//! └────────────────────────────────────────────┘
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use asynchronix::model::{Model, SetupContext};
|
use asynchronix::model::{BuildContext, Model, ProtoModel};
|
||||||
use asynchronix::ports::{EventBuffer, Output};
|
use asynchronix::ports::{EventBuffer, Output};
|
||||||
use asynchronix::simulation::{Mailbox, SimInit, SimulationError};
|
use asynchronix::simulation::{Mailbox, SimInit, SimulationError};
|
||||||
use asynchronix::time::MonotonicTime;
|
use asynchronix::time::MonotonicTime;
|
||||||
@ -32,36 +34,59 @@ mod stepper_motor;
|
|||||||
|
|
||||||
pub use stepper_motor::{Driver, Motor};
|
pub use stepper_motor::{Driver, Motor};
|
||||||
|
|
||||||
pub struct MotorAssembly {
|
/// A prototype for `MotorAssembly`.
|
||||||
|
pub struct ProtoMotorAssembly {
|
||||||
pub position: Output<u16>,
|
pub position: Output<u16>,
|
||||||
init_pos: u16,
|
init_pos: u16,
|
||||||
load: Output<f64>,
|
|
||||||
pps: Output<f64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MotorAssembly {
|
impl ProtoMotorAssembly {
|
||||||
|
/// The prototype has a public constructor.
|
||||||
pub fn new(init_pos: u16) -> Self {
|
pub fn new(init_pos: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position: Default::default(),
|
position: Default::default(),
|
||||||
init_pos,
|
init_pos,
|
||||||
load: Default::default(),
|
|
||||||
pps: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the pulse rate (sign = direction) [Hz] -- input port.
|
// Input methods are in the model itself.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The parent model which submodels are the driver and the motor.
|
||||||
|
pub struct MotorAssembly {
|
||||||
|
/// Private output for submodel connection.
|
||||||
|
pps: Output<f64>,
|
||||||
|
/// Private output for submodel connection.
|
||||||
|
load: Output<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MotorAssembly {
|
||||||
|
/// The model now has a module-private constructor.
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pps: Default::default(),
|
||||||
|
load: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pulse rate (sign = direction) [Hz] -- input port.
|
||||||
pub async fn pulse_rate(&mut self, pps: f64) {
|
pub async fn pulse_rate(&mut self, pps: f64) {
|
||||||
self.pps.send(pps).await;
|
self.pps.send(pps).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Torque applied by the load [N·m] -- input port.
|
/// Torque applied by the load [N·m] -- input port.
|
||||||
pub async fn load(&mut self, torque: f64) {
|
pub async fn load(&mut self, torque: f64) {
|
||||||
self.load.send(torque).await;
|
self.load.send(torque).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model for MotorAssembly {
|
impl Model for MotorAssembly {}
|
||||||
fn setup(&mut self, setup_context: &SetupContext<Self>) {
|
|
||||||
|
impl ProtoModel for ProtoMotorAssembly {
|
||||||
|
type Model = MotorAssembly;
|
||||||
|
|
||||||
|
fn build(self, ctx: &BuildContext<Self>) -> MotorAssembly {
|
||||||
|
let mut assembly = MotorAssembly::new();
|
||||||
let mut motor = Motor::new(self.init_pos);
|
let mut motor = Motor::new(self.init_pos);
|
||||||
let mut driver = Driver::new(1.0);
|
let mut driver = Driver::new(1.0);
|
||||||
|
|
||||||
@ -70,17 +95,20 @@ impl Model for MotorAssembly {
|
|||||||
let driver_mbox = Mailbox::new();
|
let driver_mbox = Mailbox::new();
|
||||||
|
|
||||||
// Connections.
|
// Connections.
|
||||||
self.pps.connect(Driver::pulse_rate, &driver_mbox);
|
assembly.pps.connect(Driver::pulse_rate, &driver_mbox);
|
||||||
self.load.connect(Motor::load, &motor_mbox);
|
assembly.load.connect(Motor::load, &motor_mbox);
|
||||||
driver.current_out.connect(Motor::current_in, &motor_mbox);
|
driver.current_out.connect(Motor::current_in, &motor_mbox);
|
||||||
// Note: it is important to clone `position` from the parent to the
|
|
||||||
// submodel so that all connections made by the user to the parent model
|
|
||||||
// are preserved. Connections added after cloning are reflected in all
|
|
||||||
// clones.
|
|
||||||
motor.position = self.position.clone();
|
|
||||||
|
|
||||||
setup_context.add_model(driver, driver_mbox, "driver");
|
// Move the prototype's output to the submodel. The `self.position`
|
||||||
setup_context.add_model(motor, motor_mbox, "motor");
|
// output can be cloned if necessary if several submodels need access to
|
||||||
|
// it.
|
||||||
|
motor.position = self.position;
|
||||||
|
|
||||||
|
// Add the submodels to the simulation.
|
||||||
|
ctx.add_submodel(driver, driver_mbox, "driver");
|
||||||
|
ctx.add_submodel(motor, motor_mbox, "motor");
|
||||||
|
|
||||||
|
assembly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +119,7 @@ fn main() -> Result<(), SimulationError> {
|
|||||||
|
|
||||||
// Models.
|
// Models.
|
||||||
let init_pos = 123;
|
let init_pos = 123;
|
||||||
let mut assembly = MotorAssembly::new(init_pos);
|
let mut assembly = ProtoMotorAssembly::new(init_pos);
|
||||||
|
|
||||||
// Mailboxes.
|
// Mailboxes.
|
||||||
let assembly_mbox = Mailbox::new();
|
let assembly_mbox = Mailbox::new();
|
||||||
|
@ -1,80 +1,97 @@
|
|||||||
//! Example: a model that reads data from the external world.
|
//! Example: a model that reads data external to the simulation.
|
||||||
//!
|
//!
|
||||||
//! This example demonstrates in particular:
|
//! This example demonstrates in particular:
|
||||||
//!
|
//!
|
||||||
//! * external world inputs (useful in cosimulation),
|
//! * processing of external inputs (useful in co-simulation),
|
||||||
//! * system clock,
|
//! * system clock,
|
||||||
//! * periodic scheduling.
|
//! * periodic scheduling.
|
||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! ┌────────────────────────────────┐
|
//! ┏━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
//! │ Simulation │
|
//! ┃ Simulation ┃
|
||||||
//! ┌────────────┐ ┌────────────┐ │ ┌──────────┐ │
|
//! ┌╌╌╌╌╌╌╌╌╌╌╌╌┐ ┌╌╌╌╌╌╌╌╌╌╌╌╌┐ ┃ ┌──────────┐ ┃
|
||||||
//! │ │ UDP │ │ message │ message │ │ message │ ┌─────────────┐
|
//! ┆ ┆ message ┆ ┆ message ┃ │ │ message ┃
|
||||||
//! │ UDP Client ├─────────►│ UDP Server ├──────────►├─────────►│ Listener ├─────────►├──►│ EventBuffer │
|
//! ┆ UDP Client ├╌╌╌╌╌╌╌╌►┆ UDP Server ├╌╌╌╌╌╌╌╌╌╌╌╂╌╌►│ Listener ├─────────╂─►
|
||||||
//! │ │ message │ │ │ │ │ │ └─────────────┘
|
//! ┆ ┆ [UDP] ┆ ┆ [channel] ┃ │ │ ┃
|
||||||
//! └────────────┘ └────────────┘ │ └──────────┘ │
|
//! └╌╌╌╌╌╌╌╌╌╌╌╌┘ └╌╌╌╌╌╌╌╌╌╌╌╌┘ ┃ └──────────┘ ┃
|
||||||
//! └────────────────────────────────┘
|
//! ┗━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::net::UdpSocket;
|
use std::net::{Ipv4Addr, UdpSocket};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
use std::thread::{self, sleep, JoinHandle};
|
use std::thread::{self, sleep, JoinHandle};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use atomic_wait::{wait, wake_one};
|
use asynchronix::model::{BuildContext, Context, InitializedModel, Model, ProtoModel};
|
||||||
|
|
||||||
use mio::net::UdpSocket as MioUdpSocket;
|
|
||||||
use mio::{Events, Interest, Poll, Token};
|
|
||||||
|
|
||||||
use asynchronix::model::{Context, InitializedModel, Model, SetupContext};
|
|
||||||
use asynchronix::ports::{EventBuffer, Output};
|
use asynchronix::ports::{EventBuffer, Output};
|
||||||
use asynchronix::simulation::{Mailbox, SimInit, SimulationError};
|
use asynchronix::simulation::{Mailbox, SimInit, SimulationError};
|
||||||
use asynchronix::time::{AutoSystemClock, MonotonicTime};
|
use asynchronix::time::{AutoSystemClock, MonotonicTime};
|
||||||
|
|
||||||
const DELTA: Duration = Duration::from_millis(2);
|
const DELTA: Duration = Duration::from_millis(2);
|
||||||
const PERIOD: Duration = Duration::from_millis(20);
|
const PERIOD: Duration = Duration::from_millis(20);
|
||||||
const N: u32 = 10;
|
const N: usize = 10;
|
||||||
const SENDER: &str = "127.0.0.1:8000";
|
const SHUTDOWN_SIGNAL: &str = "<SHUTDOWN>";
|
||||||
const RECEIVER: &str = "127.0.0.1:9000";
|
const SENDER: (Ipv4Addr, u16) = (Ipv4Addr::new(127, 0, 0, 1), 8000);
|
||||||
|
const RECEIVER: (Ipv4Addr, u16) = (Ipv4Addr::new(127, 0, 0, 1), 9000);
|
||||||
|
|
||||||
/// Model that receives external input.
|
/// Prototype for the `Listener` Model.
|
||||||
pub struct Listener {
|
pub struct ProtoListener {
|
||||||
/// Received message.
|
/// Received message.
|
||||||
pub message: Output<String>,
|
pub message: Output<String>,
|
||||||
|
|
||||||
|
/// Notifier to start the UDP client.
|
||||||
|
start: Notifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtoListener {
|
||||||
|
fn new(start: Notifier) -> Self {
|
||||||
|
Self {
|
||||||
|
message: Output::default(),
|
||||||
|
start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtoModel for ProtoListener {
|
||||||
|
type Model = Listener;
|
||||||
|
|
||||||
|
/// Start the UDP Server immediately upon model construction.
|
||||||
|
fn build(self, _: &BuildContext<Self>) -> Listener {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let external_handle = thread::spawn(move || {
|
||||||
|
Listener::listen(tx, self.start);
|
||||||
|
});
|
||||||
|
|
||||||
|
Listener::new(self.message, rx, external_handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Model that asynchronously receives messages external to the simulation.
|
||||||
|
pub struct Listener {
|
||||||
|
/// Received message.
|
||||||
|
message: Output<String>,
|
||||||
|
|
||||||
/// Receiver of external messages.
|
/// Receiver of external messages.
|
||||||
rx: Receiver<String>,
|
rx: Receiver<String>,
|
||||||
|
|
||||||
/// External sender.
|
|
||||||
tx: Option<Sender<String>>,
|
|
||||||
|
|
||||||
/// Synchronization with client.
|
|
||||||
start: Arc<AtomicU32>,
|
|
||||||
|
|
||||||
/// Synchronization with simulation.
|
|
||||||
stop: Arc<AtomicBool>,
|
|
||||||
|
|
||||||
/// Handle to UDP Server.
|
/// Handle to UDP Server.
|
||||||
external_handle: Option<JoinHandle<()>>,
|
server_handle: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Listener {
|
impl Listener {
|
||||||
/// Creates a Listener.
|
/// Creates a Listener.
|
||||||
pub fn new(start: Arc<AtomicU32>) -> Self {
|
pub fn new(
|
||||||
start.store(0, Ordering::Relaxed);
|
message: Output<String>,
|
||||||
|
rx: Receiver<String>,
|
||||||
let (tx, rx) = channel();
|
server_handle: JoinHandle<()>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
message: Output::default(),
|
message,
|
||||||
rx,
|
rx,
|
||||||
tx: Some(tx),
|
server_handle: Some(server_handle),
|
||||||
start,
|
|
||||||
stop: Arc::new(AtomicBool::new(false)),
|
|
||||||
external_handle: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,82 +102,39 @@ impl Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UDP server.
|
/// Starts the UDP server.
|
||||||
///
|
fn listen(tx: Sender<String>, start: Notifier) {
|
||||||
/// Code is based on the MIO UDP example.
|
let socket = UdpSocket::bind(RECEIVER).unwrap();
|
||||||
fn listener(tx: Sender<String>, start: Arc<AtomicU32>, stop: Arc<AtomicBool>) {
|
|
||||||
const UDP_SOCKET: Token = Token(0);
|
|
||||||
let mut poll = Poll::new().unwrap();
|
|
||||||
let mut events = Events::with_capacity(10);
|
|
||||||
let mut socket = MioUdpSocket::bind(RECEIVER.parse().unwrap()).unwrap();
|
|
||||||
poll.registry()
|
|
||||||
.register(&mut socket, UDP_SOCKET, Interest::READABLE)
|
|
||||||
.unwrap();
|
|
||||||
let mut buf = [0; 1 << 16];
|
let mut buf = [0; 1 << 16];
|
||||||
|
|
||||||
// Wake up the client.
|
// Wake up the client.
|
||||||
start.store(1, Ordering::Relaxed);
|
start.notify();
|
||||||
wake_one(&*start);
|
|
||||||
|
|
||||||
'process: loop {
|
loop {
|
||||||
// Wait for UDP packet or end of simulation.
|
match socket.recv_from(&mut buf) {
|
||||||
if let Err(err) = poll.poll(&mut events, Some(Duration::from_secs(1))) {
|
Ok((packet_size, _)) => {
|
||||||
if err.kind() == ErrorKind::Interrupted {
|
if let Ok(message) = std::str::from_utf8(&buf[..packet_size]) {
|
||||||
// Exit if simulation is finished.
|
if message == SHUTDOWN_SIGNAL {
|
||||||
if stop.load(Ordering::Relaxed) {
|
break;
|
||||||
break 'process;
|
}
|
||||||
}
|
// Inject external message into simulation.
|
||||||
|
if tx.send(message.into()).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == ErrorKind::Interrupted => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break 'process;
|
_ => {
|
||||||
}
|
break;
|
||||||
|
|
||||||
for event in events.iter() {
|
|
||||||
match event.token() {
|
|
||||||
UDP_SOCKET => loop {
|
|
||||||
match socket.recv_from(&mut buf) {
|
|
||||||
Ok((packet_size, _)) => {
|
|
||||||
if let Ok(message) = std::str::from_utf8(&buf[..packet_size]) {
|
|
||||||
// Inject external message into simulation.
|
|
||||||
if tx.send(message.into()).is_err() {
|
|
||||||
break 'process;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Err(e) if e.kind() == ErrorKind::WouldBlock => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
break 'process;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
panic!("Got event for unexpected token: {:?}", event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Exit if simulation is finished.
|
|
||||||
if stop.load(Ordering::Relaxed) {
|
|
||||||
break 'process;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
poll.registry().deregister(&mut socket).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model for Listener {
|
impl Model for Listener {
|
||||||
/// Start UDP Server on model setup.
|
|
||||||
fn setup(&mut self, _: &SetupContext<Self>) {
|
|
||||||
let tx = self.tx.take().unwrap();
|
|
||||||
let start = Arc::clone(&self.start);
|
|
||||||
let stop = Arc::clone(&self.stop);
|
|
||||||
self.external_handle = Some(thread::spawn(move || {
|
|
||||||
Self::listener(tx, start, stop);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize model.
|
/// Initialize model.
|
||||||
async fn init(self, context: &Context<Self>) -> InitializedModel<Self> {
|
async fn init(self, context: &Context<Self>) -> InitializedModel<Self> {
|
||||||
// Schedule periodic function that processes external events.
|
// Schedule periodic function that processes external events.
|
||||||
@ -174,13 +148,40 @@ impl Model for Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Listener {
|
impl Drop for Listener {
|
||||||
/// Notify UDP Server that simulation is over and wait for server shutdown.
|
/// Wait for UDP Server shutdown.
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.stop.store(true, Ordering::Relaxed);
|
self.server_handle.take().map(|handle| {
|
||||||
let handle = self.external_handle.take();
|
let _ = handle.join();
|
||||||
if let Some(handle) = handle {
|
});
|
||||||
handle.join().unwrap();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A synchronization barrier that can be unblocked by a notifier.
|
||||||
|
struct WaitBarrier(Arc<(Mutex<bool>, Condvar)>);
|
||||||
|
|
||||||
|
impl WaitBarrier {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Arc::new((Mutex::new(false), Condvar::new())))
|
||||||
|
}
|
||||||
|
fn notifier(&self) -> Notifier {
|
||||||
|
Notifier(self.0.clone())
|
||||||
|
}
|
||||||
|
fn wait(self) {
|
||||||
|
let _unused = self
|
||||||
|
.0
|
||||||
|
.1
|
||||||
|
.wait_while(self.0 .0.lock().unwrap(), |pending| *pending)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A notifier for the associated synchronization barrier.
|
||||||
|
struct Notifier(Arc<(Mutex<bool>, Condvar)>);
|
||||||
|
|
||||||
|
impl Notifier {
|
||||||
|
fn notify(self) {
|
||||||
|
*self.0 .0.lock().unwrap() = false;
|
||||||
|
self.0 .1.notify_one();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,16 +192,17 @@ fn main() -> Result<(), SimulationError> {
|
|||||||
|
|
||||||
// Models.
|
// Models.
|
||||||
|
|
||||||
// Client-server synchronization.
|
// Synchronization barrier for the UDP client.
|
||||||
let start = Arc::new(AtomicU32::new(0));
|
let start = WaitBarrier::new();
|
||||||
|
|
||||||
let mut listener = Listener::new(Arc::clone(&start));
|
// Prototype of the listener model.
|
||||||
|
let mut listener = ProtoListener::new(start.notifier());
|
||||||
|
|
||||||
// Mailboxes.
|
// Mailboxes.
|
||||||
let listener_mbox = Mailbox::new();
|
let listener_mbox = Mailbox::new();
|
||||||
|
|
||||||
// Model handles for simulation.
|
// Model handles for simulation.
|
||||||
let mut message = EventBuffer::new();
|
let mut message = EventBuffer::with_capacity(N + 1);
|
||||||
listener.message.connect_sink(&message);
|
listener.message.connect_sink(&message);
|
||||||
|
|
||||||
// Start time (arbitrary since models do not depend on absolute time).
|
// Start time (arbitrary since models do not depend on absolute time).
|
||||||
@ -218,32 +220,39 @@ fn main() -> Result<(), SimulationError> {
|
|||||||
|
|
||||||
// External client that sends UDP messages.
|
// External client that sends UDP messages.
|
||||||
let sender_handle = thread::spawn(move || {
|
let sender_handle = thread::spawn(move || {
|
||||||
// Wait until UDP Server is ready.
|
let socket = UdpSocket::bind(SENDER).unwrap();
|
||||||
wait(&start, 0);
|
|
||||||
|
// Wait until the UDP Server is ready.
|
||||||
|
start.wait();
|
||||||
|
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
let socket = UdpSocket::bind(SENDER).unwrap();
|
|
||||||
socket.send_to(i.to_string().as_bytes(), RECEIVER).unwrap();
|
socket.send_to(i.to_string().as_bytes(), RECEIVER).unwrap();
|
||||||
if i % 3 == 0 {
|
if i % 3 == 0 {
|
||||||
sleep(PERIOD * i)
|
sleep(PERIOD * i as u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socket
|
||||||
});
|
});
|
||||||
|
|
||||||
// Advance simulation, external messages will be collected.
|
// Advance simulation, external messages will be collected.
|
||||||
simu.step_by(Duration::from_secs(2))?;
|
simu.step_by(Duration::from_secs(2))?;
|
||||||
|
|
||||||
|
// Shut down the server.
|
||||||
|
let socket = sender_handle.join().unwrap();
|
||||||
|
socket
|
||||||
|
.send_to(SHUTDOWN_SIGNAL.as_bytes(), RECEIVER)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Check collected external messages.
|
// Check collected external messages.
|
||||||
let mut packets = 0_u32;
|
let mut packets = 0_u32;
|
||||||
for _ in 0..N {
|
for _ in 0..N {
|
||||||
// UDP can reorder packages, we are expecting that on not too loaded
|
// Check all messages accounting for possible UDP packet re-ordering,
|
||||||
// localhost packages would not be dropped
|
// but assuming no packet loss.
|
||||||
packets |= 1 << message.next().unwrap().parse::<u8>().unwrap();
|
packets |= 1 << message.next().unwrap().parse::<u8>().unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(packets, u32::MAX >> 22);
|
assert_eq!(packets, u32::MAX >> 22);
|
||||||
assert_eq!(message.next(), None);
|
assert_eq!(message.next(), None);
|
||||||
|
|
||||||
sender_handle.join().unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,19 @@
|
|||||||
//! ```text
|
//! ```text
|
||||||
//! ┌────────┐
|
//! ┌────────┐
|
||||||
//! │ │
|
//! │ │
|
||||||
//! ┌───►│ Load ├───► Power
|
//! ┌──◄►│ Load ├───► Power
|
||||||
//! │ │ │
|
//! │ │ │
|
||||||
//! │ └────────┘
|
//! │ └────────┘
|
||||||
//! │
|
//! │
|
||||||
//! │ ┌────────┐
|
//! │ ┌────────┐
|
||||||
//! │ │ │
|
//! │ │ │
|
||||||
//! ├───►│ Load ├───► Power
|
//! ├──◄►│ Load ├───► Power
|
||||||
//! │ │ │
|
//! │ │ │
|
||||||
//! │ └────────┘
|
//! │ └────────┘
|
||||||
//! │
|
//! │
|
||||||
//! │ ┌────────┐
|
//! │ ┌────────┐
|
||||||
//! ┌──────────┐ voltage► │ │ │
|
//! ┌──────────┐ voltage► │ │ │
|
||||||
//! Voltage setting ●────►│ │◄────────────┴───►│ Load ├───► Power
|
//! Voltage setting ●────►│ │►◄───────────┴──◄►│ Load ├───► Power
|
||||||
//! │ Power │ ◄current │ │
|
//! │ Power │ ◄current │ │
|
||||||
//! │ supply │ └────────┘
|
//! │ supply │ └────────┘
|
||||||
//! │ ├───────────────────────────────► Total power
|
//! │ ├───────────────────────────────► Total power
|
||||||
|
@ -4,14 +4,17 @@
|
|||||||
//!
|
//!
|
||||||
//! * self-scheduling methods,
|
//! * self-scheduling methods,
|
||||||
//! * model initialization,
|
//! * model initialization,
|
||||||
//! * simulation monitoring with event streams.
|
//! * simulation monitoring with buffered event sinks.
|
||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! ┌──────────┐ ┌──────────┐
|
//! ┌──────────┐
|
||||||
//! PPS │ │ coil currents │ │ position
|
//! PPS │ │ coil currents ┌─────────┐
|
||||||
//! Pulse rate ●─────────►│ Driver ├───────────────►│ Motor ├──────────►
|
//! Pulse rate ●─────────►│ Driver ├───────────────►│ │
|
||||||
//! (±freq) │ │ (IA, IB) │ │ (0:199)
|
//! (±freq) │ │ (IA, IB) │ │ position
|
||||||
//! └──────────┘ └──────────┘
|
//! └──────────┘ │ Motor ├──────────►
|
||||||
|
//! torque │ │ (0:199)
|
||||||
|
//! Load ●─────────────────────────────────────►│ │
|
||||||
|
//! └─────────┘
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@ -136,7 +139,7 @@ impl Driver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the pulse rate (sign = direction) [Hz] -- input port.
|
/// Pulse rate (sign = direction) [Hz] -- input port.
|
||||||
pub async fn pulse_rate(&mut self, pps: f64, context: &Context<Self>) {
|
pub async fn pulse_rate(&mut self, pps: f64, context: &Context<Self>) {
|
||||||
println!(
|
println!(
|
||||||
"Model instance {} at time {}: setting pps: {:.2}",
|
"Model instance {} at time {}: setting pps: {:.2}",
|
||||||
|
@ -1,19 +1,39 @@
|
|||||||
//! Model components.
|
//! Model components.
|
||||||
//!
|
//!
|
||||||
//! # Model trait
|
//! # Models and model prototypes
|
||||||
//!
|
//!
|
||||||
//! Every model must implement the [`Model`] trait. This trait defines
|
//! Every model must implement the [`Model`] trait. This trait defines an
|
||||||
//! * a setup method, [`Model::setup()`], which main purpose is to create,
|
//! asynchronous initialization method, [`Model::init`], which main purpose is
|
||||||
//! connect and add to the simulation bench submodels and perform other setup
|
//! to enable models to perform specific actions when the simulation starts,
|
||||||
//! steps,
|
//! i.e. after all models have been connected and added to the simulation.
|
||||||
//! * an asynchronous initialization method, [`Model::init()`], which main
|
//!
|
||||||
//! purpose is to enable models to perform specific actions only once all
|
//! It is frequently convenient to expose to users a model builder type—called a
|
||||||
//! models have been connected and migrated to the simulation, but before the
|
//! *model prototype*—rather than the final model. This can be done by
|
||||||
//! simulation actually starts.
|
//! implementing the `ProtoModel`, 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.
|
||||||
|
//!
|
||||||
|
//! 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, however, is hierarchical model
|
||||||
|
//! building. When a parent model contains sub-models, these sub-models 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, which upon invocation of `ProtoModel::build()` are moved to the
|
||||||
|
//! appropriate sub-models (note that the `build` method also allows adding
|
||||||
|
//! sub-models 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.
|
||||||
//!
|
//!
|
||||||
//! #### Examples
|
//! #### Examples
|
||||||
//!
|
//!
|
||||||
//! A model that does not require setup and initialization can simply use the
|
//! A model that does not require initialization or building can simply use the
|
||||||
//! default implementation of the `Model` trait:
|
//! default implementation of the `Model` trait:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
@ -25,27 +45,19 @@
|
|||||||
//! impl Model for MyModel {}
|
//! impl Model for MyModel {}
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Otherwise, custom `setup()` or `init()` methods can be implemented:
|
//! If a default action is required during simulation initialization, the `init`
|
||||||
|
//! methods can be explicitly implemented:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use std::future::Future;
|
//! use asynchronix::model::{Context, InitializedModel, Model};
|
||||||
//! use std::pin::Pin;
|
|
||||||
//!
|
|
||||||
//! use asynchronix::model::{Context, InitializedModel, Model, SetupContext};
|
|
||||||
//!
|
//!
|
||||||
//! pub struct MyModel {
|
//! pub struct MyModel {
|
||||||
//! // ...
|
//! // ...
|
||||||
//! }
|
//! }
|
||||||
//! impl Model for MyModel {
|
//! impl Model for MyModel {
|
||||||
//! fn setup(
|
|
||||||
//! &mut self,
|
|
||||||
//! setup_context: &SetupContext<Self>) {
|
|
||||||
//! println!("...setup...");
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! async fn init(
|
//! async fn init(
|
||||||
//! mut self,
|
//! mut self,
|
||||||
//! context: &Context<Self>
|
//! ctx: &Context<Self>
|
||||||
//! ) -> InitializedModel<Self> {
|
//! ) -> InitializedModel<Self> {
|
||||||
//! println!("...initialization...");
|
//! println!("...initialization...");
|
||||||
//!
|
//!
|
||||||
@ -54,6 +66,61 @@
|
|||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Finally, if a model builder is required, the `ProtoModel` trait can be
|
||||||
|
//! explicitly implemented:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use asynchronix::model::{BuildContext, InitializedModel, Model, ProtoModel};
|
||||||
|
//! use asynchronix::ports::Output;
|
||||||
|
//!
|
||||||
|
//! /// The final model.
|
||||||
|
//! pub struct Multiplier {
|
||||||
|
//! // Private outputs and requestors stored in a form that constitutes an
|
||||||
|
//! // implementation detail and should not be exposed to the user.
|
||||||
|
//! my_outputs: Vec<Output<usize>>
|
||||||
|
//! }
|
||||||
|
//! impl Multiplier {
|
||||||
|
//! // Private constructor: the final model is only built by the prototype
|
||||||
|
//! // model.
|
||||||
|
//! fn new(
|
||||||
|
//! value_times_1: Output<usize>,
|
||||||
|
//! value_times_2: Output<usize>,
|
||||||
|
//! value_times_3: Output<usize>,
|
||||||
|
//! ) -> Self {
|
||||||
|
//! Self {
|
||||||
|
//! my_outputs: vec![value_times_1, value_times_2, value_times_3]
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Public inputs and repliers to be used by the user during bench
|
||||||
|
//! // construction.
|
||||||
|
//! pub async fn my_input(&mut self, my_data: usize) {
|
||||||
|
//! for (i, output) in self.my_outputs.iter_mut().enumerate() {
|
||||||
|
//! output.send(my_data*(i + 1)).await;
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! impl Model for Multiplier {}
|
||||||
|
//!
|
||||||
|
//! pub struct ProtoMultiplier {
|
||||||
|
//! // Prettyfied outputs exposed to the user.
|
||||||
|
//! pub value_times_1: Output<usize>,
|
||||||
|
//! pub value_times_2: Output<usize>,
|
||||||
|
//! pub value_times_3: Output<usize>,
|
||||||
|
//! }
|
||||||
|
//! impl ProtoModel for ProtoMultiplier {
|
||||||
|
//! type Model = Multiplier;
|
||||||
|
//!
|
||||||
|
//! fn build(
|
||||||
|
//! mut self,
|
||||||
|
//! _: &BuildContext<Self>
|
||||||
|
//! ) -> Multiplier {
|
||||||
|
//! Multiplier::new(self.value_times_1, self.value_times_2, self.value_times_3)
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//!
|
||||||
//! # Events and queries
|
//! # Events and queries
|
||||||
//!
|
//!
|
||||||
//! Models can exchange data via *events* and *queries*.
|
//! Models can exchange data via *events* and *queries*.
|
||||||
@ -169,57 +236,22 @@
|
|||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
pub use context::{Context, SetupContext};
|
pub use context::{BuildContext, Context};
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
|
|
||||||
/// Trait to be implemented by all models.
|
/// Trait to be implemented by simulation models.
|
||||||
///
|
///
|
||||||
/// This trait enables models to perform specific actions during setup and
|
/// This trait enables models to perform specific actions during initialization.
|
||||||
/// initialization. The [`Model::setup()`] method is run only once when models
|
/// The [`Model::init()`] method is run only once all models have been connected
|
||||||
/// are being added to the simulation bench. This method allows in particular
|
/// and migrated to the simulation bench, but before the simulation actually
|
||||||
/// sub-models to be created, connected and added to the simulation.
|
/// starts. A common use for `init` is to send messages to connected models at
|
||||||
///
|
/// the beginning of the simulation.
|
||||||
/// The [`Model::init()`] method is run only once all models have been connected and
|
|
||||||
/// migrated to the simulation bench, but before the simulation actually starts.
|
|
||||||
/// A common use for `init` is to send messages to connected models at the
|
|
||||||
/// beginning of the simulation.
|
|
||||||
///
|
///
|
||||||
/// The `init` function converts the model to the opaque `InitializedModel` type
|
/// The `init` function converts the model to the opaque `InitializedModel` type
|
||||||
/// to prevent an already initialized model from being added to the simulation
|
/// to prevent an already initialized model from being added to the simulation
|
||||||
/// bench.
|
/// bench.
|
||||||
pub trait Model: Sized + Send + 'static {
|
pub trait Model: Sized + Send + 'static {
|
||||||
/// Performs model setup.
|
|
||||||
///
|
|
||||||
/// This method is executed exactly once for all models of the simulation
|
|
||||||
/// when the [`SimInit::add_model()`](crate::simulation::SimInit::add_model)
|
|
||||||
/// method is called.
|
|
||||||
///
|
|
||||||
/// The default implementation does nothing.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::future::Future;
|
|
||||||
/// use std::pin::Pin;
|
|
||||||
///
|
|
||||||
/// use asynchronix::model::{InitializedModel, Model, SetupContext};
|
|
||||||
///
|
|
||||||
/// pub struct MyModel {
|
|
||||||
/// // ...
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl Model for MyModel {
|
|
||||||
/// fn setup(
|
|
||||||
/// &mut self,
|
|
||||||
/// setup_context: &SetupContext<Self>
|
|
||||||
/// ) {
|
|
||||||
/// println!("...setup...");
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn setup(&mut self, _: &SetupContext<Self>) {}
|
|
||||||
|
|
||||||
/// Performs asynchronous model initialization.
|
/// Performs asynchronous model initialization.
|
||||||
///
|
///
|
||||||
/// This asynchronous method is executed exactly once for all models of the
|
/// This asynchronous method is executed exactly once for all models of the
|
||||||
@ -271,3 +303,36 @@ impl<M: Model> From<M> for InitializedModel<M> {
|
|||||||
InitializedModel(model)
|
InitializedModel(model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait to be implemented by 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
|
||||||
|
/// automatically called when a model or submodel prototype is added to the
|
||||||
|
/// simulation using
|
||||||
|
/// [`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;
|
||||||
|
|
||||||
|
/// Builds the model.
|
||||||
|
///
|
||||||
|
/// This method is invoked when the
|
||||||
|
/// [`SimInit::add_model()`](crate::simulation::SimInit::add_model) or
|
||||||
|
/// [`BuildContext::add_submodel`] method is called.
|
||||||
|
fn build(self, ctx: &BuildContext<Self>) -> Self::Model;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every model can be used as a prototype for itself.
|
||||||
|
impl<M: Model> ProtoModel for M {
|
||||||
|
type Model = Self;
|
||||||
|
|
||||||
|
fn build(self, _: &BuildContext<Self>) -> Self::Model {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::fmt;
|
|||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
use crate::simulation::{self, LocalScheduler, Mailbox};
|
use crate::simulation::{self, LocalScheduler, Mailbox};
|
||||||
|
|
||||||
use super::Model;
|
use super::{Model, ProtoModel};
|
||||||
|
|
||||||
/// A local context for models.
|
/// A local context for models.
|
||||||
///
|
///
|
||||||
@ -95,75 +95,102 @@ impl<M: Model> fmt::Debug for Context<M> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A setup context for models.
|
/// Context available when building a model from a model prototype.
|
||||||
///
|
///
|
||||||
/// A `SetupContext` can be used by models during the setup stage to
|
/// A `BuildContext` can be used to add the sub-models of a hierarchical model
|
||||||
/// create submodels and add them to the simulation bench.
|
/// to the simulation bench.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// A model that contains two connected submodels.
|
/// A model that multiplies its input by four using two sub-models that each
|
||||||
|
/// multiply their input by two.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ┌───────────────────────────────────────┐
|
||||||
|
/// │ MyltiplyBy4 │
|
||||||
|
/// │ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
/// │ │ │ │ │ │
|
||||||
|
/// Input ●─────┼──►│ MultiplyBy2 ├──►│ MultiplyBy2 ├───┼─────► Output
|
||||||
|
/// f64 │ │ │ │ │ │ f64
|
||||||
|
/// │ └─────────────┘ └─────────────┘ │
|
||||||
|
/// │ │
|
||||||
|
/// └───────────────────────────────────────┘
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::time::Duration;
|
/// use std::time::Duration;
|
||||||
/// use asynchronix::model::{Model, SetupContext};
|
/// use asynchronix::model::{BuildContext, Model, ProtoModel};
|
||||||
/// use asynchronix::ports::Output;
|
/// use asynchronix::ports::Output;
|
||||||
/// use asynchronix::simulation::Mailbox;
|
/// use asynchronix::simulation::Mailbox;
|
||||||
///
|
///
|
||||||
/// #[derive(Default)]
|
/// #[derive(Default)]
|
||||||
/// pub struct SubmodelA {
|
/// struct MultiplyBy2 {
|
||||||
/// out: Output<u32>,
|
/// pub output: Output<i32>,
|
||||||
/// }
|
/// }
|
||||||
|
/// impl MultiplyBy2 {
|
||||||
|
/// pub async fn input(&mut self, value: i32) {
|
||||||
|
/// self.output.send(value * 2).await;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// impl Model for MultiplyBy2 {}
|
||||||
///
|
///
|
||||||
/// impl Model for SubmodelA {}
|
/// pub struct MultiplyBy4 {
|
||||||
|
/// // Private forwarding output.
|
||||||
|
/// forward: Output<i32>,
|
||||||
|
/// }
|
||||||
|
/// impl MultiplyBy4 {
|
||||||
|
/// pub async fn input(&mut self, value: i32) {
|
||||||
|
/// self.forward.send(value).await;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// impl Model for MultiplyBy4 {}
|
||||||
///
|
///
|
||||||
/// #[derive(Default)]
|
/// pub struct ProtoMultiplyBy4 {
|
||||||
/// pub struct SubmodelB {}
|
/// pub output: Output<i32>,
|
||||||
|
/// }
|
||||||
|
/// impl ProtoModel for ProtoMultiplyBy4 {
|
||||||
|
/// type Model = MultiplyBy4;
|
||||||
///
|
///
|
||||||
/// impl SubmodelB {
|
/// fn build(
|
||||||
/// pub async fn input(&mut self, value: u32) {
|
/// self,
|
||||||
/// println!("Received {}", value);
|
/// ctx: &BuildContext<Self>)
|
||||||
|
/// -> MultiplyBy4 {
|
||||||
|
/// let mut mult = MultiplyBy4 { forward: Output::default() };
|
||||||
|
/// let mut submult1 = MultiplyBy2::default();
|
||||||
|
///
|
||||||
|
/// // Move the prototype's output to the second multiplier.
|
||||||
|
/// let mut submult2 = MultiplyBy2 { output: self.output };
|
||||||
|
///
|
||||||
|
/// // Forward the parent's model input to the first multiplier.
|
||||||
|
/// let submult1_mbox = Mailbox::new();
|
||||||
|
/// mult.forward.connect(MultiplyBy2::input, &submult1_mbox);
|
||||||
|
///
|
||||||
|
/// // Connect the two multiplier submodels.
|
||||||
|
/// let submult2_mbox = Mailbox::new();
|
||||||
|
/// submult1.output.connect(MultiplyBy2::input, &submult2_mbox);
|
||||||
|
///
|
||||||
|
/// // Add the submodels to the simulation.
|
||||||
|
/// ctx.add_submodel(submult1, submult1_mbox, "submultiplier 1");
|
||||||
|
/// ctx.add_submodel(submult2, submult2_mbox, "submultiplier 2");
|
||||||
|
///
|
||||||
|
/// mult
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl Model for SubmodelB {}
|
|
||||||
///
|
|
||||||
/// #[derive(Default)]
|
|
||||||
/// pub struct Parent {}
|
|
||||||
///
|
|
||||||
/// impl Model for Parent {
|
|
||||||
/// fn setup(
|
|
||||||
/// &mut self,
|
|
||||||
/// setup_context: &SetupContext<Self>) {
|
|
||||||
/// let mut a = SubmodelA::default();
|
|
||||||
/// let b = SubmodelB::default();
|
|
||||||
/// let a_mbox = Mailbox::new();
|
|
||||||
/// let b_mbox = Mailbox::new();
|
|
||||||
/// let a_name = setup_context.name().to_string() + "::a";
|
|
||||||
/// let b_name = setup_context.name().to_string() + "::b";
|
|
||||||
///
|
|
||||||
/// a.out.connect(SubmodelB::input, &b_mbox);
|
|
||||||
///
|
|
||||||
/// setup_context.add_model(a, a_mbox, a_name);
|
|
||||||
/// setup_context.add_model(b, b_mbox, b_name);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SetupContext<'a, M: Model> {
|
pub struct BuildContext<'a, P: ProtoModel> {
|
||||||
/// Mailbox of the model.
|
/// Mailbox of the model.
|
||||||
pub mailbox: &'a Mailbox<M>,
|
pub mailbox: &'a Mailbox<P::Model>,
|
||||||
context: &'a Context<M>,
|
context: &'a Context<P::Model>,
|
||||||
executor: &'a Executor,
|
executor: &'a Executor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, M: Model> SetupContext<'a, M> {
|
impl<'a, P: ProtoModel> BuildContext<'a, P> {
|
||||||
/// Creates a new local context.
|
/// Creates a new local context.
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
mailbox: &'a Mailbox<M>,
|
mailbox: &'a Mailbox<P::Model>,
|
||||||
context: &'a Context<M>,
|
context: &'a Context<P::Model>,
|
||||||
executor: &'a Executor,
|
executor: &'a Executor,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -178,16 +205,26 @@ impl<'a, M: Model> SetupContext<'a, M> {
|
|||||||
&self.context.name
|
&self.context.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new model and its mailbox to the simulation bench.
|
/// Adds a sub-model to the simulation bench.
|
||||||
///
|
///
|
||||||
/// The `name` argument needs not be unique (it can be an empty string) and
|
/// The `name` argument needs not be unique. If an empty string is provided,
|
||||||
/// is used for convenience for model instance identification (e.g. for
|
/// it is replaced by the string `<unknown>`.
|
||||||
/// logging purposes).
|
///
|
||||||
pub fn add_model<N: Model>(&self, model: N, mailbox: Mailbox<N>, name: impl Into<String>) {
|
/// The provided name is appended to that of the parent model using a dot as
|
||||||
|
/// a separator (e.g. `parent_name.child_name`) to build an identifier. This
|
||||||
|
/// identifier is used for logging or error-reporting purposes.
|
||||||
|
pub fn add_submodel<S: ProtoModel>(
|
||||||
|
&self,
|
||||||
|
model: S,
|
||||||
|
mailbox: Mailbox<S::Model>,
|
||||||
|
name: impl Into<String>,
|
||||||
|
) {
|
||||||
let mut submodel_name = name.into();
|
let mut submodel_name = name.into();
|
||||||
if !self.context.name().is_empty() && !submodel_name.is_empty() {
|
if submodel_name.is_empty() {
|
||||||
submodel_name = self.context.name().to_string() + "." + &submodel_name;
|
submodel_name = String::from("<unknown>");
|
||||||
}
|
};
|
||||||
|
submodel_name = self.context.name().to_string() + "." + &submodel_name;
|
||||||
|
|
||||||
simulation::add_model(
|
simulation::add_model(
|
||||||
model,
|
model,
|
||||||
mailbox,
|
mailbox,
|
||||||
|
@ -19,18 +19,18 @@
|
|||||||
//!
|
//!
|
||||||
//! #### Example
|
//! #### Example
|
||||||
//!
|
//!
|
||||||
//! This example demonstrates a submodel inside a parent model. The output of
|
//! This example demonstrates two submodels inside a parent model. The output of
|
||||||
//! the submodel is a clone of the parent model output. Both outputs remain
|
//! the submodel and of the main model are clones and remain therefore always
|
||||||
//! therefore always connected to the same inputs.
|
//! connected to the same inputs.
|
||||||
//!
|
//!
|
||||||
//! For a more comprehensive example demonstrating output cloning in submodels
|
//! For a more comprehensive example demonstrating hierarchical model
|
||||||
//! assemblies, see the [`assembly example`][assembly].
|
//! assemblies, see the [`assembly example`][assembly].
|
||||||
//!
|
//!
|
||||||
//! [assembly]:
|
//! [assembly]:
|
||||||
//! https://github.com/asynchronics/asynchronix/tree/main/asynchronix/examples/assembly.rs
|
//! https://github.com/asynchronics/asynchronix/tree/main/asynchronix/examples/assembly.rs
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use asynchronix::model::{Model, SetupContext};
|
//! use asynchronix::model::{BuildContext, Model, ProtoModel};
|
||||||
//! use asynchronix::ports::Output;
|
//! use asynchronix::ports::Output;
|
||||||
//! use asynchronix::simulation::Mailbox;
|
//! use asynchronix::simulation::Mailbox;
|
||||||
//!
|
//!
|
||||||
@ -39,9 +39,9 @@
|
|||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl ChildModel {
|
//! impl ChildModel {
|
||||||
//! pub fn new() -> Self {
|
//! pub fn new(output: Output<u64>) -> Self {
|
||||||
//! Self {
|
//! Self {
|
||||||
//! output: Default::default(),
|
//! output,
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
@ -49,10 +49,16 @@
|
|||||||
//! impl Model for ChildModel {}
|
//! impl Model for ChildModel {}
|
||||||
//!
|
//!
|
||||||
//! pub struct ParentModel {
|
//! pub struct ParentModel {
|
||||||
|
//! output: Output<u64>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Model for ParentModel {}
|
||||||
|
//!
|
||||||
|
//! pub struct ProtoParentModel {
|
||||||
//! pub output: Output<u64>,
|
//! pub output: Output<u64>,
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl ParentModel {
|
//! impl ProtoParentModel {
|
||||||
//! pub fn new() -> Self {
|
//! pub fn new() -> Self {
|
||||||
//! Self {
|
//! Self {
|
||||||
//! output: Default::default(),
|
//! output: Default::default(),
|
||||||
@ -60,13 +66,15 @@
|
|||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl Model for ParentModel {
|
//! impl ProtoModel for ProtoParentModel {
|
||||||
//! fn setup(&mut self, setup_context: &SetupContext<Self>) {
|
//! type Model = ParentModel;
|
||||||
//! let mut child = ChildModel::new();
|
//!
|
||||||
//! let child_mbox = Mailbox::new();
|
//! fn build(self, ctx: &BuildContext<Self>) -> ParentModel {
|
||||||
//! child.output = self.output.clone();
|
//! let mut child = ChildModel::new(self.output.clone());
|
||||||
//! let child_name = setup_context.name().to_string() + "::child";
|
//!
|
||||||
//! setup_context.add_model(child, child_mbox, child_name);
|
//! ctx.add_submodel(child, Mailbox::new(), "child");
|
||||||
|
//!
|
||||||
|
//! ParentModel { output: self.output }
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -146,7 +146,7 @@ use recycle_box::{coerce_box, RecycleBox};
|
|||||||
|
|
||||||
use crate::channel::ChannelObserver;
|
use crate::channel::ChannelObserver;
|
||||||
use crate::executor::{Executor, ExecutorError};
|
use crate::executor::{Executor, ExecutorError};
|
||||||
use crate::model::{Context, Model, SetupContext};
|
use crate::model::{BuildContext, Context, Model, ProtoModel};
|
||||||
use crate::ports::{InputFn, ReplierFn};
|
use crate::ports::{InputFn, ReplierFn};
|
||||||
use crate::time::{AtomicTime, Clock, MonotonicTime};
|
use crate::time::{AtomicTime, Clock, MonotonicTime};
|
||||||
use crate::util::seq_futures::SeqFuture;
|
use crate::util::seq_futures::SeqFuture;
|
||||||
@ -647,9 +647,9 @@ impl From<SchedulingError> for SimulationError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a model and its mailbox to the simulation bench.
|
/// Adds a model and its mailbox to the simulation bench.
|
||||||
pub(crate) fn add_model<M: Model>(
|
pub(crate) fn add_model<P: ProtoModel>(
|
||||||
mut model: M,
|
model: P,
|
||||||
mailbox: Mailbox<M>,
|
mailbox: Mailbox<P::Model>,
|
||||||
name: String,
|
name: String,
|
||||||
scheduler: Scheduler,
|
scheduler: Scheduler,
|
||||||
executor: &Executor,
|
executor: &Executor,
|
||||||
@ -658,9 +658,9 @@ pub(crate) fn add_model<M: Model>(
|
|||||||
let span = tracing::span!(target: env!("CARGO_PKG_NAME"), tracing::Level::INFO, "model", name);
|
let span = tracing::span!(target: env!("CARGO_PKG_NAME"), tracing::Level::INFO, "model", name);
|
||||||
|
|
||||||
let context = Context::new(name, LocalScheduler::new(scheduler, mailbox.address()));
|
let context = Context::new(name, LocalScheduler::new(scheduler, mailbox.address()));
|
||||||
let setup_context = SetupContext::new(&mailbox, &context, executor);
|
let build_context = BuildContext::new(&mailbox, &context, executor);
|
||||||
|
|
||||||
model.setup(&setup_context);
|
let model = model.build(&build_context);
|
||||||
|
|
||||||
let mut receiver = mailbox.0;
|
let mut receiver = mailbox.0;
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
|
@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use crate::channel::ChannelObserver;
|
use crate::channel::ChannelObserver;
|
||||||
use crate::executor::{Executor, SimulationContext};
|
use crate::executor::{Executor, SimulationContext};
|
||||||
use crate::model::Model;
|
use crate::model::ProtoModel;
|
||||||
use crate::time::{AtomicTime, MonotonicTime, TearableAtomicTime};
|
use crate::time::{AtomicTime, MonotonicTime, TearableAtomicTime};
|
||||||
use crate::time::{Clock, NoClock};
|
use crate::time::{Clock, NoClock};
|
||||||
use crate::util::priority_queue::PriorityQueue;
|
use crate::util::priority_queue::PriorityQueue;
|
||||||
@ -57,13 +57,13 @@ impl SimInit {
|
|||||||
|
|
||||||
/// Adds a model and its mailbox to the simulation bench.
|
/// Adds a model and its mailbox to the simulation bench.
|
||||||
///
|
///
|
||||||
/// The `name` argument needs not be unique (it can be the empty string) and
|
/// The `name` argument needs not be unique. If an empty string is provided,
|
||||||
/// is used for convenience for the model instance identification (e.g. for
|
/// it is replaced by the string `<unknown>`. This name serves an identifier
|
||||||
/// logging purposes).
|
/// for logging or error-reporting purposes.
|
||||||
pub fn add_model<M: Model>(
|
pub fn add_model<P: ProtoModel>(
|
||||||
mut self,
|
mut self,
|
||||||
model: M,
|
model: P,
|
||||||
mailbox: Mailbox<M>,
|
mailbox: Mailbox<P::Model>,
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
|
@ -22,7 +22,7 @@ pub(crate) struct CachedRwLock<T: Clone> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> CachedRwLock<T> {
|
impl<T: Clone> CachedRwLock<T> {
|
||||||
/// Creates a new cached read-write lock in an ulocked state.
|
/// Creates a new cached read-write lock in unlocked state.
|
||||||
pub(crate) fn new(t: T) -> Self {
|
pub(crate) fn new(t: T) -> Self {
|
||||||
let shared = t.clone();
|
let shared = t.clone();
|
||||||
Self {
|
Self {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user