状态机与状态模式
最近在学习GUI框架,发现GUI本质上就是一个大状态机。以EW为例,在每次loop的update之前,都会判断这次是否有input/signal/device/timer这四类会改变系统状态的外部变量(这些就是各种各样的condition)。如果有,再去执行对应的slot(也就是action),执行完再把各个对应的component的状态更改掉(或者是gui框架observer一个variable或者property, 当这个状态变化的时候,执行对用的function); 关于是先改变状态再执行action, 还是先执行action再改变状态,我个人认为,取决于这个具体的状态是否是一个持续性的动作。你比方说,通过GUI设置一个开关的打开,假如这个打开过程需要一段时间才能完成,这种情况就是先执行完action, 判断返回值成功后再修改状态。否则假如有另外一个线程在监控状态,它就会得到一个错误的信息。也就是有可能,monitor得到的是open, 结果实际上还没有打开,甚至过了一会,打开动作失败也是有可能的。那么有没有需要先改变状态,然后再执行action的情况呢,也是有的。
比如我前面说的对property的observer. 这个是一个什么机制呢?就是说,gui的loop在判断input/signal/device/timer, 比如有一个消息推送,machine内部的某个状态发生了变化,比如说电量,或者信号强度,这种变化需要反应到UI上绘制出来,那么就是在对应的控件上会有一个value记录当前值,一旦发现某个loop中,这个value发生变化,那么就执行对应的slot,发出提示或者重绘控件。这两种情况都是存在的,需要根据具体场景去具体分析。
说了不少,下面看一些具体的案例来学习其中的一些技巧。比如,汽车上面有一个安全带控制器,其工作场景是:如果有人坐在座椅上,但是固定时间内没有系好安全带,那么将打开蜂鸣器进行提示。该系统具有三个传感器输入,分别是座椅传感器,安全带传感器,定时器,以及一个输出蜂鸣器。
下面我们简单画一下这个场景的状态图:
个人认为,这里选取状态是有一定方法和技巧的:状态是输入传感器组合的子集
状态图有了,下面就要开始编码了。这里面最关键的部分是对State的抽象,以及将各个状态子类化:
class CState { public: virtual void checkPara(CInput* input) { if(!ptr) {return;} } virtual doProcessInput(CInput* input, CMachine *machine) { checkPara(input); processInput(input); } virtual void processInput(CInput* input) = 0; virtual const char* type(void) = 0; }; class CStateIdle : public CState { void processInput(CInput* input, CMachine *machine) override { switch(input->getType()) { case SIG_SEATED: machine->startTimer(1000); machine->setState(Seated_NoBelt_NoTimeout); break; default: printf("do nothing in current state: %s\n", type()); } } const char* type(void) override { return STR(StateIdle); } }; class CStateSeated_NoBelt_NoTimeout : public CState { const char* type(void) override { return STR(Seated_NoBelt_NoTimeout); } void processInput(CInput* input, CMachine *machine) override { switch(input->getType()) { case SIG_TIME_OUT: machine->openBeeper(1000); machine->setState(Seated_NoBelt_Timeout); break; case SIG_SEATED_LEAVE: machine->resetTimer(); machine->stopTimer(); default: printf("do nothing in current state: %s\n", type()); break; } } }; class CStateSeated_NoBelt_Timeout : public CState { const char* type(void) override { return STR(Seated_NoBelt_Timeout); } void processInput(CInput* input, CMachine *machine) override { switch(input->getType()) { case SIG_BELTED: machine->closeBeeper(); machine->resetTimer(); machine->setState(Seated_Belted); break; case SIG_SEATED_LEAVE: machine->closeBeeper(); machine->resetTimer(); machine->stopTimer(); machine->setState(Idle); default: printf("do nothing in current state: %s\n", type()); break; } } }; class CStateSeated_Belted : public CState { const char* type(void) override { return STR(Seated_Belted); } void processInput(CInput* input, CMachine *machine) override { switch(input->getType()) { case SIG_BELTED_LEAVE: machine->openBeeper(); machine->setState(Seated_NoBelt_Timeout); default: printf("do nothing in current state: %s\n", type()); break; } } }; class Machine { public: typedef enum { StateIdle, Seated_NoBelt_NoTimeout Seated_NoBelt_Timeout, Seated_Belted } MachineState; Machine() { mIdle = new CStateIdle; mNoBeltNoTimeout = new CStateSeated_NoBelt_NoTimeout; mNoBeltTimeout = new CStateSeated_NoBelt_Timeout; mSeatedBelted = new CStateSeated_Belted; } void openBeeper(); void closeBeeper(); void startTimer(); void stopTimer(); void resetTimer(); CInput* getInput(void); void setState(MachineState state) { switch(sig) { case Idle: mState = mIdleState; break; case Seated_NoBelt_NoTimeout: mState = mSeatedNoBeltNoTimeout ; break; case Seated_NoBelt_Timeout: mState = mSeatedNoBeltTimeout ; break; case Seated_Belted: mState = mSeatedBelted; break; default: break; } } private: CState* mState; CStateIdle *mIdle; CStateSeated_NoBelt_NoTimeout* mNoBeltNoTimeout; CStateSeated_NoBelt_Timeout *mNoBeltTimeout; CStateSeated_Belted* mSeatedBelted; };
在主循环中,不断收集事件,然后驱动这个状态机运行:
void machine_logic(Machine *machine) { while(1) { CInput* input = machine->getInput(); machine_state->processInput(input); } }
input事件的收集一般放到另外一个单独的线程,这里简单示意一下
void machine_input(Machine *machine) { while(1) { if(machine->isBelted() != machine->isBelted()) { input->sendToQue(); } } }
以上