最近接觸了SCXML這個狀態描述文本,簡單來講就是描述了整個狀態的變遷過程的一種XML格式的表格。Qt labs中有一個項目就是QScxml,它基於QStateMachine上層制作,可以直接讀取SCXML格式的文件生成內部狀態對象和成員,可以直接在Qt中進行狀態變遷,十分方便。
先來簡單介紹一下SCXML的格式,以
<scxml initial="FirstState" version="0.9" xmlns="http://www.w3.org/2005/07/scxml">
作為整個SCXML的開頭,scxml標簽旁的initial表示狀態機啟動之后進入的第一個初始化狀態,在這里我寫了FirstState,表示狀態幾一啟動,就進入了FirstState.
<state id="FirstState" initial="FirstChildNode">
以state標簽開頭表示了狀態的基本概念,其中的id是作為該狀態的索引號給你之后寫target進行索引,這個時候同學會看到又出現了一個initial,這時的FirstChildNode表示此時的FirstState並不是一個原子狀態,而是一個組合狀態的父狀態。而FirstChildNode恰恰就是它的子狀態。也就是說進入了FirstState之后,就會立即進入FirstChildNode,期間如果你調用了<onentry>和<onexit>標簽,你會發現調用了多次,不必奇怪,其實你的狀態是進入了一層一層中的最里層,每進一層就會調用<onentry>和<onexit>。
<transition event="Key.A" target="SecondState"></transition> <transition event="Key.B" target="ThirdState"></transition>
又來新東西了,這個<transition>標簽表示真正的事務處理過程,之后的event屬性表示你傳遞給QScxml中的postNamedEvent(const QString)函數中的QString,所以我之前提過那個id的作用,就是全局的索引號,同時請注意:SCXML中默認的event是前綴查找,也就是說對於event="GameTest"來說,你輸入"GameTest","Game","Game.","Game.*"效果是完全一樣的,不過我試了下在QScxml中只有第一種和第三種有效(官方說明)。之后的<target>自然很好理解,就是你在這個狀態下,經過了event事件,達到了target狀態(target不能接收函數,必須是字符串狀態變量而cond可以接收函數或者字符串)。例子中就表示無論你在FirstState中的哪個孩子中,只要你收到了Key.A事件,你都會跳出子狀態乃至父狀態,直接跳到對應的SecondState中去。注意:寫在父狀態中的translation是給它以及它的孩子全局共享的,如果你覺得你可能在孩子節點中對於某一個事件你不滿意,你想要重寫,那你完全可以在FirstChildNode中寫下
<transition event="Key.A" target="FourhState"></transition> <transition event="Key.B" target="FifthState"></transition>
這個時候狀態機會優先處理最子層的事務處理,如果狀態機發現在最子層並沒有完成該事件(包括沒有找到該translation和找到translation可是cond為false)都會將事件向上傳遞給父狀態進行處理。
在來說說比較有用的標簽<cond>,這個標簽可以放在<translation>中也可以放在<if>中,當放在<translation中時>
<transition event="Key.A" target="FourhState" cond="isTrue"></transition>
表明當isTrue為true的時候,target才真正進行轉移(在這里isTrue即可以是簡單變量也可以是script函數來返回bool值),比較常用的用法有
<translation event="Key.A"> <if cond="isTrue()"> <script>FuncA()</script> <elseif cond="isFalse()"/> <script>FuncB()</script> <else/> <script>FuncC()</script> </if> </translation>
表明事件Key.A來的時候進行cond判斷來調用相應的script。
另外我們也可以用到狀態機在上而下處理事件的機制,來進行靈活的target動態轉換工作.
<transition event="Key.A" target="A" cond="isTrue()" /> <transition event="Key.A" target="B" >
細心的你一定會發現,怎么兩個translation的event一樣。其實這種用法在W3C的examples中也提到過,因為狀態機在上而下的處理機制,你可以在斷言為false的時候有一個默認的target,而在true的時候進入你事先設定的target,可以非常靈活的使用這種機制進行判斷.
另外介紹一下兩個也比較重要的標簽,在上文也提到過<onentry>和<onexit>,
<onentry> <script> enterState("A"); </script> </onentry> <onexit> <script> exitState("B"); </script>
表明在進入和退出該狀態的時候自動觸發的事件,這里默認調用的script,你可以很靈活的控制狀態切換時應該需要的工作.
QScxml中有一個功能非常強大的函數
void QScxml::registerObject (QObject* o, const QString & name, bool recursive)
用它進行注冊之后,你可以在SCXML的文件中寫各種script function,比如你注冊的時候m_scxml->registerObject(this, "Widget", true),這個時候你就可以在SCXML文件中寫下
<script> function show() {
Widget.show();
} </script>
表明無論你在translation還是onentry還是onexit中的<script>標簽寫show()這個函數,你最終都會通過QScxml這個強大的類讓你可以和它進行交互。如果你嫌寫script function麻煩,
你也可以直接在<script>標簽中寫上
Widget.show();
一樣可以直接運行。script function的強大不僅僅在於可以通過QScxml讓你和你的對象進行交互,同時它也可以用來做斷言cond判斷,比如
function isTrue()
{
return Widget.isGood();
}
你可以非常靈活的實現你自己類的斷言函數,配合之前的translation中的cond做到動態切換target,非常方便。
今天就簡單介紹到這里,在開始接觸SCXML的時候發現國內的資料很少,寫這篇博文也當貢獻自己的一份力了,更多的內容需要你自己去挖掘,希望你會喜歡這篇文章,留下你的腳印,給我支持,謝謝:)
