2012-12-27 19 views
5

Próbuję skonfigurować serializację wielofunkcyjną dla sieciowej gry wideo na urządzenia mobilne. Ponieważ jest on połączony w sieć, podczas początkowego połączenia muszę serializować wszystkie dane dotyczące stanu gry, jednak gdy gra jest w toku, będę musiał tylko serializować pewne zmiany. Metody składowania i ładowania będące częścią biblioteki serializacji kryteriów wzmocnienia mają tylko numer wersji jako parametr. To, co chciałbym móc zrobić, to mieć więcej parametrów, dzięki czemu mogę zmienić warunki dla tego, co zostanie zapisane i załadowane na podstawie czegoś więcej niż tylko numeru wersji.Rozszerzanie serializacji doładowania

Boost serialization docs are here, for reference.

Oto co zwykły impuls serializacji save metoda obecnie wygląda następująco:

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

Oto, co chciałbym osiągnąć:

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 
} 

Wątpię mogę zrobić boost biblioteka wywołaj moją metodę serializacji, której chcę, ponieważ przeciążała ona operatorów wykonanych w celu wywołania jej ze specyfikacją Podpis fikcyjny w pierwszym przykładzie powyżej. Mogę sobie wyobrazić wywołanie mojej własnej wersji save z poziomu połączenia save pokazanego w pierwszym przykładzie, a może pobrać state_flags z oddzielnej lokalizacji. Czy ktoś ma jakieś pomysły na to, jak można to zrobić w czysty sposób, czy jakieś dobre alternatywy?

EDYCJA: Znalazłem inny problem. Muszę serializować obiekty, które niekoniecznie należą do klasy, ale dokumentacja nie wspomina o żadnej z nich.

Oto prosty przykład:

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. 
    } 
}; 

Chciałbym tylko szeregować SomeClass i pozwól, aby dotarły do ​​Bar, ale w tym przypadku jest to klasa, która jest częścią trzeciej partii Library/silnika, a nie coś Mogę modyfikować. Czy serializacja Boost pozwoli mi na serializację i deserializację w ten sposób?

Odpowiedz

3

EDYCJA: nowa odpowiedź została dodana poniżej w celu rozwiązania faktycznego problemu.

Twoje pytanie sugeruje, że deserializujesz wielokrotnie ten sam obiekt. Jest to poszlakowe, jeśli jest czyste, czy nie. Jeśli na przykład masz szachownicę, zechcesz zsynchronizować początkową pozycję figur (aby kontynuować od ostatniej zapisanej gry). Aby komunikować ruchy podczas grania, lepszym pomysłem jest wysłanie pojedynczych ruchów jako oddzielnych obiektów (które następnie zostaną zastosowane do obiektu płyty po otrzymaniu) zamiast transmitowania całego obiektu płyty, który przenosi tylko to, co zostało zmienione, jeśli jest już "zainicjalizowane". W ten sposób można najpierw sprawdzić poprawność danych wejściowych i zignorować nieprawidłowe ruchy. W każdym razie, chciałem o tym wspomnieć, przejdźmy dalej.

Jeśli istnieje obiekt, który może być synchronizowany wiele razy, z danymi elementu, które należy przekazać tylko raz, niech obiekt zdecyduje, czy jest "zainicjowany" czy nie (i w konsekwencji, jeśli musi przekazać wszystko lub tylko zestaw podrzędny) za pomocą flagi (która nie jest serializowana).

Następnie możesz sprawdzić flagę w kodzie serializacji obiektu, tak jak w kodzie, który wysłałeś (z wyjątkiem tego, że flaga nie jest parametrem metody serializacji, , ale zmienną składową obiektu, który masz de/serializowanie).Jeśli flaga jest ustawiona, usuń/serializuj wszystko i zresetuj flagę. Zarówno klient, jak i serwer muszą mieć ten sam stan flagi lub serializacja.

Alternatywnie możesz najpierw serializować flagę, aby poinformować odbiorcę, jak należy przeprowadzić deserializację (na przykład jeden bit dla każdej grupy danych członka).

Należy pamiętać, że deserializacja musi być zgodna z serializacją; Musisz wyodrębnić te same obiekty w tej samej kolejności, w jakiej były serializowane.

Można jednak szeregować klasy polimorficzne, biorąc pod uwagę, że są serializowane na tym samym poziomie w hierarchii klas, ponieważ są deserializowane (w razie wątpliwości, rzutuj na wskaźnik bazowy podczas wysyłania i deserializacji za pomocą wskaźnika bazowego, jak również).

Jeśli chodzi o drugie pytanie, szukamy non-intrusive serialization. Nieinwazyjna serializacja wywołuje funkcje wolnostojące i przekazuje obiekt do serializacji jako parametr (w ten sposób std :: vector i boost :: shared_ptr są serializowane). Możesz użyć BOOST_SERIALIZATION_SPLIT_FREE, aby podzielić wolnostojącą funkcję serialize() na save() i load(). Dla intruzywnej serializacji jest to BOOST_SERIALIZATION_SPLIT_MEMBER.

Aby napisać uogólnioną funkcję de/serializacji (który przenosić obiekty przez sieć na przykład) można użyć szablonów:

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

Ograniczenie tej metody jest to, że odbiornik musi wiedzieć, jaki rodzaj obiektu został wysłany. Jeśli chcesz wysłać przypadkowych obiektów, ich polimorficzny:

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: jeśli trzeba kontrolować w jaki sposób (niestandardowe) metody serializacji/funkcje zachowują się, w oparciu o stan nieznanych typów będąc serializowane, możesz zaimplementować własną klasę archiwum, która przechowuje stan. Funkcje serializacji mogą następnie zapytać o stan i odpowiednio do tego działać.

Ten stan (lub odpowiednia podstawa) musi być również serializowany, aby wskazać sposób deserializacji danych. Na przykład, to "inne zachowanie" funkcji serializacji może być pewnego rodzaju kompresją, a stan jest rodzajem stosowanej kompresji.

Oto minimalny przykład niestandardowego archiwum wyjściowego. Aby uzyskać więcej informacji, przeczytaj Derivation from an Existing Archive i przeglądaj źródła boost.

Biorąc pod uwagę klasę nie można modyfikować:

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

Ty chcesz serializacji i i/lub s na podstawie stanu nieznanego do klasy. Możesz utworzyć wrapper, aby przekształcić go do postaci szeregowej i dodać stan, ale to nie zadziała, jeśli obiekt znajduje się wewnątrz wektora (lub innej klasy).

To może być łatwiejsze archiwum świadomi stanu Zamiast:

#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> 

Teraz państwowej ustawić i zapytania:

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

i sposób ustawić stan (jest to tylko bardzo prosty przykład):

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

Wreszcie (nieinwazyjne) funkcja serializacji:

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) 

A może to zostać wykorzystane w następujący sposób:

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

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

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

Celem tego przykładu jest tylko zilustrować zasadę. Jest zbyt podstawowy dla kodu produkcyjnego.

+0

Problem mam z Boost jest, że muszę serializacji członków klasy, co chcę robić w niektórych przypadkach jest serializacji tylko części klasy, części, które mogą nie być zmiennymi składowymi, ale raczej dane zbudowane z części zmiennych składowych. Nie będę w pełni serializować wszystkich danych za każdym razem, będę serializować tylko dane potrzebne do ustalenia, co się zmieniło. Tylko początkowa serializacja będzie zawierać wszystkie dane. –

+0

W oparciu o moje ogólne potrzeby zdecydowałem jednak, że nie będę korzystać z Boostowania serializacji. Przeważnie chciałem mieć bibliotekę, która dzieliłaby typowe typy danych na/z binarnych, ale nie było to dokładnie to, czego szukałem. Prawdopodobnie po prostu napiszę własne metody konwersji typów do/z binarnych i użyję oddzielnej biblioteki do kompresowania/dekompresji bitstreamów dla mnie. Twoja odpowiedź zbliżyła się do tego, co było mi potrzebne na podstawie mojego pytania, więc zaznaczę to jako odpowiedź na moje pytanie. Dzięki. –

+1

@NicFoster i miałem zamiar usunąć odpowiedź, ponieważ źle zrozumiałem twoje pytanie. nie krępuj się i nie odzywaj, dopóki ja (lub ktoś inny) nie zaproponuje odpowiedniego rozwiązania. Chcesz serializować klasę, której nie możesz zmodyfikować w środku (na przykład) wektora i chcesz kontrolować sposób przekształcania jej do postaci szeregowej bez powrotu do zmiennej globalnej. –

0

Wymyśliłem rozwiązanie tego problemu, a chociaż nie było to idealne, pomyślałem, że i tak warto go opublikować. Zasadniczo konfiguruję klasę singleton do zarządzania wysyłaniem wszystkich żądań serializacji i ta klasa będzie śledzić najnowsze flagi bitów, które były używane dla tego żądania. Tak więc podczas serializacji lub deserializacji te metody mogą wyszukiwać te flagi. To pozwoliło mi na wywołanie metod Boosta w celu uzyskania bardziej niezawodnego zestawu metod, które mogłyby wykorzystać te flagi do selektywnego serializowania tylko niektórych elementów.

// 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

moim obecnym pomysłem jest stworzenie niestandardowego archiwum, które może przechowywać flagi.następnie można ustawić te flagi za pomocą manipulatora takiego jak iomanip. na przykład 'archive << arflag (COMPLETE) << foo << arflag (MINIMAL) << bar;' w twoim zapisie możesz po prostu zapytać flagi za pomocą 'ar.get_flags()'. –

+0

Wierzę, że coś, o czym wspomniałeś, zadziała, dla typów niestandardowych, gdzie 'Foo' i' Bar' mogą implementować niestandardową serializację opartą na flagach, podobną do tego, co mam powyżej. Ograniczeniem, które napotykam dzięki zwiększeniu, jest to, że oczekuje on serializacji pełnych elementów danych, a czasami muszę tylko szeregować surowe dane. Na przykład, jeśli mam zmienną składową 'std :: vector ', ale po prostu muszę serializować jej rozmiar w sieci, czy istnieje sposób, aby to zrobić bez posiadania innej zmiennej składowej, która reprezentuje jej rozmiar (który byłby niezwykle trudne do utrzymania)? –

+1

Co gorsza, zawsze możesz napisać nieinwazyjny serializator dla samego wektora (nie włączaj '/ serialization/vector.hpp' w tym przypadku!) I tylko serializować jego rozmiar, gdy jest to odpowiednie. Musisz jednak powiedzieć odbiorcy, że wystarczy wyodrębnić rozmiar (ponieważ nie wysyłasz rzeczywistych elementów) z flagą. –

0

Oto prostszy sposób:

// 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 
}