Multiplayer, Networking und Netcode

  • Das Thema liegt mir eigentlich schon lange auf der Zunge, aber da es mit BF4 wieder mehr als aktuell ist, dachte ich mal ich bringe ein wenig Licht ins Dunkel rund um Netzwerke, und diesen ominösen Netcode der an allem Schuld ist. Oder eher warum er es meist nicht ist.


    Als erstes sei gesagt, dass es hier nicht, nichtens und schon gar nicht um FPS Einbrüche geht, diese haben mit Laggs, mit denen ich mich hier beschäftigen will, wenig bis gar nichts zu tun, zudem reichen die Gründe für FPS Probleme oder Leistungseinbrüche deutlich zu weit, als das ich so ein Thema unbezahlt oder gar in einem einfachen Forenpost angehen würde ;) Ich werfe einfach mal ein paar Stichwörter in den Raum: Pipelining, Speculative execution, Caches, Reordering und Compiler Consistency. Die Lust an FPS Diskussionen verloren? Gut, dann können wir anfangen:



    Was ist ein Lag?


    Unter Lags verstehen wir weithin die Verzögerung zwischen dem einspeisen von Informationen ins System bis zum ankommen bzw. auslesen beim Empfänger. Davon sind zum einen Datentransfers übers Netzwerk, aber auch zbsp. die Übertragung der Bilddaten vom PC zum Bildschirm betroffen, auch wenn letztere heutzutage in einen unbemerkbaren Bereich gerutscht sind (und immer noch nichts mit FPS zu tun haben). Im folgenden werde ich das allgemeine Verständnis des Begriffs ein wenig dehnen, ohne etwas an der Substanz der Definition zu ändern, genauer gesagt möchte ich die Verzögerung zwischen dem klicken der Maustaste und dem Feedback in Form eines Bildes beim "Empfänger" zbsp. des Schusses analysieren.



    Ja aber ich hab nur 20 ms zum Server, das sollte doch nicht auffallen?


    Richtig, den wenigsten wird eine Verzögerung von 20 ms auffallen. Viele würden hier wohl direkt wieder aufschreien und "Scheiß Netcode" rufen. Natürlich spielt dieser eine wichtige Rolle, im Nachfolgenden werde ich aber ein Beispiel aufzeigen, das ein wenig klarer macht dass viele der uns heute in Online-Spielen bekannten Probleme keinesfalls auf die Programmierer zurückzuführen ist.


    Disclaimer: Die im folgenden benutzten Zahlen sind Schätzungs- und Erfahrungswerte, die nicht für alles und jeden zutreffen werden.


    Schauen wir uns mal an was passiert nachdem man in BF4 die Maustaste klickt.


    Zuerst wird der Input von der Maus als Input für die CPU über Kabel und Mainboard weitergeleitet. Von dieser Verzögerung können wir aber eigentlich getrost abstrahieren. Man sollte sich nur merken dass hier bereits Zeit vergeht.


    Als nächstes muss dieser Input als Interupt vom Prozessor verarbeitet werden. Dies geschieht nicht zwingend direkt, ist meist aber recht schnell erledigt. Was nun Zeit beansprucht ist die Verarbeitung dieses Inputs in der Spielelogik, und hier kommt bereits der erste Punkt an dem ich nicht weiß, wie BF4 diesen handhabt. Üblich ist es aber, diese Inputs zur Verarbeitung des nächsten Logikupdates (im folgenden "Ticks") zu speichern und erst dann zu verwerten. Diese Updates finden in mehr oder minder regelmäßigen Zyklen ab. Ich gehe hier erst einmal von einem sehr niedrigen Wert von 20ms/tick aus. (Minecraft arbeitet in 50ms Intervallen) d.h. die Verarbeitung des Inputs dauert bis zu 20ms, ohne dass der Server auch nur etwas davon ahnt.


    Nehmen wir als nächstes an dass dieser Updatezyklus sehr schnell geht, und die Info quasi sofort an den Server geschickt wird. Eigentlich würde ich hier nochmal bis zu 10 ms veranschlagen gerade da der Output auch noch von der CPU zur Netzwerkkarte use. gesendet werden muss, da ich aber wenig Ahnung davon habe was BF4 tatsächlich macht, lasse ich diesen Wert nochmal weg. Aber auch hier, merkt euch dass nochmals minimal Zeit vergangen ist.


    Klingt bisher doch noch gar nicht so schlimm, oder? Ab hier wird es aber hässlich: Die Daten brauchen im Mittel 20ms zum verschicken. Leider ist das aber auch nur die halbe Wahrheit. Von diesen Informationen die nun vom Client zum Server geschickt werden, gehen Pakete verloren, verspäten sich, und kommen vor allem nicht in der Reihenfolge an in der sie abgeschickt wurden (Berechnugnsaufwand). Im schlechtesten Fall dauert es mehrere 100 ms bis das Paket dann tatsächlich ankommt, und bei der Anzahl an versendeten Paketen kann das durchaus öfter während einer Spielesession passieren. Wir gehen aber hier der Einfachheit halber vom "perfekten" Mittelwert aus.


    Nun sind die Daten am Server. Aber was nun? Errinnert euch an die angesprochenen Ticks. Auch hier wird es wohl wieder bis zu 20 ms dauern bis der Input verarbeitet, und die daraus folgende Änderung am Zustand des Spiels berechnet und verschickt wird (wenn es nicht gar eher 2-3 Ticks dauert).


    Im nächsten Schritt werden die Daten vom Server wieder an den Client gesendet. Wieder ca. 20ms. Langsam summiert sich diese kleine Zahl auf, nicht wahr?


    Die Daten kommen beim Client an, und werden von diesem wieder im nächsten Tick verarbeitet, wir zählen also nochmal 20ms drauf. So, nun muss das Update auch noch als Bild beim Spieler ankommen, wieder haben wir eine kleine Verzögerung. Diese kleinen Verzögerungen fangen langsam aber auch an sich aufzusummieren, wenn ich das gerade so recht sehe.



    So, das Bild ist da, der Spieler vor dir sieht nun dass bei dir eine Kugel aus dem Lauf kommt (wenn überhaupt schon). Wieviel Zeit ist nun Vergangen seit du die Maustaste geklickt hast?


    80ms, ohne den ganzen "Kleinigkeiten" die ich zwischendurch unterschlagen habe, wie Datenverlust oder interne Datentransfen. Mit diesen werden es gerne mal 100+ ms, ohne dass ich die eigentliche Berechnungszeit einberechnet habe. Der oftgenannte Übeltäter "Netcode" war noch gar nicht zugange, und wir sind bereits in einem durchaus merkbaren Bereich. 100 ms reichen locker dass zbsp. solche Szenarien wie Doppel-KOs oder dass man eigentlich schon in Deckung ist noch getroffen wird, weil beim anderen Spieler noch dein Ellbogen rausguckt, Reaktionszeiten von 100ms sind bei mittleren bis besseren Spielern gar nicht so unüblich. Auf der Autobahn mit 160 km/h wäre man in der Zeit 4 Meter gefahren.


    Das Problem wird nun sein, dass ihr meist eher zwischen 30 und 50 ms Pendeln werdet. Klingt jetzt auf einmal nach viel mehr, nicht wahr? ;)


    Und nun kommt der böse Netcode ins Spiel: Er muss nun möglichst alle Daten an die Clients und den Server verteilen, ohne dass der Spieler zu viel davon mitkriegt. Nur wie macht man das wenn die Übertragung an sich schon bemerkbar langsam ist? So gut wie möglich, mehr geht nunmal nicht. Wobei ich an der Stelle dazu sagen möchte, dass es durchaus sein kann dass der Netcode eines Spiels zum Himmel stinkt, das möchte ich auf keinsten Fall verneinen. Ich möchte nur nochmal dazu anregen darüber nachzudenken ob nun diese Zehntel Sekunde die ihr nicht hattet um zu reagieren, nun wirklich die Schuld irgendeines Programmierers ist, oder ob hier doch einmal einfache Hardwarebegrenzungen Schuld waren.

  • Also im Großen und Ganzen stimmt die Sache mit dem Input natürlich, allerdings wird der Input immer zu Beginn eines Ticks abgefangen und hat auch meistens nochmal einen eigenen Ticktimer der deutlich schneller als 20ms tickt. Das hängt z.B. schon allein damit zusammen das du mit 20ms/Tick dich noch nichtmals mit der Maus ruckelfrei umschauen könntest. Ich denke die Input-Ticks werden im allgemeinen so irgendwo bei 1 bis 2ms/Tick liegen, eben damit hier nicht die große Verzögerung entsteht.


    Bei den restlichen Zeiten muss ich dir allerdings zustimmen, die Engine tickt wahrscheinlich irgendwo um die 20 bis 30ms rum (ich glaube bei Battlefield 3 waren es damals 30ms/Tick wenn ich mich richtig an den Vortrag von einem BF3-Entwickler auf der GC erinnere).
    Alles in allem wird also der "Mausklick" wohl so um die 50 bis 70ms brauchen um zum Server und zurück zu gelangen.


    Aber genau dort setzt ja der Netcode dann an:
    Die Aufgabe eines Netcodes ist es den "Spielstand" bei kleinen Differenzen immer wieder anzugleichen, ohne das die Spieler davon etwas mitbekommen. Das können "kleine" Dinge sein, wie eben die Position eines Spielers auf der Karte oder eben die Abhandlung von Schüssen.
    Einige sagen jetzt vielleicht "ja.. bei Quake hat das auch gut funktioniert", aber Quake hat auch einen komplett anderen Ansatz als z.B. Battlefield.


    Bei Quake haben z.B. Geschosse im Allgemeinen (bis auf die Raketen) eine unendliche Ausbreitungsgeschwindigkeit und der Server übernimmt die komplette Berechnung. Das heisst: Der Spieler drückt und der Server schaut anhand der aktuellen Position und Waffe ob der Spieler getroffen hat und weiss dies sofort. Er informiert also nur noch den Spieler und sendet ihm z.B. seine neuen Healthpunkte (Das ist jetzt natürlich stark vereinfacht dargestellt).


    Bei Battlefield ist der Ansatz schon ein komplett anderer, weshalb hier die von Quake genannte Variante total wegfällt. Jedes (wirklich jedes) Geschoss hat in Battlefield eine echte Geschwindigkeit, weshalb der Server das genau berechnen müsste, was eine ganze Menge Leistung kostet. Hier kommt das allerdings das Problem der Latenz ins Spiel so das der Server nicht genau weiß wo der Spieler steht. Er kann ja z.B. weitergelaufen sein, aber das Paket ist noch nicht angekommen. Für diese Fälle gibt es ziemlich komplizierte Algorithmen und das Ganze schimpft sich dann "Movement prediction", d.h. der Server (und die Clienten auch) versucht anhand der letzten Daten zu berechnen wo der Spieler jetzt stehen könnte. Das kommt in so gut wie jedem Spiel vor und bei Counter-Strike/Half-Life ist dies sogar ab und zu mal zu sehen gewesen (Spieler warpen sich auf einmal ein bisschen zur Seite oder ähnliches).


    Da das ganze wahrscheinlich sehr rechenintensiv ist bei vielen Geschossen die in BF so rumfliegen hat man sich dort wohl für einen Hybrid entschieden, welcher im ersten Moment sehr komisch erscheint, aber der wahrscheinlich nicht ohne Grund gewählt wurde. Die Leute bei Dice sind ja nicht totale Neulinge weshalb sie wahrscheinlich schon andere Dinge versucht haben in internen Tests. Was ich mich allerdings frage warum die Retail im Bezug auf die Beta IMHO so abfällt, ähnlich wie es bei BF3 war. Die Beta schien zumindest eine andere Art der Hit-Detection zu besitzen als die Retail...

  • OK, da geht GeMo noch ein bisschen mehr ins Detail :ugly: Klar sollte aber sein, dass es alles andere als einfach ist diese "extremen" Differenzen auszugleichen (wir reden von mehreren hundert Millionen CPU Zyklen), da der Client einfach deutlich zu spät die "echten" Informationen der anderen Clients ranbekommt.


    Eventuell sollte ich das mit dem Input aber nochmal aufklären, es sollte zusammengefasst mit dem senden der Info an den Server bis zu 20 ms dauern. Lokal wird zbsp das Umschauen, wie du bereits geschrieben hast öfter angepasst. Aber auch hier gibt es kaum einen Grund das öfter als die aktuell Framerate (im generellen ~16ms) zu tun.