ワーカースレッドでNavigationのFindPathやProjectPointがしたいです。

【開発環境】

  • Windows 11
  • UE 5.5.4 カスタムビルド

お世話になっております。

今、自前の経路探索(のようなこと)を行う関数があるのですが、処理負荷が高くゲームスレッドを圧迫してしまっているため、処理の大部分をワーカースレッド(TaskGraphなど)に移せないかを模索しております。

その処理の中では、UNavigationSystemV1::FindPathToActorSynchronously()やUNavigationSystemV1::ProjectPointToNavigation()を複数回使っているのですが、これらの関数はワーカースレッドで使っても問題ない(スレッドセーフ)ものでしょうか?

UNavigationSystemV1::FindPathAsync()という関数も見つけましたが、やりたいことは「ゲームスレッドで非同期FindPathをリクエストして、結果をコールバックで受け取る」ということではなく、「ワーカースレッド上での一連の処理の中で、FindPath(やProjectPoint)を複数回行いたい(結果はすぐその場で用いるだけでゲームスレッドに結果のUNavigationPathを持ち出したりはしない)」ということです。

UNavigationSystemV1::PerformAsyncQueries() ←これはワーカースレッドで動く想定の関数とお見受けしますが、ここでやられているように、ANavigationData::FindPath()(やANavigationData::ProjectPoint())であれば、ワーカースレッドで安全に使えるのでしょうか?

<br/>

ご教示よろしくお願いします。

FindPathとProjectPointはナビメッシュ自体にconst付きでアクセスするため書き換えは起こらずワーカースレッド上で動作するはずです。​

しかし同期オブジェクトなどで保護されているわけではないので、並列でナビメッシュを書き換えるような動作は不定な動作を発生させることがあることにご注意ください。

Suzuki様

ご回答ありがとうございます。

追加で何点か質問させてください。

>並列でナビメッシュを書き換えるような動作は不定な動作を発生させることがある

[Q1]. ということは、安全に使えるのは以下のようなケースに限られるという認識で正しいでしょうか?

  1. RuntimeGeneration=Static かつ WorldPartition化ナビメッシュでない。
  2. RuntimeGeneration=Static かつ WorldPartition化ナビメッシュであるが「Loading Range」をかなり大きくするなどして、空間分割ロードを使わない状態。

当方プロジェクトでは、現在まで上記1.の設定で開発を進めておりましたが、RuntimeGeneration=DMOについても使用の検討をしているところでした。(以前、こちらのスレッドでご相談させていただきました。→ [Content removed]

[Q2]. DMOによる更新自体は”ゲームスレッドでの同期処理(1フレーム内で完了する処理)”という認識で正しいでしょうか?

[Q3]. 以下は、仮説のなかの疑問になりますが、認識の誤りなどございましたらご指摘いただけますと幸いです。

---------------------------

DMOにした場合、ワーカースレッドでFindPath()を使う処理を非同期で実行中に、NavModifierによる更新がかかると問題があるため、非同期処理中はSetUpdateNavOctreeOnComponentChange(false)にしておけば 競合を防げそうではあります。しかし、そのようにして一旦防いだ後、非同期処理終了後に改めてDMO更新をかけるというようなことはできるのでしょうか?更新をブロックしたNavModifierをキャッシュしておいて、あとで手動で更新をかけるというような手段はとれるのでしょうか?(≒NavModifierに対して明示的に更新を促すような機能はありますでしょうか?)

---------------------------

ご回答よろしくお願いします。

Navmeshの更新はキューイングされ、TG_PrePhysicsより前のUNavigationSystemV1::Tickで処理されます。

Navmeshの更新処理は同期または非同期で複数Tickにまたがって処理が行われますが、データへの更新はUNavigationSystemV1::Tick内で行われます(FRecastNavMeshGenerator::ProcessTileTasksAsyncAndGetUpdatedTilesを参照してください)。このタイミングで並列してNavigationにまつわる処理が行われることは想定されていません。

プロジェクト側で実装する非同期でのクエリがタスクグラフで行われている場合いずれかのTickGroupの範囲内で実行されるはずなので、TG_PrePhysicsよりに起こる更新処理と競合することはありませんが、プールスレッドなどを用いてTickを跨いで実行するようなことを検討されていて、次のTickまで処理が継続するような場合には干渉する可能性が出てきます。

これで1~3のご質問のご回答になっていると思いますが、不明瞭な部分があればコメントいただけますと幸いです。

Suzuki様

ご回答ありがとうございます。

Navmeshの更新タイミングについて承知しました。

ワーカースレッドで行う処理は、フレームをまたがないようにゲームスレッドのOnEndFrameのタイミングでTaskをWaitさせるようにして保証しようと思います。