2010-02-01 110 views
3

我有一個狀態機,如下所述。狀態機執行

我們可以從兩種起始狀態中的一種開始,但我們必須擊中所有4個握手狀態。從那裏,我們可以傳輸數據的有效載荷或接收數據的有效載荷。然後,我們回到我們原來的起始狀態。

握手:

- > StartingState1 - > FinalState1 - > StartingState2 - > FinalState2

- > StartingState2 - > FinalState2 - > StartingState1 - > FinalState1

有效負載轉移:

- > SendPayload - > SendEnd - > StartingState?

- > ReceivePayload - > ReceiveEnd - > StartingState?

下面的代碼代表我目前的架構。不幸的是,在每個過程結束時,我沒有足夠的信息從各州內知道下一個狀態是什麼,我應該打。

根據我的要求,有沒有人有任何建議來改進這個架構?

感謝, 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()); 
    } 
} 
+0

這聽起來像是一個純粹的FSM設計問題,而不是代碼問題。 – 2010-02-01 22:03:19

+0

@Hamish - 你對改進的FSM設計有什麼建議嗎? – PaulH 2010-02-01 22:40:55

+0

我建議你研究狀態機語言和編譯器。我的直覺表明,有些工具會根據狀態機描述生成框架或模板。 – 2010-02-01 23:06:22

回答

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 

的主要區別是採用新的狀態S12F12S21F21。在實現方面,您幾乎可以肯定只從S2推導出S12,從F2推導出F12,從S1推導S21和從F2推導出F21,並覆蓋轉換函數以進入正確狀態。

(道歉爲所有你的狀態首字母縮寫)。

+0

我認爲你的解決方案最適合我現有的架構。 感謝您的建議! – PaulH 2010-02-02 15:37:58

3

你看boost::statechart庫?

+0

通常,我很樂意使用提升。但是,我需要在不合並外部庫的情況下執行此操作。 – PaulH 2010-02-01 22:40:21

+0

大部分的提升只是標題。 – 2010-02-02 00:37:12

2

我建議從函數對象或函數指針的角度進行設計。

一個簡單的狀態機可以使用數組或std::map來實現。使用當前狀態作爲索引並檢索新狀態或指向狀態函數的指針。

更復雜的狀態機旅行從一個狀態到另一個根據過渡事件。簡單地實施,這需要一個'嵌套'陣列。一個過渡容器的容器。第一次訪問爲您提供狀態的轉換表。使用當前轉換作爲轉換表中的索引來返回處理此轉換的函數的函數指針。

可以使用不同的數據結構,所有這些都取決於狀態機的複雜性。

一個不錯的想法是有一個表驅動狀態機。這允許對引擎進行編碼和測試一次。更改狀態機涉及更改表中的數據。該表可能能夠存在於可執行文件之外,這意味着可執行文件不必更改。這個概念可以通過使用動態庫來擴展,從而減少了更改可執行文件的需求。

這只是我的建議,我可能是錯的(解釋自丹尼斯米勒)。

+0

你有鏈接到這種狀態機的例子嗎? – PaulH 2010-02-01 23:09:50

2

下面是使用由托馬斯提出的方法的例子:

#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想象。 ;-)

0

您可以使用Petri網建模您的狀態機。這使您可以定義非常簡單和非常複雜的狀態機。 要實現您指定的狀態機/ petri網,您可以使用像PTN Engine這樣的引擎。

它允許你在Petri網構造器中定義整個狀態機。你可以集成你自己的函數,當達到一個給定的狀態時被調用,以及觸發狀態改變的函數。