Skip to main content

nros_core/
clock.rs

1//! Clock API for nros
2//!
3//! This module provides clock abstraction for different time sources:
4//! - **SystemTime**: Wall clock time (affected by system time changes)
5//! - **SteadyTime**: Monotonic time (not affected by system time changes)
6//! - **RosTime**: Simulation time (can be paused/scaled)
7//!
8//! # Example
9//!
10//! ```text
11//! use nros::clock::{Clock, ClockType};
12//!
13//! // Create a system clock
14//! let clock = Clock::system();
15//! let now = clock.now();
16//! println!("Current time: {} sec", now.sec);
17//!
18//! // Create a steady clock for measuring durations
19//! let clock = Clock::steady();
20//! let start = clock.now();
21//! // ... do work ...
22//! let elapsed = clock.now() - start;
23//! ```
24//!
25//! # no_std Support
26//!
27//! Without the `std` feature, clocks return time based on an internal
28//! counter that must be updated manually via `update_time()`. This is
29//! suitable for embedded systems with RTIC or bare-metal polling loops.
30
31use crate::time::Time;
32
33// AtomicI64 is not available on all platforms (e.g., thumbv7em-none-eabihf)
34// Use AtomicI64 when available, otherwise use a simpler approach
35#[cfg(target_has_atomic = "64")]
36use core::sync::atomic::{AtomicI64, Ordering};
37
38// For platforms without 64-bit atomics, use two 32-bit values
39#[cfg(not(target_has_atomic = "64"))]
40use core::sync::atomic::{AtomicI32, Ordering};
41
42/// Type of clock to use for time queries
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub enum ClockType {
45    /// System time (wall clock)
46    ///
47    /// This clock reflects the system's real-time clock and may be
48    /// affected by NTP adjustments, user changes, or daylight saving time.
49    /// Use for timestamps that need to correlate with real-world time.
50    #[default]
51    SystemTime,
52
53    /// Steady/monotonic time
54    ///
55    /// This clock is guaranteed to be monotonically increasing and is
56    /// not affected by system time changes. Use for measuring durations
57    /// and timeouts.
58    SteadyTime,
59
60    /// ROS time (simulation time)
61    ///
62    /// This clock can be overridden for simulation purposes. When a
63    /// ROS time override is active, `now()` returns the overridden time.
64    /// Otherwise, it falls back to system time.
65    RosTime,
66}
67
68// On platforms with 64-bit atomics, use AtomicI64 directly
69#[cfg(target_has_atomic = "64")]
70mod atomic_time {
71    use super::*;
72
73    /// Global ROS time override (nanoseconds since epoch)
74    /// When set to a non-negative value, `Clock::now()` for `RosTime` clocks
75    /// will return this value instead of system time.
76    pub(super) static ROS_TIME_OVERRIDE_NANOS: AtomicI64 = AtomicI64::new(-1);
77
78    /// Global steady time counter (nanoseconds)
79    /// For `no_std` environments, this counter must be updated manually.
80    pub(super) static STEADY_TIME_NANOS: AtomicI64 = AtomicI64::new(0);
81
82    pub(super) fn get_ros_override() -> i64 {
83        ROS_TIME_OVERRIDE_NANOS.load(Ordering::Relaxed)
84    }
85
86    pub(super) fn set_ros_override(nanos: i64) {
87        ROS_TIME_OVERRIDE_NANOS.store(nanos, Ordering::Relaxed);
88    }
89
90    pub(super) fn get_steady() -> i64 {
91        STEADY_TIME_NANOS.load(Ordering::Relaxed)
92    }
93
94    pub(super) fn set_steady(nanos: i64) {
95        STEADY_TIME_NANOS.store(nanos, Ordering::Relaxed);
96    }
97
98    pub(super) fn add_steady(delta: i64) {
99        STEADY_TIME_NANOS.fetch_add(delta, Ordering::Relaxed);
100    }
101}
102
103// On platforms without 64-bit atomics, use split 32-bit values
104// Note: This is not fully atomic but works for single-threaded embedded contexts
105#[cfg(not(target_has_atomic = "64"))]
106mod atomic_time {
107    use super::*;
108
109    // Split into high and low 32-bit parts
110    static ROS_TIME_OVERRIDE_LOW: AtomicI32 = AtomicI32::new(-1);
111    static ROS_TIME_OVERRIDE_HIGH: AtomicI32 = AtomicI32::new(-1);
112    static STEADY_TIME_LOW: AtomicI32 = AtomicI32::new(0);
113    static STEADY_TIME_HIGH: AtomicI32 = AtomicI32::new(0);
114
115    pub(super) fn get_ros_override() -> i64 {
116        let high = ROS_TIME_OVERRIDE_HIGH.load(Ordering::Relaxed);
117        let low = ROS_TIME_OVERRIDE_LOW.load(Ordering::Relaxed);
118        if high < 0 {
119            -1
120        } else {
121            ((high as i64) << 32) | (low as u32 as i64)
122        }
123    }
124
125    pub(super) fn set_ros_override(nanos: i64) {
126        if nanos < 0 {
127            ROS_TIME_OVERRIDE_HIGH.store(-1, Ordering::Relaxed);
128            ROS_TIME_OVERRIDE_LOW.store(-1, Ordering::Relaxed);
129        } else {
130            ROS_TIME_OVERRIDE_HIGH.store((nanos >> 32) as i32, Ordering::Relaxed);
131            ROS_TIME_OVERRIDE_LOW.store(nanos as i32, Ordering::Relaxed);
132        }
133    }
134
135    pub(super) fn get_steady() -> i64 {
136        let high = STEADY_TIME_HIGH.load(Ordering::Relaxed);
137        let low = STEADY_TIME_LOW.load(Ordering::Relaxed);
138        ((high as i64) << 32) | (low as u32 as i64)
139    }
140
141    pub(super) fn set_steady(nanos: i64) {
142        STEADY_TIME_HIGH.store((nanos >> 32) as i32, Ordering::Relaxed);
143        STEADY_TIME_LOW.store(nanos as i32, Ordering::Relaxed);
144    }
145
146    pub(super) fn add_steady(delta: i64) {
147        let current = get_steady();
148        set_steady(current.saturating_add(delta));
149    }
150}
151
152/// A clock for querying time
153///
154/// Clocks provide access to different time sources. Each node typically
155/// has an associated clock, but you can also create standalone clocks.
156#[derive(Debug, Clone, Copy)]
157pub struct Clock {
158    clock_type: ClockType,
159}
160
161impl Default for Clock {
162    fn default() -> Self {
163        Self::system()
164    }
165}
166
167impl Clock {
168    /// Create a new clock of the specified type
169    pub const fn new(clock_type: ClockType) -> Self {
170        Self { clock_type }
171    }
172
173    /// Create a system time clock
174    ///
175    /// System time reflects the real-world wall clock time.
176    pub const fn system() -> Self {
177        Self {
178            clock_type: ClockType::SystemTime,
179        }
180    }
181
182    /// Create a steady (monotonic) time clock
183    ///
184    /// Steady time is guaranteed to only increase and is not affected
185    /// by system time changes.
186    pub const fn steady() -> Self {
187        Self {
188            clock_type: ClockType::SteadyTime,
189        }
190    }
191
192    /// Create a ROS time clock
193    ///
194    /// ROS time can be overridden for simulation. When no override is
195    /// active, it returns system time.
196    pub const fn ros_time() -> Self {
197        Self {
198            clock_type: ClockType::RosTime,
199        }
200    }
201
202    /// Get the clock type
203    pub const fn clock_type(&self) -> ClockType {
204        self.clock_type
205    }
206
207    /// Get the current time from this clock
208    ///
209    /// # Platform behavior
210    ///
211    /// - **With `std`**: Uses `std::time::SystemTime` or `std::time::Instant`
212    /// - **Without `std`**: Uses internal counters that must be updated manually
213    #[cfg(feature = "std")]
214    pub fn now(&self) -> Time {
215        match self.clock_type {
216            ClockType::SystemTime => {
217                let duration = std::time::SystemTime::now()
218                    .duration_since(std::time::UNIX_EPOCH)
219                    .unwrap_or_default();
220                Time::new(duration.as_secs() as i32, duration.subsec_nanos())
221            }
222            ClockType::SteadyTime => {
223                // Use the atomic counter for steady time
224                let nanos = atomic_time::get_steady();
225                Time::from_nanos(nanos)
226            }
227            ClockType::RosTime => {
228                let override_nanos = atomic_time::get_ros_override();
229                if override_nanos >= 0 {
230                    Time::from_nanos(override_nanos)
231                } else {
232                    // Fall back to system time
233                    Clock::system().now()
234                }
235            }
236        }
237    }
238
239    /// Get the current time from this clock (no_std version)
240    ///
241    /// Returns time based on internal counters. You must call
242    /// `update_steady_time()` periodically to advance steady time.
243    #[cfg(not(feature = "std"))]
244    pub fn now(&self) -> Time {
245        match self.clock_type {
246            ClockType::SystemTime | ClockType::SteadyTime => {
247                let nanos = atomic_time::get_steady();
248                Time::from_nanos(nanos)
249            }
250            ClockType::RosTime => {
251                let override_nanos = atomic_time::get_ros_override();
252                if override_nanos >= 0 {
253                    Time::from_nanos(override_nanos)
254                } else {
255                    let nanos = atomic_time::get_steady();
256                    Time::from_nanos(nanos)
257                }
258            }
259        }
260    }
261
262    /// Set a ROS time override
263    ///
264    /// When set, all `RosTime` clocks will return this time instead of
265    /// system time. This is useful for simulation.
266    ///
267    /// # Arguments
268    /// * `nanos` - Nanoseconds since epoch
269    pub fn set_ros_time_override(nanos: i64) {
270        atomic_time::set_ros_override(nanos);
271    }
272
273    /// Set a ROS time override from a Time value
274    pub fn set_ros_time_override_time(time: Time) {
275        Self::set_ros_time_override(time.to_nanos());
276    }
277
278    /// Clear the ROS time override
279    ///
280    /// After clearing, `RosTime` clocks will return system time again.
281    pub fn clear_ros_time_override() {
282        atomic_time::set_ros_override(-1);
283    }
284
285    /// Check if a ROS time override is active
286    pub fn is_ros_time_override_active() -> bool {
287        atomic_time::get_ros_override() >= 0
288    }
289
290    /// Get the current ROS time override value (if active)
291    pub fn get_ros_time_override() -> Option<Time> {
292        let nanos = atomic_time::get_ros_override();
293        if nanos >= 0 {
294            Some(Time::from_nanos(nanos))
295        } else {
296            None
297        }
298    }
299
300    /// Update the steady time counter
301    ///
302    /// For `no_std` environments, call this periodically from your
303    /// main loop or RTIC task to advance the steady clock.
304    ///
305    /// # Arguments
306    /// * `delta_nanos` - Nanoseconds elapsed since last call
307    pub fn update_steady_time(delta_nanos: i64) {
308        atomic_time::add_steady(delta_nanos);
309    }
310
311    /// Update the steady time counter (milliseconds version)
312    ///
313    /// Convenience method for RTIC tasks using millisecond intervals.
314    ///
315    /// # Arguments
316    /// * `delta_ms` - Milliseconds elapsed since last call
317    pub fn update_steady_time_ms(delta_ms: u64) {
318        let delta_nanos = delta_ms as i64 * 1_000_000;
319        Self::update_steady_time(delta_nanos);
320    }
321
322    /// Set the steady time counter to a specific value
323    ///
324    /// Use this to initialize the clock or synchronize with an external
325    /// time source.
326    pub fn set_steady_time(nanos: i64) {
327        atomic_time::set_steady(nanos);
328    }
329
330    /// Get the current steady time counter value
331    pub fn get_steady_time_nanos() -> i64 {
332        atomic_time::get_steady()
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn test_clock_type_default() {
342        let clock_type = ClockType::default();
343        assert_eq!(clock_type, ClockType::SystemTime);
344    }
345
346    #[test]
347    fn test_clock_constructors() {
348        let system = Clock::system();
349        assert_eq!(system.clock_type(), ClockType::SystemTime);
350
351        let steady = Clock::steady();
352        assert_eq!(steady.clock_type(), ClockType::SteadyTime);
353
354        let ros = Clock::ros_time();
355        assert_eq!(ros.clock_type(), ClockType::RosTime);
356
357        let custom = Clock::new(ClockType::SteadyTime);
358        assert_eq!(custom.clock_type(), ClockType::SteadyTime);
359    }
360
361    #[test]
362    fn test_clock_default() {
363        let clock = Clock::default();
364        assert_eq!(clock.clock_type(), ClockType::SystemTime);
365    }
366
367    #[test]
368    fn test_ros_time_override() {
369        // Clear any existing override
370        Clock::clear_ros_time_override();
371        assert!(!Clock::is_ros_time_override_active());
372        assert!(Clock::get_ros_time_override().is_none());
373
374        // Set override
375        let override_time = Time::new(1234567890, 123456789);
376        Clock::set_ros_time_override_time(override_time);
377        assert!(Clock::is_ros_time_override_active());
378        assert_eq!(Clock::get_ros_time_override(), Some(override_time));
379
380        // ROS clock should return override time
381        let ros_clock = Clock::ros_time();
382        let now = ros_clock.now();
383        assert_eq!(now, override_time);
384
385        // Clear override
386        Clock::clear_ros_time_override();
387        assert!(!Clock::is_ros_time_override_active());
388    }
389
390    #[test]
391    fn test_steady_time_update() {
392        // Reset steady time
393        Clock::set_steady_time(0);
394        assert_eq!(Clock::get_steady_time_nanos(), 0);
395
396        // Update by milliseconds
397        Clock::update_steady_time_ms(100);
398        assert_eq!(Clock::get_steady_time_nanos(), 100_000_000);
399
400        // Update by nanoseconds
401        Clock::update_steady_time(500_000_000);
402        assert_eq!(Clock::get_steady_time_nanos(), 600_000_000);
403
404        // Steady clock should return updated time
405        let steady_clock = Clock::steady();
406        let now = steady_clock.now();
407        assert_eq!(now.to_nanos(), 600_000_000);
408    }
409
410    #[test]
411    #[cfg(feature = "std")]
412    #[cfg_attr(miri, ignore)] // Miri doesn't support clock_gettime with isolation
413    fn test_system_clock_returns_nonzero() {
414        let clock = Clock::system();
415        let now = clock.now();
416        // System time should be after Unix epoch (positive)
417        assert!(now.sec > 0);
418    }
419}