
U svibnju ove godine sudjelovao sam kao igrač na . Primijetio sam da kada broj igrača dostigne određeni broj, svake nekoliko minuta dio njih se 'ispušta'. Na sreću za vas (ali ne i za mene), bio sam jedan od onih igrača koji su se isključivali svaki put, čak i uz dobru vezu. Uzeo sam to kao osobni izazov i počeo istraživati razloge problema. Nakon tri tjedna debagiranja, testiranja i ispravki, greška je nap finally ispravljena, ali to putovanje nije bilo tako jednostavno.
Problemi multiplayer igara vrlo su teški za praćenje. Obično se javljaju u vrlo specifičnim uvjetima mrežnih postavki i u vrlo specifičnim stanjima igre (u ovom slučaju - u prisutnosti više od 200 igrača). Čak i kada se uspije reproducirati problem, nije ga moguće pravilno debagirati, jer umetanje kontrolnih točaka zaustavlja igru, zbunjuje tajmere i obično dovodi do prekida veze zbog isteka vremena čekanja. No, zahvaljujući upornosti i izvrsnom alatu zvanom , uspio sam saznati što se događa.
Ukratko: zbog greške i nepotpune implementacije simulacije stanja kašnjenja, klijent se ponekad našao u situaciji kada je morao u jednom ciklusu poslati mrežni paket koji se sastoji od akcija igrača koje odabire približno 400 gaming entiteta (nazivamo ga 'megapak'). Nakon toga, server ne samo da mora pravilno primiti sve te ulazne akcije, nego ih i poslati svim ostalim klijentima. Ako imate 200 klijenata, to brzo postaje problem. Kanal prema serveru brzo se zaguši, što dovodi do gubitka paketa i kaskade ponovo zatraženih paketa. Odgađanje ulaznih akcija tada dovodi do još više klijenata koji počinju slati megapakete, a njihova lavina postaje sve jača. Sretnim klijentima uspijeva se oporaviti, svi ostali se 'ispuštaju'.

Problem je bio dovoljno temeljit i trebalo mi je 2 tjedna da ga ispravim. Tehnička je priroda, pa ću u nastavku objasniti sočne tehničke detalje. No, prvo morate znati da je od verzije 0.17.54, objavljene 4. lipnja, multijezgarni način rada postao stabilniji, a skrivanje latencije znatno manje glitzy (manje usporavanja i teleportacija). Osim toga, promijenio sam način skrivanja latencije u borbi i nadam se da će zbog toga biti malo fluidnija.
Multiplayer megapak - tehnički detalji
Ako pojednostavim, multiplayer u igri radi na sljedeći način: svi klijenti simuliraju stanje igre, primajući i šaljući samo unos igrača (koji se naziva "unosi akcije", Input Actions). Glavni zadatak servera je prenošenje Input Actions i kontrola toga da svi klijenti izvode iste akcije u jednom ciklusu. Više o tome možete pročitati u postu .
Budući da server mora donositi odluke o tome koje akcije treba izvršiti, putanja koju akcija igrača prelazi je otprilike ovakva: akcija igrača -> klijent igre -> mreža -> server -> mreža -> klijent igre. To znači da se svaka akcija igrača izvršava tek nakon što napravi put prema naprijed i natrag kroz mrežu. Zbog toga bi igra izgledala strašno usporeno, pa je gotovo odmah nakon pojave multijezgarni sustav uveden mehanizam za skrivanje latencije. Skrivenje latencije imituje unos igrača bez uzimanja u obzir akcija drugih igrača i donošenja odluka od strane servera.

U Factorio postoji stanje igre Game State — to je potpuno stanje mape, igrača, entiteta i svega ostalog. Određeno je deterministički simulirano u svim klijentima na temelju akcija dobivenih od servera. Stanje igre je sveto i ako ikada počne odstupati od servera ili bilo kojeg drugog klijenta, nastaje nesinkronizacija.
Osim Game State imamo stanje latencije Latency State. Sadrži mali podskup osnovnog stanja. Latency State nije sveto i jednostavno predstavlja sliku o tome kako će izgledati stanje igre u budućnosti na temelju unosa igrača Input Actions.
Za to čuvamo kopiju kreiranih Input Actions u redu za latenciju.

Na kraju procesa klijentova slika izgleda otprilike ovako:
- Primjenjujemo Input Actions sve akcije na Game State onako kako su te ulazne radnje primljene sa servera.
- Uklanjamo iz reda kašnjenja sve Input Actions, koje su, prema podacima sa servera, već primijenjene na Game State.
- Brišemo Latency State i resetiramo ga kako bi izgledalo upravo onako kako i Game State.
- Primjenjujemo sve akcije iz reda kašnjenja na Latency State.
- Na temelju podataka Game State i Latency State renderiramo igru igraču.
Sve se to ponavlja u svakom taktu.
Prekomplicirano? Ne opuštajte se, to još nije sve. Kako bismo nadoknadili nepouzdanost internetskih veza, stvorili smo dva mehanizma:
- Propušteni taktovi: kada server odluči da Input Actions će biti izvršeni u igračkom taktu, ukoliko ne primi Input Actions neke igrače (na primjer, zbog povećane kašnjenja), ne će čekati, već će obavijestiti tog klijenta "nisam uzeo u obzir vaše Input Actions, pokušat ću ih dodati u sljedeći takt." To je napravljeno kako bi ažuriranje karte zbog problema s vezom (ili računalom) jednog igrača nije usporavalo sve ostale. Treba napomenuti da Input Actions se ne ignoriraju, već jednostavno odgađaju.
- Kašnjenje cijelog putovanja od kraja do kraja: server pokušava pretpostaviti kolika je kašnjenje u prijenosu podataka od kraja do kraja između klijenta i servera za svakog klijenta. Svakih 5 sekundi, prema potrebi, raspravlja s klijentom o novoj kašnjenju (ovisno o tome kako se ponašala veza u prošlosti), i odgovarajuće povećava ili smanjuje kašnjenje prijenosa podataka.
Ovi mehanizmi sami po sebi su prilično jednostavni, ali kada se koriste zajedno (što se često događa kod problema s povezivanjem), logika koda postaje teška za upravljanje i ima mnogo rubnih slučajeva. Osim toga, kada ovi mehanizmi dođu u igru, server i red čekanja kašnjenja moraju ispravno implementirati poseban Input Action naziv StopMovementInTheNextTick. Zbog toga, u slučaju problema s vezom, lik neće sam od sebe trčati (na primjer, pod vlak).
Sada vam trebam objasniti kako radi odabir entiteta. Jedan od prenosivih tipova Input Action — to je promjena stanja izbora entiteta. To obavještava sve o kojoj entitet igrač usmjerava miš. Kao što se može primijetiti, to je jedna od najčešćih unosa koje klijenti šalju, stoga smo optimizirali kako bi zauzimalo što manje propusnosti. To je implementirano tako da umjesto spremanja apsolutnih, visokopreciznih koordinata karte, igra pohranjuje niskoprecizni relativni pomak od prethodnog izbora. To dobro funkcionira jer se odabir mišem obično događa vrlo blizu prethodnog odabira. Iz toga proizlaze dva važna zahtjeva: Input Actions nikada se ne smiju preskočiti i moraju se izvršavati u ispravnom redoslijedu. Ti zahtjevi se zadovoljavaju za Game State. Ali budući da je izazov Stanje latencije u tome da „izgleda dovoljno dobro“ za igrača, u stanju latencija oni nisu zadovoljeni. Latency State ne uzima u obzir , vezane uz preskakanje takta i promjene latencije prijenosa naprijed-natrag.
Već možete naslutiti kuda ovo vodi. Napokon počinjemo vidjeti uzroke problema megapaketa. Korijen problema leži u tome što odluka o tome treba li prenijeti radnju promjene izbora oslanja se na Latency State, a to stanje ne sadrži uvijek točne informacije. Stoga se megapaketi generiraju približno ovako:
- Igrač se suočava s problemima s vezom.
- U igru ulaze mehanizmi preskakanja taktova i regulacije latencije prijenosa naprijed-natrag.
- Red stanja latencija ne uzima u obzir te mehanizme. To dovodi do toga da se neki radnje uklanjaju prerano ili se izvršavaju u pogrešnom redoslijedu, što rezultira netočnim Latency State.
- Igrač gubi problem s vezom i simulira do 400 taktova kako bi sustigao poslužitelj.
- U svakom takt generira se i priprema za slanje na poslužitelj nova radnja promjene izbora entiteta.
- Klijent šalje poslužitelju megapaketi s više od 400 promjena izbora entiteta (i s drugim akcijama: stanje pucanja, hodanja itd. također su patili od ovog problema).
- Poslužitelj prima 400 ulaznih akcija. Budući da mu nije dopušteno preskočiti nijednu ulaznu akciju, naređuje svim klijentima da izvrše te akcije i šalje ih putem mreže.
Ironija je u tome što je mehanizam, koji je trebao uštedjeti propusnost kanala, zapravo stvarao ogromne mrežne pakete.
Riješili smo ovaj problem ispravivši sve rubne slučajeve ažuriranja i podrške reda kašnjenja. Iako je to trajalo prilično dugo, na kraju se isplatilo sve pravilno implementirati, umjesto da se oslanjamo na brze hackove.
Izvor: habr.com
