首先我們還是先把程式碼列出來看看吧,這是一個citizen錶的部分statechart,取自Harel的paper。我無意把David Harel使用的citizen錶的所有statechart都轉成scxml,想必只使用其中一部分大家就能舉一反三。
#include <StateMachineManager.h> #include <uncopyable.h> #include <iostream> using namespace std; using namespace SCM; std::string watch_scxml = "\ <scxml non-unique='on,off'> \ <state id='time'> \ <transition event='a' target='alarm1'/> \ <transition event='c_down' target='wait'/> \ </state> \ <state id='wait'> \ <transition event='c_up' target='time'/> \ <transition event='2_sec' target='update'/> \ </state> \ <state id='update'> \ <history id='histu' type='shallow'/> \ <transition event='d' target='histu'/> \ <state id='sec'> \ <transition event='c' target='1min'/> \ </state> \ <state id='1min'> \ <transition event='c' target='10min'/> \ </state> \ <state id='10min'> \ <transition event='c' target='hr'/> \ </state> \ <state id='hr'> \ <transition event='c' target='time'/> \ </state> \ </state> \ <state id='alarm1' history='shallow'> \ <transition event='a' target='alarm2'/> \ <state id='off' > \ <transition event='d' target='on'/> \ </state> \ <state id='on' > \ <transition event='d' target='off'/> \ </state> \ </state> \ <state id='alarm2' history='shallow'> \ <transition event='a' target='chime'/> \ <state id='off' > \ <transition event='d' target='on'/> \ </state> \ <state id='on' > \ <transition event='d' target='off'/> \ </state> \ </state> \ <state id='chime' history='shallow'> \ <transition event='a' target='stopwatch'/> \ <state id='off' > \ <transition event='d' target='on'/> \ </state> \ <state id='on' > \ <transition event='d' target='off'/> \ </state> \ </state> \ <state id='stopwatch' history='deep'> \ <transition event='a' target='time'/> \ <state id='zero'> \ <transition event='b' target='on,regular'/> \ </state> \ <parallel> \ <state id='run'> \ <state id='on' > \ <transition event='b' target='off'/> \ </state> \ <state id='off' > \ <transition event='b' target='on'/> \ </state> \ </state> \ <state id='display'> \ <state id='regular'> \ <transition event='d' cond='In(on)' target='lap'/> \ <transition event='d' cond='In(off)' target='zero'/> \ </state> \ <state id='lap'> \ <transition event='d' target='regular'/> \ </state> \ </state> \ </parallel> \ </state> \ </scxml> \ "; class TheMachine : public Uncopyable { StateMachine *mach_; int hr_; int min_; int sec_; public: TheMachine() : hr_(0) , min_(0) , sec_(0) { mach_ = StateMachineManager::instance()->getMach("watch"); mach_->retain(); vector<string> const&states = mach_->get_all_states(); cout << "we have states: " << endl; for (size_t state_idx=0; state_idx < states.size(); ++state_idx) { State *st = mach_->getState(states[state_idx]); if (st == 0) continue; // <- state machine itself for (int i=0; i < st->depth(); ++i) { cout << " "; } cout << states[state_idx] << endl; mach_->setActionSlot ("onentry_" + states[state_idx], boost::bind (&TheMachine::onentry_report_state, this, false)); mach_->setActionSlot ("onexit_" + states[state_idx], boost::bind (&TheMachine::onexit_report_state, this)); } cout << endl; mach_->setActionSlot ("onentry_sec", boost::bind (&TheMachine::onentry_sec, this)); mach_->setActionSlot ("onentry_1min", boost::bind (&TheMachine::onentry_1min, this)); mach_->setActionSlot ("onentry_10min", boost::bind (&TheMachine::onentry_10min, this)); mach_->setActionSlot ("onentry_hr", boost::bind (&TheMachine::onentry_hr, this)); mach_->StartEngine(); } ~TheMachine () { mach_->release(); } void onentry_sec() { if (mach_->re_enter_state()) { sec_ = 0; } onentry_report_state(true); } void onentry_1min() { if (mach_->re_enter_state()) { ++min_; } onentry_report_state(true); } void onentry_10min() { if (mach_->re_enter_state()) { min_ += 10; } onentry_report_state(true); } void onentry_hr() { if (mach_->re_enter_state()) { ++hr_; } onentry_report_state(true); } void onentry_report_state(bool with_time) { ++sec_; cout << "enter state " << mach_->getEnterState()->state_uid(); if (with_time) { cout << ". time " << hr_ << ":" << min_ << ":" << sec_; } cout << endl; } void onexit_report_state() { cout << "exit state " << mach_->getEnterState()->state_uid() << endl; } void test () { // time mach_->enqueEvent("c_down"); // -> wait mach_->enqueEvent("2_sec"); // -> update, you will use registerTimedEvent to generate event after 2 seconds mach_->enqueEvent("d"); // reset, 1 second mach_->enqueEvent("d"); // reset, 1 second mach_->enqueEvent("d"); // reset, 1 second mach_->enqueEvent("c"); // -> 1min state mach_->enqueEvent("d"); // 1 min mach_->enqueEvent("d"); // 2 min mach_->enqueEvent("c"); // -> 10min state mach_->enqueEvent("d"); // 12 min mach_->enqueEvent("c"); // -> hr state mach_->enqueEvent("d"); // 1 hr mach_->enqueEvent("d"); // 2 hr mach_->enqueEvent("c"); // -> time mach_->enqueEvent("a"); // -> alarm1 mach_->enqueEvent("d"); mach_->enqueEvent("a"); // -> alarm2 mach_->enqueEvent("d"); mach_->enqueEvent("a"); // -> chime mach_->enqueEvent("d"); mach_->enqueEvent("a"); // -> stopwatch.zero mach_->enqueEvent("b"); // -> run.on mach_->enqueEvent("d"); // -> display.lap mach_->enqueEvent("a"); // -> time mach_->enqueEvent("d"); // no effect mach_->enqueEvent("a"); // -> alarm1 mach_->enqueEvent("d"); mach_->enqueEvent("a"); // -> alarm2 mach_->enqueEvent("d"); mach_->enqueEvent("a"); // -> chime mach_->enqueEvent("d"); mach_->enqueEvent("a"); // -> stopwatch, what's run and display in? mach_->frame_move(0); } }; int main(int argc, char* argv[]) { AutoReleasePool apool; StateMachineManager::instance()->set_scxml("watch", watch_scxml); { TheMachine mach; mach.test (); } return 0; }
那個 '2_sec' 的event是你的程式在經過2秒之後再enqueEvent()進去的,雖然應該很清楚這邊只是因為範例的關係我們才這樣寫,怕你誤會還是說一聲。使用StateMachine的話可以用registerTimedEvent()來達成喲,像這樣:
mach_->registerTimedEvent(2.0, "2_sec", false);注意第三個參數,如果你給它true的話,你要保存它傳回的指標(加上retain),當時間到的時候系統會根據該物件是不是還有外部reference來決定要不要執行enqueEvent的動作。如此,你可以有機會「取消」該動作,只要在時間到之前先release保留的指標就可免除event被發出來。
好,我們回頭看看scxml的定義。請看'update' state的部分,它有一行<history id='histu' type='shallow'/> 的設定。如果你己經研究了scxml的spec的話,應該知道有設定history的state在機器重新回到該state時會進入原本所在的substate。所以像這邊如果你原本在'hr'的substate,然後按了'c'鍵,系統將會離開'update' state, 進入'time' state。當下次系統再次回到'update' state時(在'time' state下按住'c'鍵超過兩秒),機器將會進入'hr' state,儘管我們定義'update'的初始substate是'sec'(當沒有設定'initial'時,第一個定義的substate就是該state的initial state)。
history的種類有兩種:'shallow'與'deep'。'shallow'只會記一層的歷史,'deep'的話會記下所有階層substate的歷史,省得你每一層都去設定history,如我們在'stopwatch'這個state所見,可以少打很多字呢。
一般來說你要設定一個state使用history, 只要用attributes的方式就行了,就像<state id='stopwatch' history='deep'> 但是有些情況你可能希望能給history一個id, 這樣你就可以指定transition的目標到該history。就像'update' state裏那樣,'d'的event發生時,機器會轉到'histu'這個'假'state,histu實際上是上次機器所在的state,所以在這邊的效果就是機器會離開目前的state然後馬上再次進入該state。StateMachine有個re_enter_state()可以檢查離開的state是不是與進入的state相同,如果相同我們就把目前正在調整的時、分、或秒的數值加1。我正在考慮把re_enter_state()改名叫 is_reenter_state(),你覺得如何呢?歡迎在下面留下comment告訴我。
在範例中雖然沒有,但是你可以用'clh(StateName)'的方式來清除一個state的history,通常會放在 transition的的 'ontransit' 中。
我們上次有學到可以使用巨集REGISTER_STATE_SLOT來注冊一個state的onentry與onexit動作。如果你看過REGISTER_STATE_SLOT的定義的話應該就知道我們是用setActionSlot的方式來建立'onentry_xxx','onexit_xxx'與boost bind物件的對應。我們在TheMachine的constructor裏使用這種方式來建立對應,因為我們只是要顯示進入與離開的state是什麼(用getEnterState()),所以用一個for loop把所有的對應都設成一樣的,這樣就不用寫幾十行的REGISTER_STATE_SLOT了。然後再覆蓋掉要特別處理的幾個state就行了。記得這只是建立對應的動作,在StartEngine()前都是可以變動的。
好了,希望借由這個範例你能了解scm是如何的簡單易用。我們下次見!