aboutsummaryrefslogtreecommitdiff
path: root/rtic-time/tests
diff options
context:
space:
mode:
authorFinomnis <Finomnis@users.noreply.github.com>2024-04-11 00:00:38 +0200
committerGitHub <noreply@github.com>2024-04-10 22:00:38 +0000
commit8c23e178f3838bcdd13662a2ffefd39ec144e869 (patch)
treef2d2cefcd6bb2876e74ee6035b5489a4a2d9590f /rtic-time/tests
parente4cc5fd17b8a2df332af0ee25c8bd7092e66afb0 (diff)
Monotonic rewrite (#874)
* Rework timer_queue and monotonic architecture Goals: * make Monotonic purely internal * make Monotonic purely tick passed, no fugit involved * create a wrapper struct in the user's code via a macro that then converts the "now" from the tick based monotonic to a fugit based timestamp We need to proxy the delay functions of the timer queue anyway, so we could simply perform the conversion in those proxy functions. * Update cargo.lock * Update readme of rtic-time * CI: ESP32: Redact esp_image: Too volatile * Fixup: Changelog double entry rebase mistake --------- Co-authored-by: Henrik Tjäder <henrik@tjaders.com>
Diffstat (limited to 'rtic-time/tests')
-rw-r--r--rtic-time/tests/delay_precision_subtick.rs101
-rw-r--r--rtic-time/tests/timer_queue.rs194
2 files changed, 104 insertions, 191 deletions
diff --git a/rtic-time/tests/delay_precision_subtick.rs b/rtic-time/tests/delay_precision_subtick.rs
index a4ef4bb..a415b65 100644
--- a/rtic-time/tests/delay_precision_subtick.rs
+++ b/rtic-time/tests/delay_precision_subtick.rs
@@ -15,46 +15,31 @@ use std::{
time::Duration,
};
-use ::fugit::ExtU64Ceil;
use cooked_waker::{IntoWaker, WakeRef};
+use fugit::ExtU64Ceil;
use parking_lot::Mutex;
-use rtic_time::{Monotonic, TimeoutError, TimerQueue};
+use rtic_time::{
+ monotonic::TimerQueueBasedMonotonic,
+ timer_queue::{TimerQueue, TimerQueueBackend},
+ Monotonic,
+};
const SUBTICKS_PER_TICK: u32 = 10;
struct SubtickTestTimer;
-static TIMER_QUEUE: TimerQueue<SubtickTestTimer> = TimerQueue::new();
+struct SubtickTestTimerBackend;
+static TIMER_QUEUE: TimerQueue<SubtickTestTimerBackend> = TimerQueue::new();
static NOW_SUBTICKS: AtomicU64 = AtomicU64::new(0);
static COMPARE_TICKS: Mutex<Option<u64>> = Mutex::new(None);
-impl Monotonic for SubtickTestTimer {
- const ZERO: Self::Instant = Self::Instant::from_ticks(0);
- const TICK_PERIOD: Self::Duration = Self::Duration::from_ticks(1);
-
- type Instant = fugit::Instant<u64, SUBTICKS_PER_TICK, 1000>;
- type Duration = fugit::Duration<u64, SUBTICKS_PER_TICK, 1000>;
-
- fn now() -> Self::Instant {
- Self::Instant::from_ticks(
- NOW_SUBTICKS.load(Ordering::Relaxed) / u64::from(SUBTICKS_PER_TICK),
- )
- }
-
- fn set_compare(instant: Self::Instant) {
- *COMPARE_TICKS.lock() = Some(instant.ticks());
- }
-
- fn clear_compare_flag() {}
-
- fn pend_interrupt() {
- unsafe {
- Self::__tq().on_monotonic_interrupt();
- }
+impl SubtickTestTimer {
+ pub fn init() {
+ SubtickTestTimerBackend::init();
}
}
-impl SubtickTestTimer {
- pub fn init() {
- Self::__tq().initialize(Self)
+impl SubtickTestTimerBackend {
+ fn init() {
+ Self::timer_queue().initialize(Self)
}
pub fn tick() -> u64 {
@@ -70,7 +55,7 @@ impl SubtickTestTimer {
// );
if subticks == 0 && Some(ticks) == *compare {
unsafe {
- Self::__tq().on_monotonic_interrupt();
+ Self::timer_queue().on_monotonic_interrupt();
}
}
@@ -85,29 +70,41 @@ impl SubtickTestTimer {
pub fn now_subticks() -> u64 {
NOW_SUBTICKS.load(Ordering::Relaxed)
}
+}
- fn __tq() -> &'static TimerQueue<Self> {
- &TIMER_QUEUE
+impl TimerQueueBackend for SubtickTestTimerBackend {
+ type Ticks = u64;
+
+ fn now() -> Self::Ticks {
+ NOW_SUBTICKS.load(Ordering::Relaxed) / u64::from(SUBTICKS_PER_TICK)
}
- /// Delay for some duration of time.
- #[inline]
- pub async fn delay(duration: <Self as Monotonic>::Duration) {
- Self::__tq().delay(duration).await;
+ fn set_compare(instant: Self::Ticks) {
+ *COMPARE_TICKS.lock() = Some(instant);
}
- /// Timeout after a specific duration.
- #[inline]
- pub async fn timeout_after<F: core::future::Future>(
- duration: <Self as Monotonic>::Duration,
- future: F,
- ) -> Result<F::Output, TimeoutError> {
- Self::__tq().timeout_after(duration, future).await
+ fn clear_compare_flag() {}
+
+ fn pend_interrupt() {
+ unsafe {
+ Self::timer_queue().on_monotonic_interrupt();
+ }
}
+
+ fn timer_queue() -> &'static TimerQueue<Self> {
+ &TIMER_QUEUE
+ }
+}
+
+impl TimerQueueBasedMonotonic for SubtickTestTimer {
+ type Backend = SubtickTestTimerBackend;
+
+ type Instant = fugit::Instant<u64, SUBTICKS_PER_TICK, 1000>;
+ type Duration = fugit::Duration<u64, SUBTICKS_PER_TICK, 1000>;
}
-rtic_time::embedded_hal_delay_impl_fugit64!(SubtickTestTimer);
-rtic_time::embedded_hal_async_delay_impl_fugit64!(SubtickTestTimer);
+rtic_time::impl_embedded_hal_delay_fugit!(SubtickTestTimer);
+rtic_time::impl_embedded_hal_async_delay_fugit!(SubtickTestTimer);
// A simple struct that counts the number of times it is awoken. Can't
// be awoken by value (because that would discard the counter), so we
@@ -144,7 +141,7 @@ impl<F: FnOnce()> Drop for OnDrop<F> {
macro_rules! subtick_test {
(@run $start:expr, $actual_duration:expr, $delay_fn:expr) => {{
// forward clock to $start
- SubtickTestTimer::forward_to_subtick($start);
+ SubtickTestTimerBackend::forward_to_subtick($start);
// call wait function
let delay_fn = $delay_fn;
@@ -164,7 +161,7 @@ macro_rules! subtick_test {
};
assert_eq!(wakecounter.get(), 0);
- SubtickTestTimer::tick();
+ SubtickTestTimerBackend::tick();
}
let expected_wakeups = {
@@ -177,7 +174,7 @@ macro_rules! subtick_test {
assert_eq!(wakecounter.get(), expected_wakeups);
// Tick again to test that we don't get a second wake
- SubtickTestTimer::tick();
+ SubtickTestTimerBackend::tick();
assert_eq!(wakecounter.get(), expected_wakeups);
assert_eq!(
@@ -191,9 +188,9 @@ macro_rules! subtick_test {
(@run_blocking $start:expr, $actual_duration:expr, $delay_fn:expr) => {{
// forward clock to $start
- SubtickTestTimer::forward_to_subtick($start);
+ SubtickTestTimerBackend::forward_to_subtick($start);
- let t_start = SubtickTestTimer::now_subticks();
+ let t_start = SubtickTestTimerBackend::now_subticks();
let finished = AtomicBool::new(false);
std::thread::scope(|s|{
@@ -204,13 +201,13 @@ macro_rules! subtick_test {
s.spawn(||{
sleep(Duration::from_millis(10));
while !finished.load(Ordering::Relaxed) {
- SubtickTestTimer::tick();
+ SubtickTestTimerBackend::tick();
sleep(Duration::from_millis(10));
}
});
});
- let t_end = SubtickTestTimer::now_subticks();
+ let t_end = SubtickTestTimerBackend::now_subticks();
let measured_duration = t_end - t_start;
assert_eq!(
$actual_duration,
diff --git a/rtic-time/tests/timer_queue.rs b/rtic-time/tests/timer_queue.rs
index 8bae385..2ef7ce3 100644
--- a/rtic-time/tests/timer_queue.rs
+++ b/rtic-time/tests/timer_queue.rs
@@ -2,63 +2,24 @@
//!
//! To run this test, you need to activate the `critical-section/std` feature.
-use std::{
- fmt::Debug,
- task::{Poll, Waker},
-};
-
use cassette::Cassette;
use parking_lot::Mutex;
-use rtic_time::{Monotonic, TimerQueue};
-
-static NOW: Mutex<Option<Instant>> = Mutex::new(None);
-
-#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
-pub struct Duration(u64);
-
-impl Duration {
- pub const fn from_ticks(millis: u64) -> Self {
- Self(millis)
- }
-
- pub fn as_ticks(&self) -> u64 {
- self.0
- }
-}
-
-impl core::ops::Add<Duration> for Duration {
- type Output = Duration;
-
- fn add(self, rhs: Duration) -> Self::Output {
- Self(self.0 + rhs.0)
- }
-}
+use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend};
-impl From<Duration> for Instant {
- fn from(value: Duration) -> Self {
- Instant(value.0)
- }
-}
-
-static WAKERS: Mutex<Vec<Waker>> = Mutex::new(Vec::new());
+mod peripheral {
+ use parking_lot::Mutex;
+ use std::{
+ sync::atomic::{AtomicU64, Ordering},
+ task::{Poll, Waker},
+ };
-#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
-pub struct Instant(u64);
+ use super::TestMonoBackend;
-impl Instant {
- const ZERO: Self = Self(0);
+ static NOW: AtomicU64 = AtomicU64::new(0);
+ static WAKERS: Mutex<Vec<Waker>> = Mutex::new(Vec::new());
pub fn tick() -> bool {
- // If we've never ticked before, initialize the clock.
- if NOW.lock().is_none() {
- *NOW.lock() = Some(Instant::ZERO);
- }
- // We've ticked before, add one to the clock
- else {
- let now = Instant::now();
- let new_time = now + Duration(1);
- *NOW.lock() = Some(new_time);
- }
+ NOW.fetch_add(1, Ordering::Release);
let had_wakers = !WAKERS.lock().is_empty();
// Wake up all things waiting for a specific time to happen.
@@ -66,22 +27,18 @@ impl Instant {
waker.wake_by_ref();
}
- let had_interrupt = TestMono::tick(false);
+ let had_interrupt = TestMonoBackend::tick(false);
had_interrupt || had_wakers
}
- pub fn now() -> Self {
- NOW.lock().clone().unwrap_or(Instant::ZERO)
+ pub fn now() -> u64 {
+ NOW.load(Ordering::Acquire)
}
- pub fn elapsed(&self) -> Duration {
- Duration(Self::now().0 - self.0)
- }
-
- pub async fn wait_until(time: Instant) {
+ pub async fn wait_until(time: u64) {
core::future::poll_fn(|ctx| {
- if Instant::now() >= time {
+ if now() >= time {
Poll::Ready(())
} else {
WAKERS.lock().push(ctx.waker().clone());
@@ -92,51 +49,21 @@ impl Instant {
}
}
-impl From<u64> for Instant {
- fn from(value: u64) -> Self {
- Self(value)
- }
-}
+static COMPARE: Mutex<Option<u64>> = Mutex::new(None);
+static TIMER_QUEUE: TimerQueue<TestMonoBackend> = TimerQueue::new();
-impl core::ops::Add<Duration> for Instant {
- type Output = Instant;
+pub struct TestMonoBackend;
- fn add(self, rhs: Duration) -> Self::Output {
- Self(self.0 + rhs.0)
- }
-}
-
-impl core::ops::Sub<Duration> for Instant {
- type Output = Instant;
-
- fn sub(self, rhs: Duration) -> Self::Output {
- Self(self.0 - rhs.0)
- }
-}
-
-impl core::ops::Sub<Instant> for Instant {
- type Output = Duration;
-
- fn sub(self, rhs: Instant) -> Self::Output {
- Duration(self.0 - rhs.0)
- }
-}
-
-static COMPARE: Mutex<Option<Instant>> = Mutex::new(None);
-static TIMER_QUEUE: TimerQueue<TestMono> = TimerQueue::new();
-
-pub struct TestMono;
-
-impl TestMono {
+impl TestMonoBackend {
pub fn tick(force_interrupt: bool) -> bool {
- let now = Instant::now();
+ let now = peripheral::now();
let compare_reached = Some(now) == Self::compare();
let interrupt = compare_reached || force_interrupt;
if interrupt {
unsafe {
- TestMono::queue().on_monotonic_interrupt();
+ TestMonoBackend::timer_queue().on_monotonic_interrupt();
}
true
} else {
@@ -144,35 +71,26 @@ impl TestMono {
}
}
- /// Initialize the monotonic.
- pub fn init() {
- Self::queue().initialize(Self);
- }
-
- /// Used to access the underlying timer queue
- pub fn queue() -> &'static TimerQueue<TestMono> {
- &TIMER_QUEUE
- }
-
- pub fn compare() -> Option<Instant> {
+ pub fn compare() -> Option<u64> {
COMPARE.lock().clone()
}
}
-impl Monotonic for TestMono {
- const ZERO: Self::Instant = Instant::ZERO;
- const TICK_PERIOD: Self::Duration = Duration::from_ticks(1);
-
- type Instant = Instant;
+impl TestMonoBackend {
+ fn init() {
+ Self::timer_queue().initialize(Self);
+ }
+}
- type Duration = Duration;
+impl TimerQueueBackend for TestMonoBackend {
+ type Ticks = u64;
- fn now() -> Self::Instant {
- Instant::now()
+ fn now() -> Self::Ticks {
+ peripheral::now()
}
- fn set_compare(instant: Self::Instant) {
- let _ = COMPARE.lock().insert(instant);
+ fn set_compare(instant: Self::Ticks) {
+ *COMPARE.lock() = Some(instant);
}
fn clear_compare_flag() {}
@@ -180,42 +98,40 @@ impl Monotonic for TestMono {
fn pend_interrupt() {
Self::tick(true);
}
+
+ fn timer_queue() -> &'static TimerQueue<Self> {
+ &TIMER_QUEUE
+ }
}
#[test]
fn timer_queue() {
- TestMono::init();
- let start = Instant::ZERO;
+ TestMonoBackend::init();
+ let start = 0;
let build_delay_test = |pre_delay: Option<u64>, delay: u64| {
- let delay = Duration::from_ticks(delay);
- let pre_delay = pre_delay.map(Duration::from_ticks);
-
let total = if let Some(pre_delay) = pre_delay {
pre_delay + delay
} else {
delay
};
- let total_millis = total.as_ticks();
async move {
// A `pre_delay` simulates a delay in scheduling,
// without the `pre_delay` being present in the timer
// queue
if let Some(pre_delay) = pre_delay {
- Instant::wait_until(start + pre_delay).await;
+ peripheral::wait_until(start + pre_delay).await;
}
- TestMono::queue().delay(delay).await;
+ TestMonoBackend::timer_queue().delay(delay).await;
- let elapsed = start.elapsed().as_ticks();
- println!("{total_millis} ticks delay reached after {elapsed} ticks");
+ let elapsed = peripheral::now() - start;
+ println!("{total} ticks delay reached after {elapsed} ticks");
// Expect a delay of one longer, to compensate for timer uncertainty
- if elapsed != total_millis + 1 {
- panic!(
- "{total_millis} ticks delay was not on time ({elapsed} ticks passed instead)"
- );
+ if elapsed != total + 1 {
+ panic!("{total} ticks delay was not on time ({elapsed} ticks passed instead)");
}
}
};
@@ -259,31 +175,31 @@ fn timer_queue() {
// We only poll the waiting futures if an
// interrupt occured or if an artificial delay
// has passed.
- if Instant::tick() {
+ if peripheral::tick() {
poll!(d1, d2, d3);
}
- if Instant::now() == 0.into() {
+ if peripheral::now() == 0 {
// First, we want to be waiting for our 300 tick delay
- assert_eq!(TestMono::compare(), Some(301.into()));
+ assert_eq!(TestMonoBackend::compare(), Some(301));
}
- if Instant::now() == 100.into() {
+ if peripheral::now() == 100 {
// After 100 ticks, we enqueue a new delay that is supposed to last
// until the 200-tick-mark
- assert_eq!(TestMono::compare(), Some(201.into()));
+ assert_eq!(TestMonoBackend::compare(), Some(201));
}
- if Instant::now() == 201.into() {
+ if peripheral::now() == 201 {
// After 200 ticks, we dequeue the 200-tick-mark delay and
// requeue the 300 tick delay
- assert_eq!(TestMono::compare(), Some(301.into()));
+ assert_eq!(TestMonoBackend::compare(), Some(301));
}
- if Instant::now() == 301.into() {
+ if peripheral::now() == 301 {
// After 300 ticks, we dequeue the 300-tick-mark delay and
// go to the 400 tick delay that is already enqueued
- assert_eq!(TestMono::compare(), Some(401.into()));
+ assert_eq!(TestMonoBackend::compare(), Some(401));
}
}