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),
        }
    }
}