【Unity】ScenarioFlowによるシナリオ実装#2-2(TaskFlowの構造)
はじめに
こんにちは.伊都アキラです.
前回の記事では,TaskFlowのセットアップを行い,非同期のScenarioMethod(ScenarioTask)を呼び出しました.そして,ScenarioTaskはキャンセルすることができることも確認しました.
今回の記事では,TaskFlowで提供されているインターフェース及びクラスの関係について解説していきます.
TaskFlowの構造
TaskFlowを利用するにあたって,重要なのはIScenarioBookReaderインターフェースと,ICancellationDecoderインターフェースです.TaskFlowを使うために,これら2つのインターフェースをそれぞれ実装するインスタンスを取得することが目標となります.
ここからはそのために,どのクラスを使えば良いか,それぞれのコンストラクタにどのようなクラスを渡せば良いかを説明していきます.
また,今回の解説ではクラス図をいくつか掲載しています.こちらから全体図を閲覧できるので,図が小さくて見にくいという方はぜひご覧ください.
IScenarioBookReader周辺の依存関係
IScenarioBookReader周辺の依存関係
上に示すのが,IScenarioBookReader
周辺の依存関係についてまとめたクラス図になります.
順にクラス及びインターフェースを見ていきましょう.
IScenarioBookReaderインターフェース
ScenarioBookを受け取り,シナリオ進行を管理するインターフェースです.メンバのUniTask ReadScenarioBookAsync(IScenarioBook, CancellationToken)
にIScenarioBook
インターフェースを渡すことで,自動的にScenarioBook中のScenarioMethodを実行してくれます.
ScenarioBookReaderクラス
IScenarioBookReader
インターフェースを実装するクラスです.また,他にはScenarioBook中の任意のラベルが付加された場所を開くための,ILabelOpenerインターフェースを実装しています.
ソースファイルにシナリオを記述する際はScenarioMethodにラベルを付加し,IScenarioBook
のOpenLabel
によってその場所を開くことができましたが,このインターフェースを利用することで,シナリオ進行中に柔軟にラベルを行き来することができるようになります.
コンストラクタには,IScenarioTaskExecuterインターフェースを要求します.
IScenarioTaskExecuterインターフェース
ScenarioBook中のScenarioMethodを実行する際,ScenarioMethodが同期処理であればそのまま実行するだけです.しかし,ScenarioMethodの返り値がUniTask,つまり非同期処理である場合に,どのように実行・キャンセルを行うかを管理する必要があります.
このインターフェースは,そのような責務を負っています.
UniTask ExecuteScenarioTaskAsync(UniTask, CancellationToken)
にScenarioTaskを渡すことによって,複数のScenarioTaskの並列実行,キャンセル処理など,柔軟な実行が可能になります.
ScenarioTaskExecuterTokenCodeDecorator
IScenarioTaskExecuter
インターフェースを実装しています.
クラス名にTokenCodeという名前が入っていますが,これはどのようにScenarioTaskを実行するかを指定するためのものです.
前回,ScenarioTaskの呼び出しのために,ソースファイルは以下のように書いていました.
前回のソースファイル
どちらのScenarioTaskも,3つ目のパラメータがCancellationToken
に対応していますが,ここではstdと記述されています.後の記事で詳しく解説しますが,これは通常通りにScenarioTaskを実行するという意味になります.
すなわち,ScenarioTaskのパラメータの内,CancellationToken
に対応するものをトークンコードと呼んでいるのであり,前回作成したCancellationToken
用のDecoderがそれを解釈することによって,様々な方法でScenarioTaskが実行されるわけです.
コンストラクタでは,INextProgressorインターフェースとIScenarioTaskExecuterインターフェース,そしてITokenCodeGetterインターフェースを要求します.
INextProgressor
は前回に解説した通り,「次のシナリオへ進む」タイミングを調整するためのものですが,IScenarioTaskExecuter
を要求するのは,あらゆるトークンコードに対応するためです.
TaskFlowで使用できる,"std"を含めた特殊なトークンコードは限られた数しかありません.それら以外のトークンコードをパラメータとして渡した場合,ScenarioTaskをどう実行するかはコンストラクタに渡した他のIScenarioTaskExecuterに委ねられます.基本的には下のScenarioTaskExecuterを,機能を拡張する場合には別のクラスを渡すことになるでしょう.
最後にITokenCodeGetter
ですが,これはScenarioTaskにどのようなトークンコードが指定されたかを知るためのものです.
ScenarioTaskExecuter
IScenarioTaskExecuter
インターフェースを実装しています.
前述のScenarioTaskExecuterTokenCodeDecorator
と同様に,ScenarioTaskの実行の仕方を管理している点は同様です.ただし,あらゆるトークンコードが許容されるという点で異なります.
このクラスでは,すべてのScenarioTaskは指定のトークンコードに紐づけられて,ディクショナリで保管されます.
そして,このクラスが実装しているIScenarioTaskAccepter
インターフェースのメンバであるUniTask AcceptScenarioTaskAsync(string)
により,保管されたScenarioTaskの終了を待つことができます.
例として,トークンコードに"TaskA"を渡したScenarioTaskを実行した場合,AcceptScenarioTask("TaskA")
を呼び出すことで,そのScenarioTaskを取得することができます.後はそれをawait
すれば良いわけですね.
コンストラクタには,ITokenCodeGetterインターフェースを持ちます.
ICancellationTokenDecoder周辺の依存関係
ICancellationTokenDecoder周辺の依存関係
上に,ITokenCodeDecoder
周辺の依存関係をまとめたクラス図を示しています.
IScenarioBookReader
同様,順にクラスとインターフェースを見ていきましょう.
ICancellationTokenDecoderインターフェース
CancellationToken用のDecoderを実装するためのインターフェースです.
非同期処理のキャンセルには,CancellationToken
を使用しますから,ScenarioTaskの引数としてもその型の使用が欠かせません.TaskFlow自体は対応するDecoderを提供しませんが,このインターフェースを実装するクラスを提供しており,実質的にDecoderとして求められる処理がそこに集約しています.
メンバとしては,文字列をCancellationToken
に変換するためのCancellationToken Decode(string)
を持ちます.
CancellationTokenDecoderTokenCodeDecoratorクラス
ICancellationTokenDecoder
インターフェースを実装しています.
IScenarioTaskExecuter
と同じように,「どのようにScenarioTaskを実行するか」を管理するクラスですが,このクラスは特にキャンセル処理の方法を管理します.
そして,クラス名にTokenCodeとあるように,このクラスではScenarioTaskに渡されたトークンコードによって好きなキャンセル処理の仕様を選ぶことができます.例えば,前回使用していたトークンコード,stdの場合は単にキャンセル可能となりますが,他のトークンコードではキャンセル不可となるものもあります.
前回のソースファイル
コンストラクタにはICancellationTokenDecoderインターフェースとICancellationProgressor,そしてITokenCodeSetterを要求しています.
まず,ICancellationTokenDecoder
について,このクラスにおいて定義されている特別なトークンコード以外がScenarioTaskに渡された場合は,ここでコンストラクタに渡された他のICancellationTokenDecoderに処理が委ねられます.基本的には下のCancellationTokenDecoderクラスを,機能を拡張したい場合は別のクラスを渡すことになるでしょう.
そして,ICancellationProgressor
については前回に解説した通り,ScenarioTaskをキャンセルする方法を提供するものです.
最後に,ITokenCodeSetter
は,ScenarioTaskに指定されたトークンコードを伝えるためのものです.ソースファイルで指定されたトークンコードはDecoderに渡され,このクラスに渡され,そしてITokenCodeSetter
とITokenCodeGetter
を通してScenarioTaskExecuter
らに渡されるわけです.
CancellationTokenDecoderクラス
ICancellationTokenDecoder
インターフェースを実装しています.
このクラスでは,CancellationTokenDecoderTokenCodeDecorator
クラスとは異なり,あらゆるトークンコードをCancellationToken
に変換することが可能です.
同様,ScenarioTaskに渡されたトークンコードとCancellationToken
を生成するためのCancellationTokenSource
が紐づけられ,後から呼び出し時に使用したトークンコードによってScenarioTaskのキャンセルが可能になります.
このクラスにおけるScenarioTaskのキャンセルには,このクラスが実装するICancellation
TokenCancelerインターフェースのメンバ,void Cancel(string) を利用します.例えば,トークンコード"TaskA"でScenarioTaskを呼び出した場合,Cancel("TaskA")
でそのScenarioTaskをキャンセルすることができます.
コンストラクタでは,ITokenCodeSetterインターフェースを要求します.
CancellationProgressorTokenCodeDecoratorクラス
ICancellationProgressor
インターフェースを実装しています.
このクラスは,ScenarioTaskのキャンセル処理の管理を行います.その方法は,後の記事で解説するトークンコードによって異なります.
コンストラクタではICancellationProgressorインターフェースを要求しており,TaskFlowの外部で実装する必要があります.実際のキャンセルを引き起こすトリガーをどのようなものにするのかは,それに任せることになります.
IDisposableインターフェース
CancellationTokenDecoder
クラス及びCancellationTokenDecoderTokenCodeDecorator
クラスは,IDisposable
インターフェースを実装するCancellationTokenSource
を利用します.
そのため,これら2つのインターフェースもIDisposable
インターフェースを実装しており,クラスを使い終わったらDispose
を呼び出す必要があります(呼び出しを忘れても,まず問題は起きませんが).
TokenCodeHolder
TokenCodeHolder
これまでに出てきたITokenCodeGetter
とITokenCodeSetter
についてですが,これらを実装するクラスをTaskFlowはTokenCodeHolderとして提供しています.
今までに出てきたクラスでITokenCodeGetter
やITokenCdeSetter
を要求するものについてはこのクラスのインスタンスを渡してやれば良いわけですが,どのクラスにも共通のインスタンスを渡さなければならないことに注意しましょう.インスタンスを複数作成してそれぞれを別のクラスに渡した場合,トークンコードの伝達が適切に行われなくなってしまいます.
良い例
//インスタンスは一つだけ作る
TokenCodeHolder tokenCodeHolder = new TokenCodeHolder();
ScenarioTaskExecuter scenarioTaskExecuter = new ScenarioTaskExecuter(tokenCodeHolder);
CancellationTokenDecoder cancellationTokenDecoder = new CancellationTokenDecoder(tokenCodeHolder);
悪い例
//別々のインスタンスを渡している
ScenarioTaskExecuter scenarioTaskExecuter = new ScenarioTaskExecuter(new TokenCodeHolder());
CancellationTokenDecoder cancellationTokenDecoder = new CancellationTokenDecoder(new TokenCodeHolder());
Progressorについて
ScenarioTaskのシナリオ進行及びキャンセルのタイミングを管理するためのINextProgressor
とICancellationProgressor
についてですが,TaskFlowがデフォルトで提供するクラスはありません(CancellationProgressorTokenCodeDecorator
はあくまで補助目的).
しかし,今後に行う予定のサンプルゲームの作成では,様々なProgressorを実装します.そこで,Progressorの実装の容易さと,拡張性の高さ,表現力の高さをお分かりいただけると思います.
サンプルゲームでは,例えば,「キー入力」「ボタンの押下」「オート進行」「スキップ進行」などをトリガーとするProgressorを実装しています.
おわりに
今回の記事では,TaskFlowが提供するインターフェース及びクラスの構造,各役割について解説しました.最後に,前回に実装したTaskFlowのセットアップの一例を再掲しておきます.
TokenCodeHolder tokenCodeHolder = new TokenCodeHolder();
SpaceKeyProgressor spaceKeyProgressor = new SpaceKeyProgressor();
IScenarioTaskExecuter scenarioTaskExecuter = new ScenarioTaskExecuterTokenCodeDecorator(
new ScenarioTaskExecuter(tokenCodeHolder),
spaceKeyProgressor,
tokenCodeHolder);
IScenarioBookReader scenarioBookReader = new ScenarioBookReader(scenarioTaskExecuter);
ICancellationTokenDecoder cancellationTokenDecoder = new CancellationTokenDecoderTokenCodeDecorator(
new CancellationTokenDecoder(tokenCodeHolder),
new CancellationProgressorTokenCodeDecorator(spaceKeyProgressor, tokenCodeHolder),
tokenCodeHolder);
クラス図で各クラスとインターフェースの関係を理解し,各役割を知ることで,このコードの意味が理解しやすくなったと思います.
次回は,TaskFlowで使用できる特別なトークンコードについて,その種類と,各トークンコードによりScenarioTaskがどのように実行されるかを解説していきます.
最後までお読みいただき,ありがとうございました.
Discussion