多少複雑なアプリなどを実装する場合、動作の推移を行う管理機構が必要になる事が多い。
やり方は色々だが、組み込みシステム向けに、シンプルな機構を実装したので紹介する。
平行処理にする程でも無い場合など、少し大きい単位で処理を分けて、影響範囲を限定的にする事が出来る。
※この方法は既に色々なシステムで利用しているが、多少アレンジした点として、以下のトピックがある。
- シーンオブジェクトによる推移管理。
- 可変引数テンプレートの利用。
- std::tuple テンプレートを使い、シーンオブジェクトを管理。
- 記憶割り当てを使わない。
まず、一つの推移単位をシーンクラスとして、インターフェースクラスを継承させている。
インターフェースクラスは以下のようなものとなっている。
※C++ では「= 0; 」とする事で「純粋仮想関数」とする事が出来る。(薄い皮のような物)
※デストラクタを「仮想」として実装しておく必要がある。
struct scene {
virtual ~scene() { } ///< デストラクタ
virtual void init() = 0; ///< シーン開始前処理
virtual void service() = 0; ///< シーン・サービス
virtual void exit() = 0; ///< シーン終了処理
};
- シーンを開始する前に必ず「init()」を呼び出す。
- シーン中は「service()」が呼ばれ続ける。
- シーンが切り替わる時、シーンを終了するタイミングで「exit()」が呼び出される。
シーン作成時、上記「scene クラス」を継承させて実体を実装する。
以下は「title」クラス。
class title : public scene {
public:
void init() override
{
}
void service() override
{
}
void exit() override
{
}
};
通常シーンは複数あり、シーン管理機構で管理される、アプリケーション側は、各シーンの中身を実装する。
シーン管理クラス(scene_director)では、個々のシーンの実体をstd::tuple で保持しており、切り替えの操作や手順を提供する。
scene_director は、可変引数テンプレートを使って、ユーザー定義のシーンクラスを複数受け取る。
template<class... Args>
class scene_director {
typedef std::tuple<Args...> SCENES;
SCENES scenes_;
scene* cur_scene_;
scene* new_scene_;
public:
scene_director() noexcept : scenes_(), cur_scene_(nullptr), new_scene_(nullptr)
{ }
template <class T>
void change() noexcept
{
auto& news = std::get<T>(scenes_);
new_scene_ = &news;
}
void service() noexcept
{
if(new_scene_ != nullptr) {
if(cur_scene_ != nullptr) cur_scene_->exit();
new_scene_->init();
cur_scene_ = new_scene_;
new_scene_ = nullptr;
}
if(cur_scene_ != nullptr) {
cur_scene_->service();
}
}
};
このテンプレートを使い、以下のように全体を定義する。
#include "scene_director.hpp"
#include "title.hpp"
#include "menu.hpp"
#include "setup.hpp"
typedef scene_director<title, menu, setup> SCENES;
SCENES scenes_;
これで大体全てなのだが、大きな問題がある、通常シーンの切り替えは、scene_director の change メソッドを使うのだが、各シーンから、scene_director にアクセスするには、scene_director のインスタンスと定義、各シーンの型などが必要で、それにアクセスする手段を設けるのは難しい。
そこで、かなり冗長でシンプルでは無いが、各シーンに通し番号を振っておき、この番号で切り替える機構を設けて、この関数呼び出しを extern しておく事にした。
各シーンの切り替えは「整数」なので、定義がシンプルでどこからでも呼び出せる。
※ただ、シーンとIDを二重に管理しなければならず、かなり痛い・・・
void change(scene_id id)
{
switch(id) {
case scene_id::title:
scenes_.change<title>();
break;
case scene_id::menu:
scenes_.change<menu>();
break;
case scene_id::setup:
scenes_.change<setup>();
break;
}
}
※他の注意点として、インスタンス化の順番を理解しておく必要がある。
scene_director がインスタンス化されるタイミングで、各シーンのコンストラクターが呼び出されるので、各シーン中で何かをインスタンス化している場合、それが関わるクラスは、事前にインスタンス化されておかなければならない。
システムが複雑になると、忘れがちなので注意する必要がある。
※ソースコードは、「RX/common/scene.hpp」にある。