#状态机 #状态 #有限状态机 #状态转换

无标准库 sad_machine

Sad Machine - 一个静态状态机宏

1 个稳定版本

1.0.0 2021 年 9 月 8 日

模拟 中排名第 313

Download history 59/week @ 2024-02-26 43/week @ 2024-03-04 91/week @ 2024-03-11 80/week @ 2024-03-18 65/week @ 2024-03-25 33/week @ 2024-04-01 38/week @ 2024-04-08 16/week @ 2024-04-15 70/week @ 2024-04-22 33/week @ 2024-04-29 22/week @ 2024-05-06 72/week @ 2024-05-13 23/week @ 2024-05-20 32/week @ 2024-05-27 25/week @ 2024-06-03 42/week @ 2024-06-10

每月下载量 122
3 个工具箱中使用(通过 xvc-pipeline

MIT/Apache 许可

38KB
870

Sad Machine

sad_machine 提供了一个宏来声明式地定义状态机及其状态之间的转换。它专注于为处理事件循环并使用状态机来跟踪其状态的程序提供良好的 API。

sad_machinesm 库的分支,移除了 traits 并仅保留了宏,并重新设计了生成的代码以更友好地使用枚举。

用法

sad_machine 仅公开一个宏,state_machine!。一个快速示例

use sad_machine::state_machine;

state_machine! {
    Lock {
        InitialStates { Locked, Unlocked }

        TurnKey {
            Locked => Unlocked
            Unlocked => Locked
        }

        BreakKeyhole {
            Locked, Unlocked => Broken
        }

        Repair {
            Broken => Locked
        }
    }
}

fn main() {
    let mut lock = Lock::locked();

    loop {
        match lock {
            Lock::Locked(m @ LockedState::FromInit) => lock = m.turn_key(),
            Lock::Unlocked(m) => lock = m.turn_key(),
            Lock::Locked(m) => lock = m.break_keyhole(),
            Lock::Broken(_) => break,
        }
    }

    assert_eq!(lock, Lock::Broken(BrokenState::FromBreakKeyhole));
}

在这个示例中,生成的宏

  • 一个名为 Lock 的枚举,包含枚举的所有状态。
  • 为每个状态创建一个枚举,包含触发转换的事件名称。对于 Unlocked 状态,枚举称为 UnlockedState 并包含两个情况 FromInit, FromTurnKey
  • 两个初始化函数:Lock::locked()Lock::unlocked(),与在 InitialStates 中定义的状态相对应。
  • 状态枚举的转换方法。对于 Broken 状态,生成一个 .repair() 方法,与 Repair 事件相对应。

sm 的 API 的一些区别

  • 生成的代码没有包装在模块中,所有枚举和函数都是 pub
  • 初始状态编码为状态枚举上的函数。
  • 转换编码为状态枚举案例中包含的对象上的方法。
  • 初始状态和转换函数都返回状态枚举。
  • 状态枚举的案例包含触发转换的事件名称。每个状态都有自己的枚举来此目的。
  • 转换不会消耗原始状态机。
  • 每个宏实例化只能定义一个状态机。

描述性示例

以下示例逐步解释了如何使用提供的宏创建一个新的状态机,然后如何在您的代码中使用创建的机器。

声明新的状态机

首先,我们从crate中导入宏

use sad_machine::state_machine;

然后,我们启动宏声明

state_machine! {

然后,提供一个机器的名称,并声明允许的初始状态列表

    Lock {
        InitialStates { Locked, Unlocked }

最后,我们声明一个或多个事件及其相关的转换

        TurnKey {
            Locked => Unlocked
            Unlocked => Locked
        }

        BreakKeyhole {
            Locked, Unlocked => Broken
        }
    }
}

完成了。我们已经定义了状态机结构,有效的转换,现在可以在代码中使用这个状态机。

使用您的状态机

您可以按照以下方式初始化机器

let sm = Lock::locked();

我们已经将机器初始化在Locked状态。sm是一个枚举,涵盖状态机的所有可能状态,每个状态都包含触发它的事件名称。状态枚举的完整模式匹配如下所示

match lock {
    Lock::Locked(LockedState::FromInit) => ..,
    Lock::Locked(LockedState::FromTurnKey) => ..,
    Lock::Locked(LockedState::FromRepair) => ..,
    Lock::Unlocked(UnlockedState::FromInit) => ..,
    Lock::Unlocked(UnlockedState::FromTurnKey) => ..,
    Lock::Broken(BrokenState::FromBreakKeyhole) => ..,
}

要将此机器转换为Unlocked状态,我们在LockedState对象上发送turn_key方法

let lock = match lock {
    Lock::Locked(locked) => locked.turn_key(),
    _ => panic!("wrong state"),
}

注意事项

sm的行为不同,当执行转换时,状态机不会消耗以前的状态,因此在进行并发操作时要小心。

由于Rust缺少枚举的私有构造函数,它也不会阻止您构建不是初始状态的状态。

为什么要分叉

sm做出的某些设计选择与我的用例冲突。

我在事件循环中使用该库

  1. 状态被存储为其枚举的Variant表示形式,并且只能在每个循环中前进一步
  2. 多个事件可以触发状态变化到某个状态,但我并不特别关心触发状态变化的事件

sm似乎有不同的设计目标

  1. transition方法返回一个Machine类型而不是枚举,这迫使我每次都必须在结果上调用.as_enum()来将其存储为Variant,但这使得在单段代码中触发多个状态转换变得容易
  2. Variant枚举的案例还包括触发状态变化的事件名称,这导致我在每个有多个入口点的状态中重复代码

sad_machine的API专注于状态枚举而不是具体状态。它生成的转换方法返回枚举,这使得在单段代码中触发多个转换变得困难,但另一方面,它消除了调用.as_enum()的冗余,并且其状态枚举不会将其事件名称编码在案例名称中,而是将其携带在其中。

此分叉保留了sm的DSL状态机定义解析器,并更改了生成的代码。

许可证

根据以下任一许可证授权

这是原始crate的许可证,我宁愿不改变它。

贡献

除非你明确表示,否则,任何有意提交以包含在根据Apache-2.0许可证定义的工作中的贡献,应如上所述双许可,不附加任何额外条款或条件。

依赖

~1.5MB
~36K SLoC