タスクとは


タスクとはどのようなものなのか、についてのお話です。

タスク / ファイル管理 / 戻る


タスク

「タスク」とは擬似的に並列処理を行うスレッドのような概念です。 ゲーム業界独自の技術で、ナムコのギャラクシアンで開発され、 そのリバースエンジニアリングによって広がっていったそうです。
 
その実体は、実行関数のアドレスを含む汎用ワークです。 これら汎用ワーク (タスク) の集合がゲームシステムに保持されており、 毎フレームごとに各汎用ワークの持つ実行関数が実行されていきます。 実行関数は基本的に自分の汎用ワークのデータを扱います。 タスクを使用したプログラムのメインルーチンは、タスク自体を処理する システムと、フレームレート処理などの根本的なシステムのみで構成され、 他のゲーム部分の処理を行うコードは全てタスクとして実装されます。
 
使い方としては、ゲームの部品となる各要素 (実行レベルで考えるので、 モジュールよりも細かく) をタスクとして実装します。 例えば、プレイヤーキャラクターが一つのタスクとなり、そのワークには プレイヤーキャラクターの座標などの情報が含まれ、 実行関数にはコントローラーからの入力を元にプレイヤーキャラクターを 動かす処理を行う関数が指定されます。
 
オブジェクト指向で考えるならば、タスクの汎用ワークがオブジェクトの インスタンス、実行関数のアドレスは毎フレーム実行されることが 保証されている特定のメソッド、と言ったところでしょうか (?)。
 
ゲームのほぼ全ての処理をタスクで実装することで、柔軟性の高い、 多人数での開発に向いたコーディングが可能になります。 処理順序を意識できる点も、ゲーム開発には向いています。
 
タスクシステムの実装は、会社・プロジェクト・個人ごとに、 考え方や捕らえ方が若干異なるため、様々な亜種や独自の機能拡張が なされています。プログラマ一人一人が自分用の実装を 持っていることもあり、方言が最も多い機能の一つではないかと思います。 以下にその拡張機能 (ほとんど基本機能といえるものもあります) の 概要を書きます。
 
タスク情報
 
実行関数へのポインタ以外にも、タスクごとにデバッグに便利なように 名前などの情報を付加することがあります。このようなタスクごとの情報を 管理するテーブルを TCB (Task Controll Block) と呼びます。
 
タスクワーク
 
TCB 以外に、各タスクが自由に使用できる任意のサイズのワーク領域を 確保しておくことがあります。実行関数の引数にそのタスクワークへの ポインタを渡すと何かと便利です。
 
タスクチェンジ
 
実行関数を差し替えて、次フレームから別の実行関数に変更すること。 タスクの状態が変化し、フレーム中の実行内容が変化するときなどに使う。 元来、タスクの基本的な機能であり、これをいかにうまく使うかがスキルの 問われるところでしたが、最近は CPU の機能向上や、通常のゲーム以外の プログラミングと同じ構造で組みたいからと、あまり使われないことも 多いようです。
 
優先度 (プライオリティ)
 
実行順序を決める値です。同じ優先度の場合には生成順に 実行されるのが慣例です。
 
親/子タスク
 
タスクどうしが木構造を構成していることが良く使われます。 この時、木構造の親子関係から親タスク・子タスクとタスクの関係で 呼びます。子タスクは親タスクより後に呼ばれることが保証され、 親タスクが破棄されたときには子タスクも破棄されるため、 依存関係のあるタスクどうしを親子関係にすることが一般的です。 例えば、エネミー (敵キャラ) の管理を行うマネージャーを親タスクに、 エネミー一体一体をその子タスクにしたりします。 タスク構造の一般的な形としては、根のすぐ下にはデバッグ、周辺機器制御、 サウンド、といった常時使われる基本機能的なタスクが配置され、シーケンス 制御の親タスクも配置されます。シーケンス制御タスクの子に文字表示、 スクリプトインタープリタ等のそのゲームでは基本的に使われているタスクと、 シーン (タイトル画面やゲーム中などの大きな区分) ごとに生成・削除される シーケンスタスク、オーバーレイ、モジュールがならび、シーケンスタスクの 子や孫にキャラクター等のゲームで主に使用されるタスクが並んでいくことに なります。
 
相対タスク
 
現在のタスクに対して相対的にプライオリティーを指定するタスクがあります。 あまり一般的ではないようです。
 
コールリンク
 
タスクチェンジが goto ならば、コールリンクは gosub に相当する処理。 あまり一般的ではないようです。
 
キルタスク
 
自ら消滅処理を行うタスクの実行関数。キルタスクにタスクチェンジすることで タスクを消滅させられます。若干トリッキーなため、あまり一般的では ないようです。
 
終了関数
 
通常、タスクの解放はその指示が与えられたときに、フラグが立ち (キルタスクにタスクチェンジされることもある)、次フレームの タスクの実行のタイミングで解放されることが一般的です。 終了関数はタスクが実際に解放されるタイミングで呼ばれる関数で、 タスクの後処理を書くのに使います。
 
遅延実行
 
実行を指定フレーム数、遅延させます。
 
スリープ
 
一時的にタスクの実行を停止します。
 
実行スキップ
 
実行を数フレームおきにしか実行しません。 全体の処理速度の重さを回避するために、間引き処理したりする時に使います。
 
検索機能
 
タスクに種別や ID を付加させて、それを元に検索できる機能です。
 
実行時間計測
 
デバッグ用にそのタスクの実行処理・描画処理にかかった時間が計測できる ようになっていると、処理速度の最適化を行うときに便利です。 ツリー構造、プライオリティ順で表示できるなどの工夫が凝らされます。
 
描画機能
 
描画に関する機能までタスクの基本機能として持っているものもあります。 大抵のタスクは描画を伴うため見通しが立ちやすくなる一方、タスク自体の 機能が煩雑化してしまう欠点もあり、一長一短といえます。
 
 
タスクシステム自体に描画機能が入っていようとなかろうと、 描画と計算処理は別々の関数で行うことが普通です。 一通りのタスクの計算処理が行われた後に、それぞれの描画関数が呼ばれます。 この時、計算処理は計算処理で、描画は描画で、優先順位を持って その順番で処理されていくことが多いです。
 
タスクシステムのデメリットとしては、関数呼び出しのオーバーヘッド、 解放タイミングに気をつけないとメモリリークを起こしやすい、 ことがあります。また、ソフトリセット時のタスク一斉消去、 ポーズ時の実行のみを飛ばす処理の実装 (特定のプライオリティのタスクの実行処理を飛ばすなどの方法で 実現します) は気をつけなければならない点です。
 
このように原始的な手法であっても、便利な機能ではありますし、 処理内容が明確に分かることなどスレッドとは異なる利点があり、 若干用いられ方も異なることから、今後ハードウェアでスレッドが サポートされてもタスクはなくなることはないでしょう。

ファイル管理

処理がタスクベースで実装されていると言うことは、一つのタスクが 長時間処理を占有するような実装はすべきではありません。 一回のタスク処理は一フレームに要する時間よりも短い (全てのタスクの処理を合わせて一フレームに要する時間以下である) 必要があるためです。
 
そのため、ファイルを読み込む処理もタスクの処理関数一回で 終了させないようにします。これを「即時復帰」と呼びます。 即時復帰で実装されているファイル読み込み関数は、読み込み 終了してなくても処理がすぐに返ります。 これは読み込みが終わっているわけではなく、裏で読み込まれているため、 その完了が確認されるまでファイル読み込みを指示したタスクは 処理を待たなければなりません。 なお、裏読み込み自体を処理するのは、ファイル管理を行うタスクが行います。
 
これに対して、一般的な読み込み終了するまで処理が帰ってこない。 全体の処理が止まる実装を「完了復帰」と呼びます。 即時復帰に比べて簡単に使えるため、画面暗転中のロードやゲームの プロトタイプ作成に完了復帰が使われることがありますが、 基本的には即時復帰のみを使って実装されたゲームのほうが 柔軟で良いと思います。

戻る