Megapack: hoe de Factorio-ontwikkelaars het probleem met multiplayer voor 200 spelers hebben weten op te lossen

Megapack: hoe de Factorio-ontwikkelaars het probleem met multiplayer voor 200 spelers hebben weten op te lossen
In mei van dit jaar heb ik als speler deelgenomen aan MMO-evenementen KatherineOfSky. Ik merkte dat wanneer het aantal spelers een bepaald aantal bereikt, er om de paar minuten een aantal van hen “eraf vallen”. Gelukkig voor jou (maar niet voor mij) was ik een van die spelers die de verbinding verbrak elke keer, zelfs met een goede verbinding. Ik vatte dit op als een persoonlijke uitdaging en ging op zoek naar de oorzaken van het probleem. Na drie weken van debuggen, testen en repareren was de bug eindelijk opgelost, maar de reis was niet zo eenvoudig.

Problemen met multiplayer-spellen zijn erg moeilijk op te sporen. Ze komen meestal voor onder zeer specifieke netwerkparameters en zeer specifieke spelomstandigheden (in dit geval met meer dan 200 spelers). En zelfs als het probleem kan worden gereproduceerd, kan het niet op de juiste manier worden opgespoord, omdat het invoegen van breekpunten het spel stopt, de timers in de war brengt en er meestal voor zorgt dat de verbinding wordt verbroken. Maar dankzij doorzettingsvermogen en een prachtig hulpmiddel genaamd onhandig Het lukte mij om erachter te komen wat er aan de hand was.

Kortom, als gevolg van een bug en een onvolledige implementatie van de latentiestatussimulatie, kwam de klant soms in een situatie terecht waarin hij een netwerkpakket moest verzenden dat bestond uit de invoerselectieacties van de speler van ongeveer 400 spelentiteiten in één klokcyclus ( we noemen dit een "megapakket"). De server moet dan niet alleen al deze invoeracties correct ontvangen, maar deze ook naar alle andere clients sturen. Als je 200 klanten hebt, wordt dit al snel een probleem. De verbinding met de server raakt snel verstopt, wat leidt tot pakketverlies en een cascade van opnieuw aangevraagde pakketten. Het uitstellen van de invoeractie zorgt er vervolgens voor dat nog meer clients megapakketten versturen, waardoor de lawine nog groter wordt. Gelukkige klanten slagen erin te herstellen; alle anderen vallen eraf.

Megapack: hoe de Factorio-ontwikkelaars het probleem met multiplayer voor 200 spelers hebben weten op te lossen
Het probleem was vrij fundamenteel en het kostte me twee weken om het op te lossen. Het is behoorlijk technisch, dus ik zal de sappige technische details hieronder uitleggen. Maar eerst moet je weten dat sinds versie 2, uitgebracht op 0.17.54 juni, de multiplayer, ondanks tijdelijke verbindingsproblemen, stabieler is geworden en dat het verbergen van vertragingen veel minder buggy is geworden (minder vertraging en teleporteren). Ik heb ook de manier veranderd waarop gevechtsvertraging wordt verborgen en ik hoop dat dit het een beetje soepeler zal maken.

Multiplayer-megapakket - Technische details

Simpel gezegd werkt multiplayer in een spel als volgt: alle clients simuleren de status van het spel, waarbij alleen spelersinvoer wordt ontvangen en verzonden (zogenaamde “invoeracties”, Invoeracties). De hoofdtaak van de server is verzenden Invoeracties en controleren of alle clients dezelfde acties uitvoeren in dezelfde klokcyclus. Meer hierover leest u in het bericht FFF-149.

Omdat de server beslissingen moet nemen over welke acties hij moet uitvoeren, bewegen de acties van de speler zich ongeveer langs dit pad: speleractie -> gameclient -> netwerk -> server -> netwerk -> gameclient. Dit betekent dat de actie van elke speler pas wordt uitgevoerd nadat hij een rondreis door het netwerk heeft gemaakt. Hierdoor leek de game vreselijk traag, dus vrijwel onmiddellijk na de introductie van multiplayer in de game werd er een mechanisme geïntroduceerd om vertragingen te verbergen. Door de vertraging te verbergen, wordt de invoer van spelers gesimuleerd zonder rekening te houden met de acties van andere spelers en de beslissingen van de server.

Megapack: hoe de Factorio-ontwikkelaars het probleem met multiplayer voor 200 spelers hebben weten op te lossen
Factorio heeft een spelstatus spel staat is de volledige staat van de kaart, speler, entiteiten en al het andere. Het wordt deterministisch gesimuleerd in alle clients op basis van de acties die van de server worden ontvangen. De spelstatus is heilig, en als deze ooit begint te verschillen van de server of een andere client, vindt desynchronisatie plaats.

Maar spel staat we hebben een staat van vertraging Latentiestatus. Het bevat een kleine subset van de grondtoestand. Latentiestatus niet heilig en vertegenwoordigt eenvoudigweg een beeld van hoe de spelstatus er in de toekomst uit zal zien op basis van de input van spelers Invoeracties.

Voor dit doeleinde slaan wij een kopie van het aangemaakte bestand op Invoeracties in de vertragingswachtrij.

Megapack: hoe de Factorio-ontwikkelaars het probleem met multiplayer voor 200 spelers hebben weten op te lossen
Dat wil zeggen, aan het einde van het proces aan de klantzijde ziet het plaatje er ongeveer zo uit:

  1. Van toepassing zijn Invoeracties alle spelers spel staat de manier waarop deze invoeracties van de server zijn ontvangen.
  2. We verwijderen alles uit de vertragingswachtrij Invoeracties, waarop volgens de server al is toegepast spel staat.
  3. Verwijderen Latentiestatus en reset het zodat het er precies hetzelfde uitziet als spel staat.
  4. We passen alle acties uit de vertragingswachtrij toe Latentiestatus.
  5. Gebaseerd op gegevens spel staat и Latentiestatus We geven het spel weer aan de speler.

Dit alles wordt in elke maat herhaald.

Te moeilijk? Ontspan niet, dit is niet alles. Om onbetrouwbare internetverbindingen te compenseren, hebben we twee mechanismen gecreëerd:

  • Gemiste ticks: wanneer de server dat beslist Invoeracties zal worden uitgevoerd op het ritme van het spel, en als hij het niet heeft ontvangen Invoeracties een speler (bijvoorbeeld vanwege toegenomen vertraging), hij zal niet wachten, maar zal deze klant informeren: “Ik heb geen rekening gehouden met uw Invoeracties, Ik zal proberen ze in de volgende balk toe te voegen. Dit wordt gedaan zodat vanwege problemen met de verbinding (of computer) van de ene speler de kaartupdate niet voor alle anderen langzamer verloopt. Het is vermeldenswaard dat Invoeracties worden niet genegeerd, maar simpelweg terzijde geschoven.
  • Volledige round-trip-latentie: De server probeert te raden wat de round-trip-latentie tussen de client en de server voor elke client is. Elke 5 seconden onderhandelt het indien nodig over een nieuwe latentie met de client (op basis van hoe de verbinding zich in het verleden heeft gedragen) en wordt de round-trip latentie dienovereenkomstig verhoogd of verlaagd.

Op zichzelf zijn deze mechanismen vrij eenvoudig, maar als ze samen worden gebruikt (wat vaak gebeurt bij verbindingsproblemen), wordt de logica van de code moeilijk te beheren en met veel randgevallen. Bovendien moeten, wanneer deze mechanismen een rol gaan spelen, de server en de vertragingswachtrij de special correct implementeren Invoeractie gerechtigd StopMovementInTheNextTick. Hierdoor zal het personage, als er verbindingsproblemen zijn, niet alleen rennen (bijvoorbeeld voor een trein).

Nu moeten we u uitleggen hoe entiteitselectie werkt. Een van de verzonden typen Invoeractie is een verandering in de entiteitselectiestatus. Het vertelt iedereen over welke entiteit de speler zweeft. Zoals u zich kunt voorstellen, is dit een van de meest voorkomende invoeracties die door clients worden verzonden. Om bandbreedte te besparen hebben we deze geoptimaliseerd zodat deze zo min mogelijk ruimte in beslag neemt. De manier waarop het werkt is dat wanneer elke entiteit wordt geselecteerd, het spel in plaats van absolute, uiterst nauwkeurige kaartcoördinaten op te slaan, een relatieve offset met lage precisie ten opzichte van de vorige selectie opslaat. Dit werkt goed omdat muisselecties vaak heel dicht bij de vorige selectie liggen. Dit stelt twee belangrijke eisen: Invoeracties Ze mogen nooit worden overgeslagen en moeten in de juiste volgorde worden uitgevoerd. Aan deze eisen wordt voldaan spel staat. Maar sinds de taak Latentiestatus als ze er "goed genoeg uitzien" voor de speler, zijn ze niet tevreden in de vertragingsstatus. Latentiestatus houdt geen rekening mee veel randgevallen, geassocieerd met het overslaan van klokcycli en veranderende transmissievertragingen heen en terug.

Je kunt al raden waar dit heen gaat. We beginnen eindelijk de redenen voor het megapack-probleem te zien. De wortel van het probleem is dat de logica voor entiteitsselectie afhankelijk is van Latentiestatus, en deze status bevat niet altijd de juiste informatie. Daarom wordt een megapakket ongeveer als volgt gegenereerd:

  1. De speler heeft verbindingsproblemen.
  2. Mechanismen voor het overslaan van klokcycli en het reguleren van de vertraging van retourtransmissie komen in het spel.
  3. De vertragingsstatuswachtrij houdt geen rekening met deze mechanismen. Dit zorgt ervoor dat sommige acties voortijdig worden verwijderd of in de verkeerde volgorde worden uitgevoerd, wat resulteert in onjuiste acties Latentiestatus.
  4. De speler heeft een verbindingsprobleem en simuleert tot 400 cycli om de server in te halen.
  5. Bij elke tik wordt een nieuwe actie, waarbij de entiteitselectie wordt gewijzigd, gegenereerd en voorbereid voor verzending naar de server.
  6. De client stuurt een megabatch van meer dan 400 entiteitselectiewijzigingen naar de server (en bij andere acties: schietstaten, loopstaten, enz. Had dit probleem ook).
  7. De server ontvangt 400 invoeracties. Omdat het geen enkele invoeractie mag overslaan, geeft het alle clients de opdracht om die acties uit te voeren en stuurt deze over het netwerk.

De ironie is dat een mechanisme dat is ontworpen om bandbreedte te besparen uiteindelijk enorme netwerkpakketten heeft gecreëerd.

We hebben dit probleem opgelost door alle randgevallen van ondersteuning voor updates en backlog-wachtrijen op te lossen. Hoewel het behoorlijk wat tijd kostte, was het uiteindelijk de moeite waard om het goed te doen in plaats van te vertrouwen op snelle hacks.

Bron: www.habr.com

Voeg een reactie