flaky_component/
lib.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3//! This is an example component that will randomly drop data being passed
4//! through it.
5//!
6//! The `main.rs` in this folder shows how it can be used.
7//!
8//! # Ports
9//!
10//! This component has two ports
11//!  - One [input port](gwr_engine::port::InPort): `rx`
12//!  - One [output put port](gwr_engine::port::OutPort): `tx`
13
14// ANCHOR: use
15
16/// The `RefCell` allows the engine to be able to access state immutably as
17/// well as mutably.
18use std::cell::RefCell;
19/// The `Rc` part of the standard library brings in types used for thread
20/// synchronisation.
21use std::rc::Rc;
22
23use async_trait::async_trait;
24use gwr_components::{connect_tx, port_rx, take_option};
25use gwr_engine::engine::Engine;
26use gwr_engine::port::{InPort, OutPort, PortStateResult};
27use gwr_engine::time::clock::Clock;
28use gwr_engine::traits::{Runnable, SimObject};
29use gwr_engine::types::{SimError, SimResult};
30use gwr_model_builder::{EntityDisplay, EntityGet};
31/// The gwr_track library provides tracing/logging features.
32use gwr_track::entity::Entity;
33use gwr_track::trace;
34/// Random library is just used by this component to implement its drop
35/// decisions.
36use rand::rngs::StdRng;
37use rand::{RngCore, SeedableRng};
38
39// ANCHOR_END: use
40
41// ANCHOR: struct
42
43/// The overall structure for this compoment.
44///
45/// Note that in this example it is a *Generic* type in that it can be used in
46/// a simulation of any type - as long as that type implements the `SimObject`
47/// trait.
48///
49/// Every entity needs to implement the `GetEntity` trait in order to provide
50/// the `entity()` access function to get at the private `entity` member. The
51/// `EntityGet` automatically implements this function for this struct.
52///
53/// The `fmt::Display` trait is used when converting a component to a string
54/// for logging/printing using "{}". Simply pass through to the entity. This can
55/// be hand-written, but the `EntityDisplay` derive writes this automatically.
56#[derive(EntityGet, EntityDisplay)]
57pub struct Flaky<T>
58where
59    T: SimObject,
60{
61    /// Every component should include an Entity that defines where in the
62    /// overall simulation hierarchy it is. The Entity is also used to
63    /// filter logging.
64    entity: Rc<Entity>,
65
66    /// Store the ratio at which packets should be dropped.
67    drop_ratio: f64,
68
69    /// Random number generator used for deciding when to drop. Note that it is
70    /// wrapped in a [RefCell] which allows it to be used mutably in the `put()`
71    /// function despite the fact that the struct will be immutable.
72    rng: RefCell<StdRng>,
73
74    /// Rx port to which to send any data that hasn't been dropped.
75    /// Again, needs to be wrapped in the [RefCell] to allow it to be changed
76    /// when components are actually connected.
77    ///
78    /// Note: It is also wrapped in an [Option] so that it can be taken out in
79    /// the `run()` function.
80    rx: RefCell<Option<InPort<T>>>,
81
82    /// Tx port to which to send any data that hasn't been dropped.
83    ///
84    /// Note: It is also wrapped in an [Option] so that it can be taken out in
85    /// the `run()` function.
86    tx: RefCell<Option<OutPort<T>>>,
87}
88// ANCHOR_END: struct
89
90// ANCHOR: implFlaky
91
92/// The next thing to do is define the generic functions for the new component.
93impl<T> Flaky<T>
94where
95    T: SimObject,
96{
97    /// In this case, the `new()` function creates the component from the
98    /// parameters provided.
99    pub fn new_and_register(
100        engine: &Engine,
101        clock: &Clock,
102        parent: &Rc<Entity>,
103        name: &str,
104        drop_ratio: f64,
105        seed: u64,
106    ) -> Result<Rc<Self>, SimError> {
107        // The entity needs to be created first because it is shared between the state
108        // and the component itself.
109        let entity = Entity::new(parent, name);
110
111        // Because it is shared it needs to be wrapped in an Rc
112        let entity = Rc::new(entity);
113
114        let rx = InPort::new(engine, clock, &entity, "rx");
115        let tx = OutPort::new(&entity, "tx");
116        // Finally, the top-level struct is created and wrapped in an Rc.
117        let rc_self = Rc::new(Self {
118            entity,
119            drop_ratio,
120            rng: RefCell::new(StdRng::seed_from_u64(seed)),
121            rx: RefCell::new(Some(rx)),
122            tx: RefCell::new(Some(tx)),
123        });
124        engine.register(rc_self.clone());
125        Ok(rc_self)
126    }
127
128    /// This provides the `InPort` to which you can connect
129    pub fn port_rx(&self) -> PortStateResult<T> {
130        // The `port_rx!` macro is the most consise way to access the rx port state
131        // when wrapped in `RefCell<Option<>>`.
132        port_rx!(self.rx, state)
133    }
134
135    /// The ports of this component are effectively defined by the functions
136    /// this component exposes. In this case, the `connect_port_tx` shows
137    /// that this component has an TX port which should be connected to an RX
138    /// port.
139    pub fn connect_port_tx(&self, port_state: PortStateResult<T>) -> SimResult {
140        // Because the State is immutable then we use the `connect_tx!` macro
141        // in order to simplify the setup when wrapped in `RefCell<Option<>>`.
142        connect_tx!(self.tx, connect ; port_state)
143    }
144
145    /// Return the next random u32
146    ///
147    /// This is wrapped in a separate function to hide the interior mutation
148    fn next_u32(&self) -> u32 {
149        self.rng.borrow_mut().next_u32()
150    }
151}
152
153#[async_trait(?Send)]
154impl<T> Runnable for Flaky<T>
155where
156    T: SimObject,
157{
158    async fn run(&self) -> SimResult {
159        let rx = take_option!(self.rx);
160        let tx = take_option!(self.tx);
161
162        loop {
163            // Receive a value from the input
164            let value = rx.get()?.await;
165
166            let next_u32 = self.next_u32();
167            let ratio = next_u32 as f64 / u32::MAX as f64;
168            if ratio > self.drop_ratio {
169                // Only pass on a percentage of the data
170                tx.put(value)?.await;
171            } else {
172                // Let the user know this value has been dropped.
173                trace!(self.entity ; "drop {}", value);
174            }
175        }
176    }
177}
178// ANCHOR_END: implFlaky