Разширяване на усилващата сериализация

Опитвам се да настроя многофункционална сериализация за мрежова видео игра за мобилни устройства. Тъй като е свързан в мрежа, по време на първоначалната връзка трябва да сериализирам всички данни за състоянието на играта, но след като играта започне, ще трябва да сериализирам само определени промени. Методите за запазване и зареждане, които са част от библиотеката за усилваща сериализация, имат само номер на версия като параметър. Това, което бих искал да мога да направя, е да имам повече параметри, така че да мога да променя условията за това, което се записва и зарежда въз основа на повече от номер на версия.

Документите за сериализиране на Boost са тук за справка.

Ето как в момента изглежда обикновеният метод за усилваща сериализация save:

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

Ето какво бих искал да постигна:

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
}

Съмнявам се, че мога да накарам библиотеката за усилване да извиква моя метод за сериализация, който искам, защото има претоварени оператори, накарани да извикат такъв със специфичния подпис в първия пример по-горе. Представям си да извикам собствената си версия на save от обаждането до save, показано в първия пример, и може би да взема state_flags от отделно място. Някой има ли идеи как това може да се направи чисто или някакви добри алтернативи?

РЕДАКТИРАНЕ: Попаднах на друг проблем. Трябва да сериализирам обекти, които не са непременно членове на клас, но в документацията не се споменава никаква поддръжка за това.

Ето един прост пример:

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

Просто бих сериализирал SomeClass и бих оставил това да премине към Bar, но в този случай това е клас, който е част от библиотека/машина на трета страна, а не нещо, което мога да модифицирам. Boost сериализацията ще ми позволи ли да сериализирам и десериализирам по този начин?


person Nic Foster    schedule 27.12.2012    source източник


Отговори (3)


РЕДАКТИРАНЕ: по-долу е добавен нов отговор за справяне с действителния проблем.

Вашият въпрос предполага, че десериализирате към един и същ обект многократно. Косвенно е дали това е чисто или не. Ако, например, имате дъска за шах, бихте искали да синхронизирате първоначалната позиция на фигурите (за да продължите от последната запазена игра). За да съобщите ходовете, докато се играе играта, може да е по-добра идея да изпратите отделните ходове като отделни обекти (които след това се прилагат към обекта на дъската, след като бъдат получени), вместо да предавате целия обект на дъската, който ще прехвърли само това, което е променено ако вече е "инициализирано". По този начин можете първо да потвърдите въвеждането и да игнорирате невалидни ходове. Както и да е, просто исках да спомена това, нека продължим.

Ако имате обект, който може да бъде синхронизиран многократно, с данни за член, които трябва да бъдат прехвърлени само веднъж, оставете обекта да реши дали е „инициализиран“ или не (и следователно, дали трябва да предаде всичко или само поднабор) чрез използване на флаг (който не е сериализиран).

След това можете да проверите флага в кода за сериализация на обекта, точно както в кода, който сте публикували (с изключение на това, че флагът не е параметър на метода за сериализация, а членска променлива на обекта, който де/сериализирате). Ако флагът е зададен, де/сериализирайте всичко и нулирайте флага. Както клиентът, така и сървърът трябва да имат едно и също състояние на флага или сериализацията се прекъсва.

Като алтернатива можете първо да сериализирате флага, за да кажете на приемника как трябва да се извърши десериализацията (например един бит за всяка членска група данни).

Имайте предвид, че десериализацията трябва да съответства на сериализацията; Трябва да извлечете същите обекти в същия ред, в който са били сериализирани.

Можете обаче да сериализирате полиморфни класове, като се има предвид, че те са сериализирани през същото ниво в йерархията на класовете, както са десериализирани (когато се съмнявате, прехвърлете към базовия указател при изпращане и десериализирайте също чрез базовия указател).

Относно втория ви въпрос, това, което търсите, е ненатрапчива сериализация. Ненатрапчивата сериализация извиква самостоятелни функции и предава обекта, който трябва да бъде сериализиран като параметър (по този начин се сериализират std::vector и boost::shared_ptr). Можете да използвате BOOST_SERIALIZATION_SPLIT_FREE, за да разделите отделната функция serialize() на save() и load(). За натрапчива сериализация е BOOST_SERIALIZATION_SPLIT_MEMBER.

За да напишете обобщена функция за де/сериализация (която например предава обекти през мрежата), можете да използвате шаблони:

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

Ограничението при този метод е, че получателят трябва да знае какъв вид обект е изпратен. Ако искате да изпратите произволни обекти, направете ги полиморфни:

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

АКТУАЛИЗАЦИЯ: Ако трябва да контролирате как се държат вашите (персонализирани) методи/функции за сериализиране въз основа на състояние, непознато за типовете, които се сериализират, можете да приложите свой собствен архивен клас, който съхранява състоянието. След това функциите за сериализиране могат да направят заявка за състоянието и да действат съответно.

Това състояние (или подходящо заместване) трябва също да бъде сериализирано, за да посочи как данните трябва да бъдат десериализирани. Например, това „различно поведение“ на функциите за сериализация може да бъде някакъв вид компресия, а състоянието е вида на използваната компресия.

Ето минимален пример за персонализиран изходен архив. За повече информация можете да прочетете Извличане от съществуващ архив и разровете източниците на усилване.

Като се има предвид клас, който не можете да променяте:

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

Искате да сериализирате i и/или s въз основа на условие, неизвестно на класа. Можете да създадете обвивка, за да го сериализирате и да добавите състоянието, но това няма да работи, ако обектът е вътре във вектор (или друг клас за този въпрос).

Вместо това може да е по-лесно да информирате архива за състоянието:

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

Сега състоянието за задаване и заявка:

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

И метод за задаване на състоянието (това е само много основен пример):

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

И накрая, (ненатрапчивата) функция за сериализация:

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)

И това може да се използва, както следва:

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

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

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

Целта на този пример е просто да илюстрира принципа. Това е твърде основно за производствен код.

person Anonymous Coward    schedule 28.12.2012
comment
Проблемът, който имам с Boost, е, че трябва да сериализирам членове на клас, това, което искам да направя в някои случаи, е да сериализирам само части от клас, части, които може дори да не са членски променливи, а по-скоро данни, конструирани от части от членски променливи . Няма да сериализирам напълно всички данни всеки път, ще сериализирам само данните, необходими, за да определя какво се е променило. Само първоначалната сериализация ще съдържа всички данни. - person Nic Foster; 28.12.2012
comment
Въз основа на общите ми нужди реших все пак да не използвам Boost сериализация. Най-вече просто исках библиотека, която да сериализира общи типове данни към/от двоични, но начинът, по който го направи boost, не беше точно това, което търсех. Вероятно просто ще напиша свои собствени методи за преобразуване на типове към/от двоични и ще използвам отделна библиотека за компресиране/декомпресиране на битовите потоци за мен. Вашият отговор обаче се доближава до това, от което имах нужда въз основа на моя въпрос, така че ще го маркирам като отговор на моя въпрос. Благодаря. - person Nic Foster; 28.12.2012
comment
@NicFoster щях да премахна отговора, тъй като не разбрах въпроса ви. чувствайте се свободни да го откажете от приемането и да отмените гласуването му, докато аз (или някой друг) не измисля правилно решение. Искате да сериализирате клас, който не можете да модифицирате вътре (например) във вектор, и искате да контролирате как да го сериализирате, без да се връщате към глобална променлива. - person Anonymous Coward; 28.12.2012
comment
да Ето друга тема, която започнах, след като разбрах, че Boost вероятно няма да работи за мен, вероятно има повече подробности за това, от което се нуждая. Не исках да включвам всичко това в тази тема, тъй като не е свързано с Boost сериализацията, за което първоначално е въпросът. Намерих решение на първоначалния си въпрос в тази тема, но то не реши пълния ми проблем, предполагам, че би било полезно да го публикувам тук, така че ще го направя скоро. - person Nic Foster; 28.12.2012
comment
@NicFoster Намерих другия ви въпрос преди 15 минути и разбрах какво всъщност търсите. Ще преработя отговора си, ако намеря правилно решение. извинения за объркването. - person Anonymous Coward; 28.12.2012
comment
@NicFoster Актуализирах отговора си, за да отговоря на действителния въпрос. - person Anonymous Coward; 29.12.2012

Намерих решение за това и въпреки че не беше идеално, реших, че може да си струва да го публикувам така или иначе. По принцип настройвам клас сингълтън, за да управлява изпращането на всички заявки за сериализация и този клас ще следи най-новите битови флагове, които са били използвани за тази заявка. Така че по време на сериализация или десериализация тези методи могат да отправят запитвания за тези флагове. Това ми позволи да извикам методите save и load на Boost към по-стабилен набор от методи, които могат да използват тези флагове за селективно сериализиране само на определени членове.

// 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
}
person Nic Foster    schedule 28.12.2012
comment
сегашната ми идея е да създам персонализиран архив, който може да съдържа флагове. след това можете да зададете тези флагове с манипулатор като iomanip. например archive << arflag(COMPLETE) << foo << arflag(MINIMAL) << bar; във вашето запазване можете просто да заявите флаговете с ar.get_flags(). - person Anonymous Coward; 28.12.2012
comment
Вярвам, че нещо подобно на това, което споменавате, ще работи за потребителски типове, където Foo и Bar могат да реализират персонализирана сериализация въз основа на флагове, подобно на това, което имам по-горе. Ограничението, с което се сблъсках с boost, е, че очаква пълните членове на данните да бъдат сериализирани и понякога просто трябва да сериализирам необработени данни. Например, ако имам std::vector<T> като членска променлива, но просто трябва да сериализирам неговия размер в мрежата, има ли някакъв начин да направя това, без да имам друга членска променлива, която представлява неговия размер (което би било изключително трудно за поддържане)? - person Nic Foster; 28.12.2012
comment
В по-лошия случай винаги можете да напишете ненатрапчив сериализатор за самия вектор (не включвайте /serialization/vector.hpp в този случай!) и да сериализирате неговия размер само когато е подходящо. Трябва обаче да кажете на получателя, че трябва само да извлече размера (тъй като не изпращате действителни елементи) с флаг. - person Anonymous Coward; 28.12.2012

Ето по-прост начин:

// 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
}
person Robert Ramey    schedule 25.11.2015