From 1ee2dfa03e6d3578ffaac810b49b726e7da9cf36 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 21:15:07 +0200 Subject: [PATCH 1/8] futures: derive defmt for Eithers --- embassy-futures/src/select.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 8cecb7fa..53fa1da6 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -4,6 +4,7 @@ use core::task::{Context, Poll}; /// Result for [`select`]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Either { /// First future finished first. First(A), @@ -60,6 +61,7 @@ where /// Result for [`select3`]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Either3 { /// First future finished first. First(A), @@ -118,6 +120,7 @@ where /// Result for [`select4`]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Either4 { /// First future finished first. First(A), From 3763baf8fa4cf332a81214eb1610afdaf5173824 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 21:15:30 +0200 Subject: [PATCH 2/8] futures: add select_slice, rename select_all to select_array. --- embassy-futures/README.md | 3 +- embassy-futures/src/select.rs | 62 +++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/embassy-futures/README.md b/embassy-futures/README.md index 971f4c83..3810bcaf 100644 --- a/embassy-futures/README.md +++ b/embassy-futures/README.md @@ -5,5 +5,6 @@ Utilities for working with futures: - [`select`](select::select) - waiting for one out of two futures to complete. - [`select3`](select::select3) - waiting for one out of three futures to complete. - [`select4`](select::select4) - waiting for one out of four futures to complete. -- [`select_all`](select::select_all) - waiting for one future in a list of futures to complete. +- [`select_array`](select::select_array) - waiting for one future in an array of futures to complete. +- [`select_slice`](select::select_slice) - waiting for one future in a slice of futures to complete. - [`yield_now`](yield_now::yield_now) - yielding the current task. diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 53fa1da6..facc2f60 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -186,28 +186,76 @@ where // ==================================================================== -/// Future for the [`select_all`] function. +/// Future for the [`select_array`] function. #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct SelectAll { +pub struct SelectArray { inner: [Fut; N], } -/// Creates a new future which will select over a list of futures. +/// Creates a new future which will select over an array of futures. /// -/// The returned future will wait for any future within `iter` to be ready. Upon +/// The returned future will wait for any future to be ready. Upon /// completion the item resolved will be returned, along with the index of the /// future that was ready. /// /// # Panics /// /// This function will panic if the array specified contains no items. -pub fn select_all(arr: [Fut; N]) -> SelectAll { +pub fn select_array(arr: [Fut; N]) -> SelectArray { assert!(N > 0); - SelectAll { inner: arr } + SelectArray { inner: arr } } -impl Future for SelectAll { +impl Future for SelectArray { + type Output = (Fut::Output, usize); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, + // its elements also cannot move. Therefore it is safe to access `inner` and pin + // references to the contained futures. + let item = unsafe { + self.get_unchecked_mut() + .inner + .iter_mut() + .enumerate() + .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { + Poll::Pending => None, + Poll::Ready(e) => Some((i, e)), + }) + }; + + match item { + Some((idx, res)) => Poll::Ready((res, idx)), + None => Poll::Pending, + } + } +} + +// ==================================================================== + +/// Future for the [`select_slice`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SelectSlice<'a, Fut> { + inner: &'a mut [Fut], +} + +/// Creates a new future which will select over a slice of futures. +/// +/// The returned future will wait for any future to be ready. Upon +/// completion the item resolved will be returned, along with the index of the +/// future that was ready. +/// +/// # Panics +/// +/// This function will panic if the slice specified contains no items. +pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> { + assert!(!slice.is_empty()); + SelectSlice { inner: slice } +} + +impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { type Output = (Fut::Output, usize); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { From 973f3b513fb85b7587312196d8f3aef75be2615f Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 21:28:26 +0200 Subject: [PATCH 3/8] futures: make `select_(slice|array)` hang intead of panicking if empty. --- embassy-futures/src/select.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index facc2f60..c0dd7ecd 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -199,11 +199,8 @@ pub struct SelectArray { /// completion the item resolved will be returned, along with the index of the /// future that was ready. /// -/// # Panics -/// -/// This function will panic if the array specified contains no items. +/// If the array is empty, the resulting future will be Pending forever. pub fn select_array(arr: [Fut; N]) -> SelectArray { - assert!(N > 0); SelectArray { inner: arr } } @@ -247,11 +244,8 @@ pub struct SelectSlice<'a, Fut> { /// completion the item resolved will be returned, along with the index of the /// future that was ready. /// -/// # Panics -/// -/// This function will panic if the slice specified contains no items. +/// If the slice is empty, the resulting future will be Pending forever. pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> { - assert!(!slice.is_empty()); SelectSlice { inner: slice } } From 764ee3b72cc8770f7c398ba1aee41fbd34f07764 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 22:51:52 +0200 Subject: [PATCH 4/8] futures: add block_on --- embassy-futures/src/block_on.rs | 31 +++++++++++++++++++++++++++++++ embassy-futures/src/lib.rs | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 embassy-futures/src/block_on.rs diff --git a/embassy-futures/src/block_on.rs b/embassy-futures/src/block_on.rs new file mode 100644 index 00000000..749fa67f --- /dev/null +++ b/embassy-futures/src/block_on.rs @@ -0,0 +1,31 @@ +use core::future::Future; +use core::pin::Pin; +use core::ptr; +use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| RawWaker::new(ptr::null(), &VTABLE), |_| {}, |_| {}, |_| {}); + +/// Run a future to completion using a busy loop. +/// +/// This calls `.poll()` on the future in a busy loop, which blocks +/// the current thread at 100% cpu usage until the future is done. The +/// future's `Waker` mechanism is not used. +/// +/// It's suitable for systems with no or limited concurrency and without +/// strict requirements around power consumption. For more complex use +/// cases, prefer using a "real" executor like `embassy-executor`, which +/// supports multiple tasks, and putting the core to sleep when no task +/// needs to do work. +pub fn block_on(mut fut: F) -> F::Output { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + loop { + if let Poll::Ready(res) = fut.as_mut().poll(&mut cx) { + return res; + } + } +} diff --git a/embassy-futures/src/lib.rs b/embassy-futures/src/lib.rs index 45bea252..41e27047 100644 --- a/embassy-futures/src/lib.rs +++ b/embassy-futures/src/lib.rs @@ -5,8 +5,10 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +mod block_on; mod select; mod yield_now; +pub use block_on::*; pub use select::*; pub use yield_now::*; From 2a0df652f37ac00ba3f568b457eabe4e352bc3c3 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 22:52:31 +0200 Subject: [PATCH 5/8] futures: add joins --- embassy-futures/src/join.rs | 252 ++++++++++++++++++++++++++++++++++++ embassy-futures/src/lib.rs | 2 + 2 files changed, 254 insertions(+) create mode 100644 embassy-futures/src/join.rs diff --git a/embassy-futures/src/join.rs b/embassy-futures/src/join.rs new file mode 100644 index 00000000..39a78ccd --- /dev/null +++ b/embassy-futures/src/join.rs @@ -0,0 +1,252 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; +use core::{fmt, mem}; + +#[derive(Debug)] +enum MaybeDone { + /// A not-yet-completed future + Future(/* #[pin] */ Fut), + /// The output of the completed future + Done(Fut::Output), + /// The empty variant after the result of a [`MaybeDone`] has been + /// taken using the [`take_output`](MaybeDone::take_output) method. + Gone, +} + +impl MaybeDone { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool { + let this = unsafe { self.get_unchecked_mut() }; + match this { + Self::Future(fut) => match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(res) => { + *this = Self::Done(res); + true + } + Poll::Pending => false, + }, + _ => true, + } + } + + fn take_output(&mut self) -> Fut::Output { + match &*self { + Self::Done(_) => {} + Self::Future(_) | Self::Gone => panic!("take_output when MaybeDone is not done."), + } + match mem::replace(self, Self::Gone) { + MaybeDone::Done(output) => output, + _ => unreachable!(), + } + } +} + +impl Unpin for MaybeDone {} + +macro_rules! generate { + ($( + $(#[$doc:meta])* + ($Join:ident, <$($Fut:ident),*>), + )*) => ($( + $(#[$doc])* + #[must_use = "futures do nothing unless you `.await` or poll them"] + #[allow(non_snake_case)] + pub struct $Join<$($Fut: Future),*> { + $( + $Fut: MaybeDone<$Fut>, + )* + } + + impl<$($Fut),*> fmt::Debug for $Join<$($Fut),*> + where + $( + $Fut: Future + fmt::Debug, + $Fut::Output: fmt::Debug, + )* + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($Join)) + $(.field(stringify!($Fut), &self.$Fut))* + .finish() + } + } + + impl<$($Fut: Future),*> $Join<$($Fut),*> { + #[allow(non_snake_case)] + fn new($($Fut: $Fut),*) -> Self { + Self { + $($Fut: MaybeDone::Future($Fut)),* + } + } + } + + impl<$($Fut: Future),*> Future for $Join<$($Fut),*> { + type Output = ($($Fut::Output),*); + + fn poll( + self: Pin<&mut Self>, cx: &mut Context<'_> + ) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + $( + all_done &= unsafe { Pin::new_unchecked(&mut this.$Fut) }.poll(cx); + )* + + if all_done { + Poll::Ready(($(this.$Fut.take_output()), *)) + } else { + Poll::Pending + } + } + } + )*) +} + +generate! { + /// Future for the [`join`](join()) function. + (Join, ), + + /// Future for the [`join3`] function. + (Join3, ), + + /// Future for the [`join4`] function. + (Join4, ), + + /// Future for the [`join5`] function. + (Join5, ), +} + +/// Joins the result of two futures, waiting for them both to complete. +/// +/// This function will return a new future which awaits both futures to +/// complete. The returned future will finish with a tuple of both results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let pair = embassy_futures::join(a, b).await; +/// +/// assert_eq!(pair, (1, 2)); +/// # }); +/// ``` +pub fn join(future1: Fut1, future2: Fut2) -> Join +where + Fut1: Future, + Fut2: Future, +{ + Join::new(future1, future2) +} + +/// Joins the result of three futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let res = embassy_futures::join3(a, b, c).await; +/// +/// assert_eq!(res, (1, 2, 3)); +/// # }); +/// ``` +pub fn join3(future1: Fut1, future2: Fut2, future3: Fut3) -> Join3 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, +{ + Join3::new(future1, future2, future3) +} + +/// Joins the result of four futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let res = embassy_futures::join4(a, b, c, d).await; +/// +/// assert_eq!(res, (1, 2, 3, 4)); +/// # }); +/// ``` +pub fn join4( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, +) -> Join4 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, +{ + Join4::new(future1, future2, future3, future4) +} + +/// Joins the result of five futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let e = async { 5 }; +/// let res = embassy_futures::join5(a, b, c, d, e).await; +/// +/// assert_eq!(res, (1, 2, 3, 4, 5)); +/// # }); +/// ``` +pub fn join5( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, + future5: Fut5, +) -> Join5 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, + Fut5: Future, +{ + Join5::new(future1, future2, future3, future4, future5) +} diff --git a/embassy-futures/src/lib.rs b/embassy-futures/src/lib.rs index 41e27047..ea135b3a 100644 --- a/embassy-futures/src/lib.rs +++ b/embassy-futures/src/lib.rs @@ -6,9 +6,11 @@ pub(crate) mod fmt; mod block_on; +mod join; mod select; mod yield_now; pub use block_on::*; +pub use join::*; pub use select::*; pub use yield_now::*; From 598689ef43a5dec1aeb8948dd8b5d0e46a62ef81 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 22:57:35 +0200 Subject: [PATCH 6/8] futures: move select* and join* to separate modules. --- embassy-futures/src/join.rs | 10 ++++++---- embassy-futures/src/lib.rs | 7 +++---- embassy-futures/src/select.rs | 2 ++ embassy-usb/src/lib.rs | 2 +- examples/nrf/src/bin/usb_hid_keyboard.rs | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/embassy-futures/src/join.rs b/embassy-futures/src/join.rs index 39a78ccd..7600d4b8 100644 --- a/embassy-futures/src/join.rs +++ b/embassy-futures/src/join.rs @@ -1,3 +1,5 @@ +//! Wait for multiple futures to complete. + use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll}; @@ -131,7 +133,7 @@ generate! { /// /// let a = async { 1 }; /// let b = async { 2 }; -/// let pair = embassy_futures::join(a, b).await; +/// let pair = embassy_futures::join::join(a, b).await; /// /// assert_eq!(pair, (1, 2)); /// # }); @@ -160,7 +162,7 @@ where /// let a = async { 1 }; /// let b = async { 2 }; /// let c = async { 3 }; -/// let res = embassy_futures::join3(a, b, c).await; +/// let res = embassy_futures::join::join3(a, b, c).await; /// /// assert_eq!(res, (1, 2, 3)); /// # }); @@ -191,7 +193,7 @@ where /// let b = async { 2 }; /// let c = async { 3 }; /// let d = async { 4 }; -/// let res = embassy_futures::join4(a, b, c, d).await; +/// let res = embassy_futures::join::join4(a, b, c, d).await; /// /// assert_eq!(res, (1, 2, 3, 4)); /// # }); @@ -229,7 +231,7 @@ where /// let c = async { 3 }; /// let d = async { 4 }; /// let e = async { 5 }; -/// let res = embassy_futures::join5(a, b, c, d, e).await; +/// let res = embassy_futures::join::join5(a, b, c, d, e).await; /// /// assert_eq!(res, (1, 2, 3, 4, 5)); /// # }); diff --git a/embassy-futures/src/lib.rs b/embassy-futures/src/lib.rs index ea135b3a..8c769bdf 100644 --- a/embassy-futures/src/lib.rs +++ b/embassy-futures/src/lib.rs @@ -6,11 +6,10 @@ pub(crate) mod fmt; mod block_on; -mod join; -mod select; mod yield_now; +pub mod join; +pub mod select; + pub use block_on::*; -pub use join::*; -pub use select::*; pub use yield_now::*; diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index c0dd7ecd..97a81a86 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -1,3 +1,5 @@ +//! Wait for the first of several futures to complete. + use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll}; diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 5a3f8ba8..b165804e 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -12,7 +12,7 @@ mod descriptor_reader; pub mod driver; pub mod types; -use embassy_futures::{select, Either}; +use embassy_futures::select::{select, Either}; use heapless::Vec; pub use self::builder::{Builder, Config}; diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index ba2159c7..7fdb0b68 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -8,7 +8,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; use defmt::*; use embassy_executor::Spawner; -use embassy_futures::{select, Either}; +use embassy_futures::select::{select, Either}; use embassy_nrf::gpio::{Input, Pin, Pull}; use embassy_nrf::usb::{Driver, PowerUsb}; use embassy_nrf::{interrupt, pac}; From 4c0f1b6354b1f1b2f87a6876bfa5d3803804cbb9 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 28 Aug 2022 23:32:46 +0200 Subject: [PATCH 7/8] futures: add join_array. --- embassy-futures/src/join.rs | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/embassy-futures/src/join.rs b/embassy-futures/src/join.rs index 7600d4b8..bc0cb530 100644 --- a/embassy-futures/src/join.rs +++ b/embassy-futures/src/join.rs @@ -1,6 +1,7 @@ //! Wait for multiple futures to complete. use core::future::Future; +use core::mem::MaybeUninit; use core::pin::Pin; use core::task::{Context, Poll}; use core::{fmt, mem}; @@ -252,3 +253,70 @@ where { Join5::new(future1, future2, future3, future4, future5) } + +// ===================================================== + +/// Future for the [`join_array`] function. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct JoinArray { + futures: [MaybeDone; N], +} + +impl fmt::Debug for JoinArray +where + Fut: Future + fmt::Debug, + Fut::Output: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("JoinArray").field("futures", &self.futures).finish() + } +} + +impl Future for JoinArray { + type Output = [Fut::Output; N]; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + for f in this.futures.iter_mut() { + all_done &= unsafe { Pin::new_unchecked(f) }.poll(cx); + } + + if all_done { + let mut array: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..N { + array[i].write(this.futures[i].take_output()); + } + Poll::Ready(unsafe { (&array as *const _ as *const [Fut::Output; N]).read() }) + } else { + Poll::Pending + } + } +} + +/// Joins the result of an array of futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// async fn foo(n: u32) -> u32 { n } +/// let a = foo(1); +/// let b = foo(2); +/// let c = foo(3); +/// let res = embassy_futures::join::join_array([a, b, c]).await; +/// +/// assert_eq!(res, [1, 2, 3]); +/// # }); +/// ``` +pub fn join_array(futures: [Fut; N]) -> JoinArray { + JoinArray { + futures: futures.map(MaybeDone::Future), + } +} From 6392f067172541a0cb9b3757a9c7a40d3b7c5e25 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 29 Aug 2022 00:54:14 +0200 Subject: [PATCH 8/8] futures: readme, docs improvements. --- embassy-futures/Cargo.toml | 5 ++++- embassy-futures/README.md | 32 +++++++++++++++++++++++++------- embassy-futures/src/block_on.rs | 2 ++ embassy-futures/src/yield_now.rs | 17 +++++++++++++++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/embassy-futures/Cargo.toml b/embassy-futures/Cargo.toml index e564f5a9..7123b7c6 100644 --- a/embassy-futures/Cargo.toml +++ b/embassy-futures/Cargo.toml @@ -6,9 +6,12 @@ edition = "2021" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-futures-v$VERSION/embassy-futures/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-futures/src/" -features = ["nightly"] +features = ["defmt"] target = "thumbv7em-none-eabi" +[package.metadata.docs.rs] +features = ["defmt"] + [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-futures/README.md b/embassy-futures/README.md index 3810bcaf..7add22c7 100644 --- a/embassy-futures/README.md +++ b/embassy-futures/README.md @@ -1,10 +1,28 @@ # embassy-futures -Utilities for working with futures: +An [Embassy](https://embassy.dev) project. + +Utilities for working with futures, compatible with `no_std` and not using `alloc`. Optimized for code size, +ideal for embedded systems. + +- Future combinators, like [`join`](join) and [`select`](select) +- Utilities to use `async` without a fully fledged executor: [`block_on`](block_on::block_on) and [`yield_now`](yield_now::yield_now). + +## Interoperability + +Futures from this crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. -- [`select`](select::select) - waiting for one out of two futures to complete. -- [`select3`](select::select3) - waiting for one out of three futures to complete. -- [`select4`](select::select4) - waiting for one out of four futures to complete. -- [`select_array`](select::select_array) - waiting for one future in an array of futures to complete. -- [`select_slice`](select::select_slice) - waiting for one future in a slice of futures to complete. -- [`yield_now`](yield_now::yield_now) - yielding the current task. diff --git a/embassy-futures/src/block_on.rs b/embassy-futures/src/block_on.rs index 749fa67f..da90351e 100644 --- a/embassy-futures/src/block_on.rs +++ b/embassy-futures/src/block_on.rs @@ -11,6 +11,8 @@ static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| RawWaker::new(ptr::null( /// the current thread at 100% cpu usage until the future is done. The /// future's `Waker` mechanism is not used. /// +/// You can use this to run multiple futures concurrently with [`join`][crate::join]. +/// /// It's suitable for systems with no or limited concurrency and without /// strict requirements around power consumption. For more complex use /// cases, prefer using a "real" executor like `embassy-executor`, which diff --git a/embassy-futures/src/yield_now.rs b/embassy-futures/src/yield_now.rs index 1ebecb91..13b10377 100644 --- a/embassy-futures/src/yield_now.rs +++ b/embassy-futures/src/yield_now.rs @@ -3,6 +3,23 @@ use core::pin::Pin; use core::task::{Context, Poll}; /// Yield from the current task once, allowing other tasks to run. +/// +/// This can be used to easily and quickly implement simple async primitives +/// without using wakers. The following snippet will wait for a condition to +/// hold, while still allowing other tasks to run concurrently (not monopolizing +/// the executor thread). +/// +/// ```rust,no_run +/// while !some_condition() { +/// yield_now().await; +/// } +/// ``` +/// +/// The downside is this will spin in a busy loop, using 100% of the CPU, while +/// using wakers correctly would allow the CPU to sleep while waiting. +/// +/// The internal implementation is: on first poll the future wakes itself and +/// returns `Poll::Pending`. On second poll, it returns `Poll::Ready`. pub fn yield_now() -> impl Future { YieldNowFuture { yielded: false } }