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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
//! Timeframes
//!
//! A [`TimeFrame`] represents a [`Timeline`] that is split into discrete slots
//!
use crate::timeline::Timeline;
use std::time::{Duration, SystemTime};
/// Identify a slot in a *specific* timeframe
///
/// The slots are not comparable to others slots made on a
/// different time frame
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
any(test, feature = "property-test-api"),
derive(test_strategy::Arbitrary)
)]
pub struct Slot(pub u64);
impl From<u64> for Slot {
fn from(slot_number: u64) -> Slot {
Slot(slot_number)
}
}
impl From<Slot> for u64 {
fn from(s: Slot) -> u64 {
s.0
}
}
/// Identify a slot in a specific timeframe and a leftover duration
#[derive(Debug)]
pub struct SlotAndDuration {
/// The slot
pub slot: Slot,
/// The offset of a specific time frame in
pub offset: Duration,
}
/// Time frame which is a timeline that is configured to be split in discrete slots
#[derive(Debug, Clone)]
pub struct TimeFrame {
timeline: Timeline,
pub(crate) slot_offset: Slot,
slot_duration: SlotDuration,
}
/// Duration of a slot
///
/// For now we only supports duration down to the seconds
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlotDuration(u64);
impl SlotDuration {
/// Create a [`SlotDuration`] from a number of seconds
///
/// # Panics
///
/// Panics if `seconds >= 600`
pub fn from_secs(seconds: u32) -> Self {
assert!(seconds < 600);
SlotDuration(seconds as u64)
}
/// Convert this [`SlotDuration`] to a [`core::time::Duration`]
pub fn to_duration(self) -> Duration {
Duration::from_secs(self.0)
}
}
impl TimeFrame {
/// Create a new time frame with a specific slot size
///
/// ```text
///
/// 0 1 2 3 4 5
/// x--------x--------x--------x--------x--------x frame ticking at per_slot
///
/// ^
/// |
/// timeline
/// ```
///
pub fn new(timeline: Timeline, per_slot: SlotDuration) -> Self {
TimeFrame {
timeline,
slot_offset: Slot(0),
slot_duration: per_slot,
}
}
/// Change time frame at a specific slot
///
/// Note this also change the beginning of this time frame, to start
///
/// ```text
/// 0 1 2 3 4 5
/// x--------x--------┳--------x--------x--------x frame ticking at SlotDuration::from_secs(9)
/// |
/// ┕---x---x---x---x---x returned frame
/// 2 3 4 5 6 7
/// ↑
/// |
/// frame.change_frame(Slot(2), SlotDuration::from_secs(4))
/// ```
///
pub fn change_frame(&self, slot: Slot, duration_per_slot: SlotDuration) -> Self {
let d = Duration::from_secs(slot.0 * self.slot_duration.0);
let new_timeline = self.timeline.advance(d);
TimeFrame {
timeline: new_timeline,
slot_offset: Slot(self.slot_offset.0 + slot.0),
slot_duration: duration_per_slot,
}
}
/// The current slot offset
pub fn slot0(&self) -> Slot {
self.slot_offset
}
/// Given a system time get the slot and associated duration leftover
pub fn slot_at_precise(&self, at: &SystemTime) -> Option<SlotAndDuration> {
match self.timeline.differential(at) {
None => None,
Some(t) => {
let slot_nb = t.0.as_secs() / self.slot_duration.0;
let e = slot_nb * self.slot_duration.0;
let d = t.0 - Duration::from_secs(e); // cannot wrap
Some(SlotAndDuration {
slot: Slot(self.slot_offset.0 + slot_nb),
offset: d,
})
}
}
}
/// Get the slot associated with the given system time.
///
/// It returns None if the system time doesn't represent a valid slot in this time frame, for
/// example if the system time is before the time frame starting point.
pub fn slot_at(&self, at: &SystemTime) -> Option<Slot> {
match self.timeline.differential(at) {
None => None,
Some(t) => {
let slot_nb = t.0.as_secs() / self.slot_duration.0;
Some(Slot(self.slot_offset.0 + slot_nb))
}
}
}
/// Get the system time associated with a slot on a specific timeframe
///
/// Note if the slot is not supposed to be in this reference frame, then
/// None is returned
pub fn slot_to_systemtime(&self, slot: Slot) -> Option<SystemTime> {
slot.0
.checked_sub(self.slot_offset.0)
.map(|sd| self.timeline.0 + Duration::from_secs(sd * self.slot_duration.0))
}
/// Returns slot duration value.
pub fn slot_duration(&self) -> u64 {
self.slot_duration.0
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::timeline::Timeline;
#[test]
pub fn it_works() {
let now = SystemTime::now();
let t0 = Timeline::new(now);
let f0 = SlotDuration::from_secs(5);
let tf0 = TimeFrame::new(t0, f0);
{
let expected_slot = Slot(16);
let x = now + Duration::from_secs(expected_slot.0 * f0.0);
assert_eq!(tf0.slot_at(&x), Some(expected_slot));
}
let f1 = SlotDuration::from_secs(2);
let tf1_start = now + Duration::from_secs(10);
let s0 = tf0.slot_at(&tf1_start);
assert_eq!(s0, Some(Slot(2)));
let s0 = s0.unwrap();
let tf1 = tf0.change_frame(s0, f1);
assert_eq!(tf1.slot_at(&tf1_start), Some(Slot(2)));
assert_eq!(tf1.slot_at(&now), None);
let t2 = tf1_start + Duration::from_secs(10);
assert_eq!(tf1.slot_at(&t2), Some(Slot(7)));
assert_eq!(tf0.slot_at(&t2), Some(Slot(4)));
}
}