Client/Shared/Server, которую использует Robust. Если нет, вам следует прочитать предыдущую документацию.
SS14 — это многопользовательская игра! Это очень важный факт, и с ним, скорее всего, придётся часто сталкиваться при написании кода. Правильное продумывание этого важно для обеспечения бесперебойной работы и отсутствия потенциальных уязвимостей безопасности.
В большинстве случаев networking включает отправку сервером некоторых важных данных клиенту, чтобы клиент мог что-то с ними сделать, например, показать пользовательский интерфейс или визуальное оформление. Это называется репликацией (replication) и в SS14 в основном обрабатывается через component states.
Component States
Component state — это просто класс данных, наследуемый отComponentState. Они определяют, какие данные отправляются клиенту, и формируются на основе данных внутри компонентов.
Как игра узнаёт, когда отправлять эти данные? Очевидно, она не отправляет их постоянно — это было бы совершенно не нужно и ужасно сказывалось бы на производительности и пропускной способности. Вместо этого серверная система должна вызвать Dirty(EntityUid uid, Component component), которая помечает entity как ‘грязную’, что означает, что в следующем тике для неё будет создан и отправлен новый component state.
Существует два специальных события для помещения данных в component states и извлечения их оттуда: ComponentGetState и ComponentHandleState. GetState всегда вызывается на сервере, а HandleState вызывается на клиенте. Однако обе подписки на события могут находиться в Shared, и это всё равно будет работать как ожидается!
Автоматическая генерация Component State
Robust Toolbox поддерживает использование source generators для значительного упрощения networking component states. Это настоятельно предпочтительнее попыток делать это вручную в большинстве ситуаций. Это работает путём использования функции C# для анализа кода до его компиляции и автоматической генерации шаблонного кода. Во-первых, ваш компонент и все его сетевые поля должны находиться вContent.Shared, а класс компонента должен быть помечен [NetworkedComponent], что включает networking в первую очередь.
Чтобы использовать source generator для автоматической репликации полей, сделайте ваш класс компонента partial, аннотируйте его [AutoGenerateComponentState] и пометьте любые поля, которые вы хотите передавать по сети, с помощью [AutoNetworkedField]. Затем, когда вы пометите компонент как dirty (или когда он впервые будет добавлен к entity), он должен Just Work™️, и клиент получит все сетевые поля.
Если у вас есть код в handle state, который вызывает какую-то функцию после установки полей (например, обновление внешнего вида), измените атрибут component state на [AutoGenerateComponentState(true)], и затем вы можете подписаться по ссылке на AfterAutoHandleStateEvent и делать там нужные вещи!
Если ваше поле требует клонирования для целей предсказания (например, словарь), вы можете изменить атрибут поля на [AutoNetworkedField(true)]. Если вам нужен более сложный networking, следует использовать ручной метод.
Пример всего кода networking, необходимого для IDCardComponent, теперь из https://github.com/space-wizards-federation/space-station-14/pull/14845:
Ручная обработка Component State
Иногда приходится использовать ручной метод, если обработка сложнее, чем просто установка полей. Возьмём в качестве примера ambient sounds (хотя в данном случае это можно было бы легко сделать с помощью автогенерации):[Serializable, NetSerializable], что требуется для любого объекта, отправляемого по сети. Этот класс определяет три переменные, которые он хочет синхронизировать с клиентом: включён ли этот звук, его дальность и громкость.
Посмотрим, как этот state конструируется на сервере:
ref ComponentGetState args, а не просто ComponentGetState args. Это требуется для некоторых событий, так как они являются типами-значениями, вызываемыми ‘по ссылке’, а не просто классами, наследующими EntityEventArgs. Это сделано для повышения производительности и не является суперважным, но полезно знать, так как это может привести к ошибкам времени выполнения, которые могут сбить с толку, если вы забудете ref.
Чтобы указать state для отправки, вы просто устанавливаете поле State в событии с вашим новым component state, сконструированным из значений на серверном компоненте. Легко!
А на клиенте (технически всё ещё в shared, но этот код выполняется только на клиенте!):
ref.
Первая строка метода просто использует продвинутое C# Pattern Matching для приведения поля Current в аргументах события к нужному нам типу state, поскольку это довольно общее событие.
Затем клиент просто использует значения, содержащиеся в component state, для синхронизации своего компонента, чтобы любой клиент-специфичный код для ambient (например, воспроизведение звуков) мог выполняться так, как задумано сервером.
Пример Component Networking
В качестве высокоуровневого примера давайте посмотрим, как атмосферные венты обрабатывают свои ambient sounds.SetAmbience система ambience вызывает Dirty на entity, что говорит серверу, что данные этой entity были обновлены и клиент должен быть уведомлён об этом. Затем, в следующем тике, сервер вызывает событие ComponentGetState на венте, и создаётся и отправляется состояние ambient sound.
Как только клиент получает его (после задержки), он вызывает ComponentHandleState на венте, что затем приводит к правильному отключению ambient sound на клиенте. Аккуратно!
Network Events
Другой основной способ связи между сервером и клиентом, помимо репликации через component states, — это сетевые события (network events) и низкоуровневыеNetMessage.
Сетевые события противопоставляются локальным событиям (RaiseLocalEvent или SubscribeLocalEvent), которые являются исключительно ‘локальными’ для той стороны сети, на которой они были вызваны, тогда как сетевые события исключительно отправляются по сети. Сетевые события используют эквивалентные RaiseNetworkEvent и SubscribeNetworkEvent.
Сетевые события содержат произвольные данные, не привязанные к какому-либо компоненту или entity в частности (что означает, что они не могут быть направленными) и могут быть отправлены в любое время как клиентом, так и сервером. При обработке сетевого события, отправленного с клиента на сервер, следует проявлять осторожность и относиться к нему как к ненадёжному, так как хакеры всегда могут отправить любые данные по своему усмотрению.
NetMessage — это низкоуровневый эквивалент сетевых событий (на самом деле, сетевые события сами создают NetMessage). Вам следует избегать их использования, если вы не знаете, что делаете, поэтому я не буду их здесь рассматривать, кроме упоминания.
Пример
Давайте посмотрим на adminhelps (также называемую системой bwoink) и увидим, как она отправляет произвольные данные, не привязанные к конкретной entity, клиентам. Вот как определяется сетевое событие:NetSerializable по тем же причинам, что упоминались выше для component states. Я не буду вдаваться в подробности конкретных данных — думаю, идея понятна.
Интересная особенность этого события в том, что оно вызывается и обрабатывается как на клиенте, так и на сервере. Итак, мы рассмотрим их отдельно.
Клиент -> Сервер
Send здесь вызывается всякий раз, когда вводится текст в UI BWOINK (tm) на клиенте:
Обработка на сервере
Окей, обработчик для этого немного велик, поэтому я сокращу его до важных частей:Обработка на клиенте
Давайте вернёмся к клиенту, чтобы увидеть, что он делает с этим новым текстовым сообщением, отправленным с сервера.Potentially Visible Set (PVS)
Вы, вероятно, слышали этот термин, если заглядывали в разработку, так как это довольно важная тема. Подумайте — в этой игре чертовски много entity! И, скорее всего, многие из них постоянно вызываютDirty, и клиентов тоже будет много. Как мы решаем, какие состояния отправлять каждому клиенту? Ответ — в системе Potentially Visible Set (PVS).
Это не будет супер-низкоуровневым обзором, но в основном PVS основан на чанках и отправляет component states только тем клиентам, которые находятся в радиусе чанка от entity. Это делается по двум основным причинам:
- Это значительно снижает пропускную способность, не отправляя component states клиентам, которые даже не видят эту entity.
- Это уменьшает возможность читерства, поскольку хакеры физически не получают никаких данных об entity, слишком далёких от них.