Skip to content

Boost.msm与状态机

msm状态机,也可以被称作是流程图,实现起来的方法有很多,现在我在这里说一下Boost.msm的实现方式。

Foa,你需要一张流程图,这张流程图决定了你整个程序的动作流程,借用一张boost官方例子

这张图里面有几个点需要注意的,也是这张图的关键点

  1. 五个状态,定义了五个流程
  2. 状态之间的连接线,定义了从一个状态转换到另一个状态的方式
  3. 初始的状态位

从第一项开始说,首先要定义一个总的状态机类,这个类继承于boost::msm::front::state_machine_def

class player_ : public boost::msm::front::state_machine_def<player_>
{
public:
    //code...
}

这个状态机的定义都放在这个类里面

在其中,定义了与这个状态机有关的组件,其中就包括了状态本体的组件

class Open : public boost::msm::front::state<>
{
public:
    template <class Event, class FSM>
    void on_entry(const Event&, FSM&) { std::cout << "entering: Open" << std::endl; }

    template <class Event, class FSM>
    void on_exit(const Event&, FSM&) { std::cout << "leaving: Open" << std::endl; }
};

本体里面其实只有两个函数,一个定义进入状态的函数,一个定义退出状态的函数。

其次要定义的就是状态与状态直接的连接函数,这个其实是作为一个list写在代码中的

struct transition_table : mpl::vector<
//    Start     Event        Target      Action                      Guard 
//   +---------+------------+-----------+---------------------------+----------------------------+ 
a_row< Stopped , play       ,  Playing  , &player_::start_playback                               >,
a_row< Stopped , open_close ,  Open     , &player_::open_drawer                                  >,
 _row< Stopped , stop       ,  Stopped                                                           >,
//   +---------+------------+-----------+---------------------------+----------------------------+ 
a_row< Open    , open_close ,  Empty    , &player_::close_drawer                                 >,
//   +---------+------------+-----------+---------------------------+----------------------------+ 
a_row< Empty   , open_close ,  Open     , &player_::open_drawer                                  >,
  row< Empty   , cd_detected,  Stopped  , &player_::store_cd_info   , &player_::good_disk_format >,
  row< Empty   , cd_detected,  Playing  , &player_::store_cd_info   , &player_::auto_start       >,
//   +---------+------------+-----------+---------------------------+----------------------------+ 
a_row< Playing , stop       ,  Stopped  , &player_::stop_playback                                >,
a_row< Playing , pause      ,  Paused   , &player_::pause_playback                               >,
a_row< Playing , open_close ,  Open     , &player_::stop_and_open                                >,
//   +---------+------------+-----------+---------------------------+----------------------------+ 
a_row< Paused  , end_pause  ,  Playing  , &player_::resume_playback                              >,
a_row< Paused  , stop       ,  Stopped  , &player_::stop_playback                                >,
a_row< Paused  , open_close ,  Open     , &player_::stop_and_open                                >
//   +---------+------------+-----------+---------------------------+----------------------------+ 
> {};

这个vector有五个参数,Start、Event、Target、Action、Guard,Start代表箭头的起始位,也就是status当前所在的位置;Event代表的是一个信号,当状态机收到一个信号的时候,就会在这个vector里面搜索以当前status为起始点时匹配这个信号的row;而收到Event信号以后,就会执行这一个row对应的Action,Action里面一般定义了一个函数的指针;执行完了Action之后,status就会变为这一个row对应的Target。Guard参数是一个特殊的参数,它是一个返回值为bool的函数,这个函数决定了一点,就是这一行row应不应该执行。

梳理一下执行的具体步骤,分为这几步:

  1. 当状态机处于某一状态时,收到一个类型为Event的触发信号
  2. 执行Guard(若有),如果返回结果为false,中断后续步骤,保留现有状态
  3. 离开现有状态,触发on_exit
  4. 执行Action(若有)
  5. 进入Target状态,触发on_entry

然后就是Guard和Action函数的组成了,这两个函数的输入都是一致的,都是由Event规定的结构体来组成的,区别就在于返回值,Action函数是一个无返回的void函数,而Guard则是一个返回值为bool的判断函数,举个栗子

struct cd_detected
{
    cd_detected(std::string name, DiskTypeEnum diskType)
        : name(name),
          disc_type(diskType)
    {
    }

    std::string name;
    DiskTypeEnum disc_type;
};

void store_cd_info(const cd_detected&) { std::cout << "player::store_cd_info\n"; }
bool good_disk_format(const cd_detected& evt)
{
    if (evt.disc_type != DISK_CD)
    {
        std::cout << "wrong disk, sorry" << std::endl;
        return false;
    }
    return true;
}

上面一个为Action函数,下面的为Guard函数,它们的输入都为规定好的Event结构体。

最后是初始状态的定义函数,如果不定义初始状态,会导致启动时找不到状态而报错

using initial_state = Empty;

这样就基本结束了,你可以写一个test函数来测试这个状态机

void pstate(const player& p)
{
    std::cout << " -> " << state_names[p.current_state()[0]] << std::endl;
}

void test()
{
    player p;
    // needed to start the highest-level SM. This will call on_entry and mark the start of the SM
    p.start();
    // go to Open, call on_exit on Empty, then action, then on_entry on Open
    p.process_event(open_close());
    pstate(p);
    p.process_event(open_close());
    pstate(p);
    // will be rejected, wrong disk type
    p.process_event(cd_detected("louie, louie", DISK_DVD));
    pstate(p);
    p.process_event(cd_detected("louie, louie", DISK_CD));
    pstate(p);
    p.process_event(play());

    // at this point, Play is active      
    p.process_event(pause());
    pstate(p);
    // go back to Playing
    p.process_event(end_pause());
    pstate(p);
    p.process_event(pause());
    pstate(p);
    p.process_event(stop());
    pstate(p);
    // event leading to the same state
    // no action method called as it is not present in the transition table
    p.process_event(stop());
    pstate(p);
}

int main()
{
    test();
    return 0;
}

TODO:错误处理

Published in技术探究

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注