
Idén májusban részt vettem Észrevettem, hogy amikor a játékosok száma eléri a bizonyos szintet, néhány percenként egy részük "lecsatlakozik". Szerencsére (de nem számomra) én is azok közé tartoztam, akik lecsatlakoztak minden alkalommal, még egy jó kapcsolat mellett is. Ezt személyes kihívásnak tekintettem, és elkezdtem keresni a probléma okait. Három hét hibakeresés, tesztelés és javítás után végre sikerült megoldani a hibát, de ez az út nem volt olyan egyszerű.
A multiplayer játékok problémáit nagyon nehéz nyomon követni. Általában nagyon specifikus hálózati beállításokkal és játékállapotokkal (ebben az esetben több mint 200 játékos jelenléte) fordulnak elő. És még ha sikerül is reprodukálni a problémát, azt megfelelően hibakeresni lehetetlen, mert a ellenőrző pontok beillesztése leállítja a játékot, zavarja az időzítéseket és általában kapcsolatmegszakításhoz vezet a várakozási idő túllépése miatt. De a kitartásomnak és egy nagyszerű eszköznek, a köszönhetően sikerült kiderítenem, mi történik.
Röviden: a hiba és a szimulációs késleltetés hiányos megvalósítása miatt a kliens időnként olyan helyzetbe került, hogy egyetlen lépésben egy hálózati csomagot kellett küldenie, amely körülbelül 400 játékos által végrehajtott választási akciót tartalmazott (ezt hívjuk „megacsomagnak”). Ezt követően a szervernek nemcsak az összes bemeneti akciót helyesen kell megkapnia, hanem azokat minden más kliensnek is el kell küldenie. Ha 200 kliens van, ez gyorsan problémává válik. A szerverhez vezető csatorna gyorsan elakad, ami csomagok elvesztéséhez és ismételt kérések láncolatához vezet. A bemeneti akciók késlekedése pedig arra ösztönöz még több klienst, hogy megacsomagokat küldjön, és az áradata egyre erősebbé válik. A szerencsés kliensek vissza tudnak állni, míg a többiek "lecsatlakoznak".

A probléma eléggé alapvető volt, és két hétig tartott a megoldása. Nagyon technikai, ezért alább részletesen kifejtem a technikai részleteket. De először is tudnia kell, hogy a 0.17.54-es verzió megjelenése óta, június 4-én, a többjátékos mód sokkal stabilabbá vált, és a késleltetések elrejtése sokkal kevésbé akadozik (kevesebb késlekedés és teleportálás). Ezenkívül megváltoztattam a késleltetések elrejtésének módját a harcban, és remélem, hogy ezáltal kissé simábbá válnak.
Többjátékos megakészlet – technikai részletek
Egyszerűsítve elmagyarázva, a játék többjátékos módja a következőképpen működik: minden kliens szimulálja a játék állapotát, csak a játékosok bemenetét (úgynevezett "bemendi akciók") fogadva és küldve. Input Actions. A szerver fő feladata az, hogy Input Actions ellenőrizze és vezérelje, hogy minden kliens ugyanazokat a cselekvéseket hajtsa végre egy menetben. Erről többet olvashat a .
bejegyzésben. Mivel a szervernek döntenie kell, hogy milyen akciókat kell végrehajtani, a játékos cselekvések körülbelül a következő úton haladnak: játékos akció -> játék kliens -> hálózat -> szerver -> hálózat -> játék kliens. Ez azt jelenti, hogy minden játékos akció csak akkor hajtható végre, miután az oda-vissza megtette az utat a hálózaton. Emiatt a játék rettenetesen akadozónak tűnne, ezért majdnem azonnal a többjátékos megjelenése után bevezették a késleltetések elrejtésének mechanizmusát. A késleltetések elrejtése szimulálja a játékos bemenetét anélkül, hogy figyelembe venné más játékosok akcióit és a szerver döntéseit.

A Factorióban van egy játéknak nevezett állapot Game State – ez a teljes térkép, játékos, entitások és minden egyéb állapota. Ez determinisztikusan van szimulálva minden kliensben a szervertől kapott akciók alapján. A játéknak ez az állapota szent, és ha valaha eltér a szervertől vagy bármely más kliens állapotától, deszinkronizáció lép fel.
Ezen kívül Game State Van nekünk egy késleltetés állapotunk Latency State. Ez tartalmaz egy kis részhalmazt a fő állapotból. Latency State Nem szent, és csak egy képet mutat arról, hogy néz ki a játék állapota a jövőben a játékos által bevitt adatok alapján. Input Actions.
Ehhez megőrzünk egy példányt a létrehozott Input Actions késleltetési sorban.

Tehát a folyamat végén az ügyfél oldalán így néz ki a helyzet:
- Alkalmazzuk Input Actions minden játékost a Game State így, ahogyan ezek a bemeneti műveletek az internetről érkeztek.
- Eltávolítjuk a késleltetési sorból az összes Input Actions, amelyeket a szerver adatai szerint már alkalmaztak a Game State.
- Eltávolítjuk Latency State és visszaállítjuk, hogy pontosan úgy nézzen ki, mint a Game State.
- Alkalmazzuk az összes műveletet a késleltetési sorból a Latency State.
- A rendelkezésre álló adatok alapján Game State és Latency State képzeljük el a játékot a játékos számára.
Ez mindegyik ütemben megismétlődik.
Túl bonyolult? Ne lazítsanak, ez még nem minden. Az internet kapcsolatok megbízhatatlanságának kompenzálására két mechanizmust hoztunk létre:
- Kihagyott ütemek: amikor a szerver úgy dönt, hogy Input Actions az ütemben végrehajtásra kerülnek, ha nem kapott meg Input Actions egy játékost (például a megnövekedett késleltetés miatt), akkor nem várakozik, hanem értesíti az ügyfelet, hogy „nem vettem figyelembe a Input Actions, megpróbálom hozzáadni őket a következő ütemhez”. Ez azért van, hogy egy játékos kapcsolatának (vagy számítógépének) problémái miatt a térkép frissítése ne lassuljon le az összes többi számára. Érdemes megjegyezni, hogy Input Actions nem ignorálásra kerülnek, hanem csak elhalasztásra.
- Teljes oda-vissza késleltetés: a szerver próbálja megjósolni, hogy mekkora a késleltetés az adatok oda-vissza továbbításában az ügyfél és a szerver között minden egyes játékos számára. Öt másodpercenként, szükség esetén, új késleltetést beszél meg az ügyféllel (attól függően, hogy a kapcsolat hogyan viselkedett a múltban), és ennek megfelelően növeli vagy csökkenti az oda-vissza adatátvitel késleltetését.
Ezek a mechanizmusok önállóan meglehetősen egyszerűek, de amikor együtt használják őket (ami gyakran előfordul kapcsolati problémák esetén), a kód logikája nehezen kezelhetővé válik, sok határhelyzettel. Ezenkívül, amikor ezek a mechanizmusok belépnek a képbe, a szervernek és a késleltetési soroknak megfelelően kell implementálniuk a különleges Input Action Nornir néven hívtak StopMovementInTheNextTick. Ennek köszönhetően a kapcsolat problémái esetén a karakter nem fut önállóan (például vonat alá).
Most el kell magyaráznom, hogyan működik az entitások kiválasztása. Az egyik átvitt típusról van szó Input Action — ez a kiválasztott entitás állapotának megváltoztatása. Ez jelzi mindenkinek, hogy a játékos melyik entitásra mutatott az egérrel. Ahogy látható, ez az egyik leggyakoribb beviteli művelet, amelyet az ügyfelek küldenek, ezért a sávszélesség megőrzése érdekében optimalizáltuk, hogy a lehető legkevesebb helyet foglaljon el. Ezt úgy valósítottuk meg, hogy minden entitás kiválasztásakor, a pontos térképkoordináták megőrzése helyett, a játék relatív eltolását mentjük el az előző választáshoz képest. Ez jól működik, mivel az egérrel történő kijelölés általában nagyon közel történik az előző kijelöléshez. Ennek következményeként két fontos követelmény merül fel: Input Actions soha nem hagyható ki, és bármilyen esetben a helyes sorrendben kell végrehajtani. Ezek a követelmények teljesülnek Game State. De mivel a feladat Késleltetési állapot abból áll, hogy 'elég jónak' tűnjön a játékos számára, késleltetés állapotában ezek nem teljesülnek. Latency State nem veszi figyelembe , amelyek a ciklusok kihagyásával és a kétirányú késleltetések megváltoztatásával kapcsolatosak.
Már gyaníthatod, hogy hova tart ez. Végre kezdjük látni a megacsomagolás problémájának okait. A probléma gyökere abban rejlik, hogy a kiválasztás módosításának átvitele során a kiválasztott entitások logikája a Latency State, és ez az állapot nem mindig tartalmazza a helyes információt. Ezért a megacsomagolás körülbelül így generálódik:
- A játékosnak problémái vannak a kapcsolattal.
- Belépnek a ciklusok kihagyására és a kétirányú késleltetés szabályozására vonatkozó mechanizmusok.
- A késleltetési állapot sor nem veszi figyelembe ezeket a mechanizmusokat. Ez oda vezet, hogy bizonyos műveletek idő előtt törlésre kerülnek, vagy helytelen sorrendben végrehajtásra kerülnek, ami helytelen Latency State.
- A játékosnak megszűnik a kapcsolati problémája, és hogy behozza magát a szerverhez, akár 400 ciklust is szimulál.
- Minden ciklusban új művelet, az entitás kiválasztási módosítása generálódik és előkészül a szerverre történő küldésre.
- Az ügyfél több mint 400 entitásváltoztatást (és egyéb műveleteket, mint például a lövés, járás stb. is érintett volt ez a probléma) tartalmazó megacsomagot küld a szervernek.
- A szerver 400 bemeneti műveletet kap. Mivel nem engedélyezett egyetlen bemeneti művelet kihagyása sem, utasítja az összes ügyfelet, hogy végezze el ezeket a műveleteket és küldje el őket a hálózaton.
A irónia abban rejlik, hogy a mechanizmus, amelyet a sávszélesség megtakarítására szántak, végül hatalmas hálózati csomagokat generált.
Ezt a problémát úgy oldottuk meg, hogy javítottuk az összes határhelyzetet a frissítésekben és a késleltetési sor támogatásában. Bár ez eléggé időigényes volt, végül megérte, hogy mindent helyesen valósítottunk meg, ahelyett, hogy gyors megoldásokra támaszkodtunk volna.
Forrás: habr.com
