Мегапакет: как разработчикам Factorio удалось решить проблему с мультиплеером на 200 игроков

Original author: Twinsen, Klonan
  • Translation
image

В мае этого года я участвовал в качестве игрока в MMO-мероприятии KatherineOfSky. Я заметил, что когда количество игроков достигает определённого числа, через каждые несколько минут часть из них «отваливается». К счастью для вас (но не для меня), я был одним из тех игроков, которые отключались каждый раз, даже при наличии хорошего подключения. Я воспринял это как личный вызов и начал искать причины проблемы. Спустя три недели отладки, тестирования и исправлений ошибка наконец устранена, но это путешествие было не таким уж простым.

Проблемы многопользовательских игр очень трудно отследить. Обычно они возникают в очень конкретных условиях параметров сетей и при очень специфичных состояниях игры (в данном случае — наличие более 200 игроков). И даже когда удаётся воспроизвести проблему, её невозможно должным образом отлаживать, потому что вставка контрольных точек останавливает игру, путает таймеры и обычно приводит к завершению соединения из-за превышения срока ожидания. Но благодаря упорности и замечательному инструменту под названием clumsy мне удалось выяснить, что же происходит.

Если вкратце: из-за ошибки и неполной реализации симуляции состояния задержки клиент иногда оказывался в ситуации, когда ему приходится за один такт отправлять сетевой пакет, состоящий из вводимых игроком действий выбора примерно 400 игровых сущностей (мы называем его «мегапакетом»). После этого сервер не только должен правильно получить все эти действия ввода, но и отправить их всем остальным клиентам. Если у тебя 200 клиентов, это быстро становится проблемой. Канал к серверу быстро забивается, что приводит к утере пакетов и каскаду повторно запрошенных пакетов. Откладывание действий ввода затем приводит к тому, что ещё больше клиентов начинает отправлять мегапакеты, и их лавина становится ещё сильнее. Удачливым клиентам удаётся восстановиться, все остальные «отваливаются».

image

Проблема была достаточно фундаментальной, и у меня ушло 2 недели на её устранение. Она довольно техническая, поэтому ниже я объясню сочные технические подробности. Но для начала вам нужно знать, что с версии 0.17.54, выпущенной 4 июня, в условиях временных проблем с подключением мультиплеер стал более стабильным, а сокрытие задержек — гораздо менее глючным (меньше торможений и телепортирования). Кроме того, я изменил способ сокрытия задержек в бою и надеюсь, что благодаря этому они будут немного более плавными.

Многопользовательский мегапакет — технические подробности


Если объяснять упрощённо, то мультиплеер в игре работает следующим образом: все клиенты симулируют состояние игры, получая и отправляя только ввод игрока (называемый «действиями ввода», Input Actions). Основная задача сервера — передача Input Actions и контроль того, что все клиенты выполняют одинаковые действия в одном такте. Подробнее об этом можно прочитать в посте FFF-149.

Так как сервер должен принимать решения о том, какие действия нужно выполнять, действия игрока движутся примерно по такому пути: действие игрока -> клиент игры -> сеть -> сервер -> сеть -> клиент игры. Это значит, что каждое действие игрока выполняется только после того, как совершит путь туда-обратно по сети. Из-за этого игра бы казалась ужасно тормозной, поэтому почти сразу же после появления в игре мультиплеера был введён механизм сокрытия задержек. Сокрытие задержке имитирует ввод игрока без учёта действий других игроков и принятия решений сервером.


В Factorio есть игровое состояние Game State — это полное состояние карты, игрока, сущностей и всего остального. Оно детерминированно симулируется во всех клиентах на основании действий, полученных от сервера. Игровое состояние священно, и если оно когда-нибудь начинает отличаться от сервера или любого другого клиента, то возникает рассинхронизация.

Кроме Game State у нас есть состояние задержек Latency State. Оно содержит небольшое подмножество основного состояния. Latency State не священно и просто представляет картину того, как будет выглядеть состояние игры в будущем на основании введённых игроком Input Actions.

Для этого мы храним копию создаваемых Input Actions в очереди задержек.


То есть в конце процесса на стороне клиента картина выглядит примерно так:

  1. Применяем Input Actions всех игроков к Game State так, как эти действия ввода были получены от сервера.
  2. Удаляем из очереди задержек все Input Actions, которые, по данным сервера, уже были применены к Game State.
  3. Удаляем Latency State и сбрасываем его, чтобы оно выглядело точно так же, как и Game State.
  4. Применяем все действия из очереди задержек к Latency State.
  5. На основании данных Game State и Latency State рендерим игру игроку.

Всё это повторяется в каждом такте.

Слишком сложно? Не расслабляйтесь, это ещё не всё. Чтобы компенсировать ненадёжность Интернет-соединений, мы создали два механизма:

  • Пропущенные такты: когда сервер решает, что Input Actions будут выполнены в такте игры, то если он не получил Input Actions какого-то игрока (например, из-за увеличившейся задержки), он не будет ждать, а сообщит этому клиенту «я не учёл твои Input Actions, постараюсь добавить их в следующий такт». Так сделано для того, чтобы из-за проблем с соединением (или с компьютером) одного игрока обновление карты не замедлялось у всех остальных. Стоит заметить, что Input Actions не игнорируются, а просто откладываются.
  • Задержка полного пути туда-обратно: сервер пытается предположить, какова задержка передачи данных туда-обратно между клиентом и сервером для каждого клиента. Каждые 5 секунд он при необходимости обсуждает с клиентом новую задержку (в зависимости от того, как вело себя подключение в прошлом), и соответствующим образом увеличивает или уменьшает задержку передачи данных туда-обратно.

Сами по себе эти механизмы довольно просты, но когда они используются совместно (что часто случается при проблемах с соединением), логика кода становится трудноуправляемой и с кучей пограничных случаев. Кроме того, когда в дело вступают эти механизмы, сервер и очередь задержек должны правильно внедрять особое Input Action под названием StopMovementInTheNextTick. Благодаря этому при проблемах с соединением персонаж не будет бежать сам по себе (например, под поезд).

Теперь нужно объяснить вам, как работает выбор сущностей. Один из передаваемых типов Input Action — это изменение состояния выбора сущности. Оно сообщает всем, на какую сущность игрок навёл курсор мыши. Как можно понять, это одно из самых частых действий ввода, отправляемых клиентами, поэтому для экономии пропускной способности канала мы оптимизировали его так, чтобы оно занимало как можно меньше места. Это реализовано так: при выборе каждой сущности вместо сохранения абсолютных, высокоточных координат карты игра сохраняет низкоточное относительное смещение от предыдущего выбора. Это хорошо работает, потому что выделение мышью обычно происходит очень близко к предыдущему выделению. Из-за этого возникают два важных требования: Input Actions никогда нельзя пропускать и необходимо выполнять их в верном порядке. Эти требования удовлетворяются для Game State. Но поскольку задача Latency state в том, чтобы «выглядеть достаточно хорошо» для игрока, в состоянии задержек они не удовлетворяются. Latency State не учитывает многие пограничные случаи, связанные с пропуском тактов и изменением задержек передачи туда-обратно.

Вы уже можете догадаться, к чему всё идёт. Наконец мы начинаем видеть причины проблемы мегапакета. Корень проблемы заключается в том, что в принятии решения о том, нужно ли передавать действие изменения выбора, логика выбора сущностей полагается на Latency State, а это состояние не всегда содержит верную информацию. Поэтому мегапакет генерируется примерно так:

  1. У игрока появились проблемы с соединением.
  2. В дело вступают механизмы пропуска тактов и регулирования задержки передачи туда-обратно.
  3. Очеред состояния задержек не учитывает эти механизмы. Это приводит к тому, что некоторые действия удаляются преждевременно или выполняются в неверном порядке, что приводит к неправильному Latency State.
  4. У игрока пропадает проблема с соединением и он, чтобы догнать сервер, симулирует до 400 тактов.
  5. В каждом такте генерируется и подготавливается к отправке на сервер новое действие изменение выбора сущности.
  6. Клиент отправляет серверу мегапакет из 400 с лишним изменений выбора сущностей (и с другими действиями: состояние стрельбы, ходьбы и т.п. тоже страдали от этой проблемы).
  7. Сервер получает 400 действий ввода. Так как ему не разрешено пропускать ни единого действия ввода, он приказывает всем клиентам выполнять эти действия и отправляет их по сети.

Ирония заключается в том, что механизм, предназначенный для экономии пропускной способности канала, в результате создавал огромные сетевые пакеты.

Мы решили эту проблему, исправив все пограничные случаи обновления и поддержки очереди задержек. Хоть это и заняло довольно много времени, в конечном итоге стоило реализовать всё правильно, а не полагаться на быстрые хаки.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 21

    +7
    Когда я смотрю на факторио (в режиме megabases) я реально не могу не восхититься работой программистов. Обрабатывать сотни тысяч объектов в реальном времени без задержек — это реально круто. В mesabases реально сотни тысяч объектов работают одновременно — от логистических роботов до инсертеров и фабрик.
      0

      А теперь умножь количество пользователей еще на 30 и многократно объем данных для обработки от каждого пользователя и получаем Eve Online с лагаловом и тиди в массзарубах.
      Хотя сейчас вроде как тестируется новое програмное обеспечение для распределении нагрузки в ммо, думаю в скором все изменится

        0
        factorio немного другое, чем «массовая зарубка» (хотя я не видел онлайн, может, они там нубов нюкают всё время). Там массовое производство и логистика, которая непрерывно работает в отсутствие человека (в этом суть правильной игры в факторио). Более того, обычно факторио работает в локальном режиме (т.е. никакого «mmo»).
          0

          А ну да, получается немного другое. Суть в огромной нагрузке описанной выше мной заключается в том что на объекты воздействует огромное количество факторов, и для того что бы пользователю вывести что-то на экран это все необходимо обсчитать на сервере. К примеру в одном месте находятся 100 объектов которые хаотично летают, после столкновения надо пересчитать траектории, прибавляем к этому что они еще и стрелают, трельба ведется из разных орудий, ракеты, турели, лазеры. уроны зависят от нескольких десятков факторов как со стороны стреляющего, так и со стороны цели. еще добавим у некоторых кораблей дронов, которые тоже имеют свои параметры движения, урона, положения в пространстве — которые так же надо проссчитывать.

            +6

            Нет, вы не понимаете. Вот у меня есть маленькая база. Я играю неспеша, медитативно. Многие люди за то же время строят в сотни раз большие базы. А многие играют по сети десятками и строят ещё большие базы. Так вот её статистика:



            1 миллион 113 тысяч сущностей, которые обновляются 60 раз в секунду. Каждая из этих сущностей имеет свою логику. И самое главное — оно совершенно не тормозит. Игра обрабатывает миллионы объектов (а у некоторых людей и миллиарды) каждые 16 мс и при этом она играбельная.


            Сколько там было кораблей в еве в бою макесимум? 7000? И как? Справлялся код? Сколько секунд просчитывался один тик?


            Факторио можно сравнивать с Евой. Как пример прекрасной и сложной работы в Факторио и не такой прекрасной в Еве. Но никак не наоборот.
            Фактория с технической точки зрения — гениальна.

              0
              Корабль это тоже могут быть тысячи объектов. Но тик может быть в еве очень долгий.
                0
                Главное, чтобы факту не убили улучшениями. Так бывает, живой пример WOT.
                +1
                и для того что бы пользователю вывести что-то на экран это все необходимо обсчитать на сервере

                Вы прочитали всю статью, и так и не поняли, что в Factorio для того чтобы вывести что-то на экран, не надо обсчитывать это на сервере?

                0
                Ну Factorio не единственный в плане действий, совершаемых без игроков. Вот в Rust добавили электрические сети и устройства, которые пашут всегда. Если на карте будет 400 домов (типично 200 игроков, у каждого обычно пара домов), и каждый будет хорошо оборудован электрооборудованием, то обсчётов тоже будет порядочно. И это на фоне реалистичной 3D графики в отличие от…
            0
            Парни, а почему бы не скипать часть действий клиентов сервером, просто аппроксимируя их с определенной точностью? По-моему это самая распространенная оптимизация в ММО, нет?
              +1
              Тогда получится рассинхрон в момент, когда действительность и аппроксимация не сойдутся
                0

                Ну так в ММО клиент получает состояние игры от сервера, а не рассчитывает его полностью самостоятельно.

                0
                Проблема эта явно была заметна не в июне, а в сентябре прошлого года, когда полковник Will (https://www.twitch.tv/colonelwill) попытался сделать массовый ивент на новом сценарии со сплошными месторождениями разной руды. Тогда основной причиной массовых проблем были признаны бесконечные сундуки. Но, как оказалось — не только они были виноваты.
                  +1
                  Оффтоп
                  Как-то неэффективно линии снабжения лабораторий организовали на КДПВ. Понятно, что нужны 4 конвейерных линии на 7 типов пакетов, но текущие 4 типа стоило на красные манипуляторы пересадить.
                    +4
                    Оффтоп
                    Этой картинке сто лет уже, она из той эпохи, когда пакетов было только 4. 3 подвозятся с фабрики, 4-й, который из инопланетной слизи, делается на месте, т.к. всего из одного компонента.
                    0
                    Вот объясните мне, что там можно делать в 200 лопат? Чисто интересно. Я понимаю, когда 2-5 человек собираются, но 200?!
                      0

                      Зерглинг-рашить кусак.

                        +2
                        О! Это отдельный вид Факторки.

                        Когда собираются 2-5 человек, то это строительная бригада. Кто-то из них обязательно бригадир, дальше идут старший мастер, мастер, подмастерье. Они создают что-то единое под чутким управлением бригадира.

                        Факторка на 20-30 человек уже распадается на обычное человеческое общество. Люди разбиваются на группы, а кто-то вообще действует индивидуально. Начинаются стройки в разных местах карты, и обязательно начинается борьба за ресурсы. Но борьба совсем не такая как в других играх. Борьба начинается как в цивилизованном человеческом обществе. Кто первым установит буры для своего мегапроекта.

                        А еще пока ты спал кто-то мог повернуть твой конвеер в другую сторону. Или врезаться в трубу. И ты не можешь объяснить этим товарищам с помощью турелей что они не правы. Ты даже не можешь киркой тюкнуть их по голове. А иногда очень хочется. Всё как в реальной жизни. Приходится договариваться, отставать, бросать и уходить.
                          0
                          А кто рулит исследованиями?
                          Турели ладно, не наводятся, а арту можно и вручную. По особо наглой рыжей морде.
                            0
                            И как, нормально играется? У меня была проблемка что мультиплеер — бесцелен кроме как «показать корешам свою базу» — исследований мира в факторио как такового нет, соревновательного момента мало — самые крутые решения можно «сгрести» блупринтом и отстроить у себя логистами в два клика, мегапроекты не притягивают (да и опять-же логистами да блупринтами строится что угодно).

                            Видимо как-то надо подбирать параметры генерации мира чтобы появлялась необходимость соперничать за ресурсы или хотя-бы бартер устраивать.
                          0
                          А им удалось починить проблему с невозможностью адекватно управлять любым наземным транспортом (не поездами)?

                          Only users with full accounts can post comments. Log in, please.