init project
This commit is contained in:
10
embassy-time-queue-utils/CHANGELOG.md
Normal file
10
embassy-time-queue-utils/CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Changelog for embassy-time-queue-utils
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.1.0 - 2024-01-11
|
||||
|
||||
Initial release
|
||||
58
embassy-time-queue-utils/Cargo.toml
Normal file
58
embassy-time-queue-utils/Cargo.toml
Normal file
@@ -0,0 +1,58 @@
|
||||
[package]
|
||||
name = "embassy-time-queue-utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Timer queue driver trait for embassy-time"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
documentation = "https://docs.embassy.dev/embassy-time-queue-utils"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"concurrency",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
# Prevent multiple copies of this crate in the same binary.
|
||||
# Needed because different copies might get different tick rates, causing
|
||||
# wrong delays if the time driver is using one copy and user code is using another.
|
||||
# This is especially common when mixing crates from crates.io and git.
|
||||
links = "embassy-time-queue"
|
||||
|
||||
[dependencies]
|
||||
heapless = "0.8"
|
||||
embassy-executor = { version = "0.7.0", path = "../embassy-executor" }
|
||||
|
||||
[features]
|
||||
#! ### Generic Queue
|
||||
|
||||
#! By default this crate uses a timer queue implementation that is faster but depends on `embassy-executor`.
|
||||
#! It will panic if you try to await any timer when using another executor.
|
||||
#!
|
||||
#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor.
|
||||
#! To enable it, enable any of the features below.
|
||||
#!
|
||||
#! The features also set how many timers are used for the generic queue. At most one
|
||||
#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used.
|
||||
#!
|
||||
#! When using embassy-time-queue-driver from libraries, you should *not* enable any `generic-queue-*` feature, to allow the
|
||||
#! end user to pick.
|
||||
|
||||
## Generic Queue with 8 timers
|
||||
generic-queue-8 = ["_generic-queue"]
|
||||
## Generic Queue with 16 timers
|
||||
generic-queue-16 = ["_generic-queue"]
|
||||
## Generic Queue with 32 timers
|
||||
generic-queue-32 = ["_generic-queue"]
|
||||
## Generic Queue with 64 timers
|
||||
generic-queue-64 = ["_generic-queue"]
|
||||
## Generic Queue with 128 timers
|
||||
generic-queue-128 = ["_generic-queue"]
|
||||
|
||||
_generic-queue = []
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-utils-v$VERSION/embassy-time-queue-utils/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-utils/src/"
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
8
embassy-time-queue-utils/README.md
Normal file
8
embassy-time-queue-utils/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# embassy-time-queue-utils
|
||||
|
||||
This crate contains timer queues to help implementing an [`embassy-time-driver`](https://crates.io/crates/embassy-time-driver).
|
||||
|
||||
As a HAL user, you should not need to depend on this crate.
|
||||
|
||||
As a HAL implementer, you need to depend on this crate if you want to implement a time driver,
|
||||
but how you should do so is documented in `embassy-time-driver`.
|
||||
1
embassy-time-queue-utils/build.rs
Normal file
1
embassy-time-queue-utils/build.rs
Normal file
@@ -0,0 +1 @@
|
||||
fn main() {}
|
||||
13
embassy-time-queue-utils/src/lib.rs
Normal file
13
embassy-time-queue-utils/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#![no_std]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "_generic-queue")]
|
||||
pub mod queue_generic;
|
||||
#[cfg(not(feature = "_generic-queue"))]
|
||||
pub mod queue_integrated;
|
||||
|
||||
#[cfg(feature = "_generic-queue")]
|
||||
pub use queue_generic::Queue;
|
||||
#[cfg(not(feature = "_generic-queue"))]
|
||||
pub use queue_integrated::Queue;
|
||||
146
embassy-time-queue-utils/src/queue_generic.rs
Normal file
146
embassy-time-queue-utils/src/queue_generic.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
//! Generic timer queue implementations.
|
||||
//!
|
||||
//! Time queue drivers may use this to simplify their implementation.
|
||||
|
||||
use core::cmp::{min, Ordering};
|
||||
use core::task::Waker;
|
||||
|
||||
use heapless::Vec;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Timer {
|
||||
at: u64,
|
||||
waker: Waker,
|
||||
}
|
||||
|
||||
impl PartialEq for Timer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.at == other.at
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Timer {}
|
||||
|
||||
impl PartialOrd for Timer {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.at.partial_cmp(&other.at)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Timer {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.at.cmp(&other.at)
|
||||
}
|
||||
}
|
||||
|
||||
/// A timer queue with a pre-determined capacity.
|
||||
pub struct ConstGenericQueue<const QUEUE_SIZE: usize> {
|
||||
queue: Vec<Timer, QUEUE_SIZE>,
|
||||
}
|
||||
|
||||
impl<const QUEUE_SIZE: usize> ConstGenericQueue<QUEUE_SIZE> {
|
||||
/// Creates a new timer queue.
|
||||
pub const fn new() -> Self {
|
||||
Self { queue: Vec::new() }
|
||||
}
|
||||
|
||||
/// Schedules a task to run at a specific time, and returns whether any changes were made.
|
||||
///
|
||||
/// If this function returns `true`, the called should find the next expiration time and set
|
||||
/// a new alarm for that time.
|
||||
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
|
||||
self.queue
|
||||
.iter_mut()
|
||||
.find(|timer| timer.waker.will_wake(waker))
|
||||
.map(|timer| {
|
||||
if timer.at > at {
|
||||
timer.at = at;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let mut timer = Timer {
|
||||
waker: waker.clone(),
|
||||
at,
|
||||
};
|
||||
|
||||
loop {
|
||||
match self.queue.push(timer) {
|
||||
Ok(()) => break,
|
||||
Err(e) => timer = e,
|
||||
}
|
||||
|
||||
self.queue.pop().unwrap().waker.wake();
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
/// Dequeues expired timers and returns the next alarm time.
|
||||
pub fn next_expiration(&mut self, now: u64) -> u64 {
|
||||
let mut next_alarm = u64::MAX;
|
||||
|
||||
let mut i = 0;
|
||||
while i < self.queue.len() {
|
||||
let timer = &self.queue[i];
|
||||
if timer.at <= now {
|
||||
let timer = self.queue.swap_remove(i);
|
||||
timer.waker.wake();
|
||||
} else {
|
||||
next_alarm = min(next_alarm, timer.at);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
next_alarm
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "generic-queue-8")]
|
||||
const QUEUE_SIZE: usize = 8;
|
||||
#[cfg(feature = "generic-queue-16")]
|
||||
const QUEUE_SIZE: usize = 16;
|
||||
#[cfg(feature = "generic-queue-32")]
|
||||
const QUEUE_SIZE: usize = 32;
|
||||
#[cfg(feature = "generic-queue-64")]
|
||||
const QUEUE_SIZE: usize = 64;
|
||||
#[cfg(feature = "generic-queue-128")]
|
||||
const QUEUE_SIZE: usize = 128;
|
||||
#[cfg(not(any(
|
||||
feature = "generic-queue-8",
|
||||
feature = "generic-queue-16",
|
||||
feature = "generic-queue-32",
|
||||
feature = "generic-queue-64",
|
||||
feature = "generic-queue-128"
|
||||
)))]
|
||||
const QUEUE_SIZE: usize = 64;
|
||||
|
||||
/// A timer queue with a pre-determined capacity.
|
||||
pub struct Queue {
|
||||
queue: ConstGenericQueue<QUEUE_SIZE>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
/// Creates a new timer queue.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
queue: ConstGenericQueue::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules a task to run at a specific time, and returns whether any changes were made.
|
||||
///
|
||||
/// If this function returns `true`, the called should find the next expiration time and set
|
||||
/// a new alarm for that time.
|
||||
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
|
||||
self.queue.schedule_wake(at, waker)
|
||||
}
|
||||
|
||||
/// Dequeues expired timers and returns the next alarm time.
|
||||
pub fn next_expiration(&mut self, now: u64) -> u64 {
|
||||
self.queue.next_expiration(now)
|
||||
}
|
||||
}
|
||||
89
embassy-time-queue-utils/src/queue_integrated.rs
Normal file
89
embassy-time-queue-utils/src/queue_integrated.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
//! Timer queue operations.
|
||||
use core::cell::Cell;
|
||||
use core::cmp::min;
|
||||
use core::task::Waker;
|
||||
|
||||
use embassy_executor::raw::TaskRef;
|
||||
|
||||
/// A timer queue, with items integrated into tasks.
|
||||
pub struct Queue {
|
||||
head: Cell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
/// Creates a new timer queue.
|
||||
pub const fn new() -> Self {
|
||||
Self { head: Cell::new(None) }
|
||||
}
|
||||
|
||||
/// Schedules a task to run at a specific time.
|
||||
///
|
||||
/// If this function returns `true`, the called should find the next expiration time and set
|
||||
/// a new alarm for that time.
|
||||
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
|
||||
let task = embassy_executor::raw::task_from_waker(waker);
|
||||
let item = task.timer_queue_item();
|
||||
if item.next.get().is_none() {
|
||||
// If not in the queue, add it and update.
|
||||
let prev = self.head.replace(Some(task));
|
||||
item.next.set(if prev.is_none() {
|
||||
Some(unsafe { TaskRef::dangling() })
|
||||
} else {
|
||||
prev
|
||||
});
|
||||
item.expires_at.set(at);
|
||||
true
|
||||
} else if at <= item.expires_at.get() {
|
||||
// If expiration is sooner than previously set, update.
|
||||
item.expires_at.set(at);
|
||||
true
|
||||
} else {
|
||||
// Task does not need to be updated.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Dequeues expired timers and returns the next alarm time.
|
||||
///
|
||||
/// The provided callback will be called for each expired task. Tasks that never expire
|
||||
/// will be removed, but the callback will not be called.
|
||||
pub fn next_expiration(&mut self, now: u64) -> u64 {
|
||||
let mut next_expiration = u64::MAX;
|
||||
|
||||
self.retain(|p| {
|
||||
let item = p.timer_queue_item();
|
||||
let expires = item.expires_at.get();
|
||||
|
||||
if expires <= now {
|
||||
// Timer expired, process task.
|
||||
embassy_executor::raw::wake_task(p);
|
||||
false
|
||||
} else {
|
||||
// Timer didn't yet expire, or never expires.
|
||||
next_expiration = min(next_expiration, expires);
|
||||
expires != u64::MAX
|
||||
}
|
||||
});
|
||||
|
||||
next_expiration
|
||||
}
|
||||
|
||||
fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
|
||||
let mut prev = &self.head;
|
||||
while let Some(p) = prev.get() {
|
||||
if unsafe { p == TaskRef::dangling() } {
|
||||
// prev was the last item, stop
|
||||
break;
|
||||
}
|
||||
let item = p.timer_queue_item();
|
||||
if f(p) {
|
||||
// Skip to next
|
||||
prev = &item.next;
|
||||
} else {
|
||||
// Remove it
|
||||
prev.set(item.next.get());
|
||||
item.next.set(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user