Crates.io | cassette |
lib.rs | cassette |
version | 0.3.0 |
source | src |
created_at | 2019-07-19 17:06:11.113372 |
updated_at | 2024-04-13 23:48:12.418394 |
description | A simple, single-future, non-blocking executor intended for building state machines |
homepage | https://github.com/jamesmunns/cassette |
repository | https://github.com/jamesmunns/cassette |
max_upload_size | |
id | 150172 |
size | 31,918 |
A simple, single-future, non-blocking executor intended for building state machines. Designed to be no-std and embedded friendly.
This executor TOTALLY IGNORES wakers and context, meaning that all async functions should expect to be polled repeatedly until completion.
So, I'm really not good at async, but I like the idea of being able to use the ability to yield or await on tasks that will require some time to complete.
The idea here is that you would write one, top level async
function that would either eventually resolve to some value, or that will run forever (to act as a state machine).
Note: This demo is available in the demo/
folder of this repo.
Here's the "context" of our state machine, describing a couple of high level behaviors, as well as individual substeps.
struct Demo {
lol: u32,
}
impl Demo {
async fn entry(&mut self) {
for _ in 0..10 {
self.entry_1().await;
self.entry_2().await;
}
}
async fn entry_1(&mut self) {
self.start_at_zero().await;
self.add_one_until_ten().await;
self.sub_one_until_zero().await;
}
async fn entry_2(&mut self) {
self.start_at_five().await;
self.sub_one_until_zero().await;
self.add_one_until_ten().await;
}
async fn start_at_zero(&mut self) {
self.lol = 0;
}
async fn start_at_five(&mut self) {
self.lol = 5;
}
async fn add_one_until_ten(&mut self) {
loop {
delay(self).await; // simulate fake delays/not ready state
self.lol += 1;
if self.lol >= 10 {
return;
}
}
}
async fn sub_one_until_zero(&mut self) {
loop {
delay(self).await; // simulate fake delays/not ready state
self.lol -= 1;
if self.lol == 0 {
return;
}
}
}
}
We can also make simple little futures for code that needs to be polled until ready:
static FAKE: AtomicU32 = AtomicU32::new(0);
struct CountFuture;
impl Future for CountFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let x = FAKE.fetch_add(1, Ordering::SeqCst);
print!("{}, ", x);
if (x % 5) == 0 {
Poll::Ready(())
} else {
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
async fn delay(ctxt: &mut Demo) {
println!("delay says lol: {}", ctxt.lol);
let x = CountFuture;
x.await;
println!("and delay!");
}
fn main() {
// Make a new struct
let mut demo = Demo { lol: 100 };
// Call the entry point future, and pin it
let x = core::pin::pin!(demo.entry());
// Give the pinned future to Cassette
// for execution
let mut cm = Cassette::new(x);
/* ... */
}
fn main() {
/* ... */
loop {
if let Some(x) = cm.poll_on() {
println!("Done!: `{:?}`", x);
break;
}
}
}
If you'd like to see a larger demo, I used Cassette to implement an I2C peripheral bootloader state machine for a thumbv6m
target. You can check out that PR for more context.
This crate is licensed under the MIT and Apache 2.0 licenses.