タスクシステム3

タスクチェンジ

タスクチェンジとは、実行関数ポインタの入れ替えを行うことです。
 
たとえば、シューティングゲームでは、敵の行動について、以下のパターンがあります。

  • 初期処理
  • 移動したり弾を撃ったりする
  • 爆発処理

これらの処理を関数ポインタの入れ替えを行うことにより実現します。
 

リングバッファ

フリータスクリストについてなのですが、
単なる空き領域のリストであるので、単方向のリストで問題ありません。
そして、末尾タスクの次を指すポインタが、先頭のダミータスクを指すようにします。
 
これをリストが輪のようになっていることから、リングバッファと呼びます。
 
こうすることにより、先頭のダミータスクの次を指すポインタが自分自身であれば、
空き領域がない、という判定をすることで実装が楽になります。
 

タスク生成の実装

タスク生成処理をプログラムレベルで考えると、以下のような手順となります。

  1. フリータスクリスト空きチェック(空きがなければNULLを返す)
  2. 空きがあればタスクを取り出す
  3. アクティブタスクリストに追加
  4. 実行関数の設定

本処理のパラメータは実行関数のポインタのみです。
 
ワークエリアまで設定してしまうと、チェック処理が面倒となるためです。
ワークエリアの設定は、本処理の呼び出しを行う関数で行います。(それについては次回)
 

タス削除の実装

  1. アクティブタスクリストから切り離し
  2. フリータスクリストに戻す

特に説明はいらないですね…(´∀`)
 

サンプル

以上を踏まえたサンプルです。
今まで説明していないこととしては、タスクが「自殺」できるようにしています。
こうすると、なにかと実装が楽になることが多いと思います。
 
Run関数でnextをあらかじめ保持しておいて、自殺対策(変な日本語だ…)をしています。

#include <iostream>
//#define NDEBUG
#include <assert.h>
using namespace std;

#define WORK_AREA_SIZE 256
// タスク構造体
struct TCB
{
	char work[WORK_AREA_SIZE];
	void (*EXEC)(TCB *tcb);
	TCB *prev;
	TCB *next;
};
// 実行関数ポインタをtypedefしておくと便利
typedef void (*EXEC)(TCB *tcb);

#define TCB_SIZE 100
TCB g_tcb[TCB_SIZE]; // newした方がいいかもしれません。
// タスクリスト
TCB *g_freeTCB;   // フリータスクリスト
TCB *g_activeTCB; // アクティブタスクリスト

// ============================================================================
// タスクリスト初期化
// ============================================================================
// 実行関数定義<ダミー>
void ExecuteDummy(TCB *tcb)
{
	// なにもしません。
}
// 初期化
void InitTaskList()
{
	// アクティブタスクリスト生成
	g_activeTCB = &g_tcb[0]; // [0]先頭ダミータスク
	g_activeTCB->prev = NULL;
	g_activeTCB->next = &g_tcb[1];
	g_activeTCB->EXEC = ExecuteDummy;
	g_activeTCB->next->prev = g_activeTCB; // [1]末尾ダミータスク
	g_activeTCB->next->next = NULL;
	g_activeTCB->next->EXEC = ExecuteDummy;

	// フリータスクリングバッファ
	g_freeTCB = &g_tcb[2]; // [2]先頭ダミータスク
	g_freeTCB->next = &g_tcb[3];
	TCB *tcb;
	for(int i = 3; i < TCB_SIZE - 1; i++)
	{
		tcb = &g_tcb[i];
		tcb->next = &g_tcb[i + 1];
	}
	tcb = &g_tcb[TCB_SIZE - 1]; // 末尾タスク
	tcb->next = g_freeTCB; // 先頭とつなぐ
}
// ============================================================================
// タスク生成
// @param Exec 実行関数ポインタ
// ============================================================================
TCB* CreateTCB(EXEC Exec)
{
	// フリータスクの領域チェック
	if(g_freeTCB->next == g_freeTCB) return NULL; // もう1個しか残ってないです
	// フリータスク取り出し
	TCB *tcb = g_freeTCB->next;
	g_freeTCB->next = tcb->next;
	// タスクに初期処理設定
	tcb->EXEC = Exec;
	// アクティブタスクに追加(先頭<ダミー>の次に追加)
	tcb->prev = g_activeTCB;
	tcb->next = g_activeTCB->next;
	tcb->next->prev = tcb;
	g_activeTCB->next = tcb;
	return tcb;
}

// ============================================================================
// タスク削除
// @param tcb タスク構造体
// ============================================================================
void DeleteTCB(TCB *tcb)
{
	// アクティブタスクから削除
	tcb->prev->next = tcb->next;
	tcb->next->prev = tcb->prev;
	// フリータスクに戻す(先頭に戻す)
	tcb->next = g_freeTCB->next;
	g_freeTCB->next = tcb;
}
// ============================================================================
// タスク実行
// ============================================================================
void Run()
{
	cout << "タスク実行" << endl;
	TCB *tcb = g_activeTCB;
	TCB *next = tcb->next; // 自殺対策
	while(next != NULL)
	{
		tcb = next;
		next = tcb->next;
		tcb->EXEC(tcb);
	}
}

// 実行関数定義
void Execute1(TCB *tcb)
{
	cout << "実行したよ" << endl;
	DeleteTCB(tcb); // 自殺
}

// メイン関数
void main()
{
	InitTaskList();
	CreateTCB(Execute1);
	Run();
	Run();
}

(つづく)