3

私は以下に説明するようなステートマシンを持っています。

2つの開始状態のいずれかで開始できますが、ハンドシェイクの4つの状態すべてをヒットする必要があります。そこから、データのペイロードを転送するか、データのペイロードを受信することができます。その後、元の開始状態に戻ります。

ハンドシェーク:

-> StartedState1-> FinalState1-> StartedState2-> FinalState2

-> StartedState2-> FinalState2-> StartedState1-> FinalState1

ペイロード転送:

-> SendPayload-> SendEnd-> StartedState?

-> ReceivePayload-> ReceiveEnd-> StartedState?

以下のコードは、私の現在のアーキテクチャを表しています。残念ながら、各プロセスの最後に、州内から次の州が何を打つべきかを知るのに十分な情報がありません。

私の要件に基づいてこのアーキテクチャを改善する方法について誰か提案がありますか?

ありがとう、PaulH

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

主な違いは、新しい状態S12F12S21およびの導入ですF21。実装に関しては、ほぼ確実に、S2 から S12、F2 から F12、S1 から S21、F2 から F21 を派生させ、遷移関数をオーバーライドして正しい状態に移行することができます。

(すべての州の頭字語をお詫びします)。

于 2010-02-01T22:18:16.653 に答える
3

boost :: statechartライブラリを見ましたか?

于 2010-02-01T22:03:54.517 に答える
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 に答える
2

関数オブジェクトまたは関数ポインターの観点から設計することをお勧めします。

単純なステート マシンは、配列または を使用して実装できますstd::map。現在の状態をインデックスとして使用し、新しい状態または状態関数へのポインターを取得します。

より複雑なステート マシンは、遷移またはイベントに基づいて、ある状態から別の状態に移動します。簡単に実装すると、これには「ネストされた」配列が必要です。遷移コンテナーのコンテナー。最初のアクセスでは、状態の遷移表が表示されます。現在の遷移を遷移テーブルへのインデックスとして使用して、この遷移を処理する関数の関数ポインターを返します。

ステート マシンの複雑さに応じて、さまざまなデータ構造を使用できます。

良いアイデアは、テーブル駆動のステート マシンを使用することです。これにより、エンジンを一度コーディングしてテストすることができます。ステート マシンの変更には、テーブル内のデータの変更が含まれます。テーブルは実行可能ファイルの外部に存在できる可能性があります。つまり、実行可能ファイルを変更する必要はありません。この概念は、動的ライブラリを使用して拡張することができ、実行可能ファイルを変更する必要性を減らします。

これは単なる私の提案であり、間違っている可能性があります (Dennis Miller からの言い換え)。

于 2010-02-01T23:04:31.307 に答える