個人第一份工做很短命,總共兩年,最後半年仍是在爲離職跟人鬥智鬥勇。真得感謝非典在那會發生了(非典犧牲了不少醫生,我曾經肺炎獲得過一個抗非典英雄的精心治療。希望再也不有非典!),全國各城拒絕外來人,因而乎,我被掃地出門。在那兩年內,哥作了很多程序,自我感受良好,但有一點始終憋屈,那就是程序越寫越複雜,剛作出來的時候還行,維護了一段時間,增長了些新功能,就開始面目全非了,邏輯愈來愈複雜,調測更是愈加困難。其實這些年我看過不少項目的代碼,也就是我那會的水平和風格。linux
有沒有什麼技術能讓程序變得簡單點呢?並且這種簡單能讓程序能夠更容易的應對更多的複雜的功能需求,測試更簡單,bug更容易定位……凡是好處,哥都不想少,不然老闆不幹。shell
固然有!一個叫作數據驅動,另外一個就是今晚要解釋的,基於數據驅動的典型方法--狀態機編程。編程
代碼連接(建議下載,並在linux上編譯運行):
http://files.cnblogs.com/files/hhao020/cshell_prj_re0.003.raride
開始狀態機編程前,須要清楚幾個概念,好比多事務會話處理,多實例會話處理等。會話處理過程當中,不該該出現阻塞操做,除非是單實例會話,不然就對其它會話不公平了。另一個就是會話可能含有多個狀態分支,好比一我的,有左手和右手,兩隻手能夠作不一樣的事,天然有不一樣的狀態。同時,手的狀態又跟人的基本狀態有關係,不然就會出現人在睡覺,手卻在敲代碼,殭屍來咯!函數
正式開始說狀態機。首先,咱們須要一大塊內存在記錄程序按照狀態機執行的遷移過程。這個不是必須的,但沒有了這個,但哥以爲這個是狀態機的一個精華,也是哥提供的狀態機的特點。沒了這個記錄,即使有了狀態機,程序仍然是裝在黑盒子裏跑,你都無法知道程序跑對了沒,跑錯了也同樣很差找緣由。在開始使用狀態機前,執行下void* zFsmServiceInit(int maxTraceNum)這個函數,爲日誌功能分配內存。狀態機跟蹤的每條記錄爲固定長度,大概在40字節,這當中有32字節是那最大8個函數地址。post
接下來,要初始化狀態機的控制組件,結構以下:測試
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鉤子,是由於我提供的代碼不須要了。固然,這個不是什麼特別考慮,這裏能夠本身加入更多內容,關鍵是狀態機可以正確使用。ui
最後,是定義狀態機的操做集,也就是定義當系統處於狀態X,收到輸入事件Y,應該執行操做a,b,c,……,並最終轉入狀態Z。spa
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> ->