Hallo.
Ich habe eine Menge an unterschiedlichen Objekten und suche schon länger nach einer Möglichkeit, diese mit geringem Aufwand zu serialisieren. An welchen Stellen benötige ich die Serialisierung?
1. Objekte Laden/Speichern
2. Benutzeroberfläche, Editor, Masken
3. Synchronisation im Netzwerk
Punkt 1 sollte relativ klar sein. Im Programm ist alles durch Objekte/Klassen/Strukturen definiert und diese wollen gespeichert und geladen werden.
Mit Punkt 2 meine ich die Möglichkeit die geladenen Objekte z.B. in einem Editor zu bearbeiten. Ich könnte einfach für jeden Objekttyp eine Maske/Formular erstellen, aber das wäre sehr aufwändig.
In Punkt 3 geht es um die Synchronisation von z.B. einem Mehrspielerspiel, an dem gerade 4 Spieler teilnehmen. Irgendwie müssen die Welten/Level untereinander synchron gehalten werden und dazu muss man Objektinformationen austauschen. Auch hier könnte ich für jeden Objekttyp eine Funktion programmieren, aber auch das wäre, wie bei den Masken, viel zu aufwändig.
Meine aktuelle Lösung/Ansatz/Prototyp ist eine einfache selbst entwickelte Datenbank (Tabellen/Spalten/Indexe/Relationen). Jedes Objekte muss in speziellen Funktionen die eigene Struktur definieren und eine SaveToDatabase and LoadFromDatabase Funktion haben. Die Verarbeitung der Datensätze muss nur einmalig programmiert werden, unabhängig davon, welches Objekt darin gerade abgebildet wird.
Bis jetzt fällt mir keine bessere Lösung ein, und bevor ich dieses "Konzept" weiter verfolge, frage ich mal lieber in die Runde, ob jemand eine bessere Vorgehensweise kennt.
[C++] Objekte serialisieren/synchronisieren etc.
- Chromanoid
- Moderator
- Beiträge: 4273
- Registriert: 16.10.2002, 19:39
- Echter Name: Christian Kulenkampff
- Wohnort: Lüneburg
Re: [C++] Objekte serialisieren/synchronisieren etc.
Als erstes würde ich keine eigene Datenbank entwickeln, sondern sowas wie sqlite verwenden.
Ich glaube Anwendungsfall 1 und 2 kann man mit dem gleichen Weg lösen, für Netzwerkübertragung kommt es sehr darauf an, was bei Dir im Spiel passiert. Da sind Delta-Updates wahrscheinlich das beste, wenn ich an die 200.000 Entities denke.
Für generische Editor-Komponenten ist Reflection natürlich ziemlich praktisch. Editor-Zusatzinformationen an Deiner Feldbeschreibung für die Speicherung in der Datenbank wären doch da sehr einfach zu realisieren. So kannst Du dann Formulare generisch erzeugen. Für Speichern von Klassen, könnte auch das hier interessant sein: https://github.com/paulftw/hiberlite So ähnlich machst Du das ja auch schon.
Ansonsten kannst Du Dir vielleicht auch mal Protocol Buffers von Google anschauen. Das wird zum Beispiel bei Diablo 3 eingesetzt (ich bin mir aber nicht sicher wofür alles genau): https://developers.google.com/protocol- ... pptutorial Siehe hier für die Definitionen, die einige Bastler extrahiert haben: https://github.com/zku/Diablo-III-Proto ... efinitions
Bei Protocol Buffers hast Du auch Möglichkeiten für Reflection, die vielleicht für den Editor (edit: und die Datenbank?) taugen. Das Protokoll an sich wäre dann auch was für die Übertragung per Netzwerk.
Die Unreal-Engine hat ja ihr tolles Property-Framework, vielleicht kann dich das auch inspirieren: https://docs.unrealengine.com/latest/IN ... roperties/
Ich glaube Anwendungsfall 1 und 2 kann man mit dem gleichen Weg lösen, für Netzwerkübertragung kommt es sehr darauf an, was bei Dir im Spiel passiert. Da sind Delta-Updates wahrscheinlich das beste, wenn ich an die 200.000 Entities denke.
Für generische Editor-Komponenten ist Reflection natürlich ziemlich praktisch. Editor-Zusatzinformationen an Deiner Feldbeschreibung für die Speicherung in der Datenbank wären doch da sehr einfach zu realisieren. So kannst Du dann Formulare generisch erzeugen. Für Speichern von Klassen, könnte auch das hier interessant sein: https://github.com/paulftw/hiberlite So ähnlich machst Du das ja auch schon.
Ansonsten kannst Du Dir vielleicht auch mal Protocol Buffers von Google anschauen. Das wird zum Beispiel bei Diablo 3 eingesetzt (ich bin mir aber nicht sicher wofür alles genau): https://developers.google.com/protocol- ... pptutorial Siehe hier für die Definitionen, die einige Bastler extrahiert haben: https://github.com/zku/Diablo-III-Proto ... efinitions
Bei Protocol Buffers hast Du auch Möglichkeiten für Reflection, die vielleicht für den Editor (edit: und die Datenbank?) taugen. Das Protokoll an sich wäre dann auch was für die Übertragung per Netzwerk.
Die Unreal-Engine hat ja ihr tolles Property-Framework, vielleicht kann dich das auch inspirieren: https://docs.unrealengine.com/latest/IN ... roperties/
Re: [C++] Objekte serialisieren/synchronisieren etc.
Vielen Dank für die Antwort!
Ich brauche z.B. überhaupt kein SQL oder Indexe über mehrere Spalten. Sehr primitiv aber einfach und schnell.
Andere größere Engines angucken finde ich immer interessant, aber da kann man sich auch leicht verlieren. ;-)
Die eigene "Datenbank" ist eigentlich so gut wie fertig, und SQLite wäre vermutlich auch Overkill (sqlite3.c hat über 200.000 Codezeilen und ist über 7 MB :shock:).Chromanoid hat geschrieben:Als erstes würde ich keine eigene Datenbank entwickeln, sondern sowas wie sqlite verwenden.
Ich brauche z.B. überhaupt kein SQL oder Indexe über mehrere Spalten. Sehr primitiv aber einfach und schnell.
Zur Netzwerk-Synchronisation sollen nur Daten übermittelt werden, die auch nötig sind. Wie schon von dir angedeutet, bei über 200.000 Entities geht es gar nicht anders. Welche Technik ich da genau einsetzen werde, bzw. wie die Auswahl der Daten zur Synchronisation erfolgen wird, ist noch unklar.Chromanoid hat geschrieben:Ich glaube Anwendungsfall 1 und 2 kann man mit dem gleichen Weg lösen, für Netzwerkübertragung kommt es sehr darauf an, was bei Dir im Spiel passiert. Da sind Delta-Updates wahrscheinlich das beste, wenn ich an die 200.000 Entities denke.
Ganu. Auch zum Protoypen erstellen/testen sollte das sehr praktisch sein, wenn man nur die Klasse und Definition programmieren muss und der Rest (Load, Safe, Formular, Netzwerk) gleich mit erledigt ist.Chromanoid hat geschrieben:Für generische Editor-Komponenten ist Reflection natürlich ziemlich praktisch. Editor-Zusatzinformationen an Deiner Feldbeschreibung für die Speicherung in der Datenbank wären doch da sehr einfach zu realisieren. So kannst Du dann Formulare generisch erzeugen.
Sieht interessant aus.Chromanoid hat geschrieben:Für Speichern von Klassen, könnte auch das hier interessant sein: https://github.com/paulftw/hiberlite So ähnlich machst Du das ja auch schon.
Sieht auch interessant aus, vor allem aufgrund einer Definition eine C++ Klasse zu generieren. In früheren Projekten habe ich aufgrund einer Datenbank Klassen generiert, das war auch recht effektiv.Chromanoid hat geschrieben:Ansonsten kannst Du Dir vielleicht auch mal Protocol Buffers von Google anschauen. Das wird zum Beispiel bei Diablo 3 eingesetzt (ich bin mir aber nicht sicher wofür alles genau): https://developers.google.com/protocol- ... pptutorial Siehe hier für die Definitionen, die einige Bastler extrahiert haben: https://github.com/zku/Diablo-III-Proto ... efinitions
Bei Protocol Buffers hast Du auch Möglichkeiten für Reflection, die vielleicht für den Editor (edit: und die Datenbank?) taugen. Das Protokoll an sich wäre dann auch was für die Übertragung per Netzwerk.
Das scheint für Formulare/Masken zu sein?Chromanoid hat geschrieben:Die Unreal-Engine hat ja ihr tolles Property-Framework, vielleicht kann dich das auch inspirieren: https://docs.unrealengine.com/latest/IN ... roperties/
Andere größere Engines angucken finde ich immer interessant, aber da kann man sich auch leicht verlieren. ;-)
Re: [C++] Objekte serialisieren/synchronisieren etc.
Was das ablegen von Daten in einer DB angeht kann ich https://www.codesynthesis.com/products/odb/ empfehlen.
Was die Netzwerkkommunikation angeht ist halt die Frage wieviel Du selber machen willst und auf welchen Systemen Du unterwegs bist?
Wenn Du das serialisieren selber performant machen willst dann vielleicht das hier:
https://stackoverflow.com/questions/483 ... 9#48304519
als Startpunkt.
Gäbe dann auch noch weitere Möglichkeiten z.B. die Nachrichtengröße zu reduzieren, kann man sich bei Protocolbuffers abgucken. :D
Was die Netzwerkkommunikation angeht ist halt die Frage wieviel Du selber machen willst und auf welchen Systemen Du unterwegs bist?
Wenn Du das serialisieren selber performant machen willst dann vielleicht das hier:
https://stackoverflow.com/questions/483 ... 9#48304519
als Startpunkt.
Gäbe dann auch noch weitere Möglichkeiten z.B. die Nachrichtengröße zu reduzieren, kann man sich bei Protocolbuffers abgucken. :D
Re: [C++] Objekte serialisieren/synchronisieren etc.
Als ich mich damit zum letzten mal beschäftigt habe (damals für mein Spiel), war das Hauptproblem tatsächlich nicht das Serialisieren an sich, sondern eher meine interne Strukturierung. Ich hatte mir damals mal boost::serialization angeschaut, was ansich ziemlich mächtig ist, aber dann doch extrem viele Probleme bereitet hat.
Das Grundproblem ist ja, dass man zum Speichern vieler Klassen viel Code schreiben muss, der unglaublich langweilig ist, weswegen man das automatisieren will (z.B. mithilfe des schon erwähnten Reflection-Mechanismus). Das funktioniert für einfache Fälle super, man bekommt seine aktuelle Position und Lebenspunkte und so super gespeichert, und auch komplexere Member, wie z.B. ganze Strukturen, weil das ganze natürlich auch hierarchisch funktioniert. Schlimm wurde es beim Thema Referenzen.
boost::serialization hatte dafür sogar einen Mechanismus, man konnte Pointer auf Objekte auf dem Heap speichern und beim Laden wurden diese neu erstellt und alle Pointer richtig gesetzt, was ansich schon echt cool ist. Damit kann man komplexe, zusammenhängende Objektgraphen automatisch serialisieren. Nur das Problem ist, dass man es ja oft nicht will. Vielleicht hat man eine Art Game Klasse, und Objekte haben einen Zeiger darauf. Lädt man ein Level, will man vielleicht nicht die ganze Game-Instanz neu erstellen, möglicherweise weil sie auch das Hauptmenü beinhaltet. Man will auch manchmal vielleicht nur Teile eines Levels laden, oder bloß ein einzelnes Objekt aus einem anderen Level importieren. Und dann muss man sich wieder komplett selber um Referenzen kümmern.
Man hat Referenzen natürlich auch an anderen Stellen, beispielsweise könnte ein Objekt eine OpenGL-Textur-ID speichern. Das ist ein Integer, und jeder Serialisierer würde damit ohne Probleme umgehen können, nur nach dem Laden wäre der Wert trotzdem komplett nutzlos.
Ich habe letztendlich doch wieder für jedes Objekt Lade/Speicher-Funktionen geschrieben, allerdings mit einigen Makros und Hilfsfunktionen, so dass es halbwegs erträglich war. Insbesondere das Laden war dann auch ein mehrstufiger Prozess, im ersten Schritt wurden Daten gelesen, im zweiten dann Referenzen (Pointer) auf andere Spielobjekte aufgelöst, Beschleunigungsstrukturen gebaut, Grafik-Resourcen geladen, und so weiter.
Eine Alternative wäre es gewesen, das ganze Programm darauf auszulegen, dass es vernünftig automatisch serialisiert werden kann - nur könnte das eben bedeuten, dass man Einbuße bei der Performance der eigentlichen Ausführung in Kauf nehmen muss und das will man ja vielleicht auch nicht.
Darüber hinaus gibt es natürlich noch mehr Überlegungen. Vielleicht schafft man es ja, seine vielen kleinen Objekte so im Speicher abzulegen, dass man sie in einem Rutsch (z.B. Array) laden/speichern kann, was die benötigte Zeit drastisch reduzieren kann (und jeder mag Spiele/Programme, die ratzfatz laden!). Oder man hat unterstützt unterschiedliche Programmversionen (klar kann man seine Octreeknoten ins Savegame schreiben - aber tut man es nicht und baut ihn jedesmal neu, kann man vielleicht von Optimierungen an dieser Stelle profitieren, und die Daten gehören ja eigentlich auch nicht zum Spielzustand - andererseits ist alles mitspeichern vielleicht einfacher und schneller implementiert).
Was ich damit nur sagen will: Natürlich gibt es nützliche Bibliotheken. Aber es gibt fundamentale Fragestellungen im Programm Design die das Serialisieren beeinflussen und 'Fehlentscheidungen' die man da macht, sind prinzipieller Natur und können durch keine clever ausgesuchte Bibliothek mehr ausgeglichen werden. Das bedeutet natürlich auch, dass man Serialisierung nicht unbedingt einfach nachträglich einbauen kann, weil man eben für eine wirklich effiziente Implementierung vielleicht das komplette Programm umstrukturieren und damit quasi neuschreiben muss.
Das alles betrifft jetzt in erster Linie deinen ersten Punkt. Theoretisch hört es sich ja so an, dass wenn man nur Serialisieren kann, damit im Prinzip alle 3 Punkte sehr einfach abgefrühstückt sind. Aber ich glaube insbesondere 1 und 3 können sich doch extrem stark unterscheiden, ich habe wenig Erfahrung mit Netzwerksynchronisation, aber ich glaube man will z.B. sicherstellen, dass man möglichst wenig Daten übertragen muss, und dass man auf jedem beteiligten Rechner viele Dinge mit exakt dem selben Ergebnis berechnen kann (damit man sie nicht synchronisieren muss), was natürlich auch fundamentale Auswirkungen auf das Gesamtdesign hat.
Das Grundproblem ist ja, dass man zum Speichern vieler Klassen viel Code schreiben muss, der unglaublich langweilig ist, weswegen man das automatisieren will (z.B. mithilfe des schon erwähnten Reflection-Mechanismus). Das funktioniert für einfache Fälle super, man bekommt seine aktuelle Position und Lebenspunkte und so super gespeichert, und auch komplexere Member, wie z.B. ganze Strukturen, weil das ganze natürlich auch hierarchisch funktioniert. Schlimm wurde es beim Thema Referenzen.
boost::serialization hatte dafür sogar einen Mechanismus, man konnte Pointer auf Objekte auf dem Heap speichern und beim Laden wurden diese neu erstellt und alle Pointer richtig gesetzt, was ansich schon echt cool ist. Damit kann man komplexe, zusammenhängende Objektgraphen automatisch serialisieren. Nur das Problem ist, dass man es ja oft nicht will. Vielleicht hat man eine Art Game Klasse, und Objekte haben einen Zeiger darauf. Lädt man ein Level, will man vielleicht nicht die ganze Game-Instanz neu erstellen, möglicherweise weil sie auch das Hauptmenü beinhaltet. Man will auch manchmal vielleicht nur Teile eines Levels laden, oder bloß ein einzelnes Objekt aus einem anderen Level importieren. Und dann muss man sich wieder komplett selber um Referenzen kümmern.
Man hat Referenzen natürlich auch an anderen Stellen, beispielsweise könnte ein Objekt eine OpenGL-Textur-ID speichern. Das ist ein Integer, und jeder Serialisierer würde damit ohne Probleme umgehen können, nur nach dem Laden wäre der Wert trotzdem komplett nutzlos.
Ich habe letztendlich doch wieder für jedes Objekt Lade/Speicher-Funktionen geschrieben, allerdings mit einigen Makros und Hilfsfunktionen, so dass es halbwegs erträglich war. Insbesondere das Laden war dann auch ein mehrstufiger Prozess, im ersten Schritt wurden Daten gelesen, im zweiten dann Referenzen (Pointer) auf andere Spielobjekte aufgelöst, Beschleunigungsstrukturen gebaut, Grafik-Resourcen geladen, und so weiter.
Eine Alternative wäre es gewesen, das ganze Programm darauf auszulegen, dass es vernünftig automatisch serialisiert werden kann - nur könnte das eben bedeuten, dass man Einbuße bei der Performance der eigentlichen Ausführung in Kauf nehmen muss und das will man ja vielleicht auch nicht.
Darüber hinaus gibt es natürlich noch mehr Überlegungen. Vielleicht schafft man es ja, seine vielen kleinen Objekte so im Speicher abzulegen, dass man sie in einem Rutsch (z.B. Array) laden/speichern kann, was die benötigte Zeit drastisch reduzieren kann (und jeder mag Spiele/Programme, die ratzfatz laden!). Oder man hat unterstützt unterschiedliche Programmversionen (klar kann man seine Octreeknoten ins Savegame schreiben - aber tut man es nicht und baut ihn jedesmal neu, kann man vielleicht von Optimierungen an dieser Stelle profitieren, und die Daten gehören ja eigentlich auch nicht zum Spielzustand - andererseits ist alles mitspeichern vielleicht einfacher und schneller implementiert).
Was ich damit nur sagen will: Natürlich gibt es nützliche Bibliotheken. Aber es gibt fundamentale Fragestellungen im Programm Design die das Serialisieren beeinflussen und 'Fehlentscheidungen' die man da macht, sind prinzipieller Natur und können durch keine clever ausgesuchte Bibliothek mehr ausgeglichen werden. Das bedeutet natürlich auch, dass man Serialisierung nicht unbedingt einfach nachträglich einbauen kann, weil man eben für eine wirklich effiziente Implementierung vielleicht das komplette Programm umstrukturieren und damit quasi neuschreiben muss.
Das alles betrifft jetzt in erster Linie deinen ersten Punkt. Theoretisch hört es sich ja so an, dass wenn man nur Serialisieren kann, damit im Prinzip alle 3 Punkte sehr einfach abgefrühstückt sind. Aber ich glaube insbesondere 1 und 3 können sich doch extrem stark unterscheiden, ich habe wenig Erfahrung mit Netzwerksynchronisation, aber ich glaube man will z.B. sicherstellen, dass man möglichst wenig Daten übertragen muss, und dass man auf jedem beteiligten Rechner viele Dinge mit exakt dem selben Ergebnis berechnen kann (damit man sie nicht synchronisieren muss), was natürlich auch fundamentale Auswirkungen auf das Gesamtdesign hat.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
https://jonathank.de/games/