Skip to main content

gwr_engine/
engine.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3use std::cell::RefCell;
4use std::future::Future;
5use std::rc::Rc;
6use std::time::Duration;
7
8use gwr_track::entity::{Entity, toplevel};
9use gwr_track::tracker::stdout_tracker;
10use gwr_track::{Tracker, trace};
11
12use crate::executor::{self, Executor, ExecutorSnapshot, ExecutorStats, Spawner};
13use crate::time::clock::Clock;
14use crate::types::{Component, SimResult};
15
16/// Use a default clock frequency of 1GHz.
17const DEFAULT_CLOCK_MHZ: f64 = 1000.0;
18
19/// Components registered to be spawned when the simulation starts.
20///
21/// Constructors for active components usually call [`Engine::register`] before
22/// returning their `Rc<Self>`. The registry is drained by [`Engine::run`],
23/// so all construction and required port connections should be complete
24/// before it is called.
25pub struct Registry {
26    entity: Rc<Entity>,
27    components: RefCell<Vec<Component>>,
28}
29
30impl Registry {
31    fn new(parent: &Rc<Entity>) -> Self {
32        Self {
33            entity: Rc::new(Entity::new(parent, "registry")),
34            components: RefCell::new(Vec::new()),
35        }
36    }
37
38    pub fn spawn_components(&self, spawner: &Spawner) {
39        let mut guard = self.components.borrow_mut();
40
41        trace!(self.entity ; "Spawning {} components", guard.len());
42
43        for component in guard.drain(..) {
44            spawner.spawn(async move { component.run().await });
45        }
46    }
47
48    pub fn register(&self, component: Component) {
49        let mut guard = self.components.borrow_mut();
50        guard.push(component);
51    }
52}
53
54/// Single-threaded asynchronous simulation runtime.
55///
56/// An application normally creates an `Engine`, obtains one or more clocks,
57/// constructs components or models with `new_and_register` constructors,
58/// connects their ports, and then calls [`run`](Self::run).
59/// Connections are intentionally part of setup:
60/// a port that is still unconnected when a task tries to use it represents a
61/// model-topology error.
62///
63/// The engine owns the executor, a spawner for additional tasks, the top-level
64/// trace [`Entity`], the shared [`Tracker`], and the registry of components
65/// that should begin running when simulation starts.
66pub struct Engine {
67    pub executor: Executor,
68    spawner: Spawner,
69    toplevel: Rc<Entity>,
70    tracker: Tracker,
71    registry: Registry,
72}
73
74impl Engine {
75    /// Create a standalone engine.
76    pub fn new(tracker: &Tracker) -> Self {
77        let toplevel = toplevel(tracker, "top");
78        let (executor, spawner) = executor::new_executor_and_spawner(&toplevel);
79        let registry = Registry::new(&toplevel);
80        Self {
81            executor,
82            spawner,
83            toplevel,
84            tracker: tracker.clone(),
85            registry,
86        }
87    }
88
89    /// Register a component that will be run as the simulation starts.
90    ///
91    /// Registration should happen during construction, before the engine run
92    /// begins. Passive helper objects that do not have independent async
93    /// behavior do not need to be registered.
94    pub fn register(&self, component: Component) {
95        self.registry.register(component);
96    }
97
98    pub fn run(&mut self) -> SimResult {
99        self.registry.spawn_components(&self.spawner);
100        self.executor.run()
101    }
102
103    /// Run the simulation while reporting executor activity after each
104    /// wall-clock interval at completed executor step boundaries.
105    ///
106    /// Queued task entries in each snapshot do not include futures parked on
107    /// clocks, ports, or events. Returns an error when `interval` is zero.
108    pub fn run_with_executor_observer(
109        &mut self,
110        interval: Duration,
111        observer: impl FnMut(ExecutorSnapshot) -> SimResult,
112    ) -> SimResult {
113        self.registry.spawn_components(&self.spawner);
114        self.executor.run_with_observer(interval, observer)
115    }
116
117    #[must_use]
118    pub fn spawner(&self) -> Spawner {
119        self.spawner.clone()
120    }
121
122    pub fn spawn(&self, future: impl Future<Output = SimResult> + 'static) {
123        self.spawner.spawn(future);
124    }
125
126    pub fn set_randomize_task_order(&self, randomize: bool) {
127        self.executor.set_randomize_task_order(randomize);
128    }
129
130    pub fn set_task_order_seed(&self, seed: u64) {
131        self.executor.set_task_order_seed(seed);
132    }
133
134    #[must_use]
135    pub fn default_clock(&mut self) -> Clock {
136        self.executor.get_clock(DEFAULT_CLOCK_MHZ)
137    }
138
139    #[must_use]
140    pub fn clock_hz(&mut self, freq_hz: f64) -> Clock {
141        self.executor.get_clock(freq_hz / 1_000_000.0)
142    }
143
144    #[must_use]
145    pub fn clock_khz(&mut self, freq_khz: f64) -> Clock {
146        self.executor.get_clock(freq_khz / 1000.0)
147    }
148
149    #[must_use]
150    pub fn clock_mhz(&mut self, freq_mhz: f64) -> Clock {
151        self.executor.get_clock(freq_mhz)
152    }
153
154    #[must_use]
155    pub fn clock_ghz(&mut self, freq_ghz: f64) -> Clock {
156        self.executor.get_clock(freq_ghz * 1000.0)
157    }
158
159    #[must_use]
160    pub fn time_now_ns(&self) -> f64 {
161        self.executor.time_now_ns()
162    }
163
164    /// Return cumulative future activity from the most recent run.
165    #[must_use]
166    pub fn executor_stats(&self) -> ExecutorStats {
167        self.executor.stats()
168    }
169
170    #[must_use]
171    pub fn top(&self) -> &Rc<Entity> {
172        &self.toplevel
173    }
174
175    #[must_use]
176    pub fn tracker(&self) -> Tracker {
177        self.tracker.clone()
178    }
179}
180
181/// Create a default engine that sends [`Track`](gwr_track::Track) events to
182/// stdout.
183///
184/// This is provided to keep documentation examples simple with fewer
185/// concepts to have to consider at once.
186impl Default for Engine {
187    fn default() -> Self {
188        let tracker = stdout_tracker(log::Level::Info);
189        Self::new(&tracker)
190    }
191}
192
193impl Drop for Engine {
194    fn drop(&mut self) {
195        // The tracker can be using a buffered writer and so it needs to be shut
196        // down cleanly to ensure that it is flushed properly.
197        self.tracker.shutdown();
198    }
199}