the_app

アプリ記述setup(),loop()の切り替え

アプリケーションはsetup(),loop()の2つの関数で記述されますが、例えば1画面ごとに大きく振る舞いが違うもの(ここではサブアプリと呼びます)を複数定義して、メニューで切り替えるといった動作を行う場合は、そのままsetup(),loop()内に記述しては煩雑になります。また変数についても管理する必要があります。

setup(), loop(), 関連する変数の切り替えを簡素化するためMWM5ライブラリでは以下の2つの仕組みを実装しています。

  • サブアプリ : setup(), loop(), 関連変数をクラスとしてまとめ、切り替える

  • アプリハンドラ: サブアプリ内で setup(), loop() を切り替える

サブアプリ

#include <mwm5.h>

// サブアプリ1
class App_Foo : public class APP_DEF {
public:
  void setup() { ... }
  void loop() { ... }
}

// サブアプリ2
class App_Bar : public class APP_DEF {
public:
  void setup() { ... }
  void loop() { ... }
}

// サブアプリ切り替え関数
static int s_change_app(
              TWE::APP_MGR& the_app, int n_appsel
            , int prev_app, int exit_id) {
    if (n_appsel == 1) the_app.new_app<App_Foo>();
    else if (n_appsel == 2) the_app.new_app<App_Bar>();
    else n_appsel = 0;
    
    return n_appsel;
}

// setup
void setup() {
    the_app.setup(s_change_app); // サブアプリ切り替え関数の登録
    the_app.new_app<App_Foo>(); // 最初に起動するサブアプリ
}

// loop()
void loop() {
  	the_app.loop(); // 必須の呼び出し
}

上記例は説明に必要な最小限のコードのみを記述します。APP_DEFを継承したApp_Foo,App_Barが、2種類のサブアプリの定義となり、各々がsetup(),loop()メンバー関数を定義しています。実際には振る舞いごとに必要なメンバー変数やその他メンバー関数が定義されます。サブアプリの登録や切り替えはライブラリ定義のクラスオブジェクトthe_appに対して操作を行います。s_change_app()関数はサブアプリを切り替える要求(サブアプリ終了時など)に対して、実際の切り替え部を実装していて、この関数をsetup()中で呼び出すthe_app.setup()のパラメータとして渡しておきます。loop()中にはthe_app.loop()を記述しておきます。

サブアプリの定義

class App_Foo : public class APP_DEF {
public:
  void setup() { ... }
  void loop() { ... }
}

void setup()void loop()を記述しておきます。setup()はサブアプリが初期化されるときに1度だけ呼び出されます。loop()the_app.loop()関数中で呼び出されます。

サブアプリの切り替え

// APP_MGR内のtypedef
typedef int (*PF_CHANGE_APP)(
    THE_APP_MGR& the_app,
    int n_appsel,
    int prev_app,
    int exit_id);

// 例: App_Foo と App_Bar で切り替える
int s_change_app(
              TWE::APP_MGR& the_app, int n_appsel
            , int prev_app, int exit_id) {
    if (n_appsel == 1 || n_appsel == -1)
        the_app.new_app<App_Foo>();
    else if (n_appsel == 2)
        the_app.new_app<App_Bar>();
    else n_appsel = 0;
    
    return n_appsel;
}

実行中のサブアプリが終了する場合に呼び出されます。サブアプリは1以上のIDにより種別を管理し、切り替えのために関数int (*)(THE_APP_MGR&, int, int, int)を実装しておきます。

パラメータn_appselには、切り替えたいアプリケーションのIDが渡されます。パラメータはthe_app.exit()で指定した値です。-1が渡された場合は、デフォルトのサブアプリに切り替えるよう記述しておきます。

  • パラメータprev_appには、直前のサブアプリIDです。直前が無ければ0となります。

  • パラメータexit_idは、サブアプリがthe_app.exit()で指定した値です。

n_appselで次に動作させるサブアプリを指定し、exit_idはサブアプリに対するパラメータとして用います。

サブアプリ切り替え関数内部ではthe_app.new_app<T>()Tは次に生成するサブアプリのクラス)を呼び出しています。この呼び出しによりクラスTのオブジェクトが生成され、同時にこれまで動作していたサブアプリのオブジェクトは破棄されます。

the_app.new_app<T>()

template <class T, class... Args>
void APP_MGR::new_app(Args&&... args)

// setup 例
void setup() {
    the_app.setup(s_change_app);
    the_app.new_app<App_Foo>(); // 最初に起動するサブアプリ
}

// サブアプリ切り替え関数例
static int s_change_app(
              TWE::APP_MGR& the_app, int n_appsel
            , int prev_app, int exit_id) {
    if (n_appsel == 1 || n_appsel == -1)
        the_app.new_app<App_Bar>();
    }
}

setup()とサブアプリ切り替え関数内で呼び出し、サブアプリのオブジェクトを生成し、同時に現在のサブアプリを破棄します。

最初に動作させるサブアプリはsetup()で生成します。

  • テンプレートパラメータTはサブアプリ定義クラスでAPP_DEFを継承している必要があります。

  • テンプレートパラメータargsは、コンストラクタに渡されるパラメータです。オブジェクトはnew T(std::forward(args)...)で生成されます。サブアプリがパラメータを指定しないデフォルトコンストラクタで構築される場合は何も指定しません。パラメータを指定する場合はサブアプリのコンストラクタの定義が必要です(例えばApp_Foo::App_Bar(int)を定義しておいてthe_app.new_app<App_Bar>(exit_id))。

the_app.exit()

void APP_MGR::exit(int exit_code, int next_app = NEXT_APP_DEFAULT)

// loop() の呼び出し階層
loop() // 大本のloop()
  the_app.loop() // APP_MGRのloop()
  
    App_Foo::loop() // サブアプリのloop()
      ...
      if (..) { // 何かの条件でサブアプリを終了する
        the_app.exit(0);
        return; // 通常はここでreturn
      }
    // App_Foo::loop()が終了した
    
    // exit がコールされていた場合は、
    // サブアプリ切り替え関数を呼び出し、
    // 次のサブアプリに切り替える。
  }
}

サブアプリのloop()内で呼び出し、loop()を抜けた直後にサブアプリを切り替えます。

上記の例のように大本のloop()からはthe_app.loop()が呼び出され、さらにサブアプリのloop()が呼び出されます。サブアプリのloop()中でthe_app.exit()が呼び出されると、the_app.loop()内でアプリケーションの切り替えが行われます。実際に切り替えているのはユーザが定義するサブアプリ切り替え関数です。

  • パラメータexit_codeは、サブアプリの終了コードを指定します。任意の数を指定できます。

  • パラメータnext_appは、次のサブアプリを明示的に指定する意味合いの識別子です。-1はデフォルトのサブアプリ、0は切り替えを行わない、1以上が次のサブアプリの識別子と意味付けをしています。

exit_codenext_appは、そのままサブアプリ切り替え関数に渡されます。サブアプリ切り替え関数は、その2つのパラメータを参照して次のサブアプリを決定します。

the_app.query_appobj()

APP_DEF* APP_MGR::query_appobj() // the_app.qurery_appobj()

// サブアプリ定義クラス内で利用
void APPDEF::set_appobj(void* p)
void* APPDEF::get_appobj()

// サブアプリ例
class App_Foo {
  TWETerm_M5_Console _scr;
public:
  App_Foo : _scr(..) {
    // 共通オブジェクトの登録
    set_appobj((void*)static_cast<ITerm*>(&_scr));
  }
  void setup();
  void loop();
};

// 大本のloop()
void loop() {
  ..
  // APPDEFオブジェクトの取得
  if (auto p = the_app.query_appobj()) {
    // 登録されたvoid*をITerm*へ強制キャスト
    if (ITerm* ptrm = reinterpret_cast<ITerm*>(p->get_appobj())) {
      ... // ptrm を用いた操作
    }
  }
}

大本のloop()からthe_appで生成したサブアプリのクラスオブジェクトを参照し、さらにサブアプリが指定したvoid*型のポインタデータを取得します。

例えば、サブアプリ構築時に共通のクラスオブジェクトを持っていて(例えばメイン画面となるターミナル画面)、大本のloop()から共通のオブジェクトにアクセスします(例えばメイン画面上の文字列を取得)。

アプリハンドラ

アプリハンドラAPP_HNDLRは、サブアプリ中での簡易的なsetup(),loop()処理の切り替えを行います。例えば、リスト一覧→リスト選択後、エラーといった複数の画面を切り替えるような動作です。

サブアプリの実装ではAPPDEFを継承しましたが、さらにAPP_HNDLR<S>(Sはサブアプリクラスを指定します)を継承します。サブアプリ中に処理関数(アプリハンドラ)を実装し、アプリハンドラを切り替えます。

class App_Foo : public APPDEF, public APP_HNDLR<App_Foo> {
  // アプリハンドラ1
	void hndr_scr_def(event_type ev, arg_type arg = 0) {
		switch (ev) {
			case EV_SETUP:
			  // setup()に相当、最初に1回だけ呼ばれる
			break;
			case EV_LOOP:
			  // loop()に相当
        if (..) { // ある条件で次のアプリハンドラに遷移したい
          // アプリハンドラ切り替え
          APP_HNDLR::new_hndlr(&App_Foo::hndlr_scr_next);
          return; // 通常はここでreturn
        }
			break;
			case EV_EXIT;
			  // 終了時=アプリハンドラ切り替え時に呼び出される
			break;
	}
	
	// ハンドラ2
	void hndlr_scr_next(event_type ev, arg_type arg = 0) {
	  ..
	}
	
public:
  ~App_Foo() { 
    APP_HNDLR::on_close();
  }
  void setup() {
    ...
    
    // 最初のアプリハンドラを登録する
    APP_HNDLR::new_hndlr(&App_Commander::hndr_screen_simple);
  }
  
  void loop() {
    // 必須呼び出し
    APP_HNDLR::loop();
    ...
  }
};

上記がアプリハンドラを用いたときのソース構造の例です。

hndr_scr_def()hndr_scr_next()がアプリハンドラ関数です。パラメータとしてevargが与えられ、evEV_SETUP,EV_LOOP,EV_EXITのいずれかになり、setup(),loop()と終了処理に対応します。

アプリハンドラ関数

// APP_HNDLR<T>内のtypedef
typedef void (T::* tpf_func_handler)(event_type ev, arg_type evarg)

アプリハンドラ関数はsetup(),loop()に相当する処理を行います。加えてハンドラが切り替えられる、つまり現在登録されているハンドラが終了するときの処理を記述できます。

class App_Foo : public APP_DEF, public APP_HNDLR<App_Foo> {
  ..
	void hndr_scr_def(event_type ev, arg_type arg = 0) {
		switch (ev) {
			case EV_SETUP:
			  // setup()に相当、最初に1回だけ呼ばれる
			break;
			case EV_LOOP:
			  // loop()に相当
        if (..) { // ある条件で次のアプリハンドラに遷移したい
          // アプリハンドラ切り替え
          APP_HNDLR::new_hndlr(&App_Foo::hndlr_scr_next);
          return; // 通常はここでreturn
        }
			break;
			case EV_EXIT;
			  // 終了時=アプリハンドラ切り替え時に呼び出される
			  // 注: サブアプリのデストラクタで APPHNDLR::on_close() を
			  //     記述してください
			break;
	}
};

パラメータevに対応した処理を記述します。

パラメータargは、APP_HNDLR::new_hnbdlr()APP_HNDLR::loop()でパラメータが与えられたときに、値が格納されます。

APP_HNDLR::new_hndlr()

void APP_HNDLR::new_hndlr(tpf_func_handler hnd_next, arg_type arg = 0)

アプリハンドラを初回生成する(サブアプリクラスのsetup())、またはアプリハンドラ内から次のアプリハンドラへ切り替えます。

  • パラメータhnd_nextはアプリハンドラ関数(メンバー関数のポインタ)を指定します。

  • パラメータargはアプリハンドラ関数のEV_SETUP呼び出し時のパラメータを指定します。(EV_SETUPによる初期化時の処理を分岐したいときに利用します)

APP_HNDLR::loop()

void APP_HNDLR::loop(arg_type arg = 0)

サブアプリのloop()から毎回呼び出します。

  • パラメータargを指定するとアプリハンドラにその値が渡されます。

APP_HNDLR::on_close()

void APP_HNDLR::on_close()

サブアプリのデストラクタから呼び出すようにしてください。

この記述を省略した場合、サブアプリのオブジェクトが破棄されるときに、アプリハンドラも破棄されますが EX_EXIT メッセージが呼び出されません。

アプリハンドラごとの独自データ

アプリハンドラは、サブアプリ内でsetup(),loop()コンテキスト切り替えの仕組みで、サブアプリ下でのメンバー関数の実行という携帯です。そのため、アクセスできるデータはサブアプリのメンバー変数となります。

ここでは、アプリハンドラ独自のデータを保持するクラスを追加します。

データを格納するクラスの準備

APPHNDLR_DCを継承したクラスを準備します。このクラスにはアプリハンドラ内で使用するデータやメンバー関数を定義します。以下の例ではDC_MyAppHndlrというクラスを定義しています。

/// APPHNDLR_DCを継承したデータ格納クラスの定義 
// publicアクセスで良いのでclassではなくstructで定義しています
struct DC_MyAppHndlr : public APPHNDLR_DC {
  static const int CLS_ID = 0;
	int get_class_id() { return CLS_ID; }
	
	MySubApp &app;
	
	// メンバ変数と操作関数
  int data1;
  void incr() { data1++ }
  
  // サブアプリ内のthe_screenに対する操作
  void display() { app.the_screen << data1; }
  
  // コンストラクタ
  DC_MyAppHndlr(MySubApp& app_) : 
              app(app_) // サブアプリオブジェクトを保存
            , APPHNDLR_DC(CLS_ID) // APPHNDLR_DCの初期化
            , data1(0) // メンバ変数の初期化
            {}
}

以下の定義を行う必要があります。

  • static const int CLS_ID : データ格納クラス種別を識別するための個別のIDです。

  • int get_class_id() : CLS_IDを戻すメンバ関数です。

  • コンストラクタ : サブアプリクラスオブジェクトをパラメータとします。このオブジェクトは上記の例ではappに保存しDC_MyAppHndlrからサブアプリにアクセスする目的で利用します。

  • 継承したAPPHNDLR_DCをAPPHNDLR_DC(CLS_ID)として初期化します。

サブアプリでのfriend宣言

DC_MyAppHndlrはサブアプリのメンバー変数や関数にアクセスすることも多いためfriend宣言しておきます。もちろん一般のクラス設計のようにpublicインタフェースを用意してカプセル化してもよいのですが、この場合、カプセル化の利点があまりなく記述が煩雑になるためです。

struct DC_My_AppHndlr;

//サブアプリ定義
class MySubApp : public TWE::APP_DEF, public APP_HNDLR<MySubApp> {
  ..
  
  void hndr_MyApp(event_type ev, arg_type arg = 0); // アプリハンドラ関数
  frined struct DC_MyAppHndlr; // friend宣言
}

アプリハンドラ内での利用

アプリハンドラではAPP_HNDLR::use<>()を用いてデータオブジェクトを取得します。オブジェクトの生成・破棄は暗黙に行われます。別のアプリハンドラに切り替えられたときにデータオブジェクトが破棄されます。

void MySubApp::hndr_MyApp(event_type ev, arg_type arg) {
  auto&& dc = APP_HNDLR::use<DC_My_AppHndlr>();
  
  switch(ev) {
  case EV_SETUP:
    ...
  break;
  
  case EV_LOOP:
    if (..) dc.incr();
  break;
  
  case EV_EXIT:
  break;
  }
}

上記の例では、アプリハンドラ関数の最初でdcを取得しています。初回呼び出しにオブジェクトが生成され、それ以降は生成されたオブジェクトを参照します。

あとは生成されたdcオブジェクトを操作します。

最終更新