2022の年末にUE::Tasksでいろいろやってみるつもりだった

UE::TasksはUE5で導入された今後TaskGraphの代替となるのマルチスレッドフレームワークです。
詳しい解説が公式ドキュメント内にすでに用意されており、説明を読むとTaskGraphより記述性に優れていてとても期待できます。

https://docs.unrealengine.com/5.1/ja/tasks-systems-in-unreal-engine/

今回はこれを使ったマルチスレッド化などについて書きたいと思います。いや思っていました。

# 旧TaskGraphと新しいTaskGraph(UE::Tasks)
両者とも同じバックエンドをつかっており、ワーカースレッドにタスクを分散させることが出来る点は同じですがその記述は大きく変わります。
スケルタルメッシュの評価関数部分をお手本にしてどのように変わるのか比較して見ましょう。

## 旧タスクグラフの記述例
旧タスクグラフでは起動するタスクに関して素性を示す宣言が必要でなかなか骨が折れます。
```c++

class FParallelAnimationEvaluationTask  //タスクの素性を宣言
{
	TWeakObjectPtr<USkeletalMeshComponent> SkeletalMeshComponent;
public:
	FParallelAnimationEvaluationTask(TWeakObjectPtr<USkeletalMeshComponent> InSkeletalMeshComponent)
		: SkeletalMeshComponent(InSkeletalMeshComponent){}

	FORCEINLINE TStatId GetStatId() const	{		RETURN_QUICK_DECLARE_CYCLE_STAT(FParallelAnimationEvaluationTask, STATGROUP_TaskGraphTasks);	}
...
中略
...
};

void USkeletalMeshUsingOldTaskGraph::DispatchParallelEvaluationTasks(float DeltaTime)
    //タスクを起動
	ParallelAnimationEvaluationTask = TGraphTask<FParallelAnimationEvaluationTask>::CreateTask().ConstructAndDispatchWhenReady(this);
    //上のタスクを前提条件としてゲームスレッド上で動くタスクを起動
	FGraphEventArray Prerequistes;
	Prerequistes.Add(ParallelAnimationEvaluationTask);
	FGraphEventRef TickCompletionEvent = TGraphTask<FParallelAnimationCompletionTask>::CreateTask(&Prerequistes).ConstructAndDispatchWhenReady(this);

    //終了同期
	if ( TickFunction )
	{
		TickFunction->GetCompletionHandle()->DontCompleteUntil(TickCompletionEvent);
	}

```
やはり記述量が多く少々手強い感じが否めません。

## 新タスクグラフの記述例(!!未検証です!!)
新しいタスクグラフではおそらく以下のように書ける **はず** です。パラメータもラムダキャプチャを通じて渡せます。
```c++
void USkeletalMeshUsingNewOne::DispatchParallelEvaluationTasks(float DeltaTime)
{
	UE::Tasks::FTask TaskOnAnyThread = UE::Tasks::Launch(UE_SOURCE_LOCATION,	[this]{	this->Evaluate();	}	);
	UE::Tasks::FTask NestedTaskOnGameThread = UE::Tasks::Launch(UE_SOURCE_LOCATION,		[this]{	this->Completion();	},
		TaskOnAnyThread/*前提条件*/, ETaskPriority::Default,	EExtendedTaskPriority::GameThreadNormalPri /*ゲームスレッドで動いてほしい*/
	);
	UE::Tasks::AddNested(NestedTaskOnGameThread);	//NestedTaskOnGameThreadが完了したらUSkeletalMeshUsingNewOne::TickComponentが終わったことになる
}
```
すごくシンプルに記述できそうな予感がしますね！しかし大変申し訳ないのですが、この書き方で正しく動作するかはいろいろあって未検証になっております。。。

## 新しいタスクグラフを試したい人生だった
5.1の時点ではデフォルトではまだ旧タスクグラフによる動作をおこないます。UE::Tasksはエンジン内の一部で使われるだけにとどまっています。

実は `TASKGRAPH_NEW_FRONTEND` というマクロをGlobalDefinitionに追加するか、TaskPrivate.hにある宣言を直接書き換えることによって、UE::Tasksによって置き換えられた「新タスクグラフ」を試すことができたしります。
**今回はこれを試していきたかったのですが、5.1.0リリースに含まれる実装では新タスクグラフを有効化すると起動時にクラッシュしてしまうため確認することができませんでした。修正を試みたのですが力及ばず時間切れになってしまいました。無念・・・**

`TASKGRAPH_NEW_FRONTEND`を有効化できないと、タスクをゲームスレッドなどのネームドスレッドに割り当てることが出来ないため、ゲームタイトルの進行に必要な処理をマルチスレッド化するための機能としての実用性を見出すのは難しいと言わざるを得ません。将来のバージョンで正しく動作するようになったら是非リベンジしたいと思っています。

※ゲームスレッドで特定の処理を走らせたい場合はUE::Tasksで動作しているタスクから旧タスクグラフの記法である`TGraphTask<F****Task>::CreateTask(...)` のような形でタスクを起動してゲームスレッドに乗せるか、同期待ち用TickFunctionを追加して`UE::Tasks::FTask::Wait`でタスク終了を待つ必要があります。
前者は前提条件(Prerequistes)にUE::Tasksを指定できずさらにTickGroupとの同期が困難で、後者は分散されたタスクが重い場合にWaitで余計な待ち時間がかかる可能性があります。


# Task Graph Insights (Unreal InsightsのTaskチャンネル)
ちょっと悲しい結果におわってしまったので、今後マルチスレッド化の強い味方になってくれるUnreal InsightsのTaskチャンネルについて紹介させてください。

https://docs.unrealengine.com/5.1/ja/task-graph-insights-in-unreal-engine-5/

この機能はマルチスレッド動作しているタスクの依存関係をUnrealInsightsのタイミングビュー上で矢印として描画する機能です！

例えばPIEでの動作状態を確認したい場合は、PIEを起動してからコンソールコマンドウィンドウを呼び出し、
```
stat namedevents
```
で詳細イベントを有効化してからtaskチャンネルを有効化してトレースを開始します。
```
Trace.File default,task
```
十分なデータが取れたら
```
Trace.Stop
```
で終了です。

ファイルが書き出されたらメニューの`Tools>Run Unreal Insights`でUnreal Insightsを起動して、utraceファイルをUnrealInsightsにドラッグアンドドロップ！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/9b96184f-35a5-8a2c-db39-2873f7a056fb.png" width=600>
起動したらタイミングビュー上のタスク同士の関連があるバーを選択すると関係するタスク間の依存関係が矢印として描画されます。
想定していない同期待ちの調査や、同時に処理されてはいけないタスクの確認などの強い味方になってくれること間違いなしです！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/df3dd7c6-5716-58a8-5379-eb31641c3694.png" width=600>