Давайте узнаем, как Mirror отправляет сообщения.
Каждое отправляемое вами сообщение будет обрабатываться пакетно до конца кадра, чтобы минимизировать пропускную способность и транспортные вызовы. К примеру, если вы отправляете много сообщений по 10 байтов, то мы могли бы вместо этого отправить одно размером в ~120 байт.
Что касается транспорта, то довольно удобно отправлять сообщения порциями по 1200 байт (смотреть MTU). Сообщения, размер которых превышает MTU, отправляются одним пакетом. Если быть точным, транспорт определяет размер партии, к которой стремится Mirror, с помощью Transport.GetBatchThreshold()
.
Mirror batching является двунаправленным. Это означает, что и клиент, и сервер пакуют свои сообщения и отправляют их в конце кадра.
{% hint style="success" %} Короче говоря, пакетная обработка значительно сокращает пропускную способность и повышает производительность. {% endhint %}
Для некоторых сетевых компонентов полезно точно знать, когда сообщение было отправлено удаленным устройством.
К примеру, NetworkTransform
получает позицию на сервере и затем выполняет интерполяцию между ними. Для плавной интерполяции нам нужно точно воссоздать то, что произошло на сервере. Для этого нам нужно знать, когда объект находился в определенном положении на сервере.
Очевидное решение - просто отправлять оба timestamp
и position
всё время:
[Rpc]
public void RpcPositionUpdate(float timestamp, Vector3 position)
{
// ...
}
На самом деле, именно так выглядит ранняя версия нашего нового NetworkTransform
компонента.
В приведенном выше коде мы тратим большую часть пропускной способности, потому что для каждого сообщения с позицией нам также нужно добавлять 4-ех байтное значение float
(или даже лучше, 8 байтов double
для лучшей точности). При синхронизации больших миров пропускная способность быстро увеличивалась бы.
NetworkTransform
это только один из многих компонентов. Несколько остальным тоже нужны такие TimeStamps, что еще больше увеличило бы пропускную способность.
Чтобы сделать жизнь легче, Mirror включает 8 байт double
для точности и timestamp
в каждой обработке (Batch). Вместо добавления этого в каждое сообщение, мы включаем это раз в ~1200 байтовую обработку, что едва сказывается на пропускной способности.
Для любого обработчика сообщений в Mirror, вы можете взять timestamp из обработки прибывшее из NetworkConnection.remoteTimeStamp
.
- на клиенте, все данные объектов поступают с сервера в виде сообщений/обработок. Таким образом, в любой момент времени, вы можете взять данные из
Rpc
/OnDeserialize
/OnMessage
обработчика, который был отправлен сервером черезNetworkClient.connection.remoteTimeStamp
.- Обратите внимание, что на клиенте мы не используем
connectionToServer
потому что только объекты, принадлежащие игроку, имеют подключение к серверу. Вместо этого мы используем на клиентеNetworkClient.connection
к серверу, который всегда будет гарантированно там находиться.
- Обратите внимание, что на клиенте мы не используем
- На сервере, только игрок владеющий объектом получает сообщения от подключенных игроков. Таким образом, в любой момент времени вы можете найти данные, находящиеся в
Cmd
/OnDeserialize
/OnMessage
перехватчике, отправленные клиентом посредствомconnectionToClient.remoteTimeStamp
.
{% hint style="info" %}
Timestamp Batching это уникальный подход для синхронизации мира в Mirror.
Например, Delta Snapshots в Quake идеально подходят для игр в жанре FPS, где весь мир помещается в одно сообщение о состоянии мира, но не идеальны для более крупных MMO-миров с большим количеством объектов. Или, возможно, вы работаете над многопользовательским текстовым приключением, в котором практически нет состояния мира, но все равно много сетевых сообщений.
Timestamp Batching хорошо вписывается в архитектуру Mirror. Это должно помочь вам сократить пропускную способность независимо от того, над каким типом проекта вы работаете.
{% endhint %}