Mega balíček: jak se vývojářům Factorio podařilo vyřešit problém s multiplayerem pro 200 hráčů

Mega balíček: jak se vývojářům Factorio podařilo vyřešit problém s multiplayerem pro 200 hráčů
V květnu tohoto roku jsem se zúčastnil jako hráč MMO události KatherineOfSky. Uvedomil jsem si, že když počet hráčů dosáhne určitého počtu, každých pár minut část z nich vypadává. Naštěstí pro vás (ale ne pro mě) jsem byl jedním z těch hráčů, kteří se odpojili každý čas, i při dobrém připojení. Vzali jsem to jako osobní výzvu a začal jsem hledat příčiny problému. Po třech týdnech ladění, testování a oprav chyby byla konečně odstraněna, ale tato cesta nebyla tak jednoduchá.

Problémy s multiplayerovými hrami se velmi těžko sledují. Obvykle se objevují za velmi specifických podmínek síťových parametrů a ve velmi specifických stavech hry (v tomto případě - více než 200 hráčů). A dokonce i když se podaří problém reprodukovat, nelze jej správně ladit, protože vkládání kontrolních bodů zastavuje hru, mateřské časovače a obvykle vede k ukončení spojení kvůli překročení časového limitu. Ale díky vytrvalosti a úžasnému nástroji jménem clumsy se mi podařilo zjistit, co se děje.

Stručně řečeno: kvůli chybě a neúplné implementaci simulace latence se klient občas ocitne v situaci, kdy musí za jeden cyklus odeslat síťový paket složený z akcí hráče, což zahrnuje zhruba 400 herních entit (nazýváme to "megabalíček"). Poté musí server nejen správně přijmout všechny tyto akce vstupu, ale také je zaslat všem ostatním klientům. Pokud máte 200 klientů, to se rychle stává problémem. Kanál k serveru se rychle zaplní, což vede ke ztrátě paketů a lavině opakovaně požadovaných paketů. Zpoždění akcí vstupu pak způsobuje, že ještě více klientů začne posílat megabalíčky, což vyvolává další lavinu. Šťastným klientům se podaří zotavit, ostatní se "odpojí".

Mega balíček: jak se vývojářům Factorio podařilo vyřešit problém s multiplayerem pro 200 hráčů
Problém byl dost zásadní a trvalo mi dva týdny, než jsem ho vyřešil. Je celkem technický, takže níže vysvětlím šťavnaté technické podrobnosti. Ale nejprve byste měli vědět, že od verze 0.17.54, vydané 4. června, se v podmínkách dočasných problémů s připojením stal multiplayer stabilnější a skrývání latencí mnohem méně chybovým (méně zpoždění a teleportování). Kromě toho jsem změnil způsob skrývání latencí v boji a doufám, že díky tomu budou o něco hladší.

Multiplayerový megabalíček — technické podrobnosti

Pokud to zjednodušíme, multiplayer ve hře funguje následovně: všichni klienti simulují stav hry tím, že pouze přijímají a odesílají vstup hráče (nazývaný „vstupní akce“ Input Actions). Hlavním úkolem serveru je předávat Input Actions a kontrolovat, že všichni klienti provádějí stejné akce v jednom taktu. O tom se můžete více dočíst v příspěvku FFF-149.

Jelikož server musí rozhodovat o tom, které akce se mají provádět, akce hráče prochází přibližně takovou cestou: akce hráče -> klient hry -> síť -> server -> síť -> klient hry. To znamená, že každá akce hráče se provádí až po tom, co se dostane tam a zpět po síti. Kvůli tomu by se hra zdála hrozně lagovat, proto byl téměř okamžitě po zavedení multiplayeru do hry zaveden mechanismus pro skrývání latencí. Skrývání latencí simuluje vstup hráče bez ohledu na akce ostatních hráčů a rozhodování serveru.

Mega balíček: jak se vývojářům Factorio podařilo vyřešit problém s multiplayerem pro 200 hráčů
Ve Factorio existuje herní stav Game State — to je úplný stav mapy, hráče, entit a všeho ostatního. Je deterministicky simulován ve všech klientech na základě akcí získaných ze serveru. Herní stav je svatý, a pokud se někdy začne lišit od serveru nebo jakéhokoli jiného klienta, dojde k desynchronizaci.

Kromě Game State máme stav latencí Latency State. Obsahuje malé podmnožství hlavního stavu. Latency State není svatý a jednoduše představuje obraz toho, jak bude budoucí stav hry vypadat na základě zadaného hráčem Input Actions.

K tomu uchováváme kopii vytvářených Input Actions v řadě latencí.

Mega balíček: jak se vývojářům Factorio podařilo vyřešit problém s multiplayerem pro 200 hráčů
Na konci procesu tedy na straně klienta situace vypadá přibližně takto:

  1. Používáme Input Actions všechny hráče na Game State tak, jak byly tyto akce přijaté ze serveru.
  2. Odstraňujeme ze seznamu zpoždění všechny Input Actions, které, podle údajů serveru, již byly aplikovány na Game State.
  3. Odstraňujeme Latency State a resetujeme jej, aby vypadal přesně tak, jak Game State.
  4. Aplikujeme všechny akce ze seznamu zpoždění na Latency State.
  5. Na základě dat Game State a Latency State renderujeme hru hráči.

To se opakuje v každém cyklu.

Příliš složité? Neztrácejte klid, to ještě není všechno. Abychom kompenzovali n spolehlivost internetových připojení, vytvořili jsme dva mechanismy:

  • Vynechané cykly: když server rozhodne, že Input Actions proběhnou v herním cyklu, pokud neobdržel Input Actions nějakého hráče (například kvůli zvýšenému zpoždění), nečeká, ale oznámí tomuto klientovi „nebral jsem v potaz tvé Input Actions, pokusím se je přidat do dalšího cyklu“. To je uděláno proto, aby se kvůli problémům se spojením (nebo počítačem) jednoho hráče nezpozdilo aktualizace mapy pro ostatní. Je třeba poznamenat, že Input Actions nejsou ignorovány, ale jednoduše odloženy.
  • Zpoždění celkové cesty tam a zpět: server se snaží odhadnout, jaká je latence přenosu dat tam a zpět mezi klientem a serverem pro každého klienta. Každých 5 sekund probíhá při potřebě diskuse s klientem o nové latenci (v závislosti na tom, jak se chovalo připojení v minulosti) a podle toho zvyšuje nebo snižuje latenci přenosu dat tam a zpět.

Tyto mechanismy samy o sobě jsou docela jednoduché, ale když se používají společně (což se často stává při problémech s připojením), logika kódu se stává obtížně ovladatelnou a má spoustu okrajových případů. Navíc, když se tyto mechanismy zapojí, server a fronta zpoždění musí správně implementovat speciální Akci vstupu nazvanou Zastavit pohyb v následujícím taktu. Díky tomu, při problémech s připojením, postava nebude běhat sama (například pod vlakem).

Nyní je třeba vám vysvětlit, jak funguje výběr entit. Jeden z předávaných typů Akci vstupu — je to změna stavu výběru entity. Oznamuje to všem, na kterou entitu hráč najezdil myší. Jak lze pochopit, je to jedna z nejčastějších vstupních akcí odesílaných klienty, proto jsme to optimalizovali tak, aby zabíralo co nejméně místa. Bylo to realizováno tak, že při výběru každé entity se místo uchování absolutních, vysoce přesných souřadnic mapy ukládá nízko přesné relativní posunutí od předchozího výběru. To funguje dobře, protože myší vybrání obvykle probíhá velmi blízko předchozímu výběru. Z toho vyplývají dvě důležitá požadavky: Input Actions nikdy nesmí být vynechány a musí být prováděny ve správném pořadí. Tyto požadavky jsou splněny pro Game State. Ale protože úkol Stav latency je v tom, aby "vypadal dostatečně dobře" pro hráče, nejsou v stavu latencí splněny. Latency State nezohledňuje mnoho okrajových případů, souvisejících s vynecháním taktů a změnou latence při přenosu tam a zpět.

Už si možná dokážete představit, kam to směřuje. Konečně začínáme vidět příčiny problému megabalíku. Kořen problému spočívá v tom, že při rozhodování o tom, zda má být přenesena akce změny výběru, logika výběru entit závisí na Latency State, a tento stav neobsahuje vždy správné informace. Proto je megabalík generován přibližně takto:

  1. Hráč má problémy s připojením.
  2. Do hry vstupují mechanismy vynechávání taktů a regulace latence při přenosu tam a zpět.
  3. Fronta stavů latencí nezohledňuje tyto mechanismy. To vede k tomu, že některé akce jsou předčasně odstraněny nebo provedeny v nesprávném pořadí, což má za následek nesprávné Latency State.
  4. Hráč má problém s připojením a přizpůsobuje se serveru, simuluje až 400 taktů.
  5. V každém taktu je generována a připravována k odeslání na server nová akce změny výběru entity.
  6. Klient odesílá serveru megabalík s více než 400 změnami výběru entit (a s dalšími akcemi: stav střelby, chůze atd. také trpěly tímto problémem).
  7. Server přijímá 400 vstupních akcí. Jelikož mu není dovoleno opustit ani jednu vstupní akci, nařizuje všem klientům, aby tyto akce provedli a odeslali je po síti.

Ironie spočívá v tom, že mechanismus určený k úspoře šířky pásma kanálu ve skutečnosti vytvářel obrovské síťové pakety.

Tento problém jsme vyřešili opravou všech okrajových případů aktualizace a podpory fronty zpoždění. Ačkoli to trvalo poměrně dlouho, nakonec stálo za to to správně implementovat, místo abychom se spolehli na rychlé hacky.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster