RXマイコンでシーン管理(タスク管理)テンプレート

多少複雑なアプリなどを実装する場合、動作の推移を行う管理機構が必要になる事が多い。
やり方は色々だが、組み込みシステム向けに、シンプルな機構を実装したので紹介する。
平行処理にする程でも無い場合など、少し大きい単位で処理を分けて、影響範囲を限定的にする事が出来る。
※この方法は既に色々なシステムで利用しているが、多少アレンジした点として、以下のトピックがある。

  • シーンオブジェクトによる推移管理。
  • 可変引数テンプレートの利用。
  • 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」にある。