Alt-F4 n°26 - Mettre du multi dans le jeu  05-03-2021

Écrit par oof2win2, édité par stringweasel, Nanogamer7, Conor_, Therenas, Firerazer,
traduit par bev, Firerazer

Sommaire

Dans ce numéro 26 de Alt-F4 (déjà une demi-année de parutions !), oof2win2 aborde le mode multijoueur de Factorio et explique certains des rouages techniques cachés. Si vous vous êtes déjà demandé ce qu’est une désynchronisation ou comment le jeu parvient à gérer des centaines de joueurs et plusieurs milliers d’entités à la fois, n’hésitez pas à vous y plonger !

Les serveurs Factorio oof2win2

La plupart d’entre nous se sont probablement déjà connectés au moins une fois à un serveur Factorio, que ce soit pour jouer avec des amis ou simplement pour voir les constructions de quelqu’un d’autre. Dans le numéro d’aujourd’hui de Alt-F4, j’expliquerai brièvement l’histoire du mode multijoueur, puis je me plongerai plus profondément dans l’explication de comment cela fonctionne techniquement. J’expliquerai entre autres l’utilisation des algorithmes entièrement déterministes et du protocole Lockstep.

L’histoire du mode multijoueur

En octobre 2014, avec la version 0.11.0 de Factorio, le mode multijoueur a été introduit dans le jeu, bien qu’il ait été développé depuis la version 0.9.4. Ce mode multijoueur était différent de celui que vous voyez aujourd’hui, vous ne pouviez pas par exemple “Rejoindre un ami” facilement via Steam ou utiliser la liste des serveurs - vous deviez connaître l’adresse IP exacte de celui-ci. Lorsque la première version du mode multijoueur est sorti, il y avait un certain nombre de bugs, comme celui-ci qui ne permettait pas aux parties en multijoueur de durer plus de 20 secondes. Il a bien sûr été corrigé moins de trois heures plus tard, comme d’habitude avec Wube. Il y avait aussi ce bug qui empêchait plus de trois personnes de se connecter en même temps - alors que, près de six ans plus tard, cette session multijoueur en a rassemblé plus de 500. Beaucoup de travail a dû être consacré au développement du mode multijoueur pour que 500 joueurs puissent se connecter en même temps.

Avec la version 0.12.0, les serveurs headless ont été ajoutés en tant que caractéristique majeure. Cela signifie que les serveurs peuvent désormais fonctionner sur des machines sans processeur graphique, ce qui réduit considérablement le coût des serveurs Factorio et améliore l’accessibilité. Cela a également permis à plusieurs instances de serveur de fonctionner en même temps sur une seule machine, ce qui est très utile dans certains cas.

Depuis la version 0.14.0, les serveurs Factorio ne mettent plus le jeu en pause pour tous les joueurs si l’ordinateur d’un joueur prend trop de temps à traiter une mise à jour. Cela signifie que si vous avez un ordinateur plus ancien, le serveur n’attendra plus que vous rattrapiez votre retard de traitement. C’est très utile sur les grands serveurs qui peuvent avoir des dizaines ou des centaines de joueurs en ligne à la fois, car on ne doit plus attendre les derniers pour pouvoir jouer.

Une approche entièrement déterministe

Comme mentionné dans le FFF-30, tous les clients doivent simuler le jeu de la même manière que le serveur, les mêmes actions exactement au même moment. Cela signifie que si une personne fait quelque chose sur son ordinateur, les instances de Factorio des autres personnes doivent faire de même. Une instance est une occurrence de quelque chose, de la même manière qu’il peut y avoir de nombreuses instances de pommes dans un panier ou d’onglets dans Chrome. Factorio est très différent de la plupart des jeux multijoueurs, comme CS:GO ou Overwatch, de sorte que les développeurs ne pouvaient pas simplement reprendre le modèle d’implémentation en multijoueur de ces jeux et le transposer dans Factorio, car cela ne fonctionnerait pas correctement.

Au contraire, lors de la création du mode multijoueur, les développeurs ont utilisé le protocole Lockstep. Dans Factorio, la connexion commence par l’envoi de la carte par le serveur. Ensuite, ce dernier vous indique uniquement si quelque chose change, en fonction des entrées des utilisateurs, par exemple si un joueur place un convoyeur à un certain endroit, meurt à cause d’un déchiqueteur (ou d’un train), etc. On vous dit seulement que ceci est arrivé à ce moment-là, votre jeu doit mettre à jour sa simulation locale par lui-même. Il ne reçoit cependant pas une mise à jour détaillée à chaque tick de toutes les choses qui se passent à ce moment, comme les robots qui se déplacent et les trains qui s’arrêtent.

La transmission de tout ce qui se passe, à chaque tick, nécessiterait beaucoup trop de bande passante de réseau, car il faudrait transférer des informations telles que “ce robot logistique s’est déplacé ici”, ce qui devrait se produire des dizaines de milliers de fois par tick avec les grosses sauvegardes. Sans parler de certaines autres informations qui conduiraient à transférer toute la sauvegarde à chaque tick, ce qui se traduirait dans certains cas par un transfert de 1500 Mo par seconde. Au lieu de cela, on vous communique uniquement les informations vraiment importantes, qui sont principalement les interactions des joueurs avec le jeu, et votre client exécute le jeu comme si personne d’autre n’était là.

Il existe de nombreuses autres façons pour un jeu de gérer le multijoueur. Par exemple, Overwatch est un jeu qui garde une trace de presque tout, de manière centralisée sur son serveur de jeu, en surveillant chaque objet, joueur, balle, etc. et qui corrige activement votre instance client si quelque chose s’est mal passée. Factorio ne surveille que les entrées des joueurs et lance une désynchronisation si quelque chose se passe mal. Je vous expliquerai plus tard ce qu’est une désynchronisation. Ces deux implémentations sont différentes car les jeux sont radicalement différents : dans Overwatch, vous avez récupéré toutes les cartes lors du téléchargement initial du jeu, il vous suffit donc de transmettre les positions des joueurs et des projectiles. Dans Factorio, en revanche, les cartes changent tout le temps.

Dans Factorio, vous avez des positions différentes pour les machines d’assemblage, les lampes, les poteaux électriques, les convoyeurs, les bras et à peu près tout le reste, car chaque base est unique. C’est la raison pour laquelle, dans Factorio, seuls les changements générés par les joueurs sont transférés. Factorio peut alors simuler le jeu comme s’il s’agissait d’un jeu en solo, en recevant simplement les changements des autres joueurs via le serveur. C’est beaucoup plus facile que de transférer la carte entière, il suffit uniquement de fournir la carte au client lorsqu’il se connecte et de lui indiquer toute autre entrée qui pourrait modifier la simulation en cours, comme un joueur qui se déplace de dix tuiles vers la droite, etc. Voyez l’image ci-dessous. Pour les curieux, le mode multijoueur d’Overwatch est expliqué ici (vidéo plus courte) et peut-être ici plus en détail, par les développeurs d’Overwatch.

Joueur : Salut à vous ! Puis-je rejoindre le serveur Factorio ?

Serveur : Oui, bien sûr ! Voici la carte en cours, téléchargez-la. [carte en annexe]

Serveur : Vous apparaissez à la position x=0, y=3

Serveur : Votre ami “M. Patate” a configuré le filtre logistique de l’emplacement 33 pour l’objet “convoyeur rapide”. Configurez-le aussi et continuez la simulation !

Serveur : Votre ami “M. Patate” s’est déplacé de 3 tuiles vers la droite

Joueur : Je me suis déplacé de 4 tuiles vers la gauche

Serveur : Confirmé. Je transmets

Extrait de discussion illustrant le fonctionnement des serveurs Factorio

— Serveur Factorio

Factorio utilise des algorithmes entièrement déterministes, qui produisent exactement le même résultat avec la même entrée. Cela signifie que les résultats ne sont jamais aléatoires, ce qui est nécessaire dans certains cas comme dans celui de Factorio. Un algorithme entièrement déterministe est nécessaire lorsque plusieurs instances de Factorio sont exécutées, afin que toutes les instances fonctionnent selon un algorithme Lockstep et restent synchronisées. La raison d’être des algorithmes entièrement déterministes serait que si vous avez des fonctions qui produisent des résultats aléatoires, vous ne pouvez pas utiliser l’architecture Lockstep, car le système entier se plante si les fonctions qui traitent les choses ne donnent pas les mêmes résultats pour chaque client, à chaque fois. Un algorithme entièrement déterministe est défini par :

  • Il ne doit pas utiliser d’autres données que celles qui entrent dans l’algorithme. Données interdites : nombres aléatoires, données stockées sur le disque, variables globales, temporisateurs (c’est-à-dire le temps écoulé depuis le démarrage du programme)
  • L’algorithme doit fonctionner de manière à ne pas être dépendant du temps

Un exemple contraire serait si plusieurs instances d’un programme écrivaient dans une feuille de calcul Excel alors qu’un autre programme est en train de lire la dernière ligne de la feuille. Cela rendrait le programme dépendant du temps, car si une des instances en écriture est retardée de quelques secondes, cela peut produire un ordre complètement différent des données dans les lignes d’Excel, fournissant au programme qui lit la dernière ligne une donnée complètement différente.

Un exemple d’algorithmes Lockstep et entièrement déterministes est la pose d’un plan par un client. Lorsque vous cliquez sur un plan pour l’importer dans la bibliothèque partagée, les icônes du plan ne sont plus grisées, comme dans l’image de droite ci-dessous. En effet, lorsque vous cliquez dessus, vous choisissez de le transférer dans la bibliothèque partagée du jeu. Lorsque vous le déposez ensuite quelque part, votre client indique au serveur que vous avez placé le plan à certaines coordonnées XY. Le serveur indique ensuite à tous les autres clients connectés qu’il a été placé là. Chaque client individuel simule ensuite la sortie de tous les robots de leur roboport, afin d’obtenir des ressources, de placer l’entité en leur possession et de revenir au roboport de leur choix. Tous les clients simulent cela par eux-mêmes, sans aucune autre entrée, et le font de la même manière grâce aux algorithmes entièrement déterministes mentionnés précédemment.

Une désynchronisation est le fait que deux ordinateurs qui sont censés faire quelque chose en même temps avec les mêmes résultats selon les algorithmes entièrement déterministes, ne le font pas. Normalement, lorsque le client et le serveur font la même chose en même temps, ils sont heureux, car ils sont synchronisés. Une désynchronisation peut se produire lorsque deux clients calculent une mise à jour avec des résultats différents, généralement en raison d’une erreur de programmation. Voyez l’image ci-dessous pour un exemple de la façon dont une désynchronisation peut se produire. Si un moddeur ou un créateur de scénario ne gère pas bien ses données, cela peut également provoquer une désynchronisation. Une désynchronisation obligera votre client à se déconnecter du serveur et à générer un rapport de désynchronisation, que les développeurs utilisent pour l’analyser.

Joueur : Hé, donc le résultat du calcul de l’énergie nette au tick 33859 est de 348. C’est correct ?

Serveur : Quoi ?? J’ai 936. Vous avez tort. Je vous envoie la carte et je vous déconnecte, vous pouvez vous reconnecter après.

Extrait de discussion illustrant ce qui se passe durant une désynchronisation

— Serveur Factorio

Vous vous demandez peut-être comment il se fait que des désynchronisations ne se produisent pas avec le déplacement des robots sur la carte ? Sûrement que s’ils sont tous capables d’effectuer une tâche et que certains robots sont sélectionnés pour l’effectuer, les différents clients pourraient choisir des robots différents, non ? Non. Tous les clients choisiront toujours le même robot au même moment parce que l’algorithme qui choisit le robot est entièrement déterministe. Deux trains arrivant en gare en provenance d’un stacker ? Toujours le même train, car c’est aussi entièrement déterministe. Quelle tourelle un cracheur décide-t-il d’attaquer dans votre avant-poste minier ? C’est aussi entièrement déterministe. Ce ne sont là que quelques exemples, mais tout dans le jeu est entièrement déterministe. Si ce n’était pas le cas, vous auriez une désynchronisation ici, une autre là, et le mode multijoueur ne serait plus du tout jouable. En multijoueur, les désynchronisations peuvent être causées par de nombreuses choses, comme la construction par robots, les simulations de l’IA des déchiqueteurs et, surtout, les choses causées par les moddeurs eux-mêmes.

Même si vous utilisez quelque chose d’aussi simple que math.random() pour obtenir un nombre aléatoire dans un de vos mods ou scénarios, les résultats seront cohérents - tous les clients obtiendront le même résultat. Ceci est dû au fait que le générateur de nombres aléatoires de Factorio est initialisé. Il reçoit un certain nombre de données au départ, qu’il utilise ensuite pour générer des nombres aléatoires dans le temps. Si vous faites en sorte que tous les clients utilisent une initialisation identique, vos nombres aléatoires seront synchronisés. Il est important de noter qu’il s’agit d’un générateur pseudo-aléatoire, et donc pas vraiment aléatoire, car il est initialisé avec un nombre prédéterminé, ce qui permet de produire le même résultat partout. Consultez ceci pour plus d’informations sur les graines aléatoires.

Vous en savez maintenant un peu plus sur ce qui se passe lorsque vous cliquez sur un serveur dans la liste des serveurs, que vous le rejoignez par adresse IP, via Steam ou via un réseau local. Les développeurs de Factorio ont travaillé très dur sur le mode multijoueur, ce qui nous a permis de créer de grandes parties telles que la session multijoueur de plus de 500 joueurs ou les configurations complexes de Clusterio, fournissant aux créateurs les outils dont ils ont besoin pour élaborer toutes ces choses amusantes. Il y a de moins en moins de limites à ce que vous pouvez faire, des bases énormes, des quantités massives de joueurs, peut-être même les deux ! Tout cela dépend de vous et de la façon dont vous le paramétrez.

Contribuer

Comme toujours, nous attendons vos contributions pour les Alt-F4, que cela soit par la soumission d’un article ou en aidant pour les traductions. Si vous avez quelque chose d’intéressant en tête que vous souhaitez partager avec la communauté, vous êtes au bon endroit. Si vous n’êtes pas sûr, nous serons heureux de vous aider en discutant structure, contenu et idées. Donc si vous voulez vous impliquer dans les Alt-F4, rejoignez-le Discord pour ne rien rater !