gwr_engine/
lib.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3// TODO: enable this warning to ensure all public interfaces are documented.
4// Enable warnings for missing documentation
5// #![warn(missing_docs)]
6
7#![doc(test(attr(warn(unused))))]
8
9//! `GWR` - The Great Western Runtime
10//!
11//! This library provides the core of the [GWR Engine](crate::engine) which
12//! executes event driven asynchronous simulation
13//! [components].
14//!
15//! # Features
16//!
17//! - `global_allocator`: When enabled this feature causes applications
18//!   dependendant on `gwr-engine` to use a global allocator selected to
19//!   deliver optimal runtime performace of the GWR engine. Currently this is
20//!   the [mimalloc](https://github.com/microsoft/mimalloc) allocator.
21//!
22//!   This feature is enabled by default. Should an application wish to use a
23//!   alternative global allocator the feature must be explicitly disabled.
24//!
25//! # Developer Guide
26//!
27//! The Developer Guide provides a document that goes through the GWR engine
28//! and related libraries in a more directed approach than the API guide can.
29//! See the `gwr-developer-guide/` folder.
30//!
31//! # Examples
32//!
33//! Make sure you look at the **examples/** folder which includes
34//! worked/documented examples. The current examples are:
35//!  - [examples/flaky-component]: a worked example of a simple two-port
36//!    component.
37//!  - [examples/flaky-with-delay]: a worked example of a simple two-port
38//!    component that has some subcomponents.
39//!  - [examples/scrambler]: a worked example of a component that registers a a
40//!    vector of subcomponents.
41//!  - [examples/sim-pipe]: simulate a flow-controlled pipeline.
42//!  - [examples/sim-restaurant]: simulate a fast food restaurant and explore
43//!    staffing profitability.
44//!  - [examples/sim-ring]: simulate a device comprising a ring of nodes.
45//!  - [examples/sim-fabric]: simulate a device comprising a rectangular fabric.
46//!
47//! [components]: ../gwr_components/index.html
48//! [examples/flaky-component]: ../flaky_component/index.html
49//! [examples/flaky-with-delay]: ../flaky_with_delay/index.html
50//! [examples/scrambler]: ../scrambler/index.html
51//! [examples/sim-pipe]: ../sim_pipe/index.html
52//! [examples/sim-restaurant]: ../sim_restaurant/index.html
53//! [examples/sim-ring]: ../sim_ring/index.html
54//! [examples/sim-fabric]: ../sim_fabric/index.html
55
56//! # Simple Application
57//!
58//! A very simple application would look like:
59//!
60//! ```rust
61//! use gwr_components::sink::Sink;
62//! use gwr_components::source::Source;
63//! use gwr_components::{connect_port, option_box_repeat};
64//! use gwr_engine::engine::Engine;
65//! use gwr_engine::run_simulation;
66//!
67//! let mut engine = Engine::default();
68//! let clock = engine.default_clock();
69//! let mut source = Source::new_and_register(&engine, engine.top(), "source", option_box_repeat!(0x123 ; 10));
70//! let sink = Sink::new_and_register(&engine, &clock, engine.top(), "sink");
71//! connect_port!(source, tx => sink, rx)
72//!     .expect("should be able to connect `Source` to `Sink`");
73//! run_simulation!(engine);
74//! assert_eq!(sink.num_sunk(), 10);
75//! ```
76
77//! Simulations can be run as purely event driven (where one event triggers one
78//! or more others) or the use of clocks can be introduced to model time. The
79//! combination of both is the most common.
80//!
81//! The [engine](crate::engine::Engine) manages the
82//! [clocks](crate::time::clock). A simple example of a component that uses the
83//! clock is the
84//! [rate limiter](../gwr_components/flow_controls/rate_limiter/index.html)
85//! which models the amount of time it takes for objects to pass through it.
86
87pub mod engine;
88pub mod events;
89pub mod executor;
90#[cfg(feature = "global_allocator")]
91mod global_allocator;
92pub mod port;
93pub mod test_helpers;
94pub mod time;
95pub mod traits;
96pub mod types;
97
98/// Spawn all component run() functions and then run the simulation.
99#[macro_export]
100macro_rules! run_simulation {
101    ($engine:ident) => {
102        $engine.run().unwrap();
103    };
104    ($engine:ident, $expect:expr) => {
105        match $engine.run() {
106            Ok(()) => panic!("Expected an error!"),
107            Err(e) => assert_eq!(&format!("{e}"), $expect),
108        }
109    };
110}
111
112/// Spawn a sub-component that is stored in an `RefCell<Option<>>`
113///
114/// This removes the sub-component from the Option and then spawns the `run()`
115/// function.
116#[macro_export]
117macro_rules! spawn_subcomponent {
118    ($($spawner:ident).+ ; $($block:ident).+) => {
119        let sub_block = $($block).+.borrow_mut().take().unwrap();
120        $($spawner).+.spawn(async move { sub_block.run().await } );
121    };
122}
123
124#[cfg(test)]
125mod tests {
126    use std::cell::{Cell, RefCell};
127    use std::rc::Rc;
128
129    use async_trait::async_trait;
130    use gwr_track::tracker::dev_null_tracker;
131
132    use crate::engine::Engine;
133    use crate::traits::Runnable;
134    use crate::types::SimResult;
135
136    struct TestComponent {
137        ran: Rc<Cell<bool>>,
138    }
139
140    #[async_trait(?Send)]
141    impl Runnable for TestComponent {
142        async fn run(&self) -> SimResult {
143            self.ran.set(true);
144            Ok(())
145        }
146    }
147
148    #[test]
149    fn spawn_subcomponent_spawns_and_runs_component() {
150        let tracker = dev_null_tracker();
151        let mut engine = Engine::new(&tracker);
152        let spawner = engine.spawner();
153        let ran = Rc::new(Cell::new(false));
154        let component = RefCell::new(Some(TestComponent { ran: ran.clone() }));
155
156        spawn_subcomponent!(spawner; component);
157
158        engine.run().unwrap();
159
160        assert!(ran.get());
161        assert!(component.borrow().is_none());
162    }
163}