Wieder einmal Netzwerkcode - Anregungen gesucht

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
blbltheworm
Beiträge: 217
Registriert: 22.09.2004 19:36
Wohnort: Auf der schönen Schwäbischen Alb

Wieder einmal Netzwerkcode - Anregungen gesucht

Beitrag von blbltheworm »

Hallo zusammen,

im letzten Jahr habe ich mit Freunden an einem kleinen 2D-Brettspiel-Simulator gearbeitet (eine einfach zu bedienende Alternative zum Tabletop-Simulator), den wir online über Remotezugriff bedient haben. Nun bin ich am überlegen das Tool auf die nächste Ebene zu heben und denke über einen Netzwerkcode nach. Das letzte mal, dass ich Netzwerk programmiert habe ist schon ein paar Jährchen her und war damals nur ein simples Kartenspiel. All zu komplex gestaltet es sich diesmal aber auch nicht (keine KI, nur das verschieben von Sprites mit der Maus und ändern von Statuswerten wie Hitpoints). Dennoch stellen sich mir ein paar Fragen, die ich gerne mit euch durchdiskutieren würde, bzw. großes Interesse an euren Erfahrungen habe:

1.) Umgang mit Benutzereingaben
Am einfachsten ist es mit einem Spieler als Server und den übrigen Spielern als Clients. Aber wie geht man am besten mit Manipulationen z.B. dem Löschen von Elementen aus einer LinkedLists oder der Veränderung von Variablenwerten um. Macht es Sinn, dass ein Client das Element bei sich direkt löscht und dann an den Server das Löschen kommuniziert oder ist es sicherer dem Server mitzuteilen, dass man etwas löschen möchte und der Server verteilt dann die Aufgabe an alle Clients (inkl. einen selbst)? D.h. nicht geschieht ohne die Aufforderung des Servers.

2.) Genereller Umgang mit LinkedLists
Wie halte ich LinkedLists am besten bei allen Teilnehmer synchron? Ich sehe z.B. das Problem dass Spieler 1 Element 5 löscht und Spieler 2 gleichzeitig Element 6. Abhängig davon in welcher Reihenfolge die Information bei Spieler 3 ankommt, wird dort dann entweder 5 & 6 (Spieler 2 war zuerst) oder 5 & 7 (Spieler 1 war zuerst und das 7. Element ist dann das 6.) gelöscht. Momentan sehe ich nur die Möglichkeit, dass jedes Element der Liste vom Server einen Unique-Identifier bekommt, was es etwas umständlicher macht. Oder gibt es hier elegantere Lösungen?

3.) Nachverfolgen von Bewegungen
Wenn ein Sprite in Echtzeit mit der Maus auf dem Bildschirm verschoben werden soll, wie kommuniziert man dies am besten an den Server/die übrigen Clients? Macht es es Sinn die einzelnen Positionen des Objekts zu übermitteln oder wird das zu sehr laggen und zuviel Traffic verursachen? Wäre es besser Vektoren für die Bewegung zu berechnen und die Clients verfolgen die Bewegung anschließend etwas Zeitversetzt nach?

4.) Umgang mit Paketverlust und fehlerhaften Paketen
Das ist auch ein Punkt aus dem ich nicht schlau geworden bin. Kümmert sich der PureBasic Netzwerk-Unterbau selbständig darum verlorene oder fehlerhafte Pakete beim Absender erneut anzufragen oder muss ich mich darum selbst kümmern?

Ich bin gespannt auf eure Erfahrungen, hoffe auf Diskussionen und sage schon mal danke für eure Unterstützung.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Wieder einmal Netzwerkcode - Anregungen gesucht

Beitrag von NicTheQuick »

blbltheworm hat geschrieben:1.) Umgang mit Benutzereingaben
Am einfachsten ist es mit einem Spieler als Server und den übrigen Spielern als Clients. Aber wie geht man am besten mit Manipulationen z.B. dem Löschen von Elementen aus einer LinkedLists oder der Veränderung von Variablenwerten um. Macht es Sinn, dass ein Client das Element bei sich direkt löscht und dann an den Server das Löschen kommuniziert oder ist es sicherer dem Server mitzuteilen, dass man etwas löschen möchte und der Server verteilt dann die Aufgabe an alle Clients (inkl. einen selbst)? D.h. nicht geschieht ohne die Aufforderung des Servers.
Ich würde sagen es kommt ein bisschen darauf an wie schnelllebig das Spiel ist. Bei Minecraft sieht man zum Beispiel deutlich, dass ein Client erst mal auf seine eigene Logik vertraut, und falls der Server nicht damit einverstanden war, kann es danach wieder rückgängig gemacht werden. Zum Beispiel zerstört man einen Block und sieht das auch, aber da die Verbindung zum Server etwas langsam ist, dauert es ein paar Frames bis der Server meldet, dass der Block gar nicht zerstört werden darf, und dann wird er beim Client auch wieder angezeigt. Solange also Client und Server die selbe Logik verfolgen, fühlt es sich schneller an. Es muss nur sichergestellt sein, dass der Server die Entscheidungen vom Client auch wieder rückgängig machen kann.
2.) Genereller Umgang mit LinkedLists
Wie halte ich LinkedLists am besten bei allen Teilnehmer synchron? Ich sehe z.B. das Problem dass Spieler 1 Element 5 löscht und Spieler 2 gleichzeitig Element 6. Abhängig davon in welcher Reihenfolge die Information bei Spieler 3 ankommt, wird dort dann entweder 5 & 6 (Spieler 2 war zuerst) oder 5 & 7 (Spieler 1 war zuerst und das 7. Element ist dann das 6.) gelöscht. Momentan sehe ich nur die Möglichkeit, dass jedes Element der Liste vom Server einen Unique-Identifier bekommt, was es etwas umständlicher macht. Oder gibt es hier elegantere Lösungen?
Hier würde ich tatsächlich mit Unique IDs arbeiten. Wie wäre es zum Beispiel mit Maps? Die folgen zwar keiner festen Reihenfolge, aber man kann feste IDs nutzen um die einzelnen Elemente anzusprechen. Mit festen Indices solltest du in dem Fall also am besten nicht arbeiten. Es sei denn du überlässt die komplette Logik dem Server und dieser sagt den Clienten immer genau und fehlerfrei, was sie wann löschen sollen. Bzw. wenn es um wenige Daten geht, kannst du ja auch regelmäßig die ganze Liste übertragen anstatt nur die inkrementellen Änderungen. Gerade wenn es mal Datenverlust bei der Übertragung gibt, willst du ja keinen Schritt zwischendrin vergessen oder doppelt machen.
3.) Nachverfolgen von Bewegungen
Wenn ein Sprite in Echtzeit mit der Maus auf dem Bildschirm verschoben werden soll, wie kommuniziert man dies am besten an den Server/die übrigen Clients? Macht es es Sinn die einzelnen Positionen des Objekts zu übermitteln oder wird das zu sehr laggen und zuviel Traffic verursachen? Wäre es besser Vektoren für die Bewegung zu berechnen und die Clients verfolgen die Bewegung anschließend etwas Zeitversetzt nach?
Hier solltest du absolute Koordinate für alle Objekte übertragen und keine relativen (das ist zumindest, was ich unter Vektoren bei dir verstehe). Klar kann es laggen, wenn die Daten über das Internet übertragen werden und die Antwortzeiten schlecht sind. Deswegen nutzt man üblicherweise UDP statt TCP, aber das hat zur Folge, dass das Daten verloren gehen können. Und deswegen musst du unbedingt absolute Koordinaten übertragen.
4.) Umgang mit Paketverlust und fehlerhaften Paketen
Das ist auch ein Punkt aus dem ich nicht schlau geworden bin. Kümmert sich der PureBasic Netzwerk-Unterbau selbständig darum verlorene oder fehlerhafte Pakete beim Absender erneut anzufragen oder muss ich mich darum selbst kümmern?
TCP ist so designed, dass alle Daten immer und in der richtigen Reihenfolge ankommen. Ganz im Gegensatz zu UDP, wo auch mal Daten nicht ankommen, falsch ankommen, oder nicht in der richtigen Reihenfolge. Dafür ist UDP aber schneller, denn der Empfänger sendet dem Sender keine Bestätigung, dass er alle Daten erhalten hat oder fragt den Sender, ob er sie wieder senden kann. Trotzdem kannst du bei TCP natürlich auch Daten verlieren, wenn jemand eine sehr schlechte WLAN-Verbindung hat, oder das Netzwerkkabel abzieht oder was auch immer. Dafür gibt es dann in der Regel Timeouts und arbeitet mit aktiven Heartbeats, d.h. der Server sendet jedem Clienten einen Ping und der Client muss darauf antworten und damit sagen "Hey, ich bin noch da."
Ich bin gespannt auf eure Erfahrungen, hoffe auf Diskussionen und sage schon mal danke für eure Unterstützung.
Erfahrungen habe ich leider wenig, aber die Theorie ist da. :D
Bild
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Wieder einmal Netzwerkcode - Anregungen gesucht

Beitrag von STARGÅTE »

  1. Kann mich Nic anschließen. Der Server sollte früher oder später die "Vertrauensperson" sein und die Clienten ggf. korrigieren, wenn sie etwas gemacht haben was sie nicht dürfen. Trotzdem sollte für ein flüssiges Spiel der Client in Echtzeit seine Aktionen durchführen und gleichzeitig dem Server melden
  2. Zu den LinkedLists: Generell betrachte ich diese wirklich nur als Listen im Netzwerk deren "eigene" Positionen völlig wurscht sind. Wenn ich z.B. n Liste voller Projektile habe die über die Karte fliegen, dann hat jedes Projektil eine vom Server definierte ID die eindeutig bei alle identifiziert werden kann. Man kann sicher überlegen ob man hier eine Map nutzt, um schneller auf ein Element mit der entsprechenden ID zugreifen zu können, aber solange PB aktuell nur String-Hash-Maps hat, macht das erst bei vielen Elementen sinn.
  3. Das Thema der Bewegungsübertragung ist ein sehr großes Thema. Hier ist man natürlich durch die Bandbreite limitiert. Ich konnte für mich feststellen, dass es flüssiger aussieht, wenn ich mehr Details zur Bewegung übertrage dafür aber eine geringere Rate nutze als nur die Position aber öfter.
    Konkretes Beispiel: Überträgt man relativ häufig nur die Position eines Elements (2 Floats) dann kommen die Pakete zimlich unausgeglichen beim anderen an und dort Hüpft das Sprite dann oft und zeitweise sogar rückwärts, weil sich Pakete vertauscht haben (UDP).
    In einem aktuellen Projekt übertrage ich mit 20 Hz die Position und den aktuellen Geschwindigkeitsvektor (und ggf. ein Zeitstempel). Nun wird aber nur beim ersten mal die gesendete Position beim Clienten direkt übernommen. Durch die Geschwindigkeit kann nun aber der Client selbst die Bewegung weiterführen. Kommt die/der nächste Position/Geschwindigkeitsvektor vergleicht der Client seine extrapolierte Position mit der empfangenen Position und passt den neuen empfangen Geschwindigkeitsvektor so an, dass er (bei 20 Hz) nach 50 ms wieder zur selben Position führen würde. Durch diese Technik sieht man zu keiner Zeit irgendein Hüpfen oder ruckeln, weil der Client die Bewegung selbst interpoliert Anhang der empfangenen Daten. Haben die Bewegungen auch viele Beschleunigungen/Abbremsungen könnte man natürlich diese Info auch noch senden und um Bewegung noch besser zu extrapolieren.
  4. Man muss hier mit dem Begriff "Paketen" aufpassen. Ein mit SendNetworkData() Datenblock ist kein Paket. Dieser Datenblock kann sowohl zwei Aufrufe von ReceiveNetworkData() nötig machen, also auch das zwei Datenblocke hintereinander mit einem Aufruf von ReceiveNetworkData() empfangen werden. Somit musst du dich selbst um die "Pakete" kümmern, also Datenblöcke mit z.B. Kopf, Info und ggf. Prüfsumme.
    Hinsichtlich TCP und UDP kannst du aber sicher sein, dass ein über TCP abgeschickter Datenblock in richtiger Reihenfolge ankommt und mit UDP nicht zwingend.
Als Erfahrungsbericht kann ich von folgende Sachen mitteilen:
Ehe ich ein Spiel fürs Netzwerk bauen konnte musst ich zunächst ein Modul schreiben das mir die Behandlung von "echten" Paketen sichert und ein Modul für die einfache Definition und Abarbeitung von Netzwerk-Ereignissen ermöglicht. Generell benutze ich immer TCP und UDP gleichzeitig, je nach Art der Daten (Chatnachrichten, Positionen, usw.). Die Gesamte Netzwerkverwaltung liegt in einem Thread, um unabhängig von der Spiel-Framerate zu sein.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
blbltheworm
Beiträge: 217
Registriert: 22.09.2004 19:36
Wohnort: Auf der schönen Schwäbischen Alb

Re: Wieder einmal Netzwerkcode - Anregungen gesucht

Beitrag von blbltheworm »

Hallo ihr zwei,

wie immer vielen Dank für eure detaillierten Antworten!
Dann fange ich mal mit experimentieren an. Ihr habt mir auf jeden Fall schon mal einiges an Zeit gespart.
@STARGATE, Der Tipp mit dem gleichzeitigen Nutzen von UDP & TCP klingt sehr gut. Bin gespannt ob ich das auch vernünftig implementiert bekomme.
Und dein Modell zur Übertragung von Bewegung ist das, was ich mit "Vektoren" meinte :D Nach dem was du schreibst, klingt es so, als wäre es den Aufwand wert das so zu implementieren.
Antworten