use core::cmp::Ordering;
use core::error;
use core::fmt;
use core::iter::Sum;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
use core::time;
use ufmt::derive::uDebug;
use crate::furi;
const MAX_INTERVAL_DURATION_TICKS: u32 = u32::MAX / 2;
const NANOS_PER_SEC_F: f64 = 1_000_000_000_f64;
const NANOS_PER_SEC: u64 = 1_000_000_000;
const NANOS_PER_MILLI: u64 = 1_000_000;
const NANOS_PER_MICRO: u64 = 1_000;
const MILLIS_PER_SEC: u32 = 1_000;
fn ns_to_ticks(nanos: u64) -> u64 {
let rate = furi::kernel::get_tick_frequency();
if rate == MILLIS_PER_SEC {
nanos / NANOS_PER_MILLI
} else {
((f64::from(rate) / NANOS_PER_SEC_F) * nanos as f64) as u64
}
}
fn ticks_to_ns(ticks: u32) -> u64 {
let rate = furi::kernel::get_tick_frequency();
if rate == MILLIS_PER_SEC {
(ticks as u64) * NANOS_PER_MILLI
} else {
((NANOS_PER_SEC_F / f64::from(rate)) * ticks as f64) as u64
}
}
#[derive(Copy, Clone, Debug, uDebug, PartialEq, Eq, Hash)]
pub struct FuriInstant(pub(super) u32);
impl FuriInstant {
#[must_use]
pub fn now() -> FuriInstant {
FuriInstant(furi::kernel::get_tick())
}
#[must_use]
pub fn duration_since(&self, earlier: FuriInstant) -> FuriDuration {
self.checked_duration_since(earlier)
.expect("earlier is later than self")
}
#[must_use]
pub fn checked_duration_since(&self, earlier: FuriInstant) -> Option<FuriDuration> {
if self >= &earlier {
Some(FuriDuration(self.0.wrapping_sub(earlier.0)))
} else {
None
}
}
#[must_use]
pub fn saturating_duration_since(&self, earlier: FuriInstant) -> FuriDuration {
self.checked_duration_since(earlier).unwrap_or_default()
}
#[must_use]
pub fn elapsed(&self) -> FuriDuration {
FuriInstant::now()
.checked_duration_since(*self)
.unwrap_or(FuriDuration::MAX)
}
pub fn checked_add(&self, duration: FuriDuration) -> Option<FuriInstant> {
if duration.0 <= MAX_INTERVAL_DURATION_TICKS {
Some(FuriInstant(self.0.wrapping_add(duration.0)))
} else {
None
}
}
pub fn checked_sub(&self, duration: FuriDuration) -> Option<FuriInstant> {
if duration.0 <= MAX_INTERVAL_DURATION_TICKS {
Some(FuriInstant(self.0.wrapping_sub(duration.0)))
} else {
None
}
}
}
impl PartialOrd for FuriInstant {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FuriInstant {
fn cmp(&self, other: &Self) -> Ordering {
if self.0 == other.0 {
Ordering::Equal
} else {
self.0
.wrapping_sub(other.0)
.cmp(&MAX_INTERVAL_DURATION_TICKS)
.reverse()
}
}
}
impl Add<FuriDuration> for FuriInstant {
type Output = FuriInstant;
fn add(self, other: FuriDuration) -> FuriInstant {
self.checked_add(other)
.expect("overflow when adding duration to instant")
}
}
impl AddAssign<FuriDuration> for FuriInstant {
fn add_assign(&mut self, other: FuriDuration) {
*self = *self + other;
}
}
impl Sub<FuriDuration> for FuriInstant {
type Output = FuriInstant;
fn sub(self, other: FuriDuration) -> FuriInstant {
self.checked_sub(other)
.expect("overflow when subtracting duration from instant")
}
}
impl SubAssign<FuriDuration> for FuriInstant {
fn sub_assign(&mut self, other: FuriDuration) {
*self = *self - other;
}
}
impl Sub<FuriInstant> for FuriInstant {
type Output = FuriDuration;
fn sub(self, other: FuriInstant) -> FuriDuration {
self.duration_since(other)
}
}
#[derive(Clone, Copy, Debug, uDebug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FuriDuration(pub(super) u32);
impl FuriDuration {
pub const ZERO: FuriDuration = FuriDuration(0);
pub const MAX: FuriDuration = FuriDuration(u32::MAX);
pub const WAIT_FOREVER: FuriDuration = FuriDuration(0xFFFFFFFF);
#[inline]
#[must_use]
pub fn from_secs(secs: u64) -> FuriDuration {
let ticks = ns_to_ticks(secs * NANOS_PER_SEC);
let ticks = u32::try_from(ticks).expect("Duration is too long");
FuriDuration(ticks)
}
#[inline]
#[must_use]
pub fn from_millis(millis: u64) -> FuriDuration {
let ticks = ns_to_ticks(millis * NANOS_PER_MILLI);
let ticks = u32::try_from(ticks).expect("Duration is too long");
FuriDuration(ticks)
}
#[inline]
#[must_use]
pub fn from_micros(micros: u64) -> FuriDuration {
let ticks = ns_to_ticks(micros * NANOS_PER_MICRO);
let ticks = u32::try_from(ticks).expect("Duration is too long");
FuriDuration(ticks)
}
#[inline]
#[must_use]
pub fn from_nanos(nanos: u64) -> FuriDuration {
let ticks = ns_to_ticks(nanos);
let ticks = u32::try_from(ticks).expect("Duration is too long");
FuriDuration(ticks)
}
#[inline]
#[must_use]
pub const fn is_zero(&self) -> bool {
self.0 == 0
}
#[inline]
pub const fn as_ticks(&self) -> u32 {
self.0
}
#[inline]
#[must_use]
pub fn as_secs(&self) -> u64 {
self.as_nanos() / NANOS_PER_SEC
}
#[inline]
#[must_use]
pub fn as_millis(&self) -> u64 {
self.as_nanos() / NANOS_PER_MILLI
}
#[inline]
#[must_use]
pub fn as_micros(&self) -> u64 {
self.as_nanos() / NANOS_PER_MICRO
}
#[inline]
#[must_use]
pub fn as_nanos(&self) -> u64 {
ticks_to_ns(self.0)
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn checked_add(self, rhs: FuriDuration) -> Option<FuriDuration> {
if let Some(ticks) = self.0.checked_add(rhs.0) {
Some(FuriDuration(ticks))
} else {
None
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn saturating_add(self, rhs: FuriDuration) -> FuriDuration {
match self.checked_add(rhs) {
Some(res) => res,
None => FuriDuration::MAX,
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn checked_sub(self, rhs: FuriDuration) -> Option<FuriDuration> {
if let Some(ticks) = self.0.checked_sub(rhs.0) {
Some(FuriDuration(ticks))
} else {
None
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn saturating_sub(self, rhs: FuriDuration) -> FuriDuration {
match self.checked_sub(rhs) {
Some(res) => res,
None => FuriDuration::ZERO,
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn checked_mul(self, rhs: u32) -> Option<FuriDuration> {
if let Some(ticks) = self.0.checked_mul(rhs) {
Some(FuriDuration(ticks))
} else {
None
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn saturating_mul(self, rhs: u32) -> FuriDuration {
match self.checked_mul(rhs) {
Some(res) => res,
None => FuriDuration::MAX,
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub const fn checked_div(self, rhs: u32) -> Option<FuriDuration> {
if rhs != 0 {
let ticks = self.0 / rhs;
Some(FuriDuration(ticks))
} else {
None
}
}
}
impl Add for FuriDuration {
type Output = FuriDuration;
fn add(self, rhs: FuriDuration) -> FuriDuration {
self.checked_add(rhs)
.expect("overflow when adding durations")
}
}
impl AddAssign for FuriDuration {
fn add_assign(&mut self, rhs: FuriDuration) {
*self = *self + rhs;
}
}
impl Sub for FuriDuration {
type Output = FuriDuration;
fn sub(self, rhs: FuriDuration) -> FuriDuration {
self.checked_sub(rhs)
.expect("overflow when subtracting durations")
}
}
impl SubAssign for FuriDuration {
fn sub_assign(&mut self, rhs: FuriDuration) {
*self = *self - rhs;
}
}
impl Mul<u32> for FuriDuration {
type Output = FuriDuration;
fn mul(self, rhs: u32) -> FuriDuration {
self.checked_mul(rhs)
.expect("overflow when multiplying duration by scalar")
}
}
impl Mul<FuriDuration> for u32 {
type Output = FuriDuration;
fn mul(self, rhs: FuriDuration) -> FuriDuration {
rhs * self
}
}
impl MulAssign<u32> for FuriDuration {
fn mul_assign(&mut self, rhs: u32) {
*self = *self * rhs;
}
}
impl Div<u32> for FuriDuration {
type Output = FuriDuration;
fn div(self, rhs: u32) -> FuriDuration {
self.checked_div(rhs)
.expect("divide by zero error when dividing duration by scalar")
}
}
impl DivAssign<u32> for FuriDuration {
fn div_assign(&mut self, rhs: u32) {
*self = *self / rhs;
}
}
impl Sum for FuriDuration {
fn sum<I: Iterator<Item = FuriDuration>>(iter: I) -> FuriDuration {
FuriDuration(iter.map(|d| d.0).sum())
}
}
impl<'a> Sum<&'a FuriDuration> for FuriDuration {
fn sum<I: Iterator<Item = &'a FuriDuration>>(iter: I) -> FuriDuration {
FuriDuration(iter.map(|d| d.0).sum())
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, uDebug)]
pub struct TryFromDurationError;
impl error::Error for TryFromDurationError {}
impl fmt::Display for TryFromDurationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("duration exceeds supported representation")
}
}
impl TryFrom<time::Duration> for FuriDuration {
type Error = TryFromDurationError;
fn try_from(value: time::Duration) -> Result<Self, Self::Error> {
let nanos: u64 = value
.as_nanos()
.try_into()
.map_err(|_| TryFromDurationError)?;
let ticks: u32 = ns_to_ticks(nanos)
.try_into()
.map_err(|_| TryFromDurationError)?;
Ok(FuriDuration(ticks))
}
}
#[flipperzero_test::tests]
mod tests {
use super::*;
use crate::println;
#[cfg(feature = "alloc")]
use {crate::furi::thread, alloc::vec::Vec};
macro_rules! assert_almost_eq {
($a:expr, $b:expr) => {{
let (a, b) = ($a, $b);
if a != b {
let (a, b) = if a > b { (a, b) } else { (b, a) };
assert!(
a - FuriDuration::from_micros(1) <= b,
"{:?} is not almost equal to {:?}",
a,
b
);
}
}};
}
#[test]
fn instant_increases() {
let a = FuriInstant::now();
loop {
let b = FuriInstant::now();
assert!(b >= a);
if b > a {
break;
}
}
}
#[cfg(feature = "alloc")]
#[test]
fn instant_increases_concurrent() {
let threads: Vec<_> = (0..8)
.map(|_| {
thread::spawn(|| {
let mut old = FuriInstant::now();
let count = 1_000; for _ in 0..count {
let new = FuriInstant::now();
assert!(new >= old);
old = new;
}
0
})
})
.collect();
for t in threads {
t.join();
}
}
#[test]
fn instant_elapsed() {
let a = FuriInstant::now();
let _ = a.elapsed();
}
#[test]
fn instant_math() {
let a = FuriInstant::now();
let b = FuriInstant::now();
println!("a: {:?}", a);
println!("b: {:?}", b);
let dur = b.duration_since(a);
println!("dur: {} ns", dur.as_nanos());
assert_almost_eq!(b - dur, a);
assert_almost_eq!(a + dur, b);
let second = FuriDuration::from_secs(1);
assert_almost_eq!(a - second + second, a);
assert_almost_eq!(
a.checked_sub(second).unwrap().checked_add(second).unwrap(),
a
);
let mut maybe_t = Some(FuriInstant::now());
let max_duration = FuriDuration::from_nanos(ticks_to_ns(u32::MAX));
for _ in 0..2 {
maybe_t = maybe_t.and_then(|t| t.checked_add(max_duration));
}
assert_eq!(maybe_t, None);
let week = FuriDuration::from_secs(60 * 60 * 24 * 7);
assert_eq!(a + week, a.checked_add(week).unwrap());
}
#[test]
fn instant_math_is_associative() {
let now = FuriInstant::now();
let offset = FuriDuration::from_millis(5);
assert_eq!((now + offset) - now, (now - now) + offset);
let tick_nanos = ticks_to_ns(1);
let now = FuriInstant::now();
let provided_offset = FuriDuration::from_nanos(tick_nanos);
let later = now + provided_offset;
let measured_offset = later - now;
assert_eq!(measured_offset, provided_offset);
}
#[test]
fn instant_checked_duration_since_nopanic() {
let now = FuriInstant::now();
let earlier = now - FuriDuration::from_secs(1);
let later = now + FuriDuration::from_secs(1);
assert_eq!(earlier.checked_duration_since(now), None);
assert_eq!(
later.checked_duration_since(now),
Some(FuriDuration::from_secs(1))
);
assert_eq!(now.checked_duration_since(now), Some(FuriDuration::ZERO));
}
#[test]
fn instant_saturating_duration_since_nopanic() {
let a = FuriInstant::now();
let ret = (a - FuriDuration::from_secs(1)).saturating_duration_since(a);
assert_eq!(ret, FuriDuration::ZERO);
}
#[test]
fn big_math() {
#[track_caller]
fn check<T: Eq + Copy + fmt::Debug>(
start: Option<T>,
op: impl Fn(&T, FuriDuration) -> Option<T>,
) {
const DURATIONS: [FuriDuration; 2] = [
FuriDuration(MAX_INTERVAL_DURATION_TICKS >> 1),
FuriDuration(50),
];
if let Some(start) = start {
assert_eq!(
op(&start, DURATIONS.into_iter().sum()),
DURATIONS.into_iter().try_fold(start, |t, d| op(&t, d))
)
}
}
let instant = FuriInstant::now();
check(
instant.checked_sub(FuriDuration(100)),
FuriInstant::checked_add,
);
check(
instant.checked_sub(FuriDuration::MAX),
FuriInstant::checked_add,
);
check(
instant.checked_add(FuriDuration(100)),
FuriInstant::checked_sub,
);
check(
instant.checked_add(FuriDuration::MAX),
FuriInstant::checked_sub,
);
}
#[test]
fn duration_try_from() {
assert_eq!(
FuriDuration::try_from(time::Duration::ZERO),
Ok(FuriDuration(0))
);
assert_eq!(
FuriDuration::try_from(time::Duration::MAX),
Err(TryFromDurationError)
)
}
}