Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

28.08.2012, 14:25

[C++] Serializierungs Problem

Für mein Spiel möchte ich die Möglichkeit haben den aktuellen Zustand des Spiels einfach abspeichern zu können. Dafür habe ich mir überlegt das ich einfach ein Interface biete das eine save/load Funktion hat die einen entsprechenden Stream mitbekommt. Am Ende muss nur jede Klasse die von dem Interface (oder einer Klasse die vom Interface erbt) erbt die Vorgänger Methode aufrufen.

Hierachie: ISerializable -> GameObject -> Spieler

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GameObject::save(...)
{
// speichere GameObject Daten
}

Spieler::save(...)
{
// speichere Spieler Daten
GameObject::save(...);
}

GameObject::load(...)
{
// lade GameObject Daten
}

Spieler::load(...)
{
// lade Spieler Daten
GameObject::load(...);
}

Momentan benutze ich noch typeid().name() um zu prüfen ob jetzt wirklich ein GameObject im stream anfängt, dass möchte ich allerdings noch änder vielleicht durch einen konstanten String der einfach nur den Klassennamen beinhaltet. Nun habe ich aber noch 2 Probleme, mit der jetzigen Funktionsweise muss ich die Daten in der gleichen Reihenfolge laden wie sie gespeichert wurde, was nicht sofort schlimm ist aber es bietet wenig komfort und Problem Nummer 2 wenn ich eine Klasse habe die nur Basis Pointer hat Serializieren möchte muss ich zur Ladezeit herausfinden welcher "Kind"-Klasse das Object angehört.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Object : public ISerializable
{
public:
void save(...)
{
// für alle Base* in m_Speicher
Base->save(...);
}

void load(...)
{
// ??
}

private:
std::list<Base*> m_Speicher;
};


Das sind bisher so meine Ansätze, haltet ihr das Desgin für Sinnvoll oder habe ich da etwas nicht bedacht das mich in Teufelsküche bringt wird? Ich weis ich könnte einfach eine fertige Lib dafür nehmen aber es macht vielmehr Spaß diese Dinge mal selbst zu machen als immer etwas fertiges zu benutzen und ich mach es ja nur um was zu lernen daher muss es auch nicht perfekt sein nur funktionieren sollte es am Ende.
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

2

28.08.2012, 14:39

Ich empfehle die Verwendung einer Object-ID um die Objekte eindeutig identifizieren zu können und um Zeiger,Referenzen etc. sinnvoll exportieren zu können. Je nach Anwendung existiert ja meist sowieso schon eine Object ID; so ist es in Mehrspielerspielen sowieso üblich allen Instanzen ne ID zu verpassen, welche man dann wunderbar benutzen kann. Falls es noch keine solche globalen IDs geben sollte, kann man diese entweder generell einbauen oder man benutzt beim Speichern einfach eine globale map zum merken der zeiger:ID verweisen.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

3

28.08.2012, 16:12

Ich habe ein IUnqiue Interface das der erbenden Klasse eine ID gibt die bei jedem neustarten des Programm unterschiedlich aber definitiv einzigartig ist und ein IGlobalUnique das eine ID gibt die auch beim neustarten des Programms gleichbleibt. Allerdings werden die IGlobalUnique nicht dynamisch sondern über einen seed string generiert. Ich hab zwar schon nen paar Sachen über GUID´s gelesen aber wie genau man die einzelnen Blöcke generieren soll habe ich noch nicht gefunden. Das Problem mit dem "wiederherstellen von unbekannten Kind-Objekten" hätte ich aber immernoch mit der ID.
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

4

28.08.2012, 17:38

Die IDs müssen nur für das entsprechende Abbild eindeutig sein und bei weitem keine GUID sein. Im Prinzip reicht es wenn du vorm Speichern bei einem Objekt anfängst du dann einfach aufsteigende Nummern verteilst. Eine sehr einfache Variante für das Speichern wäre (pseudocode):

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
serialize(ptr_data_map,ref_to_id_counter)
   ref_to_id_counter += 1
   class_identifier = get_class_name()
   own_attributes = serialize_data_fields_of_this_element()
   children_ids = []


   for child_ptr in this:
        if child_ptr not in ptr_data_map:
             child_ptr.serialize(ptr_data_map,id_counter)
        children_ids.append(ptr_data_map[child_ptr][0])

   ptr_data_map[this_ptr] = list(ref_to_id_counter,class_identifier,own_attributes + children_ids)
[...]
#main
ptr_data_map = dict()
counter = 1
serialize(ptr_data_container,counter)

file = open(filename)
for obj_ptr,data in ptr_data_map:
    obj_id,obj_type,obj_data = data
    file.write(obj_id)
    file.write(obj_type)
    file.write(obj_data)

Grundidee ist es dafür zu sorgen dass alle benötigten Elemente genau einmal serialisiert und deren Daten dann in einer Map gespeichert werden. Dazu traversiert man alles erreichbaren Elemente und speichert für das deserialisieren im Objekt selbst nur die ID der Kinder ab. Beim Schreiben der serialisierten Daten könnte man sich auch die offsets zu den einzelnen Objektdaten merken, wenn man nicht direkt am Anfang alle Objekte deserialisieren kann/will und daher einen random access bräuchte.
Beim derserialisieren kann man dann erstmal alle Objekte in eine Map einlesen wo man über die ID dann die Objektdaten bzw den Objektzeiger erhält, wenn das Objekt bereits deserialisert wurde. Anhand des Objektyps kann man dann die entsprechende Klasse auswählen. So würde ich wahrscheinlich ran gehen, wenn ich jetzt aktuell aus dem Stehgreif was komplett neues aufziehen müsste. Ich selbst nutze zwar ein anderes System im Rahmen einer Netzwerklib, welches ohne explizite Objekttyp-Informationen auskommt, aber hat wiederum andere Nachteile.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Nox« (28.08.2012, 18:17)


5

28.08.2012, 20:42

Die IDs müssen nur für das entsprechende Abbild eindeutig sein und bei weitem keine GUID sein. Ich selbst nutze zwar ein anderes System im Rahmen einer Netzwerklib, welches ohne explizite Objekttyp-Informationen auskommt, aber hat wiederum andere Nachteile.

Hört sich intressant an, kannst du mir dazu einen Suchbegriff geben?
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

6

29.08.2012, 00:00

Sozusagen als Referenz könntest du dir boost::serialization angucken. In der Dokumentation werden auch ein paar der vorkommende Probleme behandelt und dgezeigt, wie boost::serialization sie löst.
Lieber dumm fragen, als dumm bleiben!

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

7

29.08.2012, 17:05

kA warum du die beiden Aussagen zusammen geschnitten hast, aber wie das mit den IDs geht, ist ja quasi im pseudocode gezeigt. Der "Trick" ist die Adressen/Referenz durch eine ID zu ersetzen. Natürlich ist es mit einer ID-Basisklasse sauberer und auch sicherer.
Bei meiner Netzwerklib nutze ich ein System wo das eigentliche Elternelement immer mit Sicherheit einem Kontainer eine bestimmte Kindsorte zuordnen kann. Ich speichere in einem Kontainer also fast immer nur eine Art von Klasse ab und nur sehr selten Basisklassenzeiger. Falls das doch mal vorkommen sollte, führe ich an der Stelle eben ne type ID ein. Andere Objekte die nur ein Verweis auf ein Kindobjekt brauchen, können die Kindobjekte nicht erstellen, sondern nur erfragen falls es schon existiert und auf Wunsch in einer Liste vermerkt werden, dass sie per Callback informiert werden wollen, wenn das erfragte Objekt erstellt wurde.

Konkrete Stichworte kann ich dir leider nicht nennen, da das meiste von mir so mit der Zeit angeeignetes Zeug ist.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

8

29.08.2012, 23:20

Ich habe schon verstanden wie du das mit den IDs meintest aber durch eine Laufzeitabhänige ID weis ich ja immer noch nicht welche Kind-Klasse geladen werden muss. Ich werde erstmal Boost Doku und Code büffeln vielleicht komme ich dann selbst drauf wie ich es umsetzen kann. Eine Sache möchte ich doch noch Fragen in dem Link von Jonathan steht unter zweitens:

Zitat

Code economy - exploit features of C++ such as RTTI, templates, and multiple inheritance, etc. where appropriate to make code shorter and simpler to use.


Heißt das Boost benutzt RTTI? Ich hab hier im Forum schon öfter gelesen das RTTI "böse" ist, warum eigentlich?
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

9

30.08.2012, 00:04

Überleg mal was RTTI ist: Ein Mechanismus der über komplizierte Umwege zur Laufzeit Dinge herausfindet, die du beim Schreiben des Code eigentlich weißt. Ich hab jedenfalls noch keine sinnvolle Anwendung von RTTI bzw. Reflection in statisch typisierten Sprachen gesehen. typeid und dynamic_cast sind ganz oben auf der Liste der Dinge, die ich in C++ nicht vermissen würde... ;)

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (30.08.2012, 00:09)


10

30.08.2012, 14:10

Überleg mal was RTTI ist: Ein Mechanismus der über komplizierte Umwege zur Laufzeit Dinge herausfindet, die du beim Schreiben des Code eigentlich weißt.

Bei meinem aktuellen Desgin weis ich eben nicht welchen Typ die Objekte haben da sie teilweise in Lua zusammengestellt werden und so zur Laufzeit immer verändert werden können, das ist auch der Grund warum ich eine Liste mit Base* habe weil ich eben bei schreiben des Codes nicht genau weis welche Objekte sich in der Base* Liste befinden können.

Ich weis nur nicht so recht wie aus den Informationen die ich habe(Klassen Name oder ID) eine Instanz zu erzeugen ohne mit etwas wie dem folgenden zu enden.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
Base* load(...)
{
if(name == kind1)
return new Kind1();
else if(name == kind2)
return new Kind2();
// usw.
}


In boost habe ich die entsprechende stelle noch nicht gefunden aber ich suche weiter.
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Sortoc« (31.08.2012, 13:05)


Werbeanzeige