Alt-F4 #26 - Das Mehr in Spieler geben  05.03.2021

Geschrieben von oof2win2, editiert von stringweasel, Nanogamer7, Conor_, Therenas, Firerazer,
übersetzt von Nanogamer7, lektoriert von dexteritas, marceljoint

Inhaltsverzeichnis

In der dieswöchigen 26. Ausgabe von Alt-F4 (schon ein halbes Jahr!) erklärt oof2win2 den Factorio Mehrspielermodus und einige der technischen Machenschaften dahinter. Falls du dich je gewundert hast, was ein Desync ist oder wie das Spiel Hunderte an Spielern und mehrere Tausend Entitäten auf einmal bewältigt, tu dir keinen Zwang an gleich einzutauchen!

Factorio Server oof2win2

Die meisten von uns sind wahrscheinlich mindestens einmal einem Factorio Server beigetreten, um mit Freunden zu spielen oder auch nur die Fabrik eines anderen anzusehen. In der heutigen Ausgabe von Alt-F4 werde ich kurz über die Geschichte vom Mehrspielermodus eingehen, und dann einen tieferen Blick darauf werfen, wie der Mehrspielermodus technisch funktioniert. Unter anderem werde ich die Verwendung von voll deterministischer und Lockstep-Algorithmen erklären.

Die Geschichte vom Mehrspielermodus

Im Oktober 2014 wurde der Mehrspielermodus mit Factorio 0.11.0 dem Spiel hinzugefügt, daran gearbeitet wurde aber schon seit Factorio 0.9.4. Dieser Mehrspieler war aber anders als der, den du heute kennst, zum Beispiel konntest du nicht einfach über Steam „Spiel beitreten“ oder den Server Browser verwenden – du musstest die genaue IP-Adresse des Servers wissen. Der erste Mehrspielermodus hatte auch einige Bugs, wie diesen Bug, der Spiele auf 20 Sekunden beschränkte. Er wurde natürlich gefixt, und ganz nach Wube‘s Art nicht einmal drei Stunden später. Es gab auch diesen Bug, welcher es nicht erlaubte, mehr als drei Spieler auf einmal zu verbinden – anders als dieses Mehrspielerspiel mit 500+ Spielern fast sechs Jahre später. Es floss ziemlich viel Arbeit in die Entwicklung des Mehrspielermodus, um 500 Spieler gleichzeitig auf einen Server beitreten zu lassen.

In Factorio 0.12.0, wurden Headless-Server als größeres Feature hinzugefügt. Das heißt, Server konnten nun auf Geräten ohne GPUs laufen, was die Kosten von Factorio Servern stark verringerte und Zugänglichkeit verbesserte. Es erlaubte auch mehrere Server-Instanzen gleichzeitig auf einer Maschine laufen zu lassen, was in manchen Fällen sehr nützlich ist.

Mit Factorio 0.14.0 pausierten Factorio-Server nicht mehr das Spiel für alle Spieler, wenn der Computer eines Spielers zu lange für ein Update braucht. Das heißt, dass, wenn du einen älteren Computer hast, der Server nicht mehr wartet, dass dein Computer beim Verarbeiten aufholt. Auf größeren Servern mit zehn bis Hunderten Spieler auf einmal ist das sehr nützlich, da niemand auf eine einzige Person warten muss, um das Spiel zu spielen.

Ein Vollständig Deterministischer Ansatz

Wie in FFF-30 bereits erwähnt, müssen alle Clients und der Server das Spiel exakt gleich simulieren, dieselben Aktionen zur selben Zeit. Das heißt, wenn eine Person etwas auf ihrem Computer macht, müssen die Instanzen anderer Spieler dasselbe tun. Eine Instanz ist ein Auftreten von etwas, zum Beispiel können viele Instanzen an Äpfel in einem Korb oder Tabs im Browser sein. Factorio hat aber wesentliche Unterschiede zu den meisten Mehrspielerspielen, wie CS:GO oder Overwatch, weshalb die Entwickler nicht einfach das Mehrspieler-Implementierung-Model dieser Spiele übernehmen konnten und auf Factorio übertragen, da es nicht richtig funktionieren würde.

Stattdessen wurde der Mehrspielermodus mit dem Lockstep-Protokoll entwickelt. In Factorio beginnt die Verbindung mit dem Server indem er dir einfach den Speicherstand schickt. Danach gibt dir der Server nur Bescheid, wenn sich etwas durch Nutzereingabe verändert, zum Beispiel wenn ein Spieler ein Fließband an irgendwelchen Koordinaten platziert, durch einen Beißer (oder Zug) stirbt etc. Dir wird dann nur gesagt, dass es passiert ist, dein Spiel muss die lokale Simulation dann selbst aktualisieren. Es wird nicht jeden Tick ein detailliertes Update über alle Sachen, die zugleich passieren, z. B. Bots, die sich bewegen und Züge, die anhalten, versendet.

Jeden einzelnen Tick zu übertragen, was alles passiert, würde eine Menge an Netzwerkbandbreite benötigen, da du Informationen wie „dieser Logistikbot hat sich hierher bewegt“ übertragen müsstest, was Zehntausende Male pro Tick in größeren Speicherständen passiert. Ganz zu schweigen von etlicher anderer Informationen, was dazu führen würde, den ganzen Speicherstand pro Tick zu übertragen, was in einigen Fällen über 1500 MB pro Sekunde sein kein. Stattdessen werden dir nur die wirklich wichtigen Informationen mitgeteilt, was hauptsächlich die Interaktionen der Spieler mit dem Spiel sind, und dann lässt dein Client das Spiel laufen, als wäre niemand anderer hier.

Es gibt viele andere Wege wie ein Spiel einen Mehrspielermodus handhaben kann. Zum Beispiel ist Overwatch ein Spiel, das fast alles zentral auf dem Spiel-Server verfolgt, jeden Gegenstand, Spieler, Kugel etc. kontrolliert, und aktiv den Client korrigiert, sollte etwas falsch sein. Factorio überprüft nur Spielereingaben und schickt einen Desync, wenn etwas schief läuft. Ich werde später noch erklären was ein Desync ist. Diese zwei Implementationen sind verschieden, weil die Spiele radikal unterschiedlich sind: In Overwatch kannst du alle Karten herunterladen, wenn du ursprünglich das Spiel installierst, damit du nur Spieler- und Projektil-Positionen übertragen musst. Aber in Factorio verändert sich die Karte die ganze Zeit.

In Factorio hast du verschiedene Positionen von Montagemaschinen, Lampen, Strommasten, Fließbändern, Greifarm-Richtung, und eigentlich alles andere auch, da jede Basis einzigartig ist. Das ist der Grund, warum in Factorio nur Spieler-verursachte Änderungen übertragen werden, damit Factorio das Spiel simulieren kann, als wäre es Einzelspieler, und nur die Änderungen vom Server erhält. Es ist wesentlich einfacher dem Client den Spielstand beim beitreten zu schicken und Änderungen, die die Simulation ändern würden mitzuteilen, wie ein Spieler, der zehn Kacheln nach rechts geht, als immer die ganze Karte zu übertragen (siehe die Grafik unten). Für die Neugierigen, der Overwatch Mehrspielermodus wurde hier (kürzeres Video) und vielleicht auch hier von den Overwatch Entwicklern im Detail erklärt.

Spieler: Hallo! Darf ich dem Factorio-Server beitreten?

Server: Ja sicher! Hier ist der aktuelle Speicherstand, ladt ihn herrunter. [Speicherstand als Anhang]

Server: Du spawnst bei x=0, y=3

Server: Dein Kollege ’Kartoffelman’ hat seinen Logistikfilterplatz 33 auf den Gegenstand „fast-transport-belt“ gesetzt. Stell das auch ein und simuliere weiter

Server: Dein Kollege ’Kartoffelman’ hat sich um 3 Kacheln nach rechts bewegt

Spieler: Ich hab mich um 4 Kacheln nach links bewegt

Server: Bestätigt. Leite weiter

Chatverlauf zur Illustration der Funktionsweise von Factorio Server

— Factorio Server

Vollständig deterministische Algorithmen werden in Factorio verwendet, und solche Algorithmen geben mit derselben Eingabe auch immer dieselbe Ausgabe. Das heißt, es gibt keine Zufälle im Ergebnis, was eine Bedingung für Fälle wie Factorio ist. Ein vollständig deterministischer Algorithmus wird benötigt, wenn mehrere Instanzen von Factorio laufen, damit sie synchronisiert bleiben. Der Grund für einen vollständig deterministischen Algorithmus ist, dass man bei Funktionen mit zufälligen Ergebnissen keine Lockstep-Architektur verwenden darf, da das System nur funktioniert, wenn jeder Client immer dasselbe Ergebnis ausgibt. Ein vollständig deterministischer Algorithmus ist folgend definiert:

  • Es dürfen keine weiteren Daten als der Input des Algorithmus verwendet werden. Unerlaubte Daten: Zufallszahlen, Speicherdaten, globale Variablen, Timer (z. B. Zeit seit Start des Programms)
  • Der Algorithmus darf nicht Zeit-sensitiv sein

Ein Beispiel des Gegenteils wäre, wenn mehrere Instanzen eines Programms in eine Excel-Tabelle schreiben würden und ein anderes Programm die letzte Zeile lesen würde. Das würde das Programm Zeit-sensitiv machen, da falls eine Instanz des schreibenden Programms um nur ein paar Sekunden verzögert wird, eine komplett unterschiedliche Reihenfolge an Excel-Reihen entsteht und das lesende Programm einen komplett unterschiedlichen Input bekommt.

Ein Beispiel von Lockstep und vollständig deterministischen Algorithmen wäre ein Nutzer, der eine Blaupause platziert. Wenn du auf eine Blaupause klickst, um sie in die geteilte Bibliothek zu importieren, ist das Blaupausenicon nicht mehr ausgegraut wie im rechten Bild unterhalb. Das ist, weil wenn du darauf klickst, wählst du, sie zur geteilten Bibliothek zu übertragen. Wenn du sie dann irgendwo platzierst, sagt dein Client dem Server, dass du die Blaupause an den Koordinaten XY platziert hast. Der Server sagt dann allen verbundenen Clients, dass die Blaupause auf den gleichen Koordinaten platziert wurde. Jeder einzelne Client simuliert dann alle Roboter, wie sie aus den Roboterhangars kommen, Ressourcen aufheben, die Entität platzieren und dann wieder in den gewählten Hangar zurückkommen. Alle Clients simulieren das selbst, ohne weitere Inputs, und machen es wegen den vorher erwähnten vollständig deterministischen Algorithmen genau gleich.

Eine Desynchronisation (Desync) passiert, wenn zwei Computer dasselbe zur selben Zeit mit denselben Ergebnissen nach dem deterministischem Algorithmus tun sollen, aber es nicht machen. Normalerweise, wenn der Client und Server dieselbe Sache zur selben Zeit machen, sind sie glücklich, dass sie synchronisiert sind („in sync“). Ein Desync kann passieren, wenn zwei Clients einen Tick mit unterschiedlichen Ergebnissen berechnen, meistens wegen einem Programmierfehler. Siehe das Bild unterhalb für ein Beispiel, wie ein Desync passieren kann. Wenn ein Modder oder Szenario-Ersteller seine Daten nicht gut handhabt, kann das auch zu einem Desync führen. Ein Desync schmeißt deinen Client vom Server und generiert einen Desync-Report, welchen Entwickler dann verwenden können, um den Desync weiter zu untersuchen.

Spieler: Hallo, also mein Ergebnis für die Berechnung des Nettostroms zu Tick 33859 ist 348. Lieg ich richtig?

Server: Was??? Ich hab 936 bekommen. Du liegst falsch. Ich schick den Spielstand nochmals und trenne deine Verbindung, du kannst später wieder beitreten

Chatverlauf zur Illustration was bei einem Desync passiert

— Factorio Server

Du wirst dich jetzt vielleicht wundern, wieso Desyncs nicht öfters passieren, mit Robotern und dergleichen über die Map schwirrend? Sicherlich wählen verschiedene Clients verschiedene Roboter für diverse Aufgaben, oder? Nein. Jeder Client wird immer denselben Roboter zur selben Zeit wählen, weil der Algorithmus, der die Roboter wählt, vollständig deterministisch ist. Zwei Züge kommen von einem Wartegleis zu einer Station? Immer derselbe Zug, da das auch vollständig deterministisch ist. Welchen Geschützturm ein Speier bei deinem Außenposten angreift? Auch vollständig deterministisch. Das waren jetzt nur ein paar Beispiele, aber alles im Spiel ist vollständig deterministisch. Wäre das nicht so, hätte man einen Desync hier, einen weiteren da, und Mehrspieler wäre überhaupt nicht mehr spielbar. Im Mehrspieler können Desyncs durch verschieden Sachen verursacht werden, wie zum Beispiel Konstruktionsroboter, Beißer-AI-Simulationen, und vor allem von Moddern selbst verursachte Sachen.

Selbst wenn du etwas so einfaches wie math.random() verwendest, um eine zufällige Zahl in einem Mod oder Szenario von dir zu bekommen, gäbe es konsistente Ergebnisse – alle Clients bekämen dasselbe Ergebnis bei der Funktion. Das liegt daran, dass Factorios Zufallszahlengenerator einen Seed (Startwert) verwendet. Er bekommt eine Nummer zum starten, welche dann verwendet wird, um zufällige Zahlen mit der Zeit zu generieren. Wenn du jedem Client denselben Seed gibt, werden die zufälligen Zahlen auch synchronisiert sein. Es ist wichtig anzumerken, dass dies ein Pseudozufallszahlengenerator ist und daher nicht echt zufällig, da er mit vorbestimmten Nummern initialisiert wird, was es erlaubt, die Ergebnisse zu reproduzieren. Siehe diesen Wikipediaeintrag für mehr Info über Seeds.

Jetzt weißt du ein bisschen mehr, was passiert, wenn du auf einen Server in der Serverliste klickst, oder über eine IP, durch Steam oder über LAN beitrittst. Die Entwickler von Factorio haben sehr hart am Mehrspielermodus gearbeitet, um uns große Spiele, wie das über-500-Spieler Event oder komplexe Clusterio-Setups zu ermöglichen, und Moddern die Werkzeuge zu geben, die sie zum Entwickeln von lustigen Sachen brauchen. Es gibt weniger und weniger Limitierungen was man tun kann, riesige Basen, massive Mengen von Spielern, vielleicht sogar beides! All das ist dir offen.

Beitragen

Wie immer suchen wir nach Leuten, die zu Alt-F4 beitragen wollen, sei es mit einem Artikel oder durch Hilfe bei Übersetzungen. Wenn du etwas Interessantes im Kopf hast, das du mit der Community in einer eleganten Art teilen möchtest, hier kannst du das tun. Falls du dir unsicher bist, beantworten wir gerne Fragen zu Inhalt und Struktur. Falls das nach etwas klingt, woran du interessiert bist, tritt unserem Discord bei, um es nicht zu verpassen!