Skip to main content

nros/
time.rs

1//! Monotonic time for portable node code (issue #504).
2//!
3//! Node packages are platform-agnostic: the same crate compiles into a
4//! POSIX, FreeRTOS, or Zephyr image, so it can reach neither the
5//! per-platform timing types nor the `nros_platform_clock_ns` C export
6//! directly. This module is the portable spelling. Uses that motivated
7//! it: `dt` in control laws (assuming the nominal period silently
8//! misintegrates whenever a callback runs late), timestamping published
9//! state, and coarse in-node watchdogs.
10//!
11//! Semantics: monotonic since an unspecified epoch (process start or
12//! boot). This is explicitly NOT ROS time — no sim-time, no epoch
13//! meaning, no cross-machine comparability. Compare instants, never
14//! interpret one absolutely.
15//!
16//! Clock source mirrors the executor's timer accounting
17//! (`nros-node/src/executor/spin.rs`), and phase-359 W10 follow-up had to
18//! RESTORE that — the sentence had gone false:
19//!
20//! - **`rmw-cffi` builds** (a platform port is linked): the platform's
21//!   `nros_platform_clock_ns` export, on either flavour. Same linkage contract
22//!   the executor and the wake primitives already rely on, so this adds no new
23//!   requirement. Resolution is whatever the platform delivers (issue #502:
24//!   sub-tick on FreeRTOS Cortex-M, tick-quantized on ThreadX).
25//! - **`std` without a port**: [`std::time::Instant`], anchored at first use.
26//!   The claim that stood here — "real and shipped, the metadata probe compiles
27//!   node code with `std` and no port" — was WRONG. The probe links
28//!   `nros-platform-cffi` with `posix-c-port` and resolves `rmw-cffi`, so it
29//!   takes the arm above. This arm has no in-tree consumer: nothing calls
30//!   `now()` anywhere in the tree.
31//! - **Neither**: no clock source; this module is absent.
32//!
33//! The order used to be the other way round, `std` first, which meant every
34//! NATIVE build (std AND a port) read `Instant` here while the executor read
35//! the port — two monotonic sources with different epochs in one image, under a
36//! doc claiming they were one. W10 moved the executor onto the port whenever a
37//! port exists; this module was not moved with it. Two clocks are only harmless
38//! while nobody compares them, and the whole purpose of this module is `dt`
39//! across callbacks the executor scheduled.
40
41#![allow(clippy::module_name_repetitions)]
42
43use core::time::Duration;
44
45/// Monotonic time since an unspecified epoch.
46///
47/// ```ignore
48/// let t0 = nros::time::now();
49/// // ... work ...
50/// let dt = nros::time::now() - t0;
51/// ```
52///
53/// Reads the platform port's `nros_platform_clock_ns` (RFC-0073) — on a hosted
54/// build too, which is the point: this and the executor must be ONE clock. What
55/// the low digits are worth is per-port; ask
56/// `nros_platform_clock_resolution_ns`.
57#[cfg(feature = "rmw-cffi")]
58#[must_use]
59pub fn now() -> Duration {
60    unsafe extern "C" {
61        fn nros_platform_clock_ns() -> u64;
62    }
63    // SAFETY: bare query of the platform's monotonic us counter; the
64    // symbol is guaranteed by whichever platform port linked the
65    // binary (the same contract the executor's default timer clock
66    // depends on).
67    Duration::from_nanos(unsafe { nros_platform_clock_ns() })
68}
69
70/// Convenience: [`now`] as whole microseconds.
71#[cfg(feature = "rmw-cffi")]
72#[must_use]
73pub fn now_us() -> u64 {
74    u64::try_from(now().as_micros()).unwrap_or(u64::MAX)
75}