#状态机 #异步 #执行器 #非阻塞 #嵌入式 #唤醒器 #单个未来

无 std cassette

一个简单、单未来、非阻塞的执行器,适用于构建状态机

8 个版本

0.3.0 2024 年 4 月 13 日
0.2.5 2024 年 1 月 9 日
0.2.4 2023 年 11 月 27 日
0.2.3 2021 年 5 月 1 日
0.0.0 2019 年 7 月 19 日

#86异步

Download history 85/week @ 2024-04-28 60/week @ 2024-05-05 275/week @ 2024-05-12 116/week @ 2024-05-19 362/week @ 2024-05-26 244/week @ 2024-06-02 186/week @ 2024-06-09 224/week @ 2024-06-16 323/week @ 2024-06-23 91/week @ 2024-06-30 45/week @ 2024-07-07 128/week @ 2024-07-14 34/week @ 2024-07-21 47/week @ 2024-07-28 362/week @ 2024-08-04 253/week @ 2024-08-11

703 每月下载量
5 crates 中使用

MIT/Apache

17KB
81

Cassette

一个简单、单未来、非阻塞的执行器,适用于构建状态机。设计为无 std 和嵌入式友好。

此执行器完全忽略了唤醒器和上下文,这意味着所有异步函数都应该预期在完成前被轮询多次。

灵感

所以,我真的不太擅长异步,但我喜欢能够使用在需要花费时间完成任务的任务上实现让出或等待的能力。

这里的想法是,你会编写一个顶级 async 函数,该函数最终会解析为某个值,或者会永远运行(作为状态机)。

工作原理

  1. 你编写一些异步函数
  2. 你调用“顶级”异步函数
  3. 你轮询它,直到它解析(或永远)

注意:此演示代码位于此仓库的 demo/ 目录 中。

步骤 1 - 你编写一些异步函数

这是我们的状态机的“上下文”,描述了几个高级行为以及各个子步骤。

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;
            }
        }
    }
}

我们还可以为需要轮询直到就绪的代码创建简单的“未来”

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!");
}

步骤 2 - 你调用“顶级”异步函数

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

    /* ... */
}

步骤 3 - 你轮询它,直到它解析(或永远)

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

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

更大的演示

如果您想看到更大的演示,我使用 Cassette 实现了一个针对 thumbv6m 目标的 I2C 外设引导加载程序状态机。您可以在 该 PR 中查看更多上下文。

许可证

本软件包采用MITApache 2.0许可证。

无运行时依赖