美文网首页
boost 状态机--中级篇

boost 状态机--中级篇

作者: 守拙圆 | 来源:发表于2017-12-25 15:50 被阅读183次

    原文:The Boost Statechart Library
    译者:penghuster

    进阶主题:数码相机

    目前一切都很好,然而,上述方法也存在以下限制:

    • 可扩展性差:只要编译器到达 state_machine::initiate() 调用所在位置,大量的模板类实例化将发生,这只有在状态机所有的状态都完整申明后才能成功。这也就是说,状态机的所有代码必须在一个单独编译单元中完成(虽然action能够被单独编译,但在这里并不是焦点)。对于更大的状态机而言,这将导致以下限制:
      • 在某种程度上,编译器为了达到内部模板实例化会造成一些限制和舍弃。这通常发生在中等尺寸的状态机上。例如,在调试模式一个通用的编译器拒绝编译任何超过3位的比特机的早期版本。这意味着编译器达到了它的极限在8个状态,24个转变和16个状态,64个转变。
      • 多程序员协同编码同一个状态机是困难的,因为每一点改动都将不可避免地导致整个状态机的重新编译。
    • 对于一个事件而言最多一个动作触发:根据 UML 一个状态可能有多个能够被同一事件触发的动作。这使得动作的互助排外守卫发挥作用。上面的例子中仅仅是一个事件最多触发一个无守卫的动作。而且,UML 概念中转接和选择点不能直接支持此概念。

    所有的这些限制可以通过自定义动作来克服。注意:滥用自定义动作很容易导致未定义行为。请在使用自定义动作前学习此文档。

    延伸状态机到多转变单元

    比如说公司想要开发一款数码相机。相机需要有如下控制功能:

    • 快门按钮,此按钮可以半按和全按。与此相关的事件分别为 EvShutterHalf, EvShutterFull 和 EvShutterReleased。
    • 设置按钮,代表的事件是 EvConfig。
    • 许多此处不关注的其它按钮。

    一个相机用例,在任何配置模式下拍照者可以半按快门,并且相机将立即进入拍照模式。下面的状态图表是完成此行为的一种方式:

    配置和拍摄状态将包括大量的内嵌状态,而空闲状态相对简单。因此决定组建两个团队。一个团队实现拍摄模式,另一个实现配置模式。两个团队已经就拍摄团队用于获取配置设置的接口达成一致。我们想要确保两个团队在最少可能接口下工作。我们放两个状态到状态转变单元中,如此机器在配置状态的改变将不会导致拍摄状态下内部工作的重编。反之亦然。

    不像之前的样例,这里的代码摘录部分表示同样效果的不同方式,这也导致了下面摘录代码不同于实际执行的样例中代码。注释中对于此类代码进行了标记。
    camera.hpp

    #ifndef CAMERA_HPP_INCLUDED
    #define CAMERA_HPP_INCLUDED
    
    #include <boost/statechart/event.hpp>
    #include <boost/statechart/state_machine.hpp>
    #include <boost/statechart/simple_state.hpp>
    #include <boost/statechart/custom_reaction.hpp>
    
    namespace sc = boost::statechart;
    
    struct EvShutterHalf : sc::event< EvShutterHalf > {};
    struct EvShutterFull : sc::event< EvShutterFull > {};
    struct EvShutterRelease : sc::event< EvShutterRelease > {};
    struct EvConfig : sc::event< EvConfig > {};
    
    struct NotShooting;
    struct Camera : sc::state_machine< Camera, NotShooting >
    {
      bool IsMemoryAvailable() const { return true; }
      bool IsBatteryLow() const { return false; }
    };
    
    struct Idle;
    struct NotShooting : sc::simple_state<
      NotShooting, Camera, Idle >
    {
      // 对于订制动作,我们仅仅指定我们可能对于一个对应事件的动作,但是这个实际动作
    //是被定义在动作成员函数,此成员函数将在 .cpp 中实现。
      typedef sc::custom_reaction< EvShutterHalf > reactions;
    
      sc::result react( const EvShutterHalf & );
    };
    
    struct Idle : sc::simple_state< Idle, NotShooting >
    {
      typedef sc::custom_reaction< EvConfig > reactions;
    
      // ...
      sc::result react( const EvConfig & );
    };
    
    #endif
    

    camera.cpp

    #include "Camera.hpp"
    
    // 下面的头文件是仅仅在此处而不会出现在camera.hpp中,拍摄和配置状态可以使用相同的模式来
    //隐藏其内部实现。这能够确保两个团队互不妨碍的相互协同工作。
    #include "Configuring.hpp"
    #include "Shooting.hpp"
    
    // not part of the Camera example
    sc::result NotShooting::react( const EvShutterHalf & )
    {
      return transit< Shooting >();
    }
    
    sc::result Idle::react( const EvConfig & )
    {
      return transit< Configuring >();
    }
    

    注意:任何调用 simple_state<>::transit<>() 和simple_state<>::terminate() (参见引用)将不可避免的析构状态对象(类似于delete this)。也就是说,此代码执行后再对此调用将会导致未定义错误。这也是为何这些函数应该仅仅被作为返回状态的一部分被调用。

    延迟事件

    拍摄状态的内部工作流程如下:

    当使用者半按快门时,将进入拍摄状态和其内部初始化状态聚焦状态。进入聚焦状态,触发相机命令焦圈对拍摄主体进行对焦。然后焦圈根据柔性焦距透镜组进行移动,并在对焦完成后立即发送 EvInFocus 事件。当然,在柔性焦距透镜组还在移动的过程中,使用者能全按快门。在没有任何预警的情况下,由于聚焦状态下没有定义此事件的动作,此结果事件 EvShutterFull 将直接丢失。因此,在相机对焦完成后,使用者将不得不再次全按快门。为了避免此问题,在 Focusing 状态中 EvShutterFull 事件将被延迟。这意味着此类型的所有事件是存储在一个独立的队列中,此队列在 Focusing 状态退出时注入主队列中。

    struct Focusing : sc::state< Focusing, Shooting >
    {
      typedef mpl::list<
        sc::custom_reaction< EvInFocus >,
        sc::deferral< EvShutterFull >
      > reactions;
    
      Focusing( my_context ctx );
      sc::result react( const EvInFocus & );
    };
    

    动作守卫

    Focused 的两个状态转变都源于 Focused,被同样但有两个互斥守卫的事件。这有一个合适的自定义动作:

    // not part of the Camera example
    sc::result Focused::react( const EvShutterFull & )
    {
      if ( context< Camera >().IsMemoryAvailable() )
      {
        return transit< Storing >();
      }
      else
      {
        // 下面是一个实际中内部动作和状态转变的一个混合。看后面如何恰当地实现此转换动作
        std::cout << "Cache memory full. Please wait...\n";
        return transit< Focused >();
      }
    }
    

    当然,自定义动作可以在状态声明的时候直接实现,这样对于代码阅读来说是更方便的。

    下面我们将用一个守卫来阻止一个转变,如果电池太低,则让外部事件对其作出反应。
    camera.cpp

    // ...
    sc::result NotShooting::react( const EvShutterHalf & )
    {
      if ( context< Camera >().IsBatteryLow() )
      {
        // 我们自己不能对于事件做出反应,故我们转移该事件到外部状态(这也是一个状态对
        //于未定义事件所应该进行的默认处理)。
        return forward_event();
      }
      else
      {
        return transit< Shooting >();
      }
    }
    // ...
    

    状态内动作

    Focused 状态的自转变也能够作为一个状态内动作进行实现,只要 Focused 没有任何进入或退出的动作,这将有同样的效果。
    shooting.cpp

    // ...
    sc::result Focused::react( const EvShutterFull & )
    {
      if ( context< Camera >().IsMemoryAvailable() )
      {
        return transit< Storing >();
      }
      else
      {
        std::cout << "Cache memory full. Please wait...\n";
        // 表明此事件可以被丢弃,因此,次分配算法将停止此事件寻找一个响应动作,
        //并此状态机将保持在 Focused 状态。
        return discard_event();
      }
    }
    // ...
    

    因为状态内动作是被守卫的,故需要采用一个 custom_reaction<>,对于无守卫的状态内动作 in_state_reaction 应该被用于更好代码可读性。

    转变动作

    按照每个转变的效果,动作应该按照如下顺序进行执行:

    1. 从最内部的激活状态开始,执行所有的退出动作,直到但不包括最内部公共上下文。
    2. 执行动作转换(如果在位的话)
    3. 从最内部的公共上下文开始,执行所有的入口动作,直到目标状态(且该状态被入口初始化状态所跟随)。
      例如:

    这里的顺序是: ~D(), ~C(), ~B(), ~A(), t(), X(), Y(), Z()。这个转换动作 t() 在最内部的公共上下文中执行,因为此时源状态已经被析构,而目标状态还没有构造。

    按照 Boost.Statechart,转换动作是公共上下文的一部分。也就是说,在 Focusing 和 Focused 之间的状态转变能够实现如下:
    shooting.hpp

    // ...
    struct Focusing;
    struct Shooting : sc::simple_state< Shooting, Camera, Focusing >
    {
      typedef sc::transition<
        EvShutterRelease, NotShooting > reactions; 
    
      // ...
      void DisplayFocused( const EvInFocus & );
    };
    
    // ...
    
    // not part of the Camera example
    struct Focusing : sc::simple_state< Focusing, Shooting >
    {
      typedef sc::transition< EvInFocus, Focused,
        Shooting, &Shooting::DisplayFocused > reactions;
    };
    

    或者,下面也是可能的(这里状态机是自服务为一个最外部上下文) :

    // not part of the Camera example
    struct Camera : sc::state_machine< Camera, NotShooting >
    {
      void DisplayFocused( const EvInFocus & );
    };
    // not part of the Camera example
    struct Focusing : sc::simple_state< Focusing, Shooting >
    {
      typedef sc::transition< EvInFocus, Focused,
        Camera, &Camera::DisplayFocused > reactions;
    };
    

    响应地,转变动作也能被下面自定义动作调用:
    Shooting.cpp:

    // ...
    sc::result Focusing::react( const EvInFocus & evt )
    {
      // We have to manually forward evt
      return transit< Focused >( &Shooting::DisplayFocused, evt );
    }
    

    相关文章

      网友评论

          本文标题:boost 状态机--中级篇

          本文链接:https://www.haomeiwen.com/subject/ngvlgxtx.html