bacnet_objects/averaging/schedule.rs
1//! When the server samples an Averaging object (Clauses 12.5.14 and 12.5.15,
2//! #1144).
3//!
4//! Samples are spaced Window_Interval / Window_Samples apart on the
5//! database's monotonic clock. The schedule restarts whenever the window is
6//! emptied, so a window filled by the server covers Window_Interval from the
7//! moment it was last reset: Attempted_Samples reaches Window_Samples only once
8//! that much time has passed (Clause 12.5.11).
9
10use std::fmt;
11use std::sync::Arc;
12use std::time::Duration;
13
14use crate::traits::MonotonicClock;
15
16/// Shortest spacing at which the server samples one Averaging object.
17///
18/// Window_Interval / Window_Samples can be well under a millisecond (one
19/// second over 1,440 samples), and every sample takes the database's write
20/// lock. A configured spacing below this floor is stretched to it, so such a
21/// window spans more than Window_Interval. Clause 12.5.14 leaves the shortest
22/// acceptable Window_Interval to the implementation.
23pub const MIN_SAMPLE_PERIOD: Duration = Duration::from_millis(100);
24
25/// The monotonic instant the next scheduled sample is due.
26#[derive(Clone, Default)]
27pub(super) struct SampleSchedule {
28 clock: Option<Arc<MonotonicClock>>,
29 /// `None` until a clock is bound.
30 next_due: Option<Duration>,
31}
32
33impl fmt::Debug for SampleSchedule {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("SampleSchedule")
36 .field("clock_bound", &self.clock.is_some())
37 .field("next_due", &self.next_due)
38 .finish()
39 }
40}
41
42impl SampleSchedule {
43 /// Bind or remove the clock and restart the schedule from its present
44 /// reading. Removing it stops scheduled sampling.
45 pub(super) fn bind(&mut self, clock: Option<Arc<MonotonicClock>>, period: Duration) {
46 self.clock = clock;
47 self.restart(period);
48 }
49
50 /// Start counting periods from now. The first sample falls one period
51 /// later, so the window holds Window_Samples attempts exactly when
52 /// Window_Interval has passed since the restart.
53 pub(super) fn restart(&mut self, period: Duration) {
54 self.next_due = self
55 .clock
56 .as_ref()
57 .map(|clock| clock().saturating_add(period));
58 }
59
60 pub(super) fn next_due(&self) -> Option<Duration> {
61 self.next_due
62 }
63
64 /// Whether a sample is due at `now`; when it is, move on to the next one.
65 ///
66 /// Due times keep a fixed cadence from the restart, so a pass that runs a
67 /// little late doesn't shift later samples. One that falls a whole period
68 /// or more behind takes a single sample and restarts the cadence from
69 /// `now` instead of catching up with a burst of samples.
70 pub(super) fn take_due(&mut self, now: Duration, period: Duration) -> bool {
71 match self.next_due {
72 Some(due) if due <= now => {
73 let next = due.saturating_add(period);
74 self.next_due = Some(if next > now {
75 next
76 } else {
77 now.saturating_add(period)
78 });
79 true
80 }
81 _ => false,
82 }
83 }
84}