我有一台状态机,如下所述。
我们可以从两种起始状态中的一种开始,但我们必须触及握手的所有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 :(得分: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
关键区别在于引入了新的州S12
,F12
,S21
和F21
。在实现方面,你几乎可以肯定只是从S2导出S12,从F2导出F12,从S1导出S21,从F2导出F21,并覆盖转换函数以进入正确的状态。
(为所有州的首字母缩略词道歉)。
答案 1 :(得分:3)
你看过boost::statechart图书馆吗?
答案 2 :(得分:2)
我建议从功能对象或函数指针的角度进行设计。
可以使用数组或std::map
实现简单的状态机。使用当前状态作为索引,并检索新状态或指向状态函数的指针。
更复杂的状态机基于转换或事件从一个州移动到另一个州。简单实现,这需要一个'嵌套'数组。过渡容器的容器。第一次访问为您提供状态的转换表。使用当前转换作为转换表的索引,以返回处理此转换的函数的函数指针。
可以使用不同的数据结构,这些都取决于状态机的复杂程度。
一个好主意是拥有一个表驱动的状态机。这允许引擎进行一次编码和测试。更改状态机涉及更改表中的数据。该表可能存在于可执行文件之外,这意味着可执行文件不必更改。可以使用动态库扩展此概念,从而减少更改可执行文件的需要。
这只是我的建议,我可能是错的(从Dennis Miller转述)。
答案 3 :(得分: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();
}
}
它在我的机器上编译和工作。您可能希望添加以下功能:
为它添加足够的通用功能,它将开始类似于Boost.StateChart想要的东西。 ; - )
答案 4 :(得分:0)
您可以使用Petri网为状态机建模。这允许您定义非常简单和非常复杂的状态机。 要实现您指定的状态机/ petri网,您可以使用PTN Engine之类的引擎。
它允许您declaratively在Petri网构造函数中定义整个状态机。您可以集成自己的函数,以便在达到给定状态时调用,以及触发状态更改的函数。