
Në maj të këtij viti, unë mora pjesë si lojtar në . Vura re se kur numri i lojtarëve arrin një numër të caktuar, pas çdo disa minutash, një pjesë e tyre "bien". Me fat 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 mora këtë si një sfidë personale dhe fillova të kërkoj arsyet e problemit. Pas tre javësh debugging, testimi dhe rregullimesh, gabimi u eliminua përfundimisht, por ky udhëtim nuk ishte aq i lehtë.
Problemet e lojërave multiplayer janë shumë të vështira për t'u gjurmuar. Ato zakonisht lindin në kushte shumë specifike të parametra të rrjetit dhe në gjendje shumë specifike të lojës (në këtë rast - praninë e mbi 200 lojtarëve). Dhe edhe kur arrin të riprodhosh problemin, është e pamundur ta debugosh siç duhet, sepse vendosja e pikave kontrolli ndalon lojën, ngatërroj temporët dhe zakonisht çon në përfundimin e lidhjes për shkak të tejkalimit të afatit. Por falë këmbënguljes dhe një mjeti të mrekullueshëm të quajtur , arrita të zbuloj se çfarë po ndodhte.
Në përmbledhje: për shkak të një gabimi dhe implementimi të papërfunduar të simulimit të gjendjes së vonesës, klienti ndonjëherë gjendej në një situatë ku duhej të dërgonte një paketë rrjeti, e cila përmbante veprimet e lojtarëve në lidhje me rreth 400 entitete lojrash (ne e quajmë "megapaketa"). Pas kësaj, serveri duhet jo vetëm të marrë saktësisht të gjitha këto veprime hyrëse, por gjithashtu të dërgojë ato te të gjithë klientët e tjerë. Nëse ke 200 klientë, kjo bëhet shpejt një problem. Kanali drejt serverit shpejt mbushet, çka çon në humbje paketash dhe një kaskadë të kërkesave të ripërsëritura për paketa. Vonimi i veprimeve hyrëse pastaj çon në atë që edhe më shumë klientë fillojnë të dërgojnë megapaketa, dhe mbingarkesa e tyre bëhet edhe më e fortë. Klientët me fat arrijnë të rikuperohen, të tjerët "bien".

Problemi ishte mjaft themelor dhe më zuri dy javë për ta zgjidhur. Ajo është mjaft teknike, prandaj më poshtë do të shpjegoj detajet teknike të hollësishme. Por fillimisht duhet të dini se nga versioni 0.17.54, i lëshuar më 4 qershor, në kushtet e problemeve të përkohshme me lidhjen, multiplayer bëhet më i qëndrueshëm, dhe fshehja e vonesave është shumë më pak me defekte (më pak ngecje dhe teleporter). Për më tepër, e kam ndryshuar mënyrën e fshehjes së vonesave në luftime dhe shpresoj që për këtë arsye ato do të jenë pak më të buta.
Megan multiplayer - detajet teknike
Nëse e shpjegojmë thjesht, multiplayer në lojë funksionon si më poshtë: të gjithë klientët simulojnë gjendjen e lojës, duke marrë dhe dërguar vetëm hyrjen e lojtarit (të quajtur "veprimet e hyrjes", Input Actions). Detyra kryesore e serverit është të transferojë Input Actions dhe të kontrollojë që të gjithë klientët të realizojnë të njëjtat veprime në një cikël. Më shumë informacion rreth kësaj mund të lexoni në postimin .
Duke qenë se serveri duhet të marrë vendime në lidhje me veprimet që duhet të realizohen, veprimi i lojtarit kalon nëpër një rrugë të tillë: veprimi i lojtarit -> klienti i lojës -> rrjeti -> serveri -> rrjeti -> klienti i lojës. Kjo do të thotë se çdo veprim i lojtarit bëhet vetëm pasi kalon në rrugën e tij përmes rrjetit. Për këtë arsye, loja do të dukej shumë e ndaluar, prandaj pothuajse menjëherë pas shfaqjes së multiplayer-it në lojë u krijua një mekanizëm për fshehjen e vonesave. Fshehja e vonesave imiton hyrjen e lojtarit pa marrë parasysh veprimet e lojtarëve të tjerë dhe vendimet e serverit.

Në Factorio ka një gjendje lojë 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 në bazë të veprimeve të marra nga serveri. Gjendja e lojës është e shenjtë, dhe nëse ndonjëherë fillon të ndryshojë nga serveri ose nga ndonjë klient tjetër, ndodh një desinkronizim.
Përveç Game State ne kemi një gjendje vonesash Latency State. Ajo përmban një nën-grup të vogël të gjendjes kryesore. Latency State nuk është e shenjtë dhe thjesht paraqet një pamje të asaj si do të duket gjendja e lojës në të ardhmen bazuar në hyrjet e lojtarëve. Input Actions.
Për këtë, ne ruajmë një kopje të krijuar Input Actions në radhën e vonesave.

Pra fillon, skena në anën e klientit duket pak a shumë kështu:
- Aplikojmë Input Actions të gjithë lojtarët në Game State ashtu siç këto veprime hyrëse u morën nga serveri.
- Fshijmë nga radhët e vonesave të gjitha Input Actions, që sipas të dhënave të serverit, tashmë ishin 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 vonesave në Latency State.
- Në bazë të të dhënave Game State dhe Latency State rendërtojmë lojën për lojtarin.
E gjithë kjo përsëritet në çdo cikël.
Për shumë të komplikuar? Mos u relaksoni, kjo nuk është e gjithë historia. Për të kompensuar pasigurinë e lidhjeve të internetit, ne kemi krijuar dy mekanizma:
- Ciklet e humbura: kur serveri vendos se Input Actions do të realizohen në ciklin e lojës, nëse ai nuk mori Input Actions asnjë lojtar (për shembull, për shkak të rritjes së vonesës), ai nuk do të presë, por do t'i thotë këtij klienti «nuk i mora parasysh të dhënat e tua, do të përpiqem t'i përfshij në ciklin e ardhshëm». Kjo është bërë që, për shkak të problemeve me lidhjen (apo me kompjuterin) të një lojtari, përditësimi i hartës të mos ngadalësohet për të gjithë të tjerët. Vlen të përmendet se Input Actionsnuk injorohen, por thjesht shtyhen. Input Actions Vonesa e kompletuar e rrugës përpara dhe pas: serveri përpiqet të supozojë se cila është vonesa e transmetimit të të dhënave mes klientit dhe serverit për secilin klient. Çdo 5 sekonda ai diskuton me klientin për një vonesë të re (në varësi të asaj si ka vepruar lidhja në të kaluarën), dhe kështu rrit apo zvogëlson përkatësisht vonesën e transmetimit të të dhënave.
- Këta mekanizma vetë janë mjaft të thjeshtë, por kur përdoren së bashku (çka ndodh shpesh në probleme me lidhjen), logjika e kodit bëhet e vështirë për t'u menaxhuar dhe me shumë raste kufitare. Për më tepër, kur këta mekanizma hyjnë në lojë, serveri dhe radhët e vonesave duhet të implementojnë saktësisht një
Input Action StopMovementInTheNextTick me emrin . Falë kësaj, në rastin e problemeve me lidhjen, personazhi nuk do të vrapojë vetë (për shembull, nën një tren).Tani duhet t'ju shpjegoj se si funksionon selektimi i entiteteve. Një nga llojet e transmetuara
Tani duhet të shpjegojmë se si funksionon zgjedhja e entiteteve. Një nga tipet e transmetuara StopMovementInTheNextTick — është një ndryshim i gjendjes së zgjedhjes së një entiteti. Ajo i komunikon të gjithëve se mbi cilin entitet ka kaluar kursori i lojtarit. Siç mund të kuptohet, ky është një nga veprimet më të shpeshta të hyrjes që dërgojnë klientët, prandaj për të kursyer kapacitetin e kanalit, ne e kemi optimizuar atë që të zërë sa më pak hapësirë. Kjo realizohet kështu: për secilën zgjedhje të entitetit, në vend që të ruajmë koordinatat absolute dhe shumë të sakta të hartës, loja ruan një zhvendosje relative me saktësi të ulët nga zgjedhja e mëparshme. Kjo funksionon mirë, sepse selektimi me miun zakonisht ndodh shumë afër zgjedhjes së mëparshme. Për këtë arsye, ndodhin dy kërkesa të rëndësishme: Input Actions nuk duhet kurrë të injorohen dhe duhet të përmbushen në rendin e duhur. Këto kërkesa përmbushen për Game State. Por qysh se detyra Stati i vonesës është "të duket mjaft mirë" për lojtarin, në gjendjen e vonesave këto nuk përmbushen. Latency State nuk merr parasysh , që lidhen me humbjen e cikle dhe ndryshimin e vonesave të dërgimit nga njëra anë në tjetrën.
Tani ju mund të filloni të kuptoni se çfarë po ndodh. Më në fund, ne fillojmë të shohim arsyet e problemit të mega-paketës. Zgjidhja e problemit është se në vendimin për të përcaktuar nëse duhet të transmitohet veprimi i ndryshimit të zgjedhjes, logjika e zgjedhjes së entiteteve mbështetet në Latency State, dhe kjo gjendje nuk përmban gjithmonë informacion të saktë. Prandaj, mega-paketa gjenerohet kështu:
- Lojtari ka probleme me lidhjen.
- Mekanizmat e humbjes së cikle dhe rregullimit të vonesave të dërgimit nga njëra anë në tjetrën hyjnë në lojë.
- Rradha e gjendjes së vonesës nuk merr parasysh këto mekanizma. Kjo rezulton në faktin se disa veprime hiqen para kohe ose kryhen në rend të gabuar, duke çuar në Latency State.
- Lojtari rimerr lidhjen dhe, për t'u përgatitur për serverin, simuluan deri në 400 cikle.
- Në çdo cikël gjenerohet dhe përgatitet për dërgim në server një veprim i ri për ndryshimin e zgjedhjes së entitetit.
- Klienti dërgon serverit një mega-pakete prej më shumë se 400 ndryshimesh zgjedhjesh të entiteteve (dhe veprime të tjera: gjendja e qëllimit, ecjes etj. gjithashtu vuajnë nga ky problem).
- Serveri merr 400 veprime hyrëse. Duke qenë se nuk i lejohet të kalojë asnjë veprim hyrës, ai urdhëron të gjithë klientët të kryejnë këto veprime dhe i dërgon ato nëpërmjet rrjetit.
Ironia qëndron në faktin se mekanizmi i diseñado për të kursyer kapacitetin e kanalit, përfundimisht krijonte paketa rrjeti të mëdha.
Ne e zgjodhëm këtë problem, duke përmirësuar të gjitha rastet kufitare të përditësimeve dhe mbështetjes së radhës të vonesave. Edhe pse kjo kërkoi mjaft kohë, në fund ia vlen për ta realizuar gjithçka siç duhet dhe jo të mbështetemi në haks të shpejtë.
Burimi: habr.com
