#state-machine #finite-state-machine #state #machine #fsm #macro

macro_machine

使用宏实现的状态机生成器

3 个不稳定版本

使用旧的Rust 2015

0.2.0 2017年10月4日
0.1.1 2017年9月20日
0.1.0 2017年9月20日

1753算法

MIT 协议

31KB
360

Rust宏中的有限状态机生成器

概述

使用此宏,您可以轻松以声明性方式实现有限状态机。

状态机由以下组成

  • 名称
  • 初始状态
  • 状态列表
  • 命令列表
  • 状态节点列表

每个 状态节点 包含

  • 状态
  • 上下文(可选)
  • 命令反应列表

每个 命令反应 包含

  • 要反应的命令
  • 用户定义的反应代码(可选)
  • 机器的下一个状态(可选)

开始的工作示例

假设我们想实现这样的机器

FSM example

相应的代码将如下所示

#[macro_use] extern crate macro_machine;
declare_machine!(
    MyMachine(A {counter: 0}) // Name and initial state with initial value
    states[A,B] // List of states
    commands[Next] // List of commands
    (A context{counter: i16}: // State node and this state context description with name binding
        >> { // Executed on state A enter
            println!("Enter A: {:?}", context);
            context.counter = context.counter + 1;
        }
        << { // Executed on state A leave
            println!("Leave A: {:?}", context);
            context.counter = context.counter + 1;
        }
        Next {
            println!("Next in A: {:?}", context);
            context.counter = context.counter + 1;
        } => B {counter: context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our counter value.
    )
    (B context{counter: i16}:
        >> {
            println!("Enter B: {:?}", context);
            context.counter = context.counter + 1;
        }
        << {
            println!("Leave B: {:?}", context);
            context.counter = context.counter + 1;
        }
        Next {
            println!("Next in B: {:?}", context);
            context.counter = context.counter + 1;
        } => A {counter: context.counter};
    )
);

fn main() {
    use MyMachine::*;
    let mut machine = MyMachine::new();
    machine.execute(&MyMachine::Commands::Next).unwrap();
    machine.execute(&MyMachine::Commands::Next).unwrap();
}

更详细的解释

最简单的状态机示例

#[macro_use] extern crate macro_machine;
declare_machine!(
    Simple(A) // Name and initial State
    states[A,B] // list of States
    commands[Next] // list of Commands
    (A: // State Node
        Next => B; // Command Reaction. Just change state to B
    )
    (B:
        Next => A; // And back to A
    )
);

因此,现在您可以使用状态机

fn main() {
    use Simple::*;
    let mut machine = Simple::new();
    machine.execute(&Simple::Commands::Next).unwrap();
    machine.execute(&Simple::Commands::Next).unwrap();
}

您可以为机器添加一些智能。

每个状态可以保存一些数据。在状态改变时,您可以在状态之间传递一些数据。这就像您创建了一个带有一些字段初始化的结构体。

#[macro_use] extern crate macro_machine;
declare_machine!(
    Simple(A{counter:0}) // Name and initial State with initial value
    states[A,B] // list of States
    commands[Next] // list of Commands
    (A context{counter:i16}: // State Node and this state context description with binding name
        Next {context.counter=context.counter+1}=> B{counter:context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our x value.
    )
    (B context{counter:i16}:
        Next {context.counter=context.counter+1}=> A{counter:context.counter};
    )
);

让我们检查我们的状态传递

fn main() {
    use Simple::*;
    let mut machine = Simple::new();

    // We are in state A and have our initial value 0
    assert!(match machine.get_current_state(){
        States::A{context}=> if context.counter == 0 {true} else {false},
        _=>false
    });
    machine.execute(&Simple::Commands::Next).unwrap();

    // We are in state B and have counter == 1
    assert!(match machine.get_current_state(){
        States::B{context}=> if context.counter == 1 {true} else {false},
        _=>false
    });
    machine.execute(&Simple::Commands::Next).unwrap();

    // Again in state A and have counter == 2
    assert!(match machine.get_current_state(){
        States::A{context}=> if context.counter == 2 {true} else {false},
        _=>false
    });
}

还有在每个状态的进入和离开时的回调。

#[macro_use] extern crate macro_machine;
declare_machine!(
    Simple(A{counter:0}) // Name and initial State with initial value
    states[A,B] // list of States
    commands[Next] // list of Commands
    (A context{counter:i16}: // State Node and this state context description with binding name
        >> {context.counter = context.counter+1;} // Execute when enter state A
        << {context.counter = context.counter+1;} // Execute when leave state A
        Next {context.counter=context.counter+1;} => B{counter:context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our x value.
    )
    (B context{counter:i16}:
        Next {context.counter=context.counter+1} => A{counter:context.counter};
    )
);
fn main() {
    use Simple::*;
    let mut machine = Simple::new();
    assert!(match machine.get_current_state(){
        // We are in state A and have value 1. Because Enter State callback executed.
        States::A{context}=> if context.counter == 1 {true} else {false},
        _=>false
    });
    machine.execute(&Simple::Commands::Next).unwrap();
    assert!(match machine.get_current_state(){
        // We are in state B and have counter == 3. Increment happen on User Code execution and execution of Leave state callback.
        States::B{context}=> {println!("context counter: {}", context.counter);if context.counter == 3 {true} else {false}},
        _=>false
    });
    machine.execute(&Simple::Commands::Next).unwrap();
    assert!(match machine.get_current_state(){
        // Again in state A and have counter == 5. Increment happen on User Code execution and on state A enter.
        States::A{context}=> if context.counter == 5 {true} else {false},
        _=>false
    });
}

机器作用域上下文的示例。此上下文存在于机器的生命周期中。

让我们计算机器的状态改变次数

#[macro_use] extern crate macro_machine;
declare_machine!(
    Simple machine_context{counter: i16} (A) // Declare machine scoped context
    states[A,B]
    commands[Next]
    (A :
        >> {machine_context.counter=machine_context.counter+1;} // Add 1 when enter in state
        Next => B; // Just switch to other state
    )
    (B :
        >> {machine_context.counter=machine_context.counter+1;}
        Next => A;
    )
);
fn main() {
    use Simple::*;
    let mut machine = Simple::new(0); // Create machine and initiate machine context by 0
    let context = machine.get_inner_context();
    assert!(context.counter == 1);
    machine.execute(&Simple::Commands::Next).unwrap();
    let context = machine.get_inner_context();
    assert!(context.counter == 2);
    machine.execute(&Simple::Commands::Next).unwrap();
    let context = machine.get_inner_context();
    assert!(context.counter == 3);
}

变更日志

0.2.0

  • 修改了Leave动作的行为。现在它在创建新的状态上下文之前执行。
  • 添加了机器作用域上下文。它可以由机器内的所有回调使用。此上下文中的数据具有机器的生命周期。

无运行时依赖项