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

Мегапакет: як распрацоўшчыкам Factorio ўдалося вырашыць праблему з мультыплэерам на 200 гульцоў
У траўні гэтага года я ўдзельнічаў у якасці гульца ў MMO-мерапрыемстве KatherineOfSky. Я заўважыў, што калі колькасць гульцоў дасягае пэўнага ліку, праз кожныя некалькі хвілін частка з іх "адвальваецца". Да шчасця для вас (але не для мяне), я быў адным з тых гульцоў, якія адключаліся кожны раз, нават пры наяўнасці добрага падключэння. Я ўспрыняў гэта як асабісты выклік і пачаў шукаць прычыны праблемы. Праз тры тыдні адладкі, тэставанні і выпраўленняў памылка нарэшце ўхіленая, але гэтае вандраванне было не такім ужо простым.

Праблемы шматкарыстальніцкіх гульняў вельмі цяжка адсачыць. Звычайна яны ўзнікаюць у вельмі канкрэтных умовах параметраў сетак і пры вельмі спецыфічных станах гульні (у дадзеным выпадку - наяўнасць больш за 200 гульцоў). І нават калі атрымоўваецца прайграць праблему, яе немагчыма належным чынам адладжваць, таму што ўстаўка кантрольных кропак спыняе гульню, блытае таймеры і звычайна прыводзіць да завяршэння злучэння з-за перавышэнні тэрміна чакання. Але дзякуючы ўпартасці і выдатнай прыладзе пад назовам нязграбны мне ўдалося высветліць, што ж адбываецца.

Калі сцісла: з-за памылкі і няпоўнай рэалізацыі сімуляцыі стану затрымкі кліент часам апыняўся ў сітуацыі, калі яму даводзіцца за адзін такт адпраўляць сеткавы пакет, які складаецца з уводзімых гульцом дзеянняў выбару прыкладна 400 гульнявых сутнасцяў (мы завем яго "мегапакетам"). Пасля гэтага сервер не толькі павінен правільна атрымаць усе гэтыя дзеянні ўводу, але і адправіць іх усім астатнім кліентам. Калі ў цябе 200 кліентаў, гэта хутка становіцца праблемай. Канал да сервера хутка забіваецца, што прыводзіць да згубы пакетаў і каскаду паўторна запытаных пакетаў. Адкладванне дзеянняў уводу затым прыводзіць да таго, што яшчэ больш кліентаў пачынае адпраўляць мегапакеты, і іх лавіна становіцца яшчэ мацней. Удачлівым кліентам атрымоўваецца аднавіцца, усе астатнія "адвальваюцца".

Мегапакет: як распрацоўшчыкам Factorio ўдалося вырашыць праблему з мультыплэерам на 200 гульцоў
Праблема была досыць фундаментальнай, і ў мяне сышло 2 тыдні на яе ўхіленне. Яна даволі тэхнічная, таму ніжэй я растлумачу сакавітыя тэхнічныя падрабязнасці. Але для пачатку вам трэба ведаць, што з версіі 0.17.54, выпушчанай 4 чэрвеня, ва ўмовах часовых праблем з падключэннем мультыплэер стаў больш стабільным, а ўтойванне затрымак - значна менш глючным (менш тармажэнняў і тэлепартавання). Акрамя таго, я змяніў спосаб утойвання затрымак у баі і спадзяюся, што дзякуючы гэтаму яны будуць крыху больш плыўнымі.

Шматкарыстальніцкі мегапакет - тэхнічныя падрабязнасці

Калі тлумачыць спрошчана, то мультыплэер у гульні працуе наступным чынам: усе кліенты сімулююць стан гульні, атрымліваючы і адпраўляючы толькі ўвод гульца (званы «дзеяннямі ўводу», Input Actions). Асноўная задача сервера - перадача Input Actions і кантроль таго, што ўсе кліенты выконваюць аднолькавыя дзеянні ў адным такце. Падрабязней пра гэта можна прачытаць у пасце FFF-149.

Паколькі сервер павінен прымаць рашэнні аб тым, якія дзеянні трэба выконваць, дзеянні гульца рухаюцца прыкладна па такім шляху: дзеянне гульца -> кліент гульні -> сетка -> сервер -> сетка -> кліент гульні. Гэта значыць, што кожнае дзеянне гульца выконваецца толькі пасля таго, як зробіць шлях туды-назад па сетцы. З-за гэтага гульня б здавалася жудасна тармазной, таму амаль адразу ж пасля з'яўлення ў гульні мультыплэера быў уведзены механізм утойвання затрымак. Утойванне затрымкі імітуе ўвод гульца без уліку дзеянняў іншых гульцоў і прыняцці рашэнняў серверам.

Мегапакет: як распрацоўшчыкам Factorio ўдалося вырашыць праблему з мультыплэерам на 200 гульцоў
У Factorio ёсць гульнявы ​​стан Game State - гэта поўны стан карты, гульца, сутнасцяў і ўсяго астатняга. Яно дэтэрмінавана сімулюецца ва ўсіх кліентах на падставе дзеянняў, атрыманых ад сервера. Гульнявы ​​стан святы, і калі ён калі-небудзь пачынае адрознівацца ад сервера ці любога іншага кліента, тое ўзнікае рассінхранізацыя.

Акрамя Game State у нас ёсць стан затрымак Latency State. Яно змяшчае невялікае падмноства асноўнага стану. Latency State не свята і проста ўяўляе карціну таго, як будзе выглядаць стан гульні ў будучыні на падставе уведзеных гульцом. Input Actions.

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

Мегапакет: як распрацоўшчыкам Factorio ўдалося вырашыць праблему з мультыплэерам на 200 гульцоў
Гэта значыць, у канцы працэсу на баку кліента карціна выглядае прыкладна так:

  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 секунд ён пры неабходнасці абмяркоўвае з кліентам новую затрымку (у залежнасці ад таго, як паводзіла сябе падлучэнне ў мінулым), і якая адпавядае выявай павялічвае або памяншае затрымку перадачы дадзеных туды-зваротна.

Самі па сабе гэтыя механізмы даволі простыя, але калі яны выкарыстоўваюцца сумесна (што часта здараецца пры праблемах са злучэннем), логіка кода становіцца цяжкакіравальнай і з кучай памежных выпадкаў. Акрамя таго, калі ў справу ўступаюць гэтыя механізмы, сервер і чарга затрымак павінны правільна ўкараняць асаблівае. Дзеянне ўводу пад назвай StopMovementInTheNextTick. Дзякуючы гэтаму пры праблемах са злучэннем персанаж не будзе бегчы сам па сабе (напрыклад, пад цягнік).

Цяпер трэба растлумачыць вам, як працуе выбар сутнасцяў. Адзін з перадаюцца тыпаў Дзеянне ўводу - Гэта змена стану выбару сутнасці. Яно паведамляе ўсім, на якую сутнасць гулец навёў курсор мышы. Як можна зразумець, гэта адно з самых частых дзеянняў уводу, якія адпраўляюцца кліентамі, таму для эканоміі прапускной здольнасці канала мы аптымізавалі яго так, каб яно займала як мага менш месцы. Гэта рэалізавана так: пры выбары кожнай сутнасці замест захавання абсалютных, высокадакладных каардынат карты гульня захоўвае нізкадакладнае адноснае зрушэнне ад папярэдняга выбару. Гэта добра працуе, таму што вылучэнне мышшу звычайна адбываецца вельмі блізка да папярэдняга вылучэння. З-за гэтага ўзнікаюць два важныя патрабаванні: Input Actions ніколі нельга прапускаць і неабходна выконваць іх у дакладным парадку. Гэтыя патрабаванні задавальняюцца для Game State. Але паколькі задача Latency state у тым, каб «выглядаць дастаткова добра» для гульца, у стане затрымак яны не задавальняюцца. Latency State не ўлічвае многія памежныя выпадкі, звязаныя з пропускам тактаў і змяненнем затрымак перадачы туды-назад.

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

  1. У гульца з'явіліся праблемы са злучэннем.
  2. У справу ўступаюць механізмы пропуску тактаў і рэгуляванні затрымкі перадачы туды-назад.
  3. Чарга стану затрымак не ўлічвае гэтыя механізмы. Гэта прыводзіць да таго, што некаторыя дзеянні выдаляюцца заўчасна ці выконваюцца ў няправільным парадку, што прыводзіць да няправільнага. Latency State.
  4. У гульца знікае праблема са злучэннем і ён, каб дагнаць сервер, сімулюе да 400 тактаў.
  5. У кожным такце генеруецца і падрыхтоўваецца да адпраўкі на сервер новае дзеянне змена выбару сутнасці.
  6. Кліент адпраўляе серверу мегапакет з 400 з лішнім змен выбару сутнасцяў (і з іншымі дзеяннямі: стан стральбы, хады і да т.п. таксама пакутавалі ад гэтай праблемы).
  7. Сервер атрымлівае 400 дзеянняў уводу. Бо яму не дазволена прапускаць ніводнага дзеяння ўводу, ён загадвае ўсім кліентам выконваць гэтыя дзеянні і адпраўляе іх па сетцы.

Іронія заключаецца ў тым, што механізм, прызначаны для эканоміі прапускной здольнасці канала, у выніку ствараў вялізныя сеткавыя пакеты.

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

Крыніца: habr.com

Дадаць каментар