3

我有一个如下所述的状态机。

我们可以从两个起始状态之一开始,但我们必须达到握手的所有 4 个状态。从那里,我们可以传输数据负载或接收数据负载。然后,我们回到原来的起始状态。

握手:

-> 起始状态 1 -> 最终状态 1 -> 起始状态 2 -> 最终状态 2

-> 起始状态 2 -> 最终状态 2 -> 起始状态 1 -> 最终状态 1

有效载荷转移:

-> SendPayload -> SendEnd -> StartingState?

-> ReceivePayload -> ReceiveEnd -> StartingState?

下面的代码代表了我当前的架构。不幸的是,在每个过程结束时,我没有足够的来自州内的信息来知道我应该达到的下一个州是什么。

有人对如何根据我的要求改进此架构有任何建议吗?

谢谢,保罗

class MyMachine;
class Payload;

class IState
{
    MyMachine* context_;

    IState( MyMachine* context ) : context_( context) {};

    virtual void Consume( byte data );

    void ChangeState( IState* state )
    {
        context_->SetState( state );
    }
}

class FinalState1 : IState
{
    void Consume( byte data )
    {
        // Either go to StartingState1, SendPayload, or ReceivePayload.
        // How can I tell from within the context of this state where I 
        // should go?
    }
}

class StartingState1 : IState
{
    void Consume( byte data )
    {
        if ( /*some condition*/ )
        {
            ChangeState( new FinalState1( context_ ) );
        } 
    }
}

class MyMachine
{
    IState* state_;
    Payload* payload_;

    void Start1( Mode mode )
    {
        state_ = new StartingState1( this );
    }

    void Start2( Mode mode )
    {
        state_ = new StartingState2( this );
    }

    void Consume( byte data )
    {
        state_->Consume( data );
    }

    void SetPayload( const Payload* payload )
    {
        payload_ = payload;
    }

    const Payload* GetPayload()
    {
        return payload_;
    }

    void SetState( State* state )
    {
        delete state_;
        state_ = state;
    }
}

// get a byte of data from some source
byte GetData();

void main()
{
    MyMachine machine;
    Payload payload;
    machine.SetPayload( payload );
    machine.Start1( Mode::SendPayload );

    // could also call:
    // machine.Start1( Mode::ReceivePayload );
    // machine.Start2( Mode::SendPayload );
    // machine.Start2( Mode::ReceivePayload );

    for(;;)
    {
        machine.Consume( GetData() );
    }
}
4

5 回答 5

5

您所拥有的并不能完全代表您系统的可能状态,但很容易将其转换为它。您需要额外的状态来表示处于状态 1 与未处于状态 2 以及处于状态 1 而处于状态 2 之间的差异(对于状态 2 也是如此)。所以你需要:

S1 S2 F1 F2 S12 F12 S21 F21
SP SE
RP RE

有过渡

S1 --> F1
F1 --> S12
S12 --> F12
F12 --> SP or F12 --> RP

S2 --> F2
F2 --> S21
S21 --> F21
F21 --> SP or F21 --> RP

SP --> SE
RP --> RE
SE --> S1 or SE --> S2
RE --> S1 or RE --> S2

关键区别在于引入了新的状态S12F12和。在实现方面,您几乎可以肯定只从 S2 派生 S12、从 F2 派生 F12、从 S1 派生 S21 和从 F2 派生 F21 并覆盖转换函数以进入正确的状态。S21F21

(为您所有的州首字母缩写表示歉意)。

于 2010-02-01T22:18:16.653 回答
3

你看过boost::statechart库吗?

于 2010-02-01T22:03:54.517 回答
2

我建议从函数对象或函数指针的角度进行设计。

可以使用数组或std::map. 使用当前状态作为索引并检索新状态或指向状态函数的指针。

更复杂的状态机根据转换事件从一种状态移动到另一种状态。简单实现,这需要一个“嵌套”数组。过渡容器的容器。第一次访问为您提供状态的转换表。使用当前转换作为转换表的索引,以返回处理此转换的函数的函数指针。

可以使用不同的数据结构,这一切都取决于您的状态机的复杂性。

一个好主意是有一个表驱动的状态机。这允许对引擎进行一次编码和测试。更改状态机涉及更改表中的数据。该表可能能够存在于可执行文件之外,这意味着可执行文件不必更改。这个概念可以通过使用动态库来扩展,减少更改可执行文件的需要。

这只是我的建议,我可能是错的(从丹尼斯米勒转述)。

于 2010-02-01T23:04:31.307 回答
2

以下是使用 Thomas 建议的方法的示例:

#include <cassert>
#include <iostream>
#include <map>

class Machine;

typedef void (*StateFunctionPtr)(Machine& context);

// State "do" functions
void starting1(Machine& context)        {std::cout << "S1 ";}
void final1(Machine& context)           {std::cout << "F1 ";}
void starting2(Machine& context)        {std::cout << "S2 ";}
void final2(Machine& context)           {std::cout << "F2 ";}
void sendPayload(Machine& context)      {std::cout << "SP ";}
void sendEnd(Machine& context)          {std::cout << "SE ";}
void receivePayload(Machine& context)   {std::cout << "RP ";}
void receiveEnd(Machine& context)       {std::cout << "RE ";}

namespace State
{
    enum Type {start, handshake1, handshake2, handshake3,
        handshake4, xferPayload, endPayload};
};

// Aggregate of state, "mode" variables, and events.
struct StateKey
{
    // Needed for use as map key
    bool operator<(const StateKey& rhs) const
    {
        return
              (state < rhs.state)
        ||  ( (state == rhs.state) && (isReceiving < rhs.isReceiving) )
        ||  ( (state == rhs.state) && (isReceiving == rhs.isReceiving)
                && (startsAt2 < rhs.startsAt2) );
    }

    bool startsAt2;
    bool isReceiving;
    State::Type state;
};

struct StateEffect
{
    StateFunctionPtr function;  // "do" function
    State::Type newState;       // state to transition to
};

struct StatePair
{
    StateKey key;
    StateEffect effect;
};

const StatePair stateTable[] =
{
    {{0, 0, State::start},       {&starting1,       State::handshake1}},
    {{0, 0, State::handshake1},  {&final1,          State::handshake2}},
    {{0, 0, State::handshake2},  {&starting2,       State::handshake3}},
    {{0, 0, State::handshake3},  {&final2,          State::handshake4}},
    {{0, 0, State::handshake4},  {&sendPayload,     State::xferPayload}},
    {{0, 0, State::xferPayload}, {&sendEnd,         State::endPayload}},
    {{0, 0, State::endPayload},  {&starting1,       State::handshake1}},

    {{0, 1, State::start},       {&starting1,       State::handshake1}},
    {{0, 1, State::handshake1},  {&final1,          State::handshake2}},
    {{0, 1, State::handshake2},  {&starting2,       State::handshake3}},
    {{0, 1, State::handshake3},  {&final2,          State::handshake4}},
    {{0, 1, State::handshake4},  {&receivePayload,  State::xferPayload}},
    {{0, 1, State::xferPayload}, {&receiveEnd,      State::endPayload}},
    {{0, 1, State::endPayload},  {&starting1,       State::handshake1}},

    {{1, 0, State::start},       {&starting2,       State::handshake1}},
    {{1, 0, State::handshake1},  {&final2,          State::handshake2}},
    {{1, 0, State::handshake2},  {&starting1,       State::handshake3}},
    {{1, 0, State::handshake3},  {&final1,          State::handshake4}},
    {{1, 0, State::handshake4},  {&sendPayload,     State::xferPayload}},
    {{1, 0, State::xferPayload}, {&sendEnd,         State::endPayload}},
    {{1, 0, State::endPayload},  {&starting2,       State::handshake1}},

    {{1, 1, State::start},       {&starting2,       State::handshake1}},
    {{1, 1, State::handshake1},  {&final2,          State::handshake2}},
    {{1, 1, State::handshake2},  {&starting1,       State::handshake3}},
    {{1, 1, State::handshake3},  {&final1,          State::handshake4}},
    {{1, 1, State::handshake4},  {&receivePayload,  State::xferPayload}},
    {{1, 1, State::xferPayload}, {&receiveEnd,      State::endPayload}},
    {{1, 1, State::endPayload},  {&starting2,       State::handshake1}}
};


class Machine
{
public:
    Machine()
    {
        // Initialize state chart map from constant state table
        const size_t tableSize = sizeof(stateTable) / sizeof(stateTable[0]);
        for (size_t row=0; row<tableSize; ++row)
        {
            stateChart_[stateTable[row].key] = stateTable[row].effect;
        }
    }

    // If startsAt2==true, then FSM will start with starting2 handshake function
    void reset(bool startsAt2, bool isReceiving)
    {
        stateKey_.startsAt2 = startsAt2;
        stateKey_.isReceiving = isReceiving;
        stateKey_.state = State::start;
    }

    void step()
    {
        StateChart::const_iterator iter = stateChart_.find(stateKey_);
        assert(iter != stateChart_.end());
        const StateEffect& effect = iter->second;
        effect.function(*this);
        stateKey_.state = effect.newState;
    }

private:
    typedef std::map<StateKey, StateEffect> StateChart;
    StateChart stateChart_;
    StateKey stateKey_;
};

int main()
{
    Machine machine;
    machine.reset(true, false);
    for (int i=0; i<20; ++i)
    {
        machine.step();
    }
}

它在我的机器上编译并运行。您可能想要添加以下功能:

  • StateEffect 中的进入/退出函数
  • StateKey 中的事件“触发器”
  • 泛化为模板。

向它添加足够多的通用特性,它就会开始类似于 Boost.StateChart 的崇拜者。;-)

于 2010-02-02T02:58:58.727 回答
0

您可以使用 Petri 网对状态机进行建模。这允许您定义非常简单和非常复杂的状态机。要实现您指定的状态机/ Petri 网,您可以使用PTN Engine之类的引擎。

它允许您在 Petri 网络构造函数中以声明方式定义整个状态机。您可以集成在达到给定状态时要调用的自己的函数,以及触发状态更改的函数。

于 2017-10-01T21:56:44.183 回答