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, 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 destination(&self) -> u64;
29    fn access_type(&self) -> AccessType;
30}
31
32/// A super-trait that objects that are passed around the simulation have to
33/// implement
34///
35///  - Clone:       It would be nice to use `Copy` instead, but given that
36///    things like `Vec` are not `Copy` we have to use `Clone` instead to allow
37///    the application to keep copies of objects sent around.
38///  - Debug:       In order to print "{:?}" objects have to at least implement
39///    Debug. We could require Display, but that requires explicit
40///    implementation.
41///  - Routable:    Allows routing.
42///  - Unique:      Allows for unique identification of `Entities`.
43///  - TotalBytes:  Allows rate limiting.
44///  - Unpin:       Required in order to be able to Unpin in port futures.
45///  - 'static:     Due to the way that futures are implemented, the lifetimes
46///    need to be `static. This means that objects may have to be placed in
47///    `Box` to make the static.
48pub trait SimObject: Clone + Debug + Display + Unique + TotalBytes + Unpin + 'static {}
49
50// Implementations for basic types that can be sent around the simulation for
51// testing
52
53// i32
54impl TotalBytes for i32 {
55    fn total_bytes(&self) -> usize {
56        size_of::<i32>()
57    }
58}
59
60impl Routable for i32 {
61    fn destination(&self) -> u64 {
62        *self as u64
63    }
64    fn access_type(&self) -> AccessType {
65        match self {
66            0 => AccessType::ReadRequest,
67            1 => AccessType::WriteRequest,
68            2 => AccessType::WriteNonPostedRequest,
69            3 => AccessType::ReadResponse,
70            4 => AccessType::WriteNonPostedResponse,
71            _ => AccessType::Control,
72        }
73    }
74}
75
76impl SimObject for i32 {}
77
78// usize
79impl TotalBytes for usize {
80    fn total_bytes(&self) -> usize {
81        size_of::<usize>()
82    }
83}
84
85impl Routable for usize {
86    fn destination(&self) -> u64 {
87        *self as u64
88    }
89    fn access_type(&self) -> AccessType {
90        match self {
91            0 => AccessType::ReadRequest,
92            1 => AccessType::WriteRequest,
93            2 => AccessType::WriteNonPostedRequest,
94            3 => AccessType::ReadResponse,
95            4 => AccessType::WriteNonPostedResponse,
96            _ => AccessType::Control,
97        }
98    }
99}
100
101impl SimObject for usize {}
102
103/// The `Event` trait defines an object that can be used as an Event
104///
105/// This is a trait that defines the `listen` function that returns a future
106/// so that it can be used in `async` code.
107///
108/// ```rust
109/// use futures::future::BoxFuture;
110/// pub trait Event<T> {
111///     fn listen(&self) -> BoxFuture<'static, T>;
112/// }
113/// ```
114pub trait Event<T> {
115    #[must_use = "Futures do nothing unless you `.await` or otherwise use them"]
116    fn listen(&self) -> BoxFuture<'static, T>;
117
118    /// Allow cloning of Boxed elements of vector for AllOf/AnyOf
119    ///
120    /// See [stack overflow post](https://stackoverflow.com/questions/69890183/how-can-i-clone-a-vecboxdyn-trait)
121    fn clone_dyn(&self) -> Box<dyn Event<T>>;
122}
123
124/// Provide Clone implementation for boxed Event
125impl<T> Clone for Box<dyn Event<T>> {
126    fn clone(self: &Box<dyn Event<T>>) -> Box<dyn Event<T>> {
127        self.clone_dyn()
128    }
129}
130
131/// Complete any pending transactions.
132pub trait Resolve {
133    /// Complete any pending update.
134    fn resolve(&self);
135}
136
137/// A [`Resolver`] is used to register any [`Resolve`] functions that need to be
138/// called.
139pub trait Resolver {
140    fn add_resolve(&self, resolve: Rc<dyn Resolve + 'static>);
141}
142
143pub type BoxFuture<'a, T> = Pin<std::boxed::Box<dyn Future<Output = T> + 'a>>;
144
145/// The `Runnable` trait defines any active functionality that is spawned by a
146/// component.
147///
148/// This is a trait that defines an `async` function and therefore currently
149/// needs to use the `#[async_trait(?Send)]` decorator that converts it to a
150/// pinned boxed result. A basic implementation of the trait looks like:
151///
152/// ```rust
153/// # use gwr_engine::types::SimResult;
154/// # use async_trait::async_trait;
155/// #[async_trait(?Send)]
156/// pub trait Runnable {
157///     async fn run(&self) -> SimResult {
158///         Ok(())
159///     }
160/// }
161/// ```
162///
163/// A default implementation is provided for any compoment that doesn't have any
164/// active behaviour.
165#[async_trait(?Send)]
166pub trait Runnable {
167    /// Provides the method that defines the active element of this component.
168    ///
169    /// Default implementation is to do nothing.
170    async fn run(&self) -> SimResult {
171        Ok(())
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    // Tests added simply for code coverage
180    #[test]
181    fn integer_sim_object_defaults_are_available() {
182        assert_eq!(0_i32.total_bytes(), size_of::<i32>());
183        assert_eq!(7_i32.destination(), 7);
184        assert_eq!(0_i32.access_type(), AccessType::ReadRequest);
185        assert_eq!(1_i32.access_type(), AccessType::WriteRequest);
186        assert_eq!(2_i32.access_type(), AccessType::WriteNonPostedRequest);
187        assert_eq!(3_i32.access_type(), AccessType::ReadResponse);
188        assert_eq!(4_i32.access_type(), AccessType::WriteNonPostedResponse);
189        assert_eq!(5_i32.access_type(), AccessType::Control);
190
191        assert_eq!(0_usize.total_bytes(), size_of::<usize>());
192        assert_eq!(7_usize.destination(), 7);
193        assert_eq!(0_usize.access_type(), AccessType::ReadRequest);
194        assert_eq!(1_usize.access_type(), AccessType::WriteRequest);
195        assert_eq!(2_usize.access_type(), AccessType::WriteNonPostedRequest);
196        assert_eq!(3_usize.access_type(), AccessType::ReadResponse);
197        assert_eq!(4_usize.access_type(), AccessType::WriteNonPostedResponse);
198        assert_eq!(5_usize.access_type(), AccessType::Control);
199    }
200
201    struct PassiveRunnable;
202
203    #[test]
204    fn runnable_default_run_completes_successfully() {
205        let runnable = PassiveRunnable;
206
207        futures::executor::LocalPool::new()
208            .run_until(runnable.run())
209            .unwrap();
210    }
211
212    #[async_trait(?Send)]
213    impl Runnable for PassiveRunnable {}
214}