Cassette A simple, single-future, non-blocking executor intended for building state machines.

Overview

Cassette

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.

Inspiration

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).

How it works

  1. You write some async functions
  2. You call the "top level" async function
  3. You poll on it until it resolves (or forever)

Note: This demo is available in the demo/ folder of this repo.

Step 1 - You write some async functions

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 {
            Poll::Pending
        }
    }
}

async fn delay(ctxt: &mut Demo) {
    println!("delay says lol: {}", ctxt.lol);
    let x = CountFuture;
    x.await;
    println!("and delay!");
}

Step 2 - You call the "top level" async function

fn main() {
    // Make a new struct
    let mut demo = Demo { lol: 100 };

    // Call the entry point future, and pin it
    let x = demo.entry();
    pin_mut!(x);

    // Give the pinned future to Cassette
    // for execution
    let mut cm = Cassette::new(x);

    /* ... */
}

Step 3 - You poll on it until it resolves (or forever)

fn main() {
    /* ... */

    loop {
        if let Some(x) = cm.poll_on() {
            println!("Done!: `{:?}`", x);
            break;
        }
    }
}

A larger demo

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.

License

This crate is licensed under the MPL v2.0.

The futures module includes code licensed under the MIT+Apache2.0 dual licenses from the futures-rs crate. Please see the upstream repository at https://github.com/rust-lang/futures-rs, and details of the license here: https://github.com/rust-lang/futures-rs#license

You might also like...
CLI tool that make it easier to perform multiple lighthouse runs towards a single target and output the result in a "plotable" format.

Lighthouse Groupie CLI tool that make it easier to perform multiple lighthouse runs towards a single target and output the result in a "plotable" form

Single-operator public liveness notification

Single-operator public liveness notification

馃悁 Building a federated alternative to reddit in rust

Lemmy A link aggregator / Reddit clone for the fediverse. Join Lemmy 路 Documentation 路 Report Bug 路 Request Feature 路 Releases 路 Code of Conduct About

Nimbus is a framework for building parachain consensus systems on cumulus-based parachains.

Cumulo -- Nimbus 鉀堬笍 Nimbus is a framework for building parachain consensus systems on cumulus-based parachains. Given the regular six-second pulse-lik

A Rust framework for building context-sensitive type conversion.

Xylem is a stateful type conversion framework for Rust.

馃悁 Building a federated link aggregator in rust

English | Espa帽ol | 袪褍褋褋泻懈泄 Lemmy A link aggregator / Reddit clone for the fediverse. Join Lemmy 路 Documentation 路 Report Bug 路 Request Feature 路 Rele

An aimless attempt at building a PC from scratch, in a vaguely eurorack/modular synth style.
An aimless attempt at building a PC from scratch, in a vaguely eurorack/modular synth style.

An aimless attempt at building a PC from scratch, in a vaguely eurorack/modular synth style.

Building a better screen reader for the Linux desktop, one step at a time.

Building a better screen reader for the Linux desktop, one step at a time.

This is a simple Telegram bot with interface to Firefly III to process and store simple transactions.
This is a simple Telegram bot with interface to Firefly III to process and store simple transactions.

Firefly Telegram Bot Fireflies are free, so beautiful. (Les lucioles sont libres, donc belles.) 鈥 Charles de Leusse, Les Contes de la nuit This is a s

Comments
  • Usage at first not really clear.

    Usage at first not really clear.

    I wasn't really sure whether i should write this issue or not. It might be possible that I'm the problem here. But still you have written this library and maybe you can help me and the next unlucky person. First of all, thank you for this library. It is a nice idea for embedded rust and I wanted to use it for a small private project. I've learned using your demo application and also the readme you have provided. Sadly there is a huge disconnect between real usage and the demo. Your work on sprocket-boot actually made it click for me. But before that I've spent 3 days of pure pain and even so it made click, I don't understand why and how. Please let me elaborate:

    This code is based on the demo. It doesn't do anything useful. The thing is that it does not compile and I don't understand why that is.

    struct SlowPrinter {
        list: VecDeque<i32>,
    }
    
    impl Future for SlowPrinter {
        type Output = ();
    
        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            if let Some(x) = self.list.pop_back() {
                println!("{}", x);
                Poll::Pending
            } else {
                Poll::Ready(())
            }
        }
    }
    async fn print_some(mut printer: SlowPrinter) {
        printer.await;
        printer.await; // !!! <-- This causes error !!!
    }
    

    VecDeque is something which doesn't implement the Copy trait. I'm fully aware why that is but I don't understand why await does require it. What is wrong with this example. Please teach me.

    error[E0382]: use of moved value: `printer`
       --> tool/src/main.rs:160:5
        |
    158 | async fn print_some(mut printer: SlowPrinter) {
        |                     ----------- move occurs because `printer` has type `SlowPrinter`, which does not implement the `Copy` trait
    159 |     printer.await;
        |            ------ `printer` moved due to this method call
    160 |     printer.await; // !!! Uncommenting causes error !!!
        |     ^^^^^^^ value used here after move
        |
    note: this function takes ownership of the receiver `self`, which moves `printer`
       --> /home/andre/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/into_future.rs:128:20
        |
    128 |     fn into_future(self) -> Self::IntoFuture;
        |                    ^^^^
    

    Then again this here does compile. My problem here is that poll_fn was not mentioned in the demo, so I missed it. Also I'm not able grasp what is different. I do realize that this is by far no issue of cassette. It is an issue with async. But I'm not even able to proper phrase the problem to get tutorials or manuals about this. Every async book or blog I've found so far doesn't provide infos about ownership in async.

    struct SlowPrinter2 {
        list: VecDeque<i32>,
    }
    impl SlowPrinter2 {
        pub fn print(&mut self) -> impl Future<Output = ()> + '_ {
            poll_fn(move |_| {
                if let Some(x) = self.list.pop_front() {
                    println!("{}", x);
                    Poll::Pending
                } else {
                    Poll::Ready(())
                }
            })
        }
    }
    
    async fn print_some2(mut printer: SlowPrinter2) {
        printer.print().await;
        printer.print().await;
    }
    
    opened by Slamy 0
  • Polling a done future

    Polling a done future

    Hi,

    I'm wondering what the reasons behind this TODO are: https://github.com/jamesmunns/cassette/blob/main/src/lib.rs#L341

    I currently have a use case where I want to poll a future that becomes read every now and then. That is a problem with cassette, because

    • Cassette::new consumes the future and there's currently no way to retrieve it back (into_inner).
    • poll_on refuses to work if it was ready once.

    My current workaround is that I have a wrapper future around my actual future and I re-create the Cassette with the wrapper every time it becomes ready. That works, but is less than optimal, though. I do not want to re-create the actual future that I'm polling, because that has side effects.

    Therefore, what are the options for a Cassette change? How could this todo!() be removed? If not, would it be acceptable, if an unwrap (consuming into_inner) would be added to retrieve the future, so that I can re-create the Cassette with the same future?

    Thanks in advance.

    opened by mbuesch 0
Owner
James Munns
Bringing Rust to new places, big and small. Managing Director @ Ferrous Systems GmbH, Core Team @ Rust Embedded Working Group
James Munns
Frame is a markdown language for creating state machines (automata) in 7 programming languages as well as generating UML documentation.

Frame Language Transpiler v0.5.1 Hi! So very glad you are interested in Frame. Frame system design markdown language for software architects and engin

Mark Truluck 35 Dec 31, 2022
A blazinlgy fast 馃殌 transpiler written in rust 馃 that fixes (pun intended) your problems

Pissfix ?? Pissfix is a blazingly fast ?? programming language that transpiles to a "interesting" and not well known programming language called "Post

null 3 Sep 28, 2023
Primitive Roblox Lua executor

rulx Primitive Roblox Lua executor Build cargo build --target i686-pc-windows-msvc --release License rulx is free and open source software licensed un

ORC Free and Open Source Software 5 Aug 16, 2022
LIBFFM - field-aware factorization machines - in Rust

LIBFFM Rust LIBFFM - field-aware factorization machines - in Rust Getting Started LIBFFM Rust is available as a Rust library and a command line tool.

Andrew Kane 1 Jan 8, 2022
Symbolically Executable Stack Machines in Rust

Symbolically Executable Stack Machines in Rust Symbolic Stack Machines is a library for implementing symbolically executable stack-based virtual machi

TannrA 64 Dec 28, 2022
Envwoman is an application, to sync your .env-files across multiple machines

Envwoman is an application, to sync your .env-files across multiple machines. The main goal is to make Envwoman secure and trustworthy, so everything is open-source and the data will never in plain-text on the server. Encryption happens client-sided via aes-gcm.

Mawoka 3 Sep 28, 2022
Cross-platform GUI written in Rust using ADB to debloat non-rooted android devices

Cross-platform GUI written in Rust using ADB to debloat non-rooted android devices. Improve your privacy, the security and battery life of your device.

w1nst0n 6.8k Jan 2, 2023
proc macros for generating mut and non-mut methods without duplicating code

mwt Hey! You! Read this before using! mwt was thrown together pretty quickly for personal use, because I couldn't find an existing crate that does thi

null 1 Dec 24, 2021
The Solid-State Register Allocator

The Solid-State Register Allocator A simple, extremely fast, reverse linear scan register allocator. See the detailed write-up for an in-depth explana

Matt Keeter 66 Dec 13, 2022
This crate defines a single macro that is a brainfunct compile-time interpreter.

Compile Protection This crate defines a single macro that is a brainfunct compile-time interpreter. One example is as follows #![recursion_limit = "18

John Marsden 7 Nov 29, 2021