2012-12-27 19 views
5

Ich versuche, Multi-Purpose-Serialisierung für ein vernetztes Videospiel für mobile Geräte einzurichten. Da es vernetzt ist, muss ich während der ersten Verbindung alle Daten für den Spielstatus serialisieren, aber sobald ein Spiel läuft, muss ich nur bestimmte Änderungen serialisieren. Die Speicher- und Ladeverfahren, die Teil der Boost-Serialisierungsbibliothek sind, haben nur eine Versionsnummer als Parameter. Was ich gerne hätte, wäre, mehr Parameter zu haben, so dass ich die Bedingungen für das, was gespeichert und geladen wird, basierend auf mehr als nur einer Versionsnummer ändern kann.Erweitern Boost Serialisierung

Boost serialization docs are here, for reference.

Hier ist, was die Ebene Boost-Serialisierung save Methode sieht derzeit wie:

template<class Archive> 
void save(Archive& ar, const unsigned int version) const 
{ 
    // Serialize stuff here 
} 

was hier ich erreichen möchte: ich die

template<class Archive> 
void save(Archive& ar, const unsigned int version, const unsigned int state_flags) const 
{ 
    if (state_flags & INITIAL_SERIALIZATION) 
    { 
     // Serialize only data needed for an initial serialization 
    } 

    // Other serialization 
} 

Ich bezweifle machen Boost-Bibliothek rufe meine Serialisierungsmethode auf, die ich will, weil sie überladene Operatoren gemacht hat, um einen mit dem speci aufzurufen fic Signatur im ersten Beispiel oben. Ich stelle mir vor, meine eigene Version von save aus dem Anruf zu save, der im ersten Beispiel gezeigt wird, anzurufen und vielleicht die state_flags von einem separaten Ort zu greifen. Hat jemand irgendwelche Ideen, wie dies sauber gemacht werden könnte, oder irgendwelche guten Alternativen?

EDIT: Ich bin in ein anderes Problem geraten. Ich muss Objekte serialisieren, die nicht notwendigerweise Mitglieder einer Klasse sind, aber die Dokumentation erwähnt keine Unterstützung dafür.

Hier ist ein einfaches Beispiel:

class Foo 
{ 
private: 
    SomeClass m_object; 

    template<class Archive> 
    void save(Archive& ar, const unsigned int version) const 
    { 
     Bar* pBar = m_object->getComponent<Bar>(); 
     ar & pBar; // <--- But pBar isn't a member of Bar, it's part of SomeClass. 
    } 
}; 

Ich würde SomeClass nur serialisiert werden und dass auf Bar rieseln lassen, aber in diesem Fall ist es eine Klasse, der Teil eines Dritten Bibliothek/Motors ist, nicht etwas, Ich kann modifizieren. Will Boost Serialisierung erlauben mir, diese Art zu serialisieren und zu deserialisieren?

Antwort

3

EDIT: neue Antwort unten hinzugefügt, um das eigentliche Problem zu adressieren.

Ihre Frage impliziert, dass Sie mehrmals auf dasselbe Objekt deserialisieren. Es ist umständlich, ob das sauber ist oder nicht. Wenn Sie zum Beispiel ein Schachbrett haben, möchten Sie die Anfangsposition der Stücke synchronisieren (um vom letzten gespeicherten Spiel fortzusetzen). Um die Züge zu kommunizieren, während das Spiel gespielt wird, kann es sinnvoller sein, die einzelnen Züge als separate Objekte zu senden (die dann nach Erhalt auf das Brettobjekt angewendet werden), anstatt das gesamte Brettobjekt zu übertragen, das nur das überträgt geändert, wenn es bereits 'initialisiert' ist. Auf diese Weise können Sie die Eingabe zuerst validieren und ungültige Bewegungen ignorieren. Jedenfalls wollte ich das nur erwähnen, lass uns weitergehen.

Wenn Sie ein Objekt haben, das mehrfach synchronisiert werden kann und nur einmal übertragen werden muss, lassen Sie das Objekt entscheiden, ob es 'initialisiert' ist oder nicht (und folglich, wenn es alles übertragen muss oder nur eine Untermenge) mit einem Flag (das nicht serialisiert ist).

Dann können Sie das Flag im Serialisierungscode des Objekts überprüfen, genau wie in dem von Ihnen geposteten Code (mit der Ausnahme, dass das Flag kein Parameter für die Serialisierungsmethode ist, sondern eine Elementvariable des Objekts, das Sie sind de/serialisieren).Wenn das Flag gesetzt ist, de/serialisieren Sie alles und setzen Sie das Flag zurück. Sowohl der Client als auch der Server müssen denselben Status des Flags haben oder die Serialisierung bricht ab.

Alternativ könnten Sie zuerst das Flag serialisieren, um dem Empfänger mitzuteilen, wie die Deserialisierung durchgeführt werden muss (z. B. ein Bit für jede Mitgliedsdatengruppe).

Beachten Sie, dass die Deserialisierung mit der Serialisierung übereinstimmen muss; Sie müssen dieselben Objekte in derselben Reihenfolge extrahieren, in der sie serialisiert wurden. Sie können jedoch polymorphe Klassen serialisieren, vorausgesetzt, sie werden auf derselben Ebene in der Klassenhierarchie serialisiert wie sie deserialisiert werden (im Zweifelsfall wird auch beim Senden und Deserialisieren durch den Basiszeiger zum Basiszeiger gewandelt)).

In Bezug auf Ihre zweite Frage, was Sie suchen, ist non-intrusive serialization. Die nicht-intrusive Serialisierung ruft freistehende Funktionen auf und übergibt das zu serialisierende Objekt als Parameter (so werden std :: vector und boost :: shared_ptr serialisiert). Sie können BOOST_SERIALIZATION_SPLIT_FREE verwenden, um die freistehende Funktion in save() und load() aufzuteilen. Für intrusive Serialisierung ist es BOOST_SERIALIZATION_SPLIT_MEMBER.

Um ein verallgemeinerte de/Serialisierungsfunktion (das überträgt ein Objekt über das Netzwerk zum Beispiel) schreiben Sie Vorlagen verwenden können:

template<typename T> 
void transmit(const T& data) { 
    // ... 
    archive << data 
    socket << archive_stream; 
} 

Die Einschränkung bei dieser Methode ist, dass der Empfänger wissen müssen, welche Art von Objekt wurde geschickt. Wenn Sie zufällige Objekte senden möchten, machen sie polymorphe:

IData* data = 0; 
archive >> data; 
switch(data->type()) { 
case TYPE_INIT: 
    return dispatch(static_cast<Board*>(data)); 
case TYPE_MOVE: 
    return dispatch(static_cast<Move*>(data)); 
case TYPE_CHAT: 
    return dispatch(static_cast<ChatMsg*>(data)); 
} 

UPDATE: Wenn Sie steuern müssen, wie Ihre (individuelle) Serialisierungsmethoden/Funktionen verhalten, basierend auf einem Zustand unbekannten Typen Wenn Sie serialisiert werden, können Sie eine eigene Archivklasse implementieren, die den Status enthält. Die Serialisierungsfunktionen können dann den Status abfragen und entsprechend handeln.

Dieser Status (oder eine entsprechende Ersetzung) muss ebenfalls serialisiert werden, um anzugeben, wie die Daten deserialisiert werden müssen. Zum Beispiel könnte dieses "unterschiedliche Verhalten" der Serialisierungsfunktionen eine Art von Komprimierung sein, und der Status ist die Art der verwendeten Komprimierung.

Hier ist ein minimales Beispiel für ein benutzerdefiniertes Ausgabearchiv. Für weitere Informationen können Sie Derivation from an Existing Archive lesen und durch die Boost-Quellen graben.

eine Klasse Da Sie nicht ändern können:

struct Foo { 
    Foo() : i(42), s("foo") {} 
    int i; 
    std::string s; 
}; 

Sie möchten unbekannt in der Klasse auf einem Zustand serialisiert i und/oder s basiert. Sie könnten einen Wrapper erstellen, um ihn zu serialisieren und den Status hinzuzufügen. Dies funktioniert jedoch nicht, wenn das Objekt in einem Vektor (oder einer anderen Klasse) liegt.

Es kann einfacher sein, um das Archiv bewusst zu machen den Staat statt:

#include <boost/archive/text_oarchive.hpp> 

// using struct to omit a bunch of friend declarations  
struct oarchive : boost::archive::text_oarchive_impl<oarchive> 
{ 
    oarchive(std::ostream& os, unsigned flags=0) 
     : boost::archive::text_oarchive_impl<oarchive>(os,flags),mask(0){} 

    // forward to base class 
    template<class T> void save(T& t) { 
     boost::archive::text_oarchive_impl<oarchive>::save(t); 
    } 

    // this is the 'state' that can be set on the archive 
    // and queried by the serialization functions 
    unsigned get_mask() const { return mask; } 
    void set_mask(unsigned m) { mask = m; } 
    void clear_mask() { mask = 0; } 
private: 
    unsigned mask; 
}; 

// explicit instantiation of class templates involved 
namespace boost { namespace archive { 
    template class basic_text_oarchive<oarchive>; 
    template class text_oarchive_impl<oarchive>; 
    template class detail::archive_serializer_map<oarchive>; 
} } 

// template implementations (should go to the .cpp) 
#include <boost/archive/impl/basic_text_oarchive.ipp> 
#include <boost/archive/impl/text_oarchive_impl.ipp> 
#include <boost/archive/impl/archive_serializer_map.ipp> 

Jetzt ist der Zustand zu setzen und Abfrage:

enum state { FULL=0x10, PARTIAL=0x20 }; 

und ein Verfahren um den Zustand zu setzen (dies ist nur ein sehr einfaches Beispiel):

oarchive& operator<<(oarchive& ar, state mask) { 
    ar.set_mask(ar.get_mask()|mask); 
    return ar; 
} 

Schließlich wird der (nicht intrusiv) Serialisierung Funktion:

namespace boost { namespace serialization { 

template<class Archive> 
void save(Archive & ar, const Foo& foo, const unsigned int version) 
{ 
    int mask = ar.get_mask(); // get state from the archive 
    ar << mask; // serialize the state! when deserializing, 
    // read the state first and extract the data accordingly 

    if(mask & FULL) 
     ar << foo.s; // only serialize s if FULL is set 
    ar << foo.i;  // otherwise serialize i only 
    ar.clear_mask(); // reset the state 
} 

} } // boost::serialization 

BOOST_SERIALIZATION_SPLIT_FREE(Foo) 

Und dies kann wie folgt verwendet werden:

int main() { 
    std::stringstream strm; 
    oarchive ar(strm); 

    Foo f; 
    ar << PARTIAL << f << FULL << f; 

    std::cout << strm.str(); 
} 

Der Zweck dieses Beispiels ist es nur um das Prinzip zu veranschaulichen. Es ist zu einfach für den Produktionscode.

+0

Das Problem, das ich mit Boost-haben ist, dass ich die Mitglieder einer Klasse serialisiert werden müssen, was ich in einigen Fällen tun möchte, ist nur Teile einer Klasse serialisiert, Teile, die möglicherweise nicht einmal Membervariablen sind, sondern Daten, die aus Teilen von Membervariablen erstellt werden. Ich werde nicht alle Daten jedes Mal vollständig serialisieren, ich werde nur die Daten serialisieren, die benötigt werden, um festzustellen, was sich geändert hat. Nur die anfängliche Serialisierung enthält alle Daten. –

+0

Basierend auf meinen allgemeinen Bedürfnissen habe ich mich entschieden, die Boost-Serialisierung nicht zu verwenden. Meistens wollte ich nur eine Bibliothek, die gewöhnliche Datentypen zu/von Binärdateien serialisiert, aber die Art und Weise, wie Boost dies tat, war nicht genau das, wonach ich suchte. Ich werde wahrscheinlich meine eigenen Methoden schreiben, um Typen in/aus binär zu konvertieren und eine separate Bibliothek zu verwenden, um die Bitströme für mich zu komprimieren/dekomprimieren. Ihre Antwort nähert sich jedoch aufgrund meiner Frage dem, was ich brauchte, daher werde ich sie als die Antwort auf meine Frage markieren. Vielen Dank. –

+1

@NicFoster Ich war im Begriff, die Antwort zu entfernen, da ich Ihre Frage missverstanden habe. fühlen Sie sich frei, es zu inakzeptabel und unupvote, bis ich (oder jemand anderes) mit einer richtigen Lösung kommt. Sie möchten eine Klasse serialisieren, die Sie beispielsweise in einem Vektor nicht ändern können, und Sie möchten steuern, wie sie serialisiert wird, ohne zu einer globalen Variablen zurückzukehren. –

0

Ich habe eine Lösung für dieses Problem gefunden, und obwohl es nicht ideal ist, dachte ich mir, dass es sich lohnt, es trotzdem zu veröffentlichen. Im Grunde habe ich eine Singleton-Klasse eingerichtet, um das Senden aller Serialisierungsanforderungen zu verwalten, und diese Klasse würde die neuesten Bit-Flags verfolgen, die für diese Anforderung verwendet wurden. Bei der Serialisierung oder Deserialisierung könnten diese Methoden also nach diesen Flags suchen. Dies ermöglichte es mir, die Methoden save und load von Boost zu einer robusteren Menge von Methoden aufzurufen, die diese Flags verwenden könnten, um nur bestimmte Member selektiv zu serialisieren.

// Boost's `save` method, which must have this exact signature 
template<class Archive> 
void save(Archive& ar, const unsigned int version) const 
{ 
    const unsigned int flags = SerializationManager::getFlags(); // SerializationManager is a singleton. 
    saveData(ar, version, flags); 
} 

// Your own custom method, which can have whichever parameters you need 
template<class Archive> 
void saveData(Archive& ar, const unsigned int version, const unsigned int state_flags) const 
{ 
    if (state_flags & INITIAL_SERIALIZATION) 
    { 
     // Serialize only data needed for an initial serialization 
    } 

    // Other serialization 
} 
+0

Meine derzeitige Idee ist es, ein benutzerdefiniertes Archiv zu erstellen, das Flags enthalten kann.Sie könnten diese Flags dann mit einem Manipulator wie iomanip setzen. zum Beispiel 'archiv << arflag (COMPLETE) << foo << arflag (MINIMAL) << bar;' In Ihrem Speicher könnten Sie einfach die Flags mit 'ar.get_flags()' abfragen. –

+0

Ich glaube, etwas wie das, was Sie erwähnen, würde funktionieren, für benutzerdefinierte Typen, wo "Foo" und "Bar" könnte eine benutzerdefinierte Serialisierung basierend auf Flags implementieren, ähnlich wie ich oben habe. Die Einschränkung, auf die ich mit Boost gestoßen bin, ist, dass es erwartet, dass vollständige Datenmitglieder serialisiert werden, und gelegentlich muss ich nur Rohdaten serialisieren. Zum Beispiel, wenn ich eine 'std :: vector ' als eine Membervariable habe, aber ich muss nur seine Größe über das Netzwerk serialisieren, gibt es eine Möglichkeit, das zu tun, ohne eine andere Mitgliedsvariable zu haben, die ihre Größe darstellt (was wäre extrem schwierig zu pflegen)? –

+1

Schlimmer noch, Sie könnten immer einen nicht-intrusiven Serializer für den Vektor selbst schreiben (in diesem Fall nicht die Datei '/ serialization/vector.hpp') und die Größe nur serialisieren, wenn es angemessen ist. Sie müssen jedoch dem Empfänger mitteilen, dass er nur die Größe (da Sie keine tatsächlichen Elemente senden) mit einem Flag extrahieren muss. –

0

Hier ist ein einfacher Weg:

// Boost's `save` method, which must have this exact signature 
template<class Archive> 
void save(Archive& ar, const unsigned int version) const 
{ 
    const unsigned int flags = SerializationManager::getFlags(); //   SerializationManager is a singleton. 
    ar << flags; 
    if(flags && INITIAL_SERIALIZATION){ 
     // Serialize only data needed for an initial serialization 
    } 
    // Other serialization 
} 
template<class Archive> 
void load(Archive& ar, const unsigned int version) const 
{ 
    const unsigned int flags = SerializationManager::getFlags(); //   SerializationManager is a singleton. 
    unsigned int flags; 
    ar >> flags; 
    if(flags && INITIAL_SERIALIZATION){ 
     // Serialize only data needed for an initial serialization 
    } 
    // Other serialization 
} 
Verwandte Themen