Megapack : comment Factorio a résolu le problème du multijoueur à 200 joueurs

Megapack : comment Factorio a résolu le problème du multijoueur à 200 joueurs
En mai de cette année, j'ai participé en tant que joueur à Événements MMO KatherineOfSky. J'ai remarqué que lorsque le nombre de joueurs atteint un certain nombre, toutes les quelques minutes certains d'entre eux "tombent". Heureusement pour vous (mais pas pour moi), j'étais l'un de ces joueurs chaque foismême avec une bonne connexion. Je l'ai pris comme un défi personnel et j'ai commencé à chercher les causes du problème. Après trois semaines de débogage, de test et de correction, le bogue est enfin corrigé, mais le voyage n'a pas été si facile.

Les problèmes dans les jeux multijoueurs sont très difficiles à détecter. Ils se produisent généralement sous des paramètres de réseau très spécifiques et dans des états de jeu très spécifiques (dans ce cas, plus de 200 joueurs). Et même lorsqu'un problème peut être reproduit, il ne peut pas être correctement débogué car l'insertion de points d'arrêt arrête le jeu, perturbe les minuteries et entraîne généralement l'expiration de la connexion en raison d'un délai d'attente. Mais grâce à la persévérance et à un merveilleux outil appelé maladroit J'ai pu comprendre ce qui se passait.

En bref, en raison d'un bogue et d'une implémentation incomplète de la simulation d'état de retard, le client s'est parfois retrouvé dans une situation où il devait envoyer un paquet réseau en un cycle d'horloge, consistant en des actions d'entrée du joueur pour sélectionner environ 400 entités de jeu ( nous l'appelons un "mégapaquet"). Après cela, le serveur doit non seulement recevoir correctement toutes ces actions d'entrée, mais également les envoyer à tous les autres clients. Si vous avez 200 clients, cela devient rapidement un problème. Le canal vers le serveur devient rapidement obstrué, ce qui entraîne une perte de paquets et une cascade de paquets redemandés. Le report des actions de saisie amène alors davantage de clients à envoyer des mégapaquets, et leur avalanche devient encore plus forte. Les clients qui réussissent parviennent à récupérer, tout le reste tombe.

Megapack : comment Factorio a résolu le problème du multijoueur à 200 joueurs
Le problème était assez fondamental, et il m'a fallu 2 semaines pour le régler. C'est assez technique, donc je vais vous expliquer les détails techniques juteux ci-dessous. Mais d'abord, il faut savoir que depuis la version 0.17.54, sortie le 4 juin, face à des problèmes de connexion temporaires, le multijoueur est devenu plus stable, et le masquage des délais est beaucoup moins bogué (moins de freinage et de téléportation). De plus, j'ai changé la façon dont les retards de combat sont cachés, et j'espère que cela les rendra un peu plus fluides.

Méga Pack Multijoueur - Détails Techniques

Pour le dire simplement, le multijoueur dans un jeu fonctionne comme ceci : tous les clients simulent l'état du jeu en recevant et en envoyant uniquement les entrées des joueurs (appelées "actions d'entrée" Actions d'entrée). La tâche principale du serveur est de transférer Actions d'entrée et s'assurer que tous les clients effectuent les mêmes actions dans le même cycle. Vous pouvez en savoir plus à ce sujet dans le post. FFF-149.

Étant donné que le serveur doit prendre des décisions sur les actions à entreprendre, les actions du joueur suivent le chemin suivant : action du joueur -> client du jeu -> réseau -> serveur -> réseau -> client du jeu. Cela signifie que chaque action du joueur n'est effectuée qu'après avoir effectué un aller-retour à travers le réseau. Pour cette raison, le jeu aurait semblé terriblement lent, donc presque immédiatement après l'apparition du multijoueur dans le jeu, un mécanisme de masquage des retards a été introduit. Le masquage de la latence simule l'entrée du joueur sans tenir compte des actions des autres joueurs et de la prise de décision du serveur.

Megapack : comment Factorio a résolu le problème du multijoueur à 200 joueurs
Factorio a un état de jeu état du jeu est l'état complet de la carte, du joueur, des entités et de tout le reste. Il est simulé de manière déterministe dans tous les clients en fonction des actions reçues du serveur. L'état du jeu est sacré, et s'il commence à différer du serveur ou de tout autre client, une désynchronisation se produit.

Mais état du jeu nous avons un état de retard État de latence. Il contient un petit sous-ensemble de l'état principal. État de latence n'est pas sacré et représente simplement une image de ce à quoi ressemblera l'état du jeu à l'avenir en fonction des contributions du joueur Actions d'entrée.

Pour ce faire, nous gardons une copie du fichier généré Actions d'entrée dans la file d'attente.

Megapack : comment Factorio a résolu le problème du multijoueur à 200 joueurs
C'est-à-dire qu'à la fin du processus côté client, l'image ressemble à ceci :

  1. Appliquer Actions d'entrée tous les joueurs à état du jeu la façon dont ces actions d'entrée ont été reçues du serveur.
  2. Supprimer tout de la file d'attente de retard Actions d'entrée, qui, selon le serveur, ont déjà été appliquées à état du jeu.
  3. Effacer État de latence et réinitialisez-le pour qu'il ressemble exactement à état du jeu.
  4. Appliquer toutes les actions de la file d'attente de retard à État de latence.
  5. Basé sur des données état du jeu и État de latence rendre le jeu au joueur.

Tout cela se répète à chaque battement.

Trop difficile? Ne vous détendez pas, ce n'est pas tout. Pour compenser les connexions Internet peu fiables, nous avons créé deux mécanismes :

  • Ticks sautés : lorsque le serveur décide que Actions d'entrée sera exécuté dans le tact du jeu, alors s'il n'a pas reçu Actions d'entrée certain joueur (par exemple, en raison d'un retard accru), il n'attendra pas, mais informera ce client "Je n'ai pas pris en compte votre Actions d'entrée, je vais essayer de les ajouter dans la barre suivante. Ceci est fait pour qu'en raison de problèmes de connexion (ou avec l'ordinateur) d'un joueur, la mise à jour de la carte ne ralentisse pas pour tous les autres. Il est à noter que Actions d'entrée ne sont pas ignorés, mais simplement reportés.
  • Latence aller-retour complète : le serveur essaie de deviner quelle est la latence aller-retour entre le client et le serveur pour chaque client. Toutes les 5 secondes, il négocie un nouveau délai avec le client selon les besoins (selon le comportement de la connexion dans le passé) et augmente ou diminue le délai aller-retour en conséquence.

En eux-mêmes, ces mécanismes sont assez simples, mais lorsqu'ils sont utilisés ensemble (ce qui arrive souvent avec des problèmes de connexion), la logique du code devient difficile à gérer et avec beaucoup de cas extrêmes. De plus, lorsque ces mécanismes entrent en jeu, le serveur et la file d'attente doivent correctement implémenter un Action d'entrée intitulé StopMovementInTheNextTick. Grâce à cela, en cas de problème de connexion, le personnage ne courra pas seul (par exemple, sous un train).

Maintenant, je dois vous expliquer comment fonctionne la sélection d'entités. Un des types passés Action d'entrée est un changement dans l'état de sélection d'une entité. Il indique à tout le monde sur quelle entité le joueur a survolé avec la souris. Comme vous pouvez le voir, c'est l'une des actions d'entrée les plus fréquentes envoyées par les clients, donc pour économiser de la bande passante, nous l'avons optimisée afin qu'elle prenne le moins de place possible. Ceci est implémenté comme ceci : lorsque chaque entité est sélectionnée, au lieu de stocker des coordonnées cartographiques absolues et de haute précision, le jeu stocke un décalage relatif de faible précision par rapport à la sélection précédente. Cela fonctionne bien car la sélection de la souris se produit généralement très près de la sélection précédente. Cela donne lieu à deux exigences importantes : Actions d'entrée ne doit jamais être ignoré et doit être effectué dans le bon ordre. Ces exigences sont remplies pour état du jeu. Mais puisque la tâche état de latence en "semblant assez bon" pour le joueur, ils ne sont pas satisfaits de l'état de retard. État de latence ne tient pas compte beaucoup de cas limitesassociés au saut d'horloges et à la modification des délais de transmission aller-retour.

Vous pouvez déjà deviner où cela mène. Enfin, nous commençons à voir les causes du problème des mégapaquets. La racine du problème est que la logique de sélection des entités repose sur État de latence, et cet état ne contient pas toujours les informations correctes. Ainsi, le mégapaquet est généré comme ceci :

  1. Le lecteur rencontre des problèmes de connexion.
  2. Les mécanismes de saut de cycles et de régulation du délai de transmission aller-retour entrent en jeu.
  3. La file d'attente d'état de retard ne tient pas compte de ces mécanismes. Cela entraîne la suppression prématurée de certaines actions ou leur exécution dans le mauvais ordre, ce qui entraîne une erreur État de latence.
  4. Le joueur n'a aucun problème de connexion et simule jusqu'à 400 cycles pour rattraper le serveur.
  5. Dans chaque cycle, une nouvelle action est générée et préparée pour être envoyée au serveur, modifiant la sélection de l'entité.
  6. Le client envoie un mégapaquet de plus de 400 modifications de sélection d'entités au serveur (et avec d'autres actions : état de déclenchement, état de marche, etc. a également souffert de ce problème).
  7. Le serveur reçoit 400 actions d'entrée. Puisqu'il n'est pas autorisé à ignorer une seule action d'entrée, il demande à tous les clients d'effectuer ces actions et les envoie sur le réseau.

L'ironie est que le mécanisme conçu pour économiser la bande passante a entraîné d'énormes paquets réseau.

Nous avons résolu ce problème en corrigeant tous les cas de bord de mise à jour et la prise en charge de la file d'attente différée. Bien que cela ait pris un certain temps, cela valait la peine de bien faire les choses plutôt que de compter sur des hacks rapides.

Source: habr.com

Ajouter un commentaire