#状态机 #状态 #事件驱动 #事件 #机器 #编译器优化

无需 std apparat

轻量级的事件驱动行为状态机

7 个版本

0.5.5 2021 年 5 月 21 日
0.5.4 2021 年 5 月 14 日
0.5.3 2021 年 4 月 18 日
0.4.0 2021 年 4 月 17 日

#804Rust 模式

每月 23 次下载

MIT 许可证

29KB
241

Apparat

轻量级的事件驱动行为状态机

显著特性

  • 无 unsafe,除非您主动启用它(见下文“特性标志”)
  • 与 no-std 兼容
  • 小巧且编译速度快
    • 少于 250 行代码
    • 无依赖项
    • 无过程宏
  • 尽管宏的数量可管理,但非常易于使用
  • 高度灵活,适用于多种用例
  • 无动态分发,使编译器优化成为可能

注意: 我仍在对此进行一些实验,因此版本低于 1.0 时,在点版本发布中可能会有破坏性 API 变更。如果您想确保这一点,请在 Cargo.toml 中指定确切的版本号。在点-点版本发布中永远不会进行破坏性更改。

特性标志

"unsafe"(默认禁用)

此特性标志在某些情况下可以促进更多的编译器优化(使用 cargo asm 验证)。这是通过在一处使用 core::hint::unreachable_unchecked() 来实现的,作者确信它是可靠的。尽管如此,使用此功能风险自负。如果您对此感兴趣,请查看代码 - 我已在其中写了一个关于我的假设的安全性注释。如果您认为我遗漏了什么,请提交一个问题。

架构和用法

您提供的类型

  • 任意数量的类型,代表不同的状态*
  • 一个上下文类型,在处理事件和转换期间可变访问。
  • 一个事件类型,状态机可以处理
  • 一个返回类型,每当处理一个事件时都会返回。如果不需要,可以使用单元类型。

* 状态类型内的数据仅在该状态内可访问。这些数据在转换时会被丢弃,在处理事件时会移动。在某些情况下,这些移动可能会被优化掉,但一般来说,为了获得最佳性能,状态类型应该相对较小且易于移动。
如果无法保持它们较小,并且您在未进行转换的情况下处理了大量事件,请考虑在状态类型中将更大的数据放在一个Box中。或者,您也可以将更大的数据作为上下文类型的一部分,这样就不会被移动或丢弃。但如果是后者,数据当然也会从其他状态中可访问。

注意:所有提供的类型都必须实现Debug

生成的实体

  • 一个包装枚举,具有所有提供的状态类型的变体
  • 为包装枚举实现ApparatWrapper特质
  • 为包装枚举实现ApparatState特质。枚举将所有该特质的调用委托给内部状态对象。
  • 为所有提供的状态实现Wrap特质(方便起见)

特质

ApparatState<StateWrapper>

必须为所有状态类型实现此特质。唯一要求的方法是handle,它接受一个事件并返回一个Handled<StateWrapper>。这仅仅是下一个状态包裹在StateWrapper中,并附带一个输出值。为了构造此返回类型,我们首先可以在我们的状态上调用.wrap(),然后在包装器上调用.with_output(...)。如果我们的输出类型实现了Default,我们也可以使用.default_output()

ApparatState特质中还有另外两个方法:initis_init。这些构成了一个初始化机制:在构造一个Apparat之后,在处理任何事件之后,都会在当前状态上调用init,直到is_init返回true。这样,单个事件可以触发多个、上下文相关的转换。这种情况在一个循环中发生,而不涉及递归。如果一个状态不需要这样,这些方法可以忽略。

TransitionFrom<OtherState, ContextData>

可以使用TransitionFrom特质来定义状态之间的特定转换。然后自动实现TransitionTo特质,因此我们可以使用 turbofish 语法调用.transition。这种设计类似于标准库中的FromInto,但TransitionFromTransitionInto还可以作为副作用修改提供的上下文。TransitionInto建议用于特质界限。

如果您需要使用相同的状态构造方法定义许多转换,可以使用 transitions 宏。在宏调用内部,可以像这样定义一个转换:StateA -> StateB::new。在这种情况下,类型 StateB 必须实现一个具有以下签名的 new 方法:fn new(prev: StateA, ctx: &mut ContextData) -> Self。如果从多个不同的状态到 StateA 的转换需要使用相同的转换方法,则第一个参数必须是泛型的。所有这些都在名为 "counter_transitions_macro" 的示例中得到了演示。

包装<StateWrapper>

Wrap<StateWrapper> trait 提供了一个 wrap 方法,将单个状态对象转换为 StateWrapper。与使用 into 相比,这更简洁,并且在更多情况下允许类型推断。宏会自动为所有状态类型实现 Wrap

示例

要查看一个更完整的示例,请查看 examples 目录中的 counter.rs

//! This state machine switches from `StateA` to `StateB` on a single event but
//! then needs three events to switch back to `StateA`. Additionally it keeps
//! track of how often it got toggled back from `StateB` to `StateA`.

use apparat::prelude::*;

// Define the necessary types
// --------------------------

// States

#[derive(Debug, Default)]
pub struct StateA;

#[derive(Debug, Default)]
pub struct StateB {
    ignored_events: usize,
}

// Context

// Data that survives state transitions and can be accessed in all states
#[derive(Debug, Default)]
pub struct ContextData {
    toggled: usize,
}

// Auto-generate the state wrapper and auto-implement traits
// ---------------------------------------------------------

// In this example we are just using the unit type for `event` and `output`
// because we are only handling one kind of event and we don't care about values
// being returned when events are handled.
build_wrapper! {
    states: [StateA, StateB],
    wrapper: MyStateWrapper, // This is just an identifier we can pick
    context: ContextData,
    event: (),
    output: (),
}

// Define transitions
// ------------------

impl TransitionFrom<StateB> for StateA {
    fn transition_from(_prev: StateB, ctx: &mut ContextData) -> Self {
        // Increase toggled value
        ctx.toggled += 1;
        println!("B -> A          | toggled: {}", ctx.toggled);
        StateA::default()
    }
}

impl TransitionFrom<StateA> for StateB {
    fn transition_from(_prev: StateA, ctx: &mut ContextData) -> Self {
        println!("A -> B          | toggled: {}", ctx.toggled);
        StateB::default()
    }
}

// Implement the `ApparatState` trait for all states
// -------------------------------------------------

impl ApparatState for StateA {
    type Wrapper = MyStateWrapper;

    fn handle(self, _event: (), ctx: &mut ContextData) -> Handled<MyStateWrapper> {
        println!("A handles event | toggled: {}", ctx.toggled);
        // Transition to `StateB`
        let state_b = self.transition::<StateB>(ctx);
        // Now we need to wrap that `state_b` in a `MyStateWrapper`...
        let state_b_wrapped = state_b.wrap();
        // ... and add an output value to turn it into a `Handled<...>`.
        state_b_wrapped.default_output()
        // If we would need a different output value or our output type wouldn't
        // implement `Default` we would have to use `.with_output(...)` instead.
    }
}

impl ApparatState for StateB {
    type Wrapper = MyStateWrapper;

    fn handle(mut self, _event: (), ctx: &mut ContextData) -> Handled<MyStateWrapper> {
        println!("B handles event | toggled: {}", ctx.toggled);
        if self.ignored_events == 2 {
            self.transition::<StateA>(ctx).wrap().default_output()
        } else {
            self.ignored_events += 1;
            self.wrap().default_output()
        }
    }
}

// Run the machine
// ---------------

fn main() {
    let mut apparat = Apparat::new(StateA::default().wrap(), ContextData::default());

    // Handle some events
    for _ in 0..10 {
        apparat.handle(());
    }
}

无运行时依赖

功能