RTコンポーネントの状態遷移
RTコンポーネントは、内部に状態遷移モデルをもっている。RTコンポーネントがとりうる状態は、下記の通り。
- 生成状態(CREATED_STATE)
この状態は、RTコンポーネントが生成された直後の状態であり、comp->initialize()が呼ばれている状態である。このinitializeメソッド内では、次のようなことが行われている。- arg=prop["exec_cxt.periodix.type"]+"?"+"rate="+prop["exec_cxt.periodic_rate"]が生成されて、 ec=RTC::Manager::instance().createContext(arg); が呼び出されてExecutionContextが生成される。
- ec->set_rate(prop["exec_cxt.periodic_rate"]); が呼ばれる。(これは、前の処理で実行されているべきではないかと思われる。
- m_elistに生成した実行コンテキストを追加する。
- ecv=RTC::ExecutionContextService::_duplicate(ec->getObjRef()); が呼び出される。これでエラーチェックが行われているだけのようである。(必要なのだろうか?)
- ec->bindComponent(this); が呼ばれて実行コンテキストからbindContextが呼ばれるとともに、実行コンテキスト内のm_compsにRTコンポーネントが追加される。bindContextは、RTコンポーネント内のm_ecMineにExecutionContextを追加する。
- on_inilialize() メソッドが呼び出される。このメソッドでは、ユーザが定義した onInitialize() が呼び出されて、m_propertiesの"configuration.active_config"を"default"に設定する。
- m_ecMine[i]->start() が呼ばれてすべての実行コンテキストが走り始める。
- 現在のOpenRTM-aistの実装では、ExecutionContextBaseには実装がなく、PeriodicExecutionContextが実装のある最上位のクラスになている。そのため以下では、実行コンテキストといえば、PeriodicExecutionContextことを指す。さて、実行コンテキストのstart()では、以下のような処理が行われる。
- 実行コンテキストに関連付けられたコンポーネントに対してon_startup()メソッドがコールされる。(invoke_on_startupファンクタ内)
- 次に、実行コンテキストのスレッドの状態の初期化をする。これは、下記のようになっている。
- m_running を trueにセット
- m_workerのMutexをロックする
- m_workerのrunning_ を trueにセット
- m_workerのConditionのsignal()をコールする。
- m_workerMutexを解除する
- そして、open(0)メソッドをコールする。このopenでは、activate()をコールしている。
- activate()メソッドは、実行コンテキストの親クラスであるcoil::Taskに実装されている。このメソッドは、m_countをチェックして、0であれば、svn_runを実行するスレッドを生成する。
- svc_run()メソッドは、自身のsvc()を実行して、それが終わればfinalize()をコールする。svc()メソッドは、m_svc変数が真であるときに繰返し実行するループ関数になっている。派生実行コンテキストでは、このsvc()メソッドを上書きすることで様々な動作を実現している。
- svc()メソッドは、ループ処理内で次のような処理を行なっている。
- m_workerのMutexをロックする
- m_running_がTrueでないならば、m_workerのConditionのwait()をコールし、シグナル待ちになる。
- m_running_がTrueになれば、関連付けられたRTコンポーネントの_sm.worker()をコールする(invoke_workerファンクタ内)また、_smは、 DFP<OpenRTM::DataFlowComponent_var>である。このDFPクラスのworkerメソッドは、RTC_Utils::StateMachine<ExecContextState, DFPBase> のworkerメソッドをコールしている。
- m_workerMutexを解除し、m_nowaitが真でないときにcoil::sleep(m_period)で一定時間待つ。
- 現在のOpenRTM-aistの実装では、ExecutionContextBaseには実装がなく、PeriodicExecutionContextが実装のある最上位のクラスになている。そのため以下では、実行コンテキストといえば、PeriodicExecutionContextことを指す。さて、実行コンテキストのstart()では、以下のような処理が行われる。
- 非活性状態(INACTIVE_STATE)
この状態は、活性状態になる準備段階であり、生成状態、エラー状態、活性状態から移行する。通常のRTコンポーネントは、実行コンテキストが実行されると、最初のステップで、生成状態からこの状態に移行する。(これは厳密には、実行コンテキストが生成され、スロット変数のDFPクラスが生成されたときである)また、エラー発生後、動作を復帰し、再度 活性状態になる時に、必ずこの状態に移行する。
- 活性状態(ACTIVE_STATE)
この状態は、RTコンポーネントが活動している状態であり、実行コンテキストの通常サイクルではこの状態にいることになる。そのため、RTコンポーネントの実行コンテキストが周期実行型の場合、on_Executionコールバック関数が実装されていれば、毎周期そのコールバック関数が実行されることになる。活性状態時に何らかのエラーが発生すれば、RTコンポーネントは、エラー状態に移行し、実行コンテキストは停止する。
非活性状態から活性状態に移行するには、RTシステムエディタ等から実行コンテキストに対してactivate_componentメソッドをコールすることで、該当RTコンポーネントが活性状態に移行する。activate_componentの処理は次のようになる。
activate_componentは、引数としてLightweightRTObject_ptrをとる。RTObjectクラスは、このLightweightRTObjectを継承しているため、実質的にはRTObjectが引数となる。ここでは、まず、実行コンテキストに関連付けられたRTコンポーネントと引数で与えられたコンポーネントが等価かどうかを_is_equivalentで評価し、等価であれば、その状態が、INACTIVE_STATEからACTIVE_STATEに変更させる。状態を変更させるのは、RTC_Utils::StateMachine<ExecContextState, DFPBase> クラスである。ここで、実行コンテキストは、実行状態になっているので、workerメソッドがコールされたタイミングで状態遷移が起こる。
- エラー状態(ERROR_STATE)
この状態は、活性状態から移行し、on_Errorコールバック関数が実装されていれば、その処理をする。この状態からは、非活性状態または、終了状態に移行する。
- 終了状態
この状態は、RTコンポーネントが終了する際に移行する状態である。OpenRTM-aistでは、特にこの状態の明記がないが、RTコンポーネントの終了処理状態である。通常は、RTコンポーネントのexit()メッソドがコールされてた状態である。- exit()メソッドでは、自分所有の実行コンテキストと自分に関連付けられている実行コンテキストを非活性状態に移行させる。そして、自分に関連付けられて実行コンテキストと自分自身との関連付けを削除する。そして、m_exiting を TRUEにセットし、finalize()メソッドが呼ぶ。
- finalize()メソッドでは、on_finalize()メッソドが実装されていれば、それを実行し、各RTコンポーネントに応じた終了処理を実行する。その後shutdown()メソッドをコールする。
- shutdown()メソッドでは、各ポート、実行コンテキストの終了処理(finalizePorts, finalizeContexts)を行い、POAから自分に関連するConfigurationと自分自身に対応するCORBAオブジェクトを不活性状態にする。そして、RTCマネージャーに対して、notifyFinalize(this)を呼び出し、終了処理を行う。
RTコンポーネントの状態マシン
RTコンポーネントは4状態をもっており、それが遷移することを述べた。この状態マシンは、RTコンポーネントに直接バインドされているわけではない。上記で出てきた実行コンテキストの中で、DFPテンプレートクラスとExecContextStateから生成されたStateMachineのテンプレートクラスである。このStateMachineでは、StateMachine.hに定義されている。実行コンテキストで実際に動作しているメソッドは、このStateMachineのworker()というメソッドである。
このメソッドは、下記のように定義されている。
void worker() { States state; sync(state); if (state.curr == state.next) { // pre-do if (m_predo[state.curr] != NULL) (m_listener->*m_predo [state.curr])(state); if (need_trans()) return; // do if (m_do[state.curr] != NULL) (m_listener->*m_do [state.curr])(state); if (need_trans()) return; // post-do if (m_postdo[state.curr] != NULL) (m_listener->*m_postdo[state.curr])(state); } else { if (m_exit[state.curr] != NULL) (m_listener->*m_exit[state.curr])(state); sync(state); if (state.curr != state.next) { state.curr = state.next; if(m_entry[state.curr] != NULL) (m_listener->*m_entry[state.curr])(state); update_curr(state.curr); } } }
これからわかることは、worker()メソッドが呼ばれるたびに、state.currとstate.nextを比較して、同じであればその状態の
- m_predo [state.curr]がセットさえれていれば、(m_listener->*m_predo [state.curr])(state) を実行。
- need_trans()が真ならば、ここで返る。
- m_do[state.curr]がセットされていれば、(m_listener->*m_do [state.curr])(state) を実行。
- m_postdo[state.curr]セットされていれば、(m_listener->*m_postdo[state.curr])(state) を実行。
を実行してることが分かる。
また状態遷移が発生する場合には、m_exit[state.curr]がセットさえていれば、 (m_listener->*m_exit[state.curr])(state);を実行し、その後、状態を変化させ、m_entry[state.curr]がセットされていれば、(m_listener->*m_entry[state.curr])(state); が実行される。
通常の実行コンテキスト(PeriodicExecutionContext)は、
m_sm.setEntryAction (ACTIVE_STATE, &DFPBase::on_activated); m_sm.setDoAction (ACTIVE_STATE, &DFPBase::on_execute); m_sm.setPostDoAction(ACTIVE_STATE, &DFPBase::on_state_update); m_sm.setExitAction (ACTIVE_STATE, &DFPBase::on_deactivated); m_sm.setEntryAction (ERROR_STATE, &DFPBase::on_aborting); m_sm.setDoAction (ERROR_STATE, &DFPBase::on_error); m_sm.setExitAction (ERROR_STATE, &DFPBase::on_reset);
と定義れされており、
- 非活性状態から活性状態に移行時 ''on_activated''を実行。
- 活性状態中は、''on_execute''を実行して、''on_state_update''を実行。
- 活性状態を抜けるときは、''on_deactivated''を実行。
- エラー状態に入るときは、''on_aborting''を実行。
- エラー状態中は、''on_error''を実行。
- エラー状態を抜けるときは、''on_reset''を実行
となる。