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