ndless_async/lib.rs
1//! Ndless-specific integration with `async`/`await`
2//!
3//! This crate provides an executor, reactor, and utilities to use Rust's
4//! `async` capabilities with the TI Nspire's timer and keypad. Note that
5//! normally `async` functions are used for I/O. However, as far as I'm aware,
6//! the TI-Nspire's OS has no support for asynchronous I/O of any sort. However,
7//! this still provides helpful utilities for doing multiple things at once,
8//! such as waiting for a key with a timeout.
9//!
10//! You'll first need to create an instance of
11//! [`AsyncListeners`][task::AsyncListeners] with `AsyncListeners::default()`.
12//! This allows you to receive events from the Nspire's timer. From there, you
13//! can pass it into [`task::block_on`], along with a `Future` of your choice.
14//!
15//! # Helpful resources
16//! Check out the [Rust Async Book](https://rust-lang.github.io/async-book/).
17//! This has useful instructions about asynchronous programming. Although it is
18//! mostly written for usage with a full operating system, everything applies
19//! here except chapters 1.4, 6.3, 8, and 10.
20//!
21//! [`futures_util`] has many useful utility functions. Add it to your project
22//! by adding the following to your Cargo.toml:
23//!
24//! ```toml
25//! futures-util = { version = "0.3.5", default-features = false, features = ["alloc", "async-await-macro"] }
26//! ```
27//!
28//! You may find its
29//! [`FuturesUnordered`](https://docs.rs/futures-util/0.3.*/futures_util/stream/futures_unordered/struct.FuturesUnordered.html)
30//! to be of help for scheduling multiple tasks. Although
31//! macros like [`join`] and [`first`] can be helpful, they aren't as efficient
32//! and flexible as it.
33//!
34//! The macros [`join`], [`select`], [`try_join`], and traits [`FutureExt`] &
35//! [`StreamExt`] are re-exported from it, so if that's all you need, you don't
36//! need to depend on it directly.
37//!
38//! # Example
39//! ```rust
40//! use futures_util::future;
41//! use ndless_async::task::{block_on, AsyncListeners};
42//! use ndless_async::{first, StreamExt};
43//! use ndless_async::keypad::KeypadListener;
44//! use ndless::input::Key;
45//!
46//! let listeners = AsyncListeners::new();
47//! let keypad = KeypadListener::new(&listeners.timer());
48//! block_on(&listeners, async {
49//! let _ = listeners.timer().timeout_ms(5000, do_stuff(&keypad)).await;
50//! listeners.timer().sleep_ms(2000).await;
51//! first!(do_other_stuff(&listeners), wait_for_esc(&keypad));
52//! });
53//!
54//! async fn wait_for_esc(keypad: &KeypadListener<'_>) {
55//! keypad
56//! .stream()
57//! .filter(|key| future::ready(key.key == Key::Esc))
58//! .next()
59//! .await;
60//! }
61//!
62//! async fn do_other_stuff(listeners: &AsyncListeners) {
63//! loop {
64//! listeners.timer().sleep_ms(1000).await;
65//! println!("1s!");
66//! }
67//! }
68//!
69//! async fn do_stuff(keypad: &KeypadListener<'_>) {
70//! use ndless_async::keypad::KeyState::*;
71//! let mut keypad = keypad.stream();
72//! while let Some(event) = keypad.next().await {
73//! println!(
74//! "Key {:?} was {}",
75//! event.key,
76//! if event.state == Released {
77//! "released"
78//! } else {
79//! "pressed"
80//! }
81//! );
82//! print!("Keys currently pressed: ");
83//! keypad
84//! .list_keys()
85//! .iter()
86//! .for_each(|key| print!("{:?} ", key));
87//! println!();
88//! if event.key == Key::Esc { break; }
89//! }
90//! }
91//! ```
92
93#![feature(wake_trait)]
94#![no_std]
95extern crate alloc;
96
97pub use futures_util::{join, select_biased as select, try_join, FutureExt, StreamExt};
98
99pub use yield_now::Yield;
100
101pub mod keypad;
102pub mod mpsc;
103pub mod task;
104pub mod timer;
105mod yield_now;
106/// Polls for the first future to complete, and then cancels the remaining ones.
107/// If you care about the return value, use [`select`]. This macro must
108/// be used in an `async` context, such as an `async fn` or `async { }` block.
109///
110/// [`FuturesUnordered`](https://docs.rs/futures-util/0.3.*/futures_util/stream/futures_unordered/struct.FuturesUnordered.html)
111/// can be more flexible and efficient than this macro when you have many
112/// `Future`s or need to dynamically add and remove them.
113///
114/// # Example
115/// The call to [`block_on`][task::block_on] completes after 5 seconds or
116/// when the escape key is pressed, whichever comes first.
117///
118/// In reality, you should use
119/// [`TimerListener::timeout`][timer::TimerListener::timeout] for this purpose.
120/// However, `first!` can be used for other, more complex cases.
121///
122/// ```
123/// use ndless_async::task::{AsyncListeners, block_on};
124/// use ndless_async::StreamExt;
125/// use ndless::input::Key;
126///
127/// let listeners = AsyncListeners::new();
128/// block_on(&listeners, async { first!(timeout(&listeners), listen_for_esc(&listeners)) });
129///
130///
131/// async fn timeout(listeners: &AsyncListeners) {
132/// listeners.timer().sleep_ms(5000).await;
133/// }
134///
135/// async fn listen_for_esc(listeners: &AsyncListeners) {
136/// let mut keypad = listeners.keypad();
137/// while let Some(event) = keypad.next().await {
138/// if event.key == Key::Esc {
139/// break;
140/// }
141/// }
142/// }
143/// ```
144#[macro_export]
145macro_rules! first {
146 ($( $arg:expr ),*) => (
147 $crate::select!($(_ = $crate::FutureExt::fuse($arg) => (),)*)
148 )
149}