Skip to main content

frclib_core/time/
mod.rs

1//! A pluggable time interface for FRC.
2//!
3//! This allows for utilities to be written and run on any platform,
4//! not caring what it should use for a time source.
5
6mod default;
7mod instant;
8
9use std::sync::atomic::{self, AtomicBool};
10
11pub use instant::Instant;
12pub use std::time::{Duration, SystemTime};
13
14/// We don't use the unit as not to depend on the units module when we don't need to
15type U64Microsecond = u64;
16
17static mut UPTIME_SOURCE: fn() -> U64Microsecond = default::default_uptime_source;
18static mut UPTIME_PAUSE: fn(bool) = default::default_uptime_pause;
19static mut SYSTEM_TIME_VALID: fn() -> bool = || true;
20static mut IMPLEMENTATION_NAME: &str = default::DEFAULT_IMPL_NAME;
21
22static IS_PAUSED: AtomicBool = AtomicBool::new(false);
23static PAUSE_IMPLEMENTED: AtomicBool = AtomicBool::new(true);
24static TIME_IMPL_FROZEN: atomic::AtomicBool = atomic::AtomicBool::new(false);
25
26/// Gets the uptime of the program in microseconds
27///
28/// This can be manipulated with [`pause`] on platforms that support it
29#[inline]
30pub fn uptime() -> Duration {
31    TIME_IMPL_FROZEN.store(true, atomic::Ordering::Relaxed);
32    Duration::from_micros(unsafe { UPTIME_SOURCE() })
33}
34
35#[allow(missing_docs)]
36#[derive(Debug, thiserror::Error, Clone, Copy)]
37pub enum PauseError {
38    #[error("Pause not implemented for {} time source", unsafe { IMPLEMENTATION_NAME })]
39    PauseNotImplemented,
40    #[error("Cannot pause if already paused")]
41    AlreadyPaused,
42    #[error("Cannot un-pause if not paused")]
43    NotPaused,
44}
45
46/// Pauses the time with platforms that support it
47///
48/// # Errors
49///  - [`PauseError::PauseNotImplemented`] if [`pause_implemented`] returns false
50///  - [`PauseError::AlreadyPaused`] if the [`is_paused`] is true and `should_pause` is true
51///  - [`PauseError::NotPaused`] if the [`is_paused`] is false and `should_pause` is false
52#[inline]
53pub fn try_pause(should_pause: bool) -> Result<(), PauseError> {
54    if IS_PAUSED.swap(should_pause, atomic::Ordering::Relaxed) == should_pause {
55        if should_pause {
56            Err(PauseError::AlreadyPaused)
57        } else {
58            Err(PauseError::NotPaused)
59        }
60    } else if !PAUSE_IMPLEMENTED.load(atomic::Ordering::Relaxed) {
61        Err(PauseError::PauseNotImplemented)
62    } else {
63        unsafe { UPTIME_PAUSE(should_pause) };
64        Ok(())
65    }
66}
67
68/// Returns true if the time source is paused
69#[inline]
70pub fn is_paused() -> bool {
71    IS_PAUSED.load(atomic::Ordering::Relaxed)
72}
73
74/// Returns true if the time source supports pausing
75#[inline]
76pub fn pause_implemented() -> bool {
77    PAUSE_IMPLEMENTED.load(atomic::Ordering::Relaxed)
78}
79
80/// Gets the current system time if its currently valid, otherwise returns None.
81/// System time validity can changed anytime unlike time-source implementation.
82#[inline]
83pub fn system_time() -> Option<SystemTime> {
84    TIME_IMPL_FROZEN.store(true, atomic::Ordering::Relaxed);
85    if unsafe { SYSTEM_TIME_VALID() } {
86        Some(SystemTime::now())
87    } else {
88        None
89    }
90}
91
92#[doc(hidden)]
93pub mod __private {
94
95    #[derive(Debug, Clone, Copy)]
96    pub struct TimeImplementation {
97        pub implementation_name: &'static str,
98        /// A custom monotomic timestamp imlementation
99        pub uptime: fn() -> super::U64Microsecond,
100        /// A custom pause implementation,
101        /// this is allowed to panic.
102        pub pause: Option<fn(bool) -> ()>,
103        /// A function returning whether the system time is valid.
104        pub system_time_valid: fn() -> bool,
105    }
106
107    ///This is called by the HAL to set the time implementation,
108    ///could be called in other places but is not recommended.
109    ///
110    /// # Safety
111    /// - This function is not thread safe and should only be called once
112    ///
113    /// # Panics
114    /// - If called more than once
115    pub unsafe fn set_time_implementation(time_imp: TimeImplementation) {
116        use std::sync::atomic::Ordering;
117        assert!(
118            !super::TIME_IMPL_FROZEN.swap(true, Ordering::SeqCst),
119            "Cannot set time source after it has been used or previously set(old: {}, new: {})",
120            super::IMPLEMENTATION_NAME,
121            time_imp.implementation_name
122        );
123        super::UPTIME_SOURCE = time_imp.uptime;
124        super::IMPLEMENTATION_NAME = time_imp.implementation_name;
125        super::SYSTEM_TIME_VALID = time_imp.system_time_valid;
126        if let Some(pause) = time_imp.pause {
127            super::PAUSE_IMPLEMENTED.store(true, Ordering::SeqCst);
128            super::UPTIME_PAUSE = pause;
129        } else {
130            super::PAUSE_IMPLEMENTED.store(false, Ordering::SeqCst);
131            super::UPTIME_PAUSE = |_| {};
132        }
133    }
134}
135
136#[cfg(test)]
137mod test {
138    use std::thread;
139
140    fn test_time() {
141        use super::*;
142        let start = uptime().as_micros();
143        thread::sleep(Duration::from_millis(100));
144        let end = uptime().as_micros();
145        assert!(end.saturating_sub(start) >= 100_000);
146    }
147
148    fn test_pause() {
149        use super::*;
150        try_pause(true).expect("Pause Error");
151        let start = uptime().as_micros();
152        thread::sleep(Duration::from_millis(1000));
153        let end = uptime().as_micros();
154        // assert!(end + 5 - start < 100);
155        assert!(end.saturating_sub(start) < 100);
156        try_pause(false).expect("Pause Error");
157        thread::sleep(Duration::from_millis(1000));
158        let end = uptime().as_micros();
159        assert!(end.saturating_sub(start) >= 1_000_000);
160    }
161
162    /// Tests all of the time functions in one thread to make it sequential
163    /// and not parallel messing up global state
164    #[test]
165    fn test_all() {
166        test_time();
167        test_pause();
168    }
169}