
Në maj të këtij viti, mora pjesë si lojtar në . Vura re se kur numri i lojtarëve arrin një numër të caktuar, çdo disa minuta një pjesë e tyre "dëshpëron". Fatmirësisht për ju (por jo për mua), unë isha një nga ata lojtarë që çaktivizoheshin çdo herë, edhe me një lidhje të mirë. E pranova këtë si një sfidë personale dhe fillova të kërkoj shkakun e problemit. Pas tri javësh debugging, testimi dhe rregullimi, gabimi u zgjidh në fund, por ky udhëtim nuk ishte aq i lehtë sa duket.
Problemet e lojërave shumëlojëshe janë shumë të vështira për t'u gjurmuar. Ato zakonisht ndodhin në kushte shumë specifike të parametrave të rrjetit dhe në situata shumë specifike të lojës (në këtë rast – me më shumë se 200 lojtarë). Dhe edhe kur arrin të riprodhosh problemin, është e pamundur ta debugosh siç duhet, sepse vendosja e pikave të kontrollit ndalon lojën, ngatërron kronometërat dhe zakonisht çon në mbylljen e lidhjes për shkak të tejkalimit të afatit. Por falë këmbënguljes sime dhe një instrumenti të mrekullueshëm të quajtur , arrita të kuptoj se çfarë po ndodhte.
Nëse e përmbledh: për shkak të një gabimi dhe implementimi të papërfunduar të simulimit të gjendjes së vonesës, klienti ndonjëherë ndodhej në një situatë ku duhej të dërgonte një paketë rrjeti, e cila përmbante veprimet e lojtarit për rreth 400 entitete lojërore (ne e quajmë "megapakë"). Pas kësaj, serveri duhet jo vetëm të marrë saktësisht të gjitha këto veprime hyrëse, por gjithashtu t'i dërgojë ato të tjerëve klientë. Nëse ke 200 klientë, kjo shpejt bëhet një problem. Kanali në server shpejt mbushet, çon në humbje paketash dhe një seri paketash të kërkuara përsëri. Vonesa në veprimet hyrëse pastaj bën që akoma më shumë klientë të fillojnë të dërgojnë megapakë, dhe përmbytja e tyre bëhet edhe më e fortë. Klientët me fat arrijnë të rikuperohen, të gjithë të tjerët "dëshprohen".

Problemi ishte mjaft themelor, dhe më mori 2 javë për ta zgjidhur. Ai është mjaft teknik, prandaj më poshtë do të shpjegoj detajet e mrekullueshme teknike. Por përpara, duhet të dini se nga versioni 0.17.54, i lëshuar më 4 qershor, nën kushte të përkohshme problemesh me lidhjen, shumëlojësia u bë më e qëndrueshme dhe fshehja e vonesave u bë shumë më pak e përsëritur (më pak ndalesa dhe teletransportime). Për më tepër, kam ndryshuar mënyrën e fshehjes së vonesave në betejë dhe shpresoj që për këtë do të jenë pak më të qetë.
Megapakë shumëlojëshe – detajet teknike
Për ta shpjeguar thjeshtë, shumëlojësia në lojë funksionon në këtë mënyrë: të gjithë klientët simulojnë gjendjen e lojës, duke marrë dhe dërguar vetëm hyrjet e lojtarëve (të quajtura "veprime hyrëse", Input Actions). Detyra kryesore e serverit është të transmetojë Input Actions dhe të kontrollojë që të gjitha klientët të kryejnë të njëjtat veprime në një cicërimë. Më shumë rreth kësaj mund të lexoni në postimin .
Sepse serveri duhet të marrë vendime për atë që duhet të bëjë, veprimi i lojtarit lëviz më shumë ose më pak në këtë rrugë: veprimi i lojtarit -> klienti i lojës -> rrjeti -> serveri -> rrjeti -> klienti i lojës. Kjo do të thotë se çdo veprim i lojtarit ekzekutohet vetëm pasi të kalojë rrugën për të dhe mbrapa përmes rrjetit. Për shkak të kësaj, loja do të dukej jashtëzakonisht e ngadalte, prandaj pothuajse menjëherë pas shfaqjes së shumëlojësisë në lojë u prezantua një mekanizëm për fshehjen e vonesave. Fshehja e vonesave imiton veprimet e lojtarit pa marrë parasysh veprimet e lojtarëve të tjerë dhe vendimet e serverit.

Në Factorio ekziston një gjendje e lojës Game State — kjo është gjendja e plotë e hartës, lojtarit, entiteteve dhe gjithçkaje tjetër. Ajo simulohet në mënyrë determinuese në të gjithë klientët bazuar në veprimet e marra nga serveri. Gjendja e lojës është e shenjtë dhe nëse ndonjëherë fillon të ndryshojë nga serveri ose çdo klient tjetër, atëherë ndodh një desinkronizim.
Përveç Game State kemi një gjendje vonese Latency State. Ajo përmban një nëngrup të vogël të gjendjes kryesore. Latency State ajo nuk është e shenjtë dhe thjesht paraqet një pamje të asaj se si do të dukej gjendja e lojës në të ardhmen bazuar në hyresat e lojtarëve Input Actions.
Për këtë, ne ruajmë një kopje të krijuar Input Actions në radhë të vonesave.

Pra, në fund të procesit në anën e klientit pamja duket më shumë ose më pak kështu:
- Aplikojmë Input Actions të gjithë lojtarët Game State ashtu siç janë marrë këto veprime hyrëse nga serveri.
- Fshijmë nga radhë të vonesave të gjitha Input Actions, që sipas të dhënave të serverit, tashmë kanë qenë aplikuar në Game State.
- Fshijmë Latency State dhe e rivendosim që të duket saktësisht si Game State.
- Aplikojmë të gjitha veprimet nga radhët e vonesës në Latency State.
- Bazuar në të dhënat Game State dhe Latency State ne rendisim lojën për lojtarin.
Kjo përsëritet në çdo takt.
Për shumë të komplikuar? Mos u shqetësoni, kjo nuk është e gjitha. Për të kompensuar pasigurinë e lidhjeve të Internetit, ne krijuam dy mekanizma:
- Taktet e humbura: kur serveri vendos se Input Actions do të realizohen në taktin e lojës, dhe nëse ai nuk ka reçipuar Input Actions një lojtar (p.sh., për shkak të rritjes së vonesës), ai nuk do të presë, por do ta informojë atë klient me "nuk e kam marrë parasysh", do të përpiqem t'i shtoj ato në taktin e ardhshëm." Kjo është bërë për të siguruar që, për shkak të problemeve me koneksionin (ose me kompjuterin) të një lojtarit, azhurnimi i hartës të mos ngadalësohet për të gjithë të tjerët. Duhet të theksohet se Input Actionsato nuk injorohen, por thjesht shtyhen. Input Actions Vonesa e rrugës përfundimtare për vajtje-ardhje: serveri përpiqet të parashikojë se cila është vonesa në transferimin e të dhënave për vajtje-ardhje midis klientit dhe serverit për çdo klient. Çdo 5 sekonda, ai kështu diskuton me klientin një vonesë të re (në varësi të mënyrës se si ishte koneksioni në të kaluarën), dhe për këtë arsye rrit ose ul vonesën e transferimit të të dhënave për vajtje-ardhje.
- Këta mekanizma në vetvete janë të thjeshtë, por kur përdoren së bashku (çka ndodh shpesh në rast të problemeve me koneksionin), logjika e kodit bëhet e vështirë të menaxhohet dhe ka shumë raste kufitar. Për më tepër, kur këta mekanizma janë në lojë, serveri dhe radhët e vonesave duhet të implementojnë saktë një të veçantë
Input Action StopMovementInTheNextTick të quajtur . Falë kësaj, në rast të problemeve me koneksionin, personazhi nuk do të vrapojë vetë (p.sh., nën tren).Tani duhet t'ju shpjegoj se si funksionon zgjedhja e entiteteve. Një nga llojet e transmetuara
është ndryshimi i gjendjes së zgjedhjes së entitetit. Ky ndryshim informon të gjithë se mbi cilin entitet lojtarin e ka kurorësuar miu. Siç mund të kuptohet, kjo është një nga veprimet më të zakonshme të inputeve të dërguara nga klientët, prandaj për të kursyer kapacitetin e kanalit, ne e kemi optimizuar atë për të zënë sa më pak hapësirë. Kjo është realizuar në këtë mënyrë: duke zgjedhur çdo entitet, në vend që të ruajmë koordinatat e sakta absolute të hartës, loja ruan një zhvendosje relative të saktë të ulët nga zgjedhja e mëparshme. Kjo funksionon mirë, sepse në shumicën e rasteve zgjedhja bëhet shumë afër zgjedhjes së mëparshme. Për shkak të kësaj, do të lindin dy kërkesa të rëndësishme: StopMovementInTheNextTick asnjëherë nuk duhet të humbasin dhe duhet të kryhen në rendin e duhur. Këto kërkesa përmbushen për Input Actions . Por, meqenëse detyra Game StateLatency state është "të duket mjaft e mirë" për lojtarin, nën gjendjen e vonesave ato nuk përmbushen. nuk merr parasysh Latency State shumë raste kufitare Tani mund të keni marrë vesh se çfarë po ndodh. Në fund, fillojmë të shohim arsyet e problemit të megapaketës. Thelbi i problemit është se, në marrjen e vendimit nëse duhet të transmetohet veprimi i ndryshimit të zgjedhjes, logjika e zgjedhjes së entiteteve mbështetet në
, e cila gjendje nuk e ka gjithmonë informacionin e saktë. Prandaj, megapaketa gjenerohet në mënyrë të tillë: Latency StateLojtari kishte probleme me koneksionin.
- Mekanizmat e humbjes së taktëve dhe rregullimit të vonesës për vajtje-ardhje intervenojnë.
- Radhët e gjendjes së vonesave nuk i marrin parasysh këta mekanizma. Kjo çon në faktin se disa veprime bëhen të fshirë paraprakisht ose realizohen në rend të gabuar, duke çuar në
- Lojtari humb problemin e koneksionit dhe, për t'u përshtatur me serverin, simulohet deri në 400 taktë. Latency State.
- Në çdo takt gjenerohet dhe përgatitet për t'u dërguar në server një veprim të ri të ndryshimit të zgjedhjes së entitetit.
- Klienti i dërgon serverit një megapaketë me më shumë se 400 ndryshime të zgjedhjes së entiteteve (edhe veprime të tjera, si gjendja e të gjuajturit, ecjes etj. gjithashtu vuajnë nga ky problem).
- Serveri merr 400 veprime inputi. Duke qenë se nuk i lejohet të humbasë asnjë veprim inputi, ai urdhëron të gjithë klientët të realizojnë këto veprime dhe i dërgon ato përmes rrjetit.
- Ironia është se mekanizmi i dizajnuar për të kursyer kapacitetin e kanalit, përfundimisht shkaktonte paketa rrjeti shumë të mëdha.
Ne e zgjidhëm këtë problem duke rregulluar të gjitha rastet kufitare të azhurnimit dhe duke mbështetur radhët e vonesave. Edhe pse kjo kërkoi shumë kohë, në fund ia doli të realizohet gjithçka siç duhet, në vend që të mbështeteshim në hack të shpejtë.
Ne e zgjidhur ky problem duke korrigjuar të gjitha rastet kufitare të përditësimit dhe mbështetjes së radhëve të vonesave. Edhe pse kjo mori një kohë të konsiderueshme, në fund ja vlen të realizosh gjithçka siç duhet, sesa të mbështetesh në hack të shpejtë.
Burimi: habr.com
