Let's Enjoy Unreal Engine

Unreal Engineを使って遊んでみましょう

UE4 BPで使える非同期タスクやマルチスレッドのプラグインを比較してみる

ブループリントでは非同期処理こそ扱えるものの、マルチスレッドな処理を通常扱うことはできません。マルチスレッドな処理は扱いが非常に難しく、バグが発生すると手が付けられないほどに問題が複雑するからだと思います。

しかしブループリントではどうしても処理が重くなってしまい、負荷をバックグラウンドに逃がしたいというケースは少なからずあります。エンジン標準機能にマルチスレッドの呼び出しは用意されておりませんが、マーケットプレイスには多彩なブループリント用マルチスレッドプラグインがありますので、今回は一通りを触ってみて、どのように使えるかを比較検証してみました。

今回検証したプラグインは以下の4つとなります。

Actions Extension:Piperift:Code Plugins - UE4 マーケットプレイス

BPThreads:Mashmashu Studio:Code Plugins - UE4 マーケットプレイス

Call Function Async:Alexandr Vasilyev:Code Plugins - UE4 マーケットプレイス

Multi Task Pro:Yaki Studios:Code Plugins - UE4 マーケットプレイス

Actions Extensionのみ無料で、残りは有料となります。Multi Task Proのみ、無料版があり、こちらは非同期処理のみで、機能はMulti Task Proの機能制限版となるので比較には入れていません。試してみたいという方は以下からどうぞ。

Multi Task:Yaki Studios:Code Plugins - UE4 マーケットプレイス


f:id:alwei:20200725184659p:plain

ちなみに今回検証したプラグインは全て最新版のUE4.25.2まで対応しています。最新バージョンでもしっかりと動作することを確認済みです。

それでは早速比較した結果を見ていきましょう。

Actions Extension

まずはActions Extensionです。

こちらは無料のプラグインとなっており、誰でもすぐに使用可能なので最初に試してみるプラグインとしても非常に良いと思います。
Actions Extensionは他のプラグインと比較してみても非常にシンプルな設計となっており、使うだけならなんと1ノードだけで使えてしまいます。

f:id:alwei:20200725191348p:plain

Actionという独自のBPを作成し、その内部で非同期処理をしたい部分を作成します。

Actionは引数を渡すこともできますし、イベントディスパッチャーを定義することでコールバックシステムにより非同期処理した情報を後から受け取ることも可能です。

が、Action Extensionはシンプル故にスレッドを作成することができません。あくまでもゲームスレッド内での処理となるので、厳密に処理をバックグラウンドに逃がすことはできません。

他のプラグインとの最大の違いは、ビヘイビアツリータスクやゲームプレイデバッガーでのデバッグに対応しているということです。AIの作成にも向いていると言えます。


Actions Extensionのまとめ

・無料でかつ非常にシンプルに非同期タスクを作成可能
・イベントディスパッチャーを定義するとコールバックを受け取ることが可能
・非同期タスクは呼び出し元と別のBP上に作成する必要あり
・ビヘイビアツリータスクやゲームプレイデバッガーに対応
・BPだけでなく、C++からも利用可能なAPI
・スレッドは作成できないので、あくまでゲームスレッド内での非同期処理

BPThreads

次はBPThreadsです。こちらは名前にThreadsとついてるだけあって、スレッドを作成可能です。

BPThreadsは既存のスレッドシステムをそのままブループリントで扱えるようなシステムです。

f:id:alwei:20200725195019p:plain

Threadコンポーネントを追加するか、Create Threadでスレッドを作成し、そのイベントとして登録された処理がスレッド内で処理することができます。スレッドはPriorityやAuto Start、Auto Loopの指定が可能で、Auto Loopを指定するとSleep Time In Secondsの指定時間でSleepした後に再度処理が実行されます。

こちらも非常に簡単に使用可能となっており、お手軽にスレッドを作成可能ですが、作り方を間違えると簡単にゲームをフリーズさせたりすることもあります。また同期オブジェクトとして、Mutexが扱えるので、共有オブジェクトはロックとアンロックを使ってスレッド間でも同期させることが可能です。共有オブジェクトとして、スレッドセーフなBooleanやWeak Object PointerやGarbage Collecotr Guardなどの機能もあります。

更にBPThreadsのみにある機能として、Asyncコンポーネントというものが用意されており、このコンポーネントを使うことでAsync Line Traceなどの非同期用に用意されたノードも利用可能です。

f:id:alwei:20200725195832p:plain

非常に多彩な機能がありながらも、BPThreadsはお手軽にスレッドを扱うことができますが、スレッド処理終了後にコールバックを返すことができません。
別の処理に結果を渡したい時などには利用できません。あくまでもスレッドを扱うことに特化している印象です。


BPThreadsのまとめ

・マルチスレッド処理が非常にお手軽に利用可能
・スレッドのPriorityやAuto Start、Auto Loopなどのオプションが豊富
・Mutexなどの同期オブジェクトを使うことで、スレッド間でオブジェクト同期可能
・作成するスレッドの指定はできない
・スレッドはBPを分ける必要がなく、どこからでも利用可能
・Asyncコンポーネントを使うことで、Async Line Traceといった機能を利用可能
・スレッド終了時のコールバックを作成することはできない

Call Function Async

次はCall Function Asyncです。

Call Function Asyncも簡単にスレッドを作成しながら、指定した名前の関数をスレッド上で実行することができます。

f:id:alwei:20200726103115p:plain

BPThreadsとは違い、こちらは関数上でスレッドを実行した後に、その結果を必ずOnExecuteFunctionイベントとしてコールバックを返すことができます。
Resultには計算した結果を格納することができるので、スレッド上での結果を受け取った後に処理を進めることができます。

それ以外の機能としては非常にシンプルで、スレッドの指定やプライオリティなどのオプションもありません。
またスレッド実行中は自動的に同期オブジェクトのMutexによるロックがかかるようになっているようです。
スレッド実行中に長時間のロックがかかるとスレッドのデッドロックが発生する可能性もあるので扱いが難しいかもしれません。
以上のことから、比較的小さい処理をスレッドで回したい時に向いていると言えます。


Call Function Asyncのまとめ

・マルチスレッド処理をお手軽に利用可能
・スレッドの処理結果をコールバックとして受け取り、結果を再利用可能
・作成するスレッドの指定はできない
・スレッドはBPを分ける必要がなく、どこからでも利用可能
・スレッド実行中は自動的にMutexのロックとアンロックがかかる
・長時間ロックをかけるとデッドロックする可能性に注意する必要あり

Multi Task Pro

最後はMulti Task Proです。

このプラグインだけは無料版のMulti Taskというものが存在しており、1種類のみの非同期タスクノードが利用可能です。

Multi Task Proは他のプラグインと比較すると、圧倒的に出来ることが多いです。非同期タスクやマルチスレッドの両方を使うことが可能で、最も柔軟性が高いと言えます。

f:id:alwei:20200726112552p:plain

Do Async TaskとDo Loop 1D~3Dはゲームスレッド内の非同期タスクとして、非同期処理やループの分散化が可能です。重たい処理やループ回数が多い際にはうまく分散化することでヒッチを回避することが可能です。Do Loopは1次元から3次元まで対応しているので、複雑なループ処理にも対応が可能です。

これらはあくまでも非同期タスクとしてゲームスレッド内で実行されるものなので、極端に重たい処理には向いていません。が、Tickで処理するよりは遥かに処理の軽量化が可能です。

f:id:alwei:20200726115105p:plain

Do Thread Taskでは名前の通りスレッド上での処理が可能です。この際にExecution Typeを指定することで、厳密ではありませんがどのようなスレッドで処理するかを決めることができます。用途に応じてスレッドを使い分けることが可能です。

また、Do Loopのスレッド版である、ParallelFor1D~3Dが存在しており、こちらはマルチスレッドでループを分散させることができますが、現在のバージョンではまだ開発中なのかオミットされています。(コードだけは残っている)

他にもHierarchicalInstancedStaticMeshをスレッド上でスポーン可能なDo Spawn Instancesといったノードも用意されています。

f:id:alwei:20200726120928p:plain

更にMulti Task Proにはスレッド間同期で便利なQueue構造体が用意されており、これらはロックフリーアルゴリズムによって全てスレッドセーフになっているので、プロデューサースレッド(データ書込スレッド)でEnqueueを実行し、コンシューマースレッド(データ読取スレッド)でDequeueを行うことで安全に別スレッド間でデータ同期が可能となっています。

他にも完全にスレッドセーフなThread Safe IntegerとThread Safe Boolというよりプリミティブな変数型も用意されているので、非同期タスクやマルチスレッド上で変数情報が同期しやすいようになっています。更にLine Traceなどのレイキャスト系もスレッドセーフに対応したノードが一通り用意されています。それらとは別に同期用オブジェクトのMutexも用意されており、自作の共有オブジェクトである場合でもスレッドセーフが維持できるようになっています。

細かい機能としては、UE4にはブループリントで一定数以上のループを検知すると処理が停止するというセーフティが入っていますが、これが多くの処理を行っている際に邪魔になってしまうことがあるので、その停止処理カウンターをリセットするReset Runawayという機能も用意されています。

これだけ聞くとMulti Task Proが他のプラグインと比較して全て勝っているように聞こえてきますが、唯一の難点として現在進行形で頻繁な開発が行われており、アップデートが頻繁に行われています。それ故に開発途中の機能があったり、多少の不安定な部分も見受けられるという点もあります。


Multi Task Proのまとめ

・非同期タスク、マルチスレッドの両方をお手軽に利用可能
・BPが最も苦手とするループ処理を分散可能なDo Loopノードがある
・非同期タスク、マルチスレッド共に終了検知可能
・作成するスレッドの種類を指定可能
・BPを分けることなく、1つのノードグラフ内で完結が可能
・スレッドセーフなQueueとIntegerとBoolとLine Trace系がある
・BPのループ停止セーフティをリセットするReset Runawayがある
・頻繁にプラグインの更新があるため、開発途中の機能や不安定な点がある

総評

それぞれのプラグインにメリット、デメリットがありますが簡単な感想を述べると…


・Actions Extension → 非同期タスクだけが欲しい場合や、ビヘイビアツリータスクでも利用したい場合によさそう。無料は強い。
・BPThreads → 純粋にスレッドだけが欲しい場合に有効。Async Line Traceが使えるのはBPThreadsだけ。
・Call Function Async → シンプルなスレッドとコールバックが欲しいのならあり。ちょっと機能が少なく、お値段が少し高め。
・Multi Task Pro → 他のプラグインの機能をほぼ持ちながら出来ることが非常に多彩。絶賛積極開発中なのでアップデートに期待。


個人的にはMulti Task Proをお勧めしたいところですが、他と比べるとお値段が高めなので、まずは無料版を使ってみるのがありかもしれません。非同期タスクやマルチスレッドはBPの最大の欠点である高負荷の処理をバックグラウンドに逃がすことが出来るため、あくまでもBPでロジックを組みたいという人には非常に強い味方となってくれるはずです。

マルチスレッド処理の共通の注意点としては、アクターのスポーン、削除、タイマー処理、Dynamic Material Instanceによる変更などはゲームスレッド以外では使えないなどの制限も多いです。スレッド上でおかしい動作があれば基本的に対応していないと思った方がよいでしょう。

最後にBPで非同期タスクやマルチスレッドを組む利点として、C++コードでスレッド処理などを組むよりも遥かにシンプルでわかりやすく、直感的に組めるのが利点と言えるでしょう。ただし、C++でも同様ですがデバッグは非常に厄介です。その点にだけ注意してぜひ使ってみましょう!


おまけ