[转]boost 状态机学习

来源:互联网 发布:手机预约挂号软件 编辑:程序博客网 时间:2024/05/01 05:40

今天看到boost有个库叫做statechart. 不禁兴趣较大,学习了一下,受益匪浅.

大体来说,这个库处理了大部分状态机uml中涉及到的点.

1. 简单状态处理

如上图,我们暂时认为acive是一个简单状态而不是一个复合状态. 那么按照状态机来说,有几个元素呢?

1) 初始状态

2) 转换事件/action

3) 中间状态

4) 结束状态 (暂无)

那么boost::statechart 重现过后,有如下对应对象

1) state_machine

2) simple_state / state

3) event

4) transition / reaction

我们来看看大致的代码对应:

1. 定义一个状态机对象

struct Machine : sc::state_machine
{
Machine()
{
  std::cout << __FUNCTION__ << std::endl;
}
~Machine()
{
  std::cout << __FUNCTION__ << std::endl;
}

};

2. 定义初始状态对象

struct StatActive : sc::simple_state
{
typedef sc::transition reactions;

StatActive()
{
  m_lElapsed = 0;
  std::cout << __FUNCTION__ << std::endl;
}
~StatActive()
{
  std::cout << __FUNCTION__ << std::endl;
}

};

3. 定义转化,注意第二步中已经贴出转换步骤代码,即typedef 部分

4. 启动状态机

Machine machine;
machine.initiate();
machine.process_event(EvReset());

machine.terminate();

就这样一个简单的状态机以及其相关的逻辑均已经体现.

下面我们来看一个复杂点的状态机,一个数码相机的例子;

  先来分析一下这个状态机有哪些元素:

1. 有3个状态

  1) notshooting (复合)

  2) shooting (复合)

  3) storing (基本)

所谓基本和复合大致的区别是符合状态内部还有小的状态机.

2. 有如下几种动作转换

  1) HalfPress 

     a) 自动对焦模式

     b) 手动对焦模式

  2) FullPress

     a) 手动对焦模式

  3) Release

下面再来看复合状态

notshooting 内部分为:

1. idle状态

2. 配置状态

有一个转换事件:

1. config

shooting分为:

1. focusing

2. focused

3. storing

事件:

1. infocus

2. fullpressed  with condition

具体代码就不贴了,可以参见  d:/boost/test/state_machine

特别说明:

  看到了focusing这个状态了吧, 一般来说这个状态是需要持续一段时间的,  所以如果这段时间内用户按下了fullpress,如果按照上面的状态机处理,则此状态将会被丢弃; 这儿如果我们做一个改进,即将此状态记录下来,然后当用户再次对焦准确后自动触发,这就是boost::derreral的作用.

typedef mpl::list<
  sc::transition< EvInfocus, StatFocused >,
//  sc::transition< EvFullPress , StatFocusing>
  sc::deferral
> reactions;

但是据我测试,如果多个fullpress的话,只会有一个生效。

一个小技法:

大部分情况下如果

typedef sc::transition reactions;

那么往往当状态发生转移时候,目标状态的对象会被构造;即通过构造函数来呼叫;

但是也可通过

typedef sc::transition< EvInFocus, Focused,
Shooting, &Shooting::DisplayFocused > reactions;
这种方式,来指定对target的呼叫,甚至可以看到这个target的处理函数还不是focused对象。

从跟踪来看,此函数的call 遭遇focused 状态的构造。

事件通知机制

目前看到的transit 方式实际上优点类似于一种同步方式;

但是实际上还可以通过另外一种方式处理,即状态切换推迟到当前的reaction结束;

比如:

StatFocused::~StatFocused(){  post_event( EvPumpingFinished() );}
那么post_event干了些啥呢?
它将一个事件放入主队列,状态机将在当前reaction处理结束后立刻处理此事件;
事件可以在reaction , enter entry , exit entry , transaction reaction 中被触发,但是在
内部的入口和出口点时候有一点复杂。
 
struct Pumping : sc::state< Pumping, Purifier >{  Pumping( my_context ctx ) : my_base( ctx )  {    post_event( EvPumpingStarted() );  }  // ...};
即需要发送的状态,必需派生自state , 且实现入口点的构造,如上.
 
只要一个入口点的动作需要和外部世界解除,比如主队列,那么就需要如此,类似的函数有:
  • simple_state<>::post_event()
  • simple_state<>::clear_shallow_history<>()
  • simple_state<>::clear_deep_history<>()
  • simple_state<>::outermost_context()
  • simple_state<>::context<>()
  • simple_state<>::state_cast<>()
  • simple_state<>::state_downcast<>()
  • simple_state<>::state_begin()
  • simple_state<>::state_end()
 
但是幸运的是一般我们总是在react或者transaction中处理,因此一般来说不需要考虑。

这儿来举个例子,比如focusing 状态到 focused 状态,实际上后一个状态由前一个状态触发,而且从实际操作来看,是很有可能在前者的entry action中完成了对焦,然后触发此事件, 因此这个可能就是一个内部动作。

我之前的例子是,类似如下方式测试的:

std::cout << "testing focusing fullpressed" << std::endl;
machine.MemAvaiable() = true;
machine.AutoFocus() = true;
machine.process_event(EvHalfPress());
machine.process_event(EvInfocus());
machine.process_event(EvFullPress());
machine.process_event(EvRelease());

即是主动发送EvInfocus ,这个就是说外部出发了focusing 后又触发infocus,实在点说,这个可能性不如内部处理来得大。

看看输出:

再来看修改过后的代码:

      std::cout << "testing focusing fullpressed" << std::endl;
machine.MemAvaiable() = true;
machine.AutoFocus() = true;
machine.process_event(EvHalfPress());
//machine.process_event(EvInfocus());
machine.process_event(EvFullPress());
machine.process_event(EvRelease());

struct StatFocusing : sc::state
{
typedef sc::transition< EvInfocus, StatFocused,
    StatShooting, &StatShooting::DisplayFocuesed > reactions;

/*
typedef mpl::list<
  sc::transition< EvInfocus, StatFocused >,
//  sc::transition< EvFullPress , StatFocusing>
  sc::deferral
> reactions;
*/
StatFocusing( my_context ctx ) : my_base( ctx )
{
  std::cout << "enter focusing" << std::endl;

  post_event(EvInfocus());
}
~StatFocusing()
{
  std::cout << "leave focusing" << std::endl;
}
};

从输出来看是一致的。

不过过程是不一致的,后者是在machine.process_event(EvHalfPress())之后
machine.process_event(EvFullPress())之前得到处理的.

历史记录的处理:

由于idle状态是个复合状态,即其有两个状态,idle 和 config;那么也就意味着用户是可以在这两个状态的任何一个,按动 half-press , full-press 进行操作后,将返回到idle状态;但是有些情况下,用户也许希望回到前一个状态,比如曾经是在配置界面进入拍摄的。

请观察图中shooting 到notshooting 直接的H*,这这个标示状态切换前需要回到历史状态,*标示状态为0或者多个。

std::cout << "testing history" << std::endl;
machine.process_event(EvConfig());
machine.process_event(EvHalfPress());
machine.process_event(EvFullPress());
machine.process_event(EvRelease());

从上图可以看到,从拍照结束后进入到了config状态,即事发前的状态.

到了这儿,其实还有一个问题,即deep_history , 但是boost还提供了一个shallow_history,

从字面来看;另外从boost的表述,deep_history实际上是保存了一个内部状态的hierachy,然后返回时候返回到最内层状态,我们继续一下测试;修改config的状态图如下:

上图我们将config的状态从简单状态修改为一个复合状态,即其内部含有一个历史图片配置,以及单张图片配置两个内部状态。

从上图看到,在返回历史记录时候层次关系式ok的。

接下来看看shallow 和deep的差别;

std::cout << "testing history" << std::endl;
machine.process_event(EvConfig());
machine.process_event(EvSingleConfig());
machine.process_event(EvHalfPress());
machine.process_event(EvFullPress());
machine.process_event(EvRelease());

如果设定为has_shallow_hisotry shallow_hisotory 那么输出很上面相似;

如果启用has_deep_hisotory deep_history 那么输出如下:

你会发现,deep的效果能够追述到内部状态的切换;

另外还存在一个has_full_history deep_history,boost的文档说其相当于 shallow & deep.

更加的具体的我从测试中未发现差异。或许唯一的差别就是,当外界有时候会以 deep_history 有时候会以shallow_history 定义历史callback时候,has_full_history 就会有随之发生变化;

为了更加清晰的展示一个实用的例子, 再来看看修改后的uml图:

即开机后进入notshooting状态,但是notshooting状态也带有上次关机前的历史记录.

特别说明,由于visio的uml图无法画出inside inner state的图,因此H+只能挑个合适的地方画,比如shooting到idle的转换间的H+,被移动到了这儿,严格来说这个H+确实应该在状态之内,即notshooting之内.

std::cout << "testing idle hisotry" << std::endl;
machine.process_event(EvPowerOn());
machine.process_event(EvConfig());
machine.process_event(EvSingleConfig());
machine.process_event(EvPowerOff());
machine.process_event(EvPowerOn());

还有一些比较正交的状态图:

或者异常处理时候的的处理,这儿不再列举,有兴趣参考

http://www.boost.org/doc/libs/1_39_0/libs/statechart/doc/tutorial.html#OrthogonalStates。

原创粉丝点击