1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
use crate::{Error, Interval, Target}; use async_std::{ sync::{Arc, Barrier}, task, }; use std::{collections::BTreeMap, hash::Hash}; /// A control object for different clocking scopes /// /// Each clock target can be configured individually via the [`Target`] /// type, returned by [`setup()`]. Additionally you need to provide /// some type that implements `Hash`. It's recomended to just use an /// enum that can be mapped onto each of your reactors internal tasks. /// /// [`Target`]: struct.Target.html /// [`setup()`]: struct.ClockCtrl.html#method.setup pub struct ClockCtrl<K> where K: Hash + Ord, { clocks: BTreeMap<K, Target>, } /// A wrapper type around different clocking strategies /// /// This type is returned by the `ClockCtl::start` function, to /// provide an easy hook for any consumer of this API to regulate /// their internal scheduling. For details on what the two run modes /// are, consult the variant docs. pub enum Scheduler { /// The clocking schedule is constrained internally /// /// This corresponds to a clock type that was configured via the /// builder API, and can internally to the `ClockCtl` controller /// regulate the schedule of the selected task. The only thing /// for you to do is poll the provided Barrier. Internal(Arc<Barrier>), /// The clock needs to be controlled externally /// /// This corresponds to not setting any additional constraints on /// the `Clock` builder, and instead letting the consumer of this /// API regulate itself: the clock control is only used as a /// toggle to determine it's runtime behaviour. External { /// A delay factor that can be added to any low-power timing /// inside the reactor delay: f32, /// One half of the barrier (give to task) a: Arc<Barrier>, /// Other half of the barrier (give to scheduler) b: Arc<Barrier>, }, } impl<K> ClockCtrl<K> where K: Hash + Ord, { /// Create a new, empty clock controller pub fn new() -> Self { Self { clocks: Default::default(), } } /// Override the default clocking scheme for a particular target /// /// It's already possible to constrain clock settings witout /// setting custom bounds, just because the consumer of the /// `ClockCtl` type can fall back to some defaults when this /// builder returns an object filled with `None`. /// /// Canonically, the default constraints could be used to enable a /// low battery mode, whereas more low power embedded platforms /// can be further optimised. pub fn setup(&mut self, trgt: K) -> &mut Target { self.clocks.entry(trgt).or_insert(Target::default()) } /// Start clock scheduler for a given task /// /// This function returns a Barrier which can be used in the /// corresponding task. pub fn start(&mut self, target: K) -> Result<Scheduler, Error> { let b = Arc::new(Barrier::new(2)); match self.clocks.remove(&target) { Some(Target { interval, fence }) => match (interval, fence) { // A raw external scheduler (None, None) => Ok(Scheduler::External { delay: 1.0, a: Arc::clone(&b), b, }), // An external scheduler, with a delay modifier (Some(Interval::Delay(d)), None) => Ok(Scheduler::External { delay: d, a: Arc::clone(&b), b, }), // A linearly timed internal scheduler (Some(Interval::Timed(dur)), None) => { let a = Arc::clone(&b); task::spawn(async move { loop { task::sleep(dur).await; a.wait().await; } }); Ok(Scheduler::Internal(b)) } // A manually clock stepped fence scheduler (Some(Interval::Stepped), Some(fence)) => { let a = Arc::clone(&b); task::spawn(async move { fence(a); }); Ok(Scheduler::Internal(b)) }, (_, _) => panic!("Invalid scheduler setup"), }, None => Err(Error::NoTarget), } } }