转载

状态机编程--让程序的复杂行为变得简洁透明

我的第一份工作很短命,总共两年,最后半年还是在为离职跟人斗智斗勇。真得感谢非典在那会发生了(非典牺牲了很多医生,我曾经肺炎得到过一个抗非典英雄的精心治疗。但愿不再有非典!),全国各城拒绝外来人,于是乎,我被扫地出门。在那两年内,哥做了不少程序,自我感觉良好,但有一点始终憋屈,那就是程序越写越复杂,刚做出来的时候还行,维护了一段时间,增加了些新功能,就开始面目全非了,逻辑越来越复杂,调测更是越发困难。其实这些年我看过很多项目的代码,也就是我那会的水平和风格。

有没有什么技术能让程序变得简单点呢?而且这种简单能让程序可以更容易的应对更多的复杂的功能需求,测试更简单,bug更容易定位……凡是好处,哥都不想少,否则老板不干。

当然有!一个叫做数据驱动,另一个就是今晚要解释的,基于数据驱动的典型方法--状态机编程。

代码链接(建议下载,并在linux上编译运行):

http://files.cnblogs.com/files/hhao020/cshell_prj_re0.003.rar

开始状态机编程前,需要清楚几个概念,比如多事务会话处理,多实例会话处理等。会话处理过程中,不应该出现阻塞操作,除非是单实例会话,否则就对其它会话不公平了。另外一个就是会话可能含有多个状态分支,比如一个人,有左手和右手,两只手可以做不同的事,自然有不同的状态。同时,手的状态又跟人的基本状态有关系,否则就会出现人在睡觉,手却在敲代码,僵尸来咯!

正式开始说状态机。首先,我们需要一大块内存在记录程序按照状态机执行的迁移过程。这个不是必须的,但没有了这个,但哥觉得这个是状态机的一个精华,也是哥提供的状态机的特色。没了这个记录,即便有了状态机,程序仍然是装在黑盒子里跑,你都没法知道程序跑对了没,跑错了也一样不好找原因。在开始使用状态机前,执行下void* zFsmServiceInit(int maxTraceNum)这个函数,为日志功能分配内存。状态机跟踪的每条记录为固定长度,大概在40字节,这当中有32字节是那最大8个函数地址。

接下来,要初始化状态机的控制组件,结构如下:

 typedef struct _Z_FSM_PANEL_TYPE_ { //zFsmEventGet_t fnEventGet; zFsmInstGet_t fnInstGet; zFsmTraceGet_t fnTraceGet; zFsmStateName_t fnStateName; zFsmEventName_t fnEventName;  zFsmStateGet_t fnStateGet; zFsmStateSet_t fnStateSet; zFsmTableGet_t fnTableGet;  zFsmMsgPrint_t fnMsgPrint; } zFsmPanel_t; 

这个是个状态机用的,就好似汽车的方向盘,轮胎等配置,没了这个,我们不能指望状态机或是汽车能开起来。我注释掉了EventGet钩子,是因为我提供的代码不需要了。当然,这个不是什么特别考虑,这里可以自己加入更多内容,关键是状态机能够正确使用。

最后,是定义状态机的操作集,也就是定义当系统处于状态X,收到输入事件Y,应该执行操作a,b,c,……,并最终转入状态Z。

 typedef struct _Z_FSM_BLOCK_TYPE_ { word_t wStateIn; word_t wEventIn; void* /*zFsmFunCall_t*/ fnCall[Z_FSM_FUNC_LIST_SIZE]; word_t wStateOut; } zFsmBlock_t; 

当然,并不一定会顺序执行完所有的操作,很多时候,会出现中间产生新事件,从而实现状态机的跳转;处理结束,也或许不会出现新的状态。另外,处理某个事件的时候,可以Post出来一系列新的事件(使用enq&deq cache实现)。做过MFC的人都知道,事件是可以send的,也可以是post的。这里直接返回的时间,就类似于那个send,而cache就对应post方法,至于send到外部queue,就不算状态机的内容,虽然也是使用状态机时可以利用的。

注意,你要觉得8个函数太多,可以减少点,这样日志追踪记录块会小很多。一般来说特定状态事件有4个处理动作就足够了。不然就要付出代价,编程的灵活性降低,每个动作要完成对事情也就多了,实际上也就使得单个函数代码行变大,函数的可共用能力变差。还有一点要说的,就是函数列表应该是void* (*fn)(),如果是zFsmFunCall_t类型,在编译状态机调度表初始化时,会出现告警。

下面是个状态机的简单例子,一个关于地铁入闸机的示范。

示例包含了状态+事件-->动作-->新状态的过程,还有一个状态+事件-->新事件的例子。不过,没有包含cache que的使用,或者多个状态分支的处理。这份状态机的源码,绝对已经包含了那些功能,因为已经在几个项目中用过了!

 /*---------------------------------------------------------- File Name  : xxx.c Description:  Author     : hhao020@gmail.com (bug fixing and consulting) Date       : 2007-05-15 ------------------------------------------------------------*/ #include "zType_Def.h" #include "zSalOS.h"   #include "zTraceApi.h" #include "zFsmApi.h"  /* A turnstile An example of a very simple mechanism that can be modeled by a state machine is a turnstile.[2][3] A turnstile, used to control access to subways and  amusement park rides, is a gate with three rotating arms at waist height,  one across the entryway. Initially the arms are locked, barring the entry,  preventing customers from passing through. Depositing a coin or token in  a slot on the turnstile unlocks the arms, allowing them to rotate by  one-third of a complete turn, allowing a single customer to push through.  After the customer passes through, the arms are locked again until another  coin is inserted.  The turnstile has two states: Locked and Unlocked.[2] There are two inputs that affect its state: putting a coin in the slot (coin) and pushing the arm (push). In the locked state, pushing on the arm has no effect; no matter how  many times the input push is given it stays in the locked state. Putting a  coin in, that is giving the machine a coin input, shifts the state from Locked to Unlocked. In the unlocked state, putting additional coins in has no effect;  that is, giving additional coin inputs does not change the state. However, a  customer pushing through the arms, giving a push input, shifts the state back  to Locked.  The turnstile state machine can be represented by a state transition table,  showing for each state the new state and the output (action) resulting from  each input  Current State    Input         Next State          Output Locked             coin         Unlocked          Release turnstile so customer can push through                  push         Locked              None Unlocked           coin         Unlocked         None                  push         Locked              When customer has pushed through lock turnstile */  //define a turnstile list typedef struct TurnstileInstanceType {   word_t wState;    int id;   char *desc;      int traceId; } turnstile_t;  LOCAL turnstile_t s_turnstileList[4];  //define msg, event and state for turnstile enum TurnstileMsgEnum {     eMsgCoin = 1,   eMsgPush,   eMsgAttack,   eMsgAudit,   eMsgRepair, }; enum TurnstileEventEnum {     eEventCoin = 1,   eEventPush,   eEventAttack,   eEventAudit,   eEventOos,   eEventRepair,   eEventUnknown, }; enum TurnstileStateEnum {   eStateLocked = 1,   eStateUnlocked,   eStateOos,   eStateUnknown, };  const char* turnstileStateName(word_t wState) {   switch(wState)   {   case eStateLocked:   return "locked";   case eStateUnlocked: return "unlocked";   case eStateOos:      return "oos";   default:;   }    return "*NA*"; }  const char* turnstileEventEnum2Name(word_t wEvent) {   switch(wEvent)   {   case eEventCoin:   return "coin";   case eEventPush:   return "push";   case eEventAttack:   return "hack";   case eEventAudit:  return "audit";   case eEventOos:    return "oos";   case eEventRepair: return "repair";   default:;   }    return "*NA*"; }  const char* turnstileMsgEnum2Name(word_t msgId) {   switch(msgId)   {   case eMsgCoin:   return "Coin";   case eMsgPush:   return "Push";   case eMsgAttack:   return "Attack";   case eMsgAudit:  return "Audit";   case eMsgRepair: return "Repair";   default:    ;   }   return "*NA*"; }  //Translate msg to event. Actually we have some better way to optimize these. //First, we can have a sorted table, then use binary search, that would be must faster; //Or we can have different value regions for messages and events. For example, if rule  //says messages must be 0x0001~0x3fff, and then we can have external events respectively (use msg as event), //and other values for internal events. In that case, we don't need a table to map message to event. word_t turnstileEventGet(zMsgHdr_t *pMsg) {   switch(pMsg->msgId)   {     case eMsgCoin: return eEventCoin;     case eMsgPush: return eEventPush;     case eMsgAttack: return eEventAttack;     case eMsgAudit:  return eEventAudit;     case eMsgRepair: return eEventRepair;     default:;   }   return eEventUnknown; }   //define fsm schedule table.  int GoodCoin(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int GoodPush(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int BadCoin(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int BadPush(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int OOS(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int Attack(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int Audit(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext); int Repair(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext);  LOCAL zFsmBlock_t s_turnstileFsmBlks[] = {   {eStateLocked,   eEventCoin, {GoodCoin, }, eStateUnlocked},   {eStateLocked,   eEventPush, {BadPush,  }, eStateLocked},   {eStateUnlocked, eEventCoin, {BadCoin,  }, eStateUnlocked},   {eStateUnlocked, eEventPush, {GoodPush, }, eStateLocked},    {eStateLocked,   eEventAudit, {Audit, }, eStateLocked},   {eStateUnlocked, eEventAudit, {Audit, }, eStateUnlocked},        {eStateOos, eEventRepair, {Repair, }, eStateLocked},    {0, eEventAttack, {Attack, }, 0},      {0, eEventAudit, {Audit, }, eStateLocked},   {0, eEventOos, {OOS, }, eStateOos},      {0, 0, {OOS, }, eStateOos}, };  //Note, sometimes an instance might have multi sub states, hence there could be more than 1 fsm table. //In that case, each of them could be defined as a panel, and programmer must define a rule for fsm to pick up a proper panel then. //but here, we only define 1 panel. LOCAL zFsmPanel_t s_turnstileFsmPanel;  //must define a service core for turnstile fsm, because we need some buffer to do trace. void *s_turnstileFsmServiceCore = 0;  void*  turnstileInstanceGet(word_t wEvent, zMsgHdr_t *pMsg) {   int nInst = pMsg->dstInst - 1;   if(nInst >= sizeof(s_turnstileList)/sizeof(s_turnstileList[0]))   {     zTraceError("invalid input dstInst:%d/n", pMsg->dstInst);     return 0;   }      return &s_turnstileList[nInst];  }  int turnstileFsmTraceGet(void* pInst, zMsgHdr_t *pMsg) {   static int traceId = 1;    if(((turnstile_t *)pInst)->traceId == 0)   {     ((turnstile_t *)pInst)->traceId = traceId ++;   }      return ((turnstile_t *)pInst)->traceId; }   int turnstileMsgPrint(zMsgHdr_t* pMsg) {   zTraceP("      MSG ID: %d %s   DEST INST: %d/n", pMsg->msgId, turnstileMsgEnum2Name(pMsg->msgId), pMsg->dstInst);    return 0; }   word_t turnstileStateGet(void *pInst) {   return ((turnstile_t *)pInst)->wState; }  int turnstileStateSet(void *pInst, word_t wState) {   ((turnstile_t *)pInst)->wState = wState;    return 0; }  zFsmBlock_t* turnstileFsmTableGet(word_t *pMaxNum) {   *pMaxNum = TBL_SIZE(s_turnstileFsmBlks);   return s_turnstileFsmBlks; }      int turnstileFsmInit(zFsmPanel_t* panel) {   panel->fnInstGet  = turnstileInstanceGet;   panel->fnTraceGet = turnstileFsmTraceGet;   panel->fnStateName= turnstileStateName;   panel->fnEventName= turnstileEventEnum2Name;    panel->fnStateGet = turnstileStateGet;   panel->fnStateSet = turnstileStateSet;   panel->fnTableGet = turnstileFsmTableGet;    panel->fnMsgPrint = turnstileMsgPrint;    word_t maxNum = 0;   zFsmBlock_t *pTable = panel->fnTableGet(&maxNum);   if(!pTable || !maxNum)   {     zTraceFatal("invalid input fsm table, panel: %p./n", panel);     return -1;   }    //sort the table, so fsm could select an action quickly   zFsmTableFormat(pTable, maxNum);    return 0; }  int turnstileInit() {   int i;    int maxFsmTrace = 500;   s_turnstileFsmServiceCore = zFsmServiceInit(maxFsmTrace);   if(!s_turnstileFsmServiceCore)   {     zTraceFatal("Failed to create serviceCore, maxTrace=%d./n", maxFsmTrace);     return -1;   }    turnstileFsmInit(&s_turnstileFsmPanel);      zMemset(&s_turnstileList, 0, sizeof(s_turnstileList));    for(i=0; i<sizeof(s_turnstileList)/sizeof(s_turnstileList[0]); i++)   {     s_turnstileList[i].id = i+1;     s_turnstileList[i].desc = "Turnstile";     s_turnstileList[i].wState = eStateLocked;   }    return 0; }  int GoodCoin(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceInfo("%s %d:: Welcome./n", pInst->desc, pInst->id);   return 0; } int GoodPush(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceInfo("%s %d:: See you next time./n", pInst->desc, pInst->id);   return 0; } int BadCoin(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceWarn("%s %d:: Thanks for your lenience.../n", pInst->desc, pInst->id);   return 0; } int BadPush(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceWarn("%s %d:: You forgot to pay.../n", pInst->desc, pInst->id);   return 0; } int OOS(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceWarn("%s %d:: Out of service.../n", pInst->desc, pInst->id);   return 0; } int Attack(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceWarn("%s %d:: Attack warning.../n", pInst->desc, pInst->id);   return eEventAudit; } int Audit(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   int auditFailRand = 0;      zTraceInfo("%s %d:: Audit.../n", pInst->desc, pInst->id);    auditFailRand = zTime();   auditFailRand &= 0x1;    zSleepUSec(700);     return (auditFailRand ? eEventOos : 0); } int Repair(word_t wEvent, turnstile_t *pInst, zMsgHdr_t *pMsg, zFsmContext_t* pContext) {   zTraceWarn("%s %d:: Under repairing.../n", pInst->desc, pInst->id);   return 0; }   int turnstileScheduler(zMsgHdr_t *pMsg) {   zFsmCacheQueue_t cacheQueue;    zMemset(&cacheQueue, 0, sizeof(cacheQueue));    word_t wEvent = turnstileEventGet(pMsg);    int maxDepth = 20;   while(maxDepth-- > 0)   {     int iRet = zFsmProcessMsg(s_turnstileFsmServiceCore, &s_turnstileFsmPanel, wEvent, pMsg, &cacheQueue.context);       if(iRet < 0)     {       zTraceError("Failed to handle event: %d %s   msg: %d %s/n", wEvent, turnstileEventEnum2Name(wEvent),                                                         pMsg->msgId, turnstileMsgEnum2Name(pMsg->msgId));       return -1;     }     else     if(iRet == wEvent)     {       zTraceError("Can't handle event: %d %s     msg: %d %s/n", wEvent, turnstileEventEnum2Name(wEvent),                                                         pMsg->msgId, turnstileMsgEnum2Name(pMsg->msgId));       return -2;     }     else     if(iRet > 0)     {       wEvent = iRet;     }     else     if(iRet == 0)     {       int iRet2 = zFsmCacheDequeue(&cacheQueue.context, &wEvent, &pMsg);       if(iRet2 == 0)       {         break; //no more event left, then quit the message process       }       else if(iRet2 < 0)       {         zTraceError("Failed to dequeue cache after event: %d %s/n", wEvent, turnstileEventEnum2Name(wEvent),                                                         pMsg->msgId, turnstileMsgEnum2Name(pMsg->msgId));         return -3;       }     }   }      return 0; }  int turnstileMsgSim(int msgId, int nInst) {   zMsgHdr_t ts_msg;    zMemset(&ts_msg, 0, sizeof(ts_msg));    //ts_msg.srcAddr = 0x12345678;   //ts_msg.dstAddr = 0x87654321;   ts_msg.srcInst = 0x9;   ts_msg.dstInst = nInst;   ts_msg.msgId = msgId;   ts_msg.msgLen = 0;      return turnstileScheduler(&ts_msg); }   int turnstileFsmTraceShow(int traceId) {   zFsmTraceShow(s_turnstileFsmServiceCore, traceId);   return 0; }  int turnstileInstanceShow(int nInst) {   if(nInst > 0 && nInst < TBL_SIZE(s_turnstileList))   {     zTraceP("turnstile instance:/n");     zTraceP("[%d] %s/n", nInst, s_turnstileFsmPanel.fnStateName(s_turnstileList[nInst-1].wState));     return 0;   }      int i;    zTraceP("turnstile list:/n");   for(i=0; i<TBL_SIZE(s_turnstileList); i++)   {         zTraceP("[%d] %s/n", i, s_turnstileFsmPanel.fnStateName(s_turnstileList[i].wState));   }      return 0; }  int turnstileShow() {     zTraceP("turnstile FSM tablen/n");     zFsmTableShow(&s_turnstileFsmPanel);    zTraceP("turnstile FSM panel:/n");   zFsmPanelShow(&s_turnstileFsmPanel);    turnstileInstanceShow(0);    zTraceP("turnstile FSM trace:/n");   turnstileFsmTraceShow(0);    return 0; } 

下面是入闸机的测试命令。prj/cmd目录下,能找到这个命令列表ts.cmd:

 ##turnstile demo test  turnstileInit();  turnstileShow();  #turnstileMsgSim(msgId, nInst) turnstileMsgSim(1, 1); turnstileMsgSim(2, 1); turnstileMsgSim(3, 1); turnstileMsgSim(4, 1); turnstileMsgSim(1, 1); turnstileMsgSim(2, 1); turnstileMsgSim(2, 1);  turnstileInstanceShow(1); turnstileFsmTraceShow(1);  turnstileMsgSim(1, 2); turnstileMsgSim(2, 2); turnstileMsgSim(2, 2); turnstileMsgSim(1, 2); turnstileMsgSim(1, 2); turnstileMsgSim(2, 2); turnstileMsgSim(1, 2);  turnstileInstanceShow(2); turnstileFsmTraceShow(2); 

最后,是状态机的测试结果:

 cshell_prj $ bin/target_a.linux.i32.exe -><cmd/ts.cmd  $1/> turnstileInit();   = 0 (0x0) <FUNCALL : size=0>  $2/> turnstileShow();  turnstile FSM tablen Table Size = 11       CURRENT STATE                      ON EVENT                      NEW STATE                    FUNCTIIONS  0 --                       oos[  3]                    repair[  6]                     locked[  1]    Repair,  1 --                  unlocked[  2]                     audit[  4]                   unlocked[  2]    Audit,  2 --                  unlocked[  2]                      push[  2]                     locked[  1]    GoodPush,  3 --                  unlocked[  2]                      coin[  1]                   unlocked[  2]    BadCoin,  4 --                    locked[  1]                     audit[  4]                     locked[  1]    Audit,  5 --                    locked[  1]                      push[  2]                     locked[  1]    BadPush,  6 --                    locked[  1]                      coin[  1]                   unlocked[  2]    GoodCoin,  7 --                      *NA*[  0]                       oos[  5]                        oos[  3]    OOS,  8 --                      *NA*[  0]                     audit[  4]                     locked[  1]    Audit,  9 --                      *NA*[  0]                      hack[  3]                       *NA*[  0]    Attack, 10 --                      *NA*[  0]                      *NA*[  0]                        oos[  3]    OOS, turnstile FSM panel: fnStateGet    = 0x08049ab1 turnstileStateGet fnStateSet    = 0x08049abc turnstileStateSet fnInstGet     = 0x080499a6 turnstileInstanceGet fnTraceGet   = 0x08049a33 turnstileFsmTraceGet fnStateName   = 0x0804988c turnstileStateName fnEventName   = 0x080498c8 turnstileEventEnum2Name fnFsmTableGet = 0x08049ada turnstileFsmTableGet turnstile list: [0] locked [1] locked [2] locked [3] locked turnstile FSM trace:  = 0 (0x0) <FUNCALL : size=0>  $3/> turnstileMsgSim(1, 1);  [Info]:target_a/Demo_Turnstile.c GoodCoin 298::Turnstile 1:: Welcome.  = 0 (0x0) <FUNCALL : size=0>  $4/> turnstileMsgSim(2, 1);  [Info]:target_a/Demo_Turnstile.c GoodPush 303::Turnstile 1:: See you next time.  = 0  (0x0) <FUNCALL : size=4>  $5/> turnstileMsgSim(3, 1);  [Warn]:target_a/Demo_Turnstile.c Attack 323::Turnstile 1:: Attack warning... [Info]:target_a/Demo_Turnstile.c Audit 330::Turnstile 1:: Audit... [Warn]:target_a/Demo_Turnstile.c OOS 318::Turnstile 1:: Out of service...  = 0  (0x0) <FUNCALL : size=4>  $6/> turnstileMsgSim(4, 1);  [Info]:target_a/Demo_Turnstile.c Audit 330::Turnstile 1:: Audit... [Warn]:target_a/Demo_Turnstile.c OOS 318::Turnstile 1:: Out of service...  = 0  (0x0) <FUNCALL : size=4>  $7/> turnstileMsgSim(1, 1);  [Warn]:target_a/Demo_Turnstile.c OOS 318::Turnstile 1:: Out of service...  = 0  (0x0) <FUNCALL : size=4>  $8/> turnstileMsgSim(2, 1);  [Warn]:target_a/Demo_Turnstile.c OOS 318::Turnstile 1:: Out of service...  = 0  (0x0) <FUNCALL : size=4>  $9/> turnstileMsgSim(2, 1);  [Warn]:target_a/Demo_Turnstile.c OOS 318::Turnstile 1:: Out of service...  = 0  (0x0) <FUNCALL : size=4>  $10/> turnstileInstanceShow(1);  turnstile instance: [1] oos  = 0 (0x0) <FUNCALL : size=252>  $11/> turnstileFsmTraceShow(1);  [   0] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 1 Coin   DEST INST: 1          1 locked + 1 coin               ==>> GoodCoin,                   = 2 unlocked,  0 *NA* [   1] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 2 Push   DEST INST: 1          2 unlocked + 2 push               ==>> GoodPush,                   = 1 locked,  0 *NA* [   2] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 3 Attack   DEST INST: 1          1 locked + 3 hack               ==>> Attack,                   = 0 *NA*,  4 audit [   3] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 0 *NA*   DEST INST: 0          1 locked + 4 audit               ==>> Audit,                   = 1 locked,  5 oos [   4] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 0 *NA*   DEST INST: 0          1 locked + 5 oos               ==>> OOS,                   = 3 oos,  0 *NA* [   5] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 4 Audit   DEST INST: 1          3 oos + 4 audit               ==>> Audit,                   = 1 locked,  5 oos [   6] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 0 *NA*   DEST INST: 0          3 oos + 5 oos               ==>> OOS,                   = 3 oos,  0 *NA* [   7] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 1 Coin   DEST INST: 1          3 oos + 1 coin               ==>> OOS,                   = 3 oos,  0 *NA* [   8] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 2 Push   DEST INST: 1          3 oos + 2 push               ==>> OOS,                   = 3 oos,  0 *NA* [   9] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 1        MSG ID: 2 Push   DEST INST: 1          3 oos + 2 push               ==>> OOS,                   = 3 oos,  0 *NA*  = 0  (0x0) <FUNCALL : size=4>  $12/> turnstileMsgSim(1, 2);  [Info]:target_a/Demo_Turnstile.c GoodCoin 298::Turnstile 2:: Welcome.  = 0 (0x0) <FUNCALL : size=224>  $13/> turnstileMsgSim(2, 2);  [Info]:target_a/Demo_Turnstile.c GoodPush 303::Turnstile 2:: See you next time.  = 0  (0x0) <FUNCALL : size=4>  $14/> turnstileMsgSim(2, 2);  [Warn]:target_a/Demo_Turnstile.c BadPush 313::Turnstile 2:: You forgot to pay...  = 0  (0x0) <FUNCALL : size=4>  $15/> turnstileMsgSim(1, 2);  [Info]:target_a/Demo_Turnstile.c GoodCoin 298::Turnstile 2:: Welcome.  = 0  (0x0) <FUNCALL : size=4>  $16/> turnstileMsgSim(1, 2);  [Warn]:target_a/Demo_Turnstile.c BadCoin 308::Turnstile 2:: Thanks for your lenience...  = 0  (0x0) <FUNCALL : size=4>  $17/> turnstileMsgSim(2, 2);  [Info]:target_a/Demo_Turnstile.c GoodPush 303::Turnstile 2:: See you next time.  = 0  (0x0) <FUNCALL : size=4>  $18/> turnstileMsgSim(1, 2);  [Info]:target_a/Demo_Turnstile.c GoodCoin 298::Turnstile 2:: Welcome.  = 0  (0x0) <FUNCALL : size=4>  $19/> turnstileInstanceShow(2);  turnstile instance: [2] unlocked  = 0  (0x0) <FUNCALL : size=4>  $20/> turnstileFsmTraceShow(2);  [  10] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 1 Coin   DEST INST: 2          1 locked + 1 coin               ==>> GoodCoin,                   = 2 unlocked,  0 *NA* [  11] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 2 Push   DEST INST: 2          2 unlocked + 2 push               ==>> GoodPush,                   = 1 locked,  0 *NA* [  12] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 2 Push   DEST INST: 2          1 locked + 2 push               ==>> BadPush,                   = 1 locked,  0 *NA* [  13] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 1 Coin   DEST INST: 2          1 locked + 1 coin               ==>> GoodCoin,                   = 2 unlocked,  0 *NA* [  14] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 1 Coin   DEST INST: 2          2 unlocked + 1 coin               ==>> BadCoin,                   = 2 unlocked,  0 *NA* [  15] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 2 Push   DEST INST: 2          2 unlocked + 2 push               ==>> GoodPush,                   = 1 locked,  0 *NA* [  16] Panel:0x807b460 Start@1449217503 Stop@1449217503 Trace: 2        MSG ID: 1 Coin   DEST INST: 2          1 locked + 1 coin               ==>> GoodCoin,                   = 2 unlocked,  0 *NA*  = 0  (0x0) <FUNCALL : size=4> -> 
正文到此结束
Loading...