Skip to content

Commit

Permalink
Merge pull request #179 from flipperzero-rs/duration-try-from
Browse files Browse the repository at this point in the history
Standardize use of `furi::time::Duration`
  • Loading branch information
dcoles authored Oct 22, 2024
2 parents e0e4a50 + a6d6b2d commit 70035ba
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 21 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

- `flipperzero::dialogs::DialogFileBrowserOptions`
- `flipperzero::furi::kernel` module exposing most `furi_kernel_*` APIs
- `as_ticks()` method to `flipperzero::furi::time::Duration`
- `flipperzero::furi::thread::sleep_ticks` function to sleep for exact duration
- `TryFrom<core::time::Duration>` for `flipperzero::furi::time::Duration`

### Changed

Expand All @@ -19,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Removed

- `flipperzero::furi::duration_to_ticks` in favour of `TryFrom` traits

## [0.12.0]

### Added
Expand Down
12 changes: 5 additions & 7 deletions crates/flipperzero/src/furi/message_queue.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use core::ffi::c_void;
use core::ptr::NonNull;
use core::time::Duration;

use flipperzero_sys as sys;
use flipperzero_sys::furi::{duration_to_ticks, Status};
use flipperzero_sys::furi::Status;

use crate::furi;
use crate::furi::time::Duration;

/// MessageQueue provides a safe wrapper around the furi message queue primitive.
pub struct MessageQueue<M: Sized> {
Expand All @@ -30,13 +30,12 @@ impl<M: Sized> MessageQueue<M> {
// Attempts to add the message to the end of the queue, waiting up to timeout ticks.
pub fn put(&self, msg: M, timeout: Duration) -> furi::Result<()> {
let mut msg = core::mem::ManuallyDrop::new(msg);
let timeout_ticks = duration_to_ticks(timeout);

let status: Status = unsafe {
sys::furi_message_queue_put(
self.hnd.as_ptr(),
&mut msg as *mut _ as *const c_void,
timeout_ticks,
timeout.as_ticks(),
)
.into()
};
Expand All @@ -46,13 +45,12 @@ impl<M: Sized> MessageQueue<M> {

// Attempts to read a message from the front of the queue within timeout ticks.
pub fn get(&self, timeout: Duration) -> furi::Result<M> {
let timeout_ticks = duration_to_ticks(timeout);
let mut out = core::mem::MaybeUninit::<M>::uninit();
let status: Status = unsafe {
sys::furi_message_queue_get(
self.hnd.as_ptr(),
out.as_mut_ptr() as *mut c_void,
timeout_ticks,
timeout.as_ticks(),
)
.into()
};
Expand Down Expand Up @@ -102,7 +100,7 @@ impl<M: Sized> Drop for MessageQueue<M> {

#[flipperzero_test::tests]
mod tests {
use core::time::Duration;
use super::*;

use flipperzero_sys::furi::Status;

Expand Down
29 changes: 27 additions & 2 deletions crates/flipperzero/src/furi/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use core::{
ptr::NonNull,
str,
};
use core::{time, u32};

#[cfg(feature = "alloc")]
use alloc::{
Expand All @@ -18,6 +19,8 @@ use alloc::{

use flipperzero_sys as sys;

use crate::furi::time::Duration;

#[cfg(feature = "alloc")]
const MIN_STACK_SIZE: usize = 1024;

Expand Down Expand Up @@ -191,18 +194,40 @@ pub fn yield_now() {
unsafe { sys::furi_thread_yield() };
}

/// Puts the current thread to sleep for at least the specified amount of time.
/// Puts the current thread to sleep for at least `duration`.
///
/// Durations under 1 hour are accurate to microseconds, while durations of
/// 1 hour or more are only accurate to milliseconds.
///
/// Will panic if requested to sleep for durations more than `2^32` microseconds (~49 days).
///
/// See [`sleep_ticks`] to sleep based on system timer ticks.
pub fn sleep(duration: core::time::Duration) {
if duration > time::Duration::from_millis(u32::MAX as u64) {
panic!("sleep exceeds maximum supported duration")
}

unsafe {
// For durations of 1h+, use delay_ms so uint32_t doesn't overflow
if duration < core::time::Duration::from_secs(3600) {
if duration < time::Duration::from_secs(3600) {
sys::furi_delay_us(duration.as_micros() as u32);
} else {
sys::furi_delay_ms(duration.as_millis() as u32);
}
}
}

/// Puts the current thread to sleep for at least `duration`.
///
/// The maximum supported duration is `2^32` ticks (system timer dependent).
///
/// See [`sleep`] to sleep based on arbitary duration.
pub fn sleep_ticks(duration: Duration) {
unsafe {
sys::furi_delay_tick(duration.as_ticks());
}
}

/// A unique identifier for a running thread.
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
Expand Down
50 changes: 48 additions & 2 deletions crates/flipperzero/src/furi/time.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
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;

Expand Down Expand Up @@ -270,6 +273,12 @@ impl Duration {
self.0 == 0
}

/// Duration as Furi Kernel ticks.
#[inline]
pub const fn as_ticks(&self) -> u32 {
self.0
}

/// Returns the total number of whole seconds contained by this `Duration`.
#[inline]
#[must_use]
Expand Down Expand Up @@ -465,9 +474,36 @@ impl<'a> Sum<&'a Duration> for Duration {
}
}

#[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 Duration {
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(Duration(ticks))
}
}

#[flipperzero_test::tests]
mod tests {
use super::{ticks_to_ns, Duration, Instant, MAX_INTERVAL_DURATION_TICKS};
use super::*;
use crate::println;

#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -603,7 +639,7 @@ mod tests {
// Check that the same result occurs when adding/subtracting each duration one at a time as when
// adding/subtracting them all at once.
#[track_caller]
fn check<T: Eq + Copy + core::fmt::Debug>(
fn check<T: Eq + Copy + fmt::Debug>(
start: Option<T>,
op: impl Fn(&T, Duration) -> Option<T>,
) {
Expand All @@ -623,4 +659,14 @@ mod tests {
check(instant.checked_add(Duration(100)), Instant::checked_sub);
check(instant.checked_add(Duration::MAX), Instant::checked_sub);
}

#[test]
fn duration_try_from() {
assert_eq!(Duration::try_from(time::Duration::ZERO), Ok(Duration(0)));

assert_eq!(
Duration::try_from(time::Duration::MAX),
Err(TryFromDurationError)
)
}
}
10 changes: 0 additions & 10 deletions crates/sys/src/furi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use core::ffi::c_char;
use core::fmt::Display;
use core::time::Duration;

/// Operation status.
/// The Furi API switches between using `enum FuriStatus`, `int32_t` and `uint32_t`.
Expand Down Expand Up @@ -127,12 +126,3 @@ impl<T> Drop for UnsafeRecord<T> {
}
}
}

/// Convert [`Duration`] to ticks.
#[inline]
pub fn duration_to_ticks(duration: Duration) -> u32 {
// This maxes out at about 50 days
let duration_ms: u32 = duration.as_millis().try_into().unwrap_or(u32::MAX);

unsafe { crate::furi_ms_to_ticks(duration_ms) }
}

0 comments on commit 70035ba

Please sign in to comment.