Netcode for GameObjectsの通信量過剰時の挙動と対策

この記事は、[KLab Engineer Advent Calendar 2022](https://qiita.com/advent-calendar/2022/klab) の4日目の記事です。
こんにちは。KLabでエンジニアをしている @tsune2ne です。

最近はNetcode for GameObjectsを中心に技術記事を書いています。

https://qiita.com/tsune2ne/items/faeb0b274b30c4ae7196

https://qiita.com/tsune2ne/items/29f1534a623a67821c30

今回は通信を投げまくったときにどうなるか・どうするべきかを調べました。

# 本題

同期通信ではRPCを送って他端末に同じイベントを起こしたり
変数をレプリケーションすることで他端末で同じ状態にしたりします。
ですが同期通信の量を過剰に増やした場合、どういう挙動になるかはライブラリの実装によります。

今回は Netcode for GameObjects で実際にサンプルを作りながらどういう挙動になるかを見ていきます。

## バージョン情報
Unity : 2021.3.11f1
Netcode for GameObjects : 1.0.2


# 通常サンプル
上下左右円形に移動するCubeを用意しました。

![builds_1x1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/78b9b6a7-d6c6-1266-6d5f-624c13865592.gif)

この縦移動をレプリケーション、横移動をRPCでLateUpdateで同期します。

### RPC:100個,レプリケーション:100個のVector3を同期
![builds_10x10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/b7004b54-11b4-2587-71b5-8aa0e231fecf.gif)

問題なく動きました。

### RPC:900個,レプリケーション:900個のVector3を同期
![builds_30x30.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/fcb45d9d-b5f0-b60f-1b0f-06133fc7c018.gif)

とても重いです。どんどん同期が遅れていきます。
自動的にパケットを減らして次のフレームを軽くするなどの処理は入ってなさそうに見えます。

### RPC:2500個,レプリケーション2500個のVector3を同期

![builds_50x50.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/a596addb-1a78-d008-4c06-0be55da387cb.gif)

Hostはカクつきながらも動きますが、ClientはCubeを表示することさえできませんでした。
また一定時間経つと自動でNetworkMangerがShutdownされる挙動をみせています。

### 実験結果
どうもNetcode for GameObjectsにはRPC/レプリケーションが増えすぎた場合の自動制御はまだ入っていないようです。
同期過多のままPJを進めると後半になって同期が始まらない、同期が遅れたまま追いついてこないなどの問題が起こるので注意しましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/22d508d3-81fe-8f72-a117-c92053838232.png" alt="RPCオーバーフローの図.png" width="500">

# 非信頼性RPCサンプル
さて、デフォルトの設定では通信量が1フレームの閾値を超えると次のフレームに持ち越されるのがサンプル１でわかりました。
じゃあ信頼性の低い通信を捨てて同期が追い付くように調整してみましょう。

信頼性の設定には `RpcDelivery.Unreliable` を利用します

```c#
[ClientRpc(Delivery = RpcDelivery.Unreliable)]
void UpdateRightClientRpc(Vector3 right) { /*...*/ }
```

これは通信の信頼性を下げるための設定です。
Unreliableでは専用のNetworkPipelineで処理されており、1回の通信量が大きく制限されています。

https://docs-multiplayer.unity3d.com/netcode/current/advanced-topics/message-system/reliability/index.html

![builds_30x30_unreliable.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/e5278d32-7535-ebf4-96da-7ae474576a06.gif)

縦軸移動のRPCが捨てられて、横軸移動のレプリケーションだけ適用されるようになりました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/592ac305-e1fb-e848-5f49-eb6f439b86fe.png" alt="RPCオーバーフローの図_unreliable.png" width="500">


# まとめ
Netcode for GameObjectsでは通信が量が過剰になった場合の自動制限処理は入っていなさそうです。
これはRPCがデフォルトで `信頼性あり` で通信しているためです。

PJの後半で一気にチューニングしようと考えていると、
PJの途中で通信遅延が追い付かなくなる・通信が勝手に切れるなど
大きなしっぺ返しがくるので気をつけましょう。

<b>・開発初期から通信データは最小限にする</b>
<b>・信頼性の設定をしてオーバーフローしても最適化される処理を入れる</b>

同期データ量が最小になる努力はこれからも必要そうです。

# おまけ
## ソースコードを読んでみる
Netcode for GameObjectsのコードは公開されています

https://github.com/Unity-Technologies/com.unity.netcode.gameobjects

RPCもレプリケーションも
・NetworkBehaviourで同期命令を取得
・FastBufferにまとめる
・MessagingSystemでキュー管理
・NetworkTransport(UnityTransport)で送信
という流れです。多分。

息まいてコード読み始めたんですが
ぱっと見ではUnityTransport.Sendのqueue.PushMessage(payload)でパケット量を制限してるようには見えますが
ここを調整したら優先度を決められるわけではなさそうです

<!--
## RPCメモ
SendMessageでRPCを送ってる？
NetworkBehaviour.__endSendServerRpc
└NetworkManager.SendMessage
└MessagingSystem.SendMessage
└MessagingSystem.SendPreSerializedMessage
└m_SendQueuesに積まれる
└m_MessageSender.Send
└NetworkManager.Send
└UnityTransport.Send
└queue.PushMessage(payload)でキャパチェック？

## レプリケーション(NetworkVariable)メモ
└NetworkBehaviour.InitializeVariables └NetworkVariableFields/m_DeliveryMappedNetworkVariableIndices
└変更があれば shouldSend=true 
└NetworkManager.SendMessage
└NetworkVariableDeltaMessage / RpcMessage
└MessagingSystem.SendMessage
└MessagingSystem.SendPreSerializedMessage
└m_SendQueuesに積まれる
└m_MessageSender.Send
└NetworkManager.Send
└UnityTransport.Send
└queue.PushMessage(payload)でキャパチェック？
-->

## UnrealEngine4ではどうなっているか

https://www.docswell.com/s/EpicGamesJapan/5WXDDZ-ue4-multiplayer-online-deep-dive-1-byking-ue4dd#p1

通信量が増えてくるとRPCを優先し、レプリケーションはあえて送信しないようになるようです。
非常に参考になるります。

# 参考資料

https://docs-multiplayer.unity3d.com/netcode/current/advanced-topics/ways-synchronize