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}