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