Skip to main content

gwr_engine/
traits.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3//! A set of common traits used across GWR Engine.
4
5use core::mem::size_of;
6use std::fmt::{Debug, Display};
7use std::future::Future;
8use std::pin::Pin;
9use std::rc::Rc;
10
11use async_trait::async_trait;
12use gwr_track::id::Unique;
13
14use crate::types::{AccessType, DeviceId, SimResult};
15
16/// The `TotalBytes` trait is used to determine how many bytes an object
17/// represents
18///
19/// This trait is used to determine how much time an object will take to be
20/// sent.
21pub trait TotalBytes {
22    fn total_bytes(&self) -> usize;
23}
24
25/// The `Routable` trait provides an interface to an object to enable it to be
26/// routed
27pub trait Routable {
28    fn dst_addr(&self) -> u64;
29    fn src_addr(&self) -> u64;
30    fn dst_device(&self) -> DeviceId {
31        DeviceId(self.dst_addr())
32    }
33    fn src_device(&self) -> DeviceId {
34        DeviceId(self.src_addr())
35    }
36    fn access_type(&self) -> AccessType;
37}
38
39/// A super-trait that objects that are passed around the simulation have to
40/// implement
41///
42/// This is the minimum object contract required by ports, flow controls,
43/// tracking, and most test harnesses. The trait intentionally combines data
44/// movement concerns (`Clone`, `Unpin`, `'static`), diagnostics (`Debug`,
45/// `Display`), observability (`Unique`), and bandwidth modelling
46/// (`TotalBytes`). Components that route objects require [`Routable`]
47/// separately.
48///
49///  - `Clone`: Allows applications and components to retain copies of values
50///    sent through the simulation, including values such as `Vec` that are not
51///    `Copy`.
52///  - `Debug` and `Display`: Support diagnostic and trace output.
53///  - `Unique`: Supplies the value's tracking ID.
54///  - `TotalBytes`: Supplies the size used by bandwidth and rate models.
55///  - `Unpin`: Allows values to be moved out of port futures safely.
56///  - `'static`: Allows port futures containing values to be owned by spawned
57///    simulation tasks.
58pub trait SimObject: Clone + Debug + Display + Unique + TotalBytes + Unpin + 'static {}
59
60// Implementations for basic types that can be sent around the simulation for
61// testing
62
63// i32
64impl TotalBytes for i32 {
65    fn total_bytes(&self) -> usize {
66        size_of::<i32>()
67    }
68}
69
70impl Routable for i32 {
71    fn dst_addr(&self) -> u64 {
72        *self as u64
73    }
74    fn src_addr(&self) -> u64 {
75        *self as u64
76    }
77    fn access_type(&self) -> AccessType {
78        match self {
79            0 => AccessType::ReadRequest,
80            1 => AccessType::WriteRequest,
81            2 => AccessType::WriteNonPostedRequest,
82            3 => AccessType::ReadResponse,
83            4 => AccessType::WriteNonPostedResponse,
84            _ => AccessType::Control,
85        }
86    }
87}
88
89impl SimObject for i32 {}
90
91// usize
92impl TotalBytes for usize {
93    fn total_bytes(&self) -> usize {
94        size_of::<usize>()
95    }
96}
97
98impl Routable for usize {
99    fn dst_addr(&self) -> u64 {
100        *self as u64
101    }
102    fn src_addr(&self) -> u64 {
103        *self as u64
104    }
105    fn access_type(&self) -> AccessType {
106        match self {
107            0 => AccessType::ReadRequest,
108            1 => AccessType::WriteRequest,
109            2 => AccessType::WriteNonPostedRequest,
110            3 => AccessType::ReadResponse,
111            4 => AccessType::WriteNonPostedResponse,
112            _ => AccessType::Control,
113        }
114    }
115}
116
117impl SimObject for usize {}
118
119/// The `Event` trait defines an object that can be used as an Event
120///
121/// This is a trait that defines the `listen` function that returns a future
122/// so that it can be used in `async` code.
123///
124/// ```rust
125/// use futures::future::BoxFuture;
126/// pub trait Event<T> {
127///     fn listen(&self) -> BoxFuture<'static, T>;
128/// }
129/// ```
130pub trait Event<T> {
131    #[must_use = "Futures do nothing unless you `.await` or otherwise use them"]
132    fn listen(&self) -> BoxFuture<'static, T>;
133
134    /// Allow cloning of Boxed elements of vector for AllOf/AnyOf
135    ///
136    /// See [stack overflow post](https://stackoverflow.com/questions/69890183/how-can-i-clone-a-vecboxdyn-trait)
137    fn clone_dyn(&self) -> Box<dyn Event<T>>;
138}
139
140/// Provide Clone implementation for boxed Event
141impl<T> Clone for Box<dyn Event<T>> {
142    fn clone(self: &Box<dyn Event<T>>) -> Box<dyn Event<T>> {
143        self.clone_dyn()
144    }
145}
146
147/// Complete any pending transactions.
148pub trait Resolve {
149    /// Complete any pending update.
150    fn resolve(&self);
151}
152
153/// A [`Resolver`] is used to register any [`Resolve`] functions that need to be
154/// called.
155pub trait Resolver {
156    fn add_resolve(&self, resolve: Rc<dyn Resolve + 'static>);
157}
158
159pub type BoxFuture<'a, T> = Pin<std::boxed::Box<dyn Future<Output = T> + 'a>>;
160
161/// The `Runnable` trait defines any active functionality that is spawned by a
162/// component.
163///
164/// Components with no independent async behavior can use the default
165/// implementation. Active components need to override [`run`](Runnable::run).
166/// Because the executor is single-threaded, active components normally share
167/// state through `Rc`, `RefCell`, and `Cell` instead of `Arc` or locks. Ports
168/// are often stored as `RefCell<Option<...>>`: setup code connects them through
169/// `&self`, and `run` takes ownership of them for the lifetime of the spawned
170/// task.
171///
172/// The `#[async_trait(?Send)]` decorator keeps the trait usable through
173/// `dyn Runnable` and produces futures suitable for the single-threaded
174/// executor. A basic implementation of the trait looks like:
175///
176/// ```rust
177/// # use gwr_engine::types::SimResult;
178/// # use async_trait::async_trait;
179/// #[async_trait(?Send)]
180/// pub trait Runnable {
181///     async fn run(&self) -> SimResult {
182///         Ok(())
183///     }
184/// }
185/// ```
186///
187/// A default implementation is provided for any component that doesn't have any
188/// active behaviour.
189#[async_trait(?Send)]
190pub trait Runnable {
191    /// Provides the method that defines the active element of this component.
192    ///
193    /// Default implementation is to do nothing.
194    async fn run(&self) -> SimResult {
195        Ok(())
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    // Tests added simply for code coverage
204    #[test]
205    fn integer_sim_object_defaults_are_available() {
206        assert_eq!(0_i32.total_bytes(), size_of::<i32>());
207        assert_eq!(7_i32.dst_addr(), 7);
208        assert_eq!(7_i32.src_addr(), 7);
209        assert_eq!(7_i32.dst_device(), DeviceId(7));
210        assert_eq!(7_i32.src_device(), DeviceId(7));
211        assert_eq!(0_i32.access_type(), AccessType::ReadRequest);
212        assert_eq!(1_i32.access_type(), AccessType::WriteRequest);
213        assert_eq!(2_i32.access_type(), AccessType::WriteNonPostedRequest);
214        assert_eq!(3_i32.access_type(), AccessType::ReadResponse);
215        assert_eq!(4_i32.access_type(), AccessType::WriteNonPostedResponse);
216        assert_eq!(5_i32.access_type(), AccessType::Control);
217
218        assert_eq!(0_usize.total_bytes(), size_of::<usize>());
219        assert_eq!(7_usize.dst_addr(), 7);
220        assert_eq!(7_usize.src_addr(), 7);
221        assert_eq!(7_usize.dst_device(), DeviceId(7));
222        assert_eq!(7_usize.src_device(), DeviceId(7));
223        assert_eq!(0_usize.access_type(), AccessType::ReadRequest);
224        assert_eq!(1_usize.access_type(), AccessType::WriteRequest);
225        assert_eq!(2_usize.access_type(), AccessType::WriteNonPostedRequest);
226        assert_eq!(3_usize.access_type(), AccessType::ReadResponse);
227        assert_eq!(4_usize.access_type(), AccessType::WriteNonPostedResponse);
228        assert_eq!(5_usize.access_type(), AccessType::Control);
229    }
230
231    struct PassiveRunnable;
232
233    #[test]
234    fn runnable_default_run_completes_successfully() {
235        let runnable = PassiveRunnable;
236
237        futures::executor::LocalPool::new()
238            .run_until(runnable.run())
239            .unwrap();
240    }
241
242    #[async_trait(?Send)]
243    impl Runnable for PassiveRunnable {}
244}