Расширение ускоренной сериализации

Я пытаюсь настроить многоцелевую сериализацию для сетевой видеоигры для мобильных устройств. Поскольку он подключен к сети, во время первоначального подключения мне нужно сериализовать все данные для состояния игры, однако, когда игра запущена, мне нужно будет сериализовать только определенные изменения. Методы сохранения и загрузки, являющиеся частью библиотеки ускоренной сериализации, имеют в качестве параметра только номер версии. Что я хотел бы сделать, так это иметь больше параметров, чтобы я мог изменять условия для того, что сохраняется и загружается, основываясь не только на номере версии.

Документы по сериализации Boost находятся здесь для справки.

Вот как в настоящее время выглядит простой метод сериализации 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
}

Я сомневаюсь, что смогу заставить библиотеку boost вызывать мой метод сериализации, который я хочу, потому что в нем есть перегруженные операторы, созданные для вызова одного с определенной сигнатурой в первом примере выше. Я представляю себе вызов моей собственной версии 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)


EDIT: новый ответ добавлен ниже для решения реальной проблемы.

Ваш вопрос подразумевает, что вы неоднократно десериализуете один и тот же объект. Это косвенно, если это чисто или нет. Если, например, у вас есть шахматная доска, вы хотели бы синхронизировать начальное положение фигур (чтобы продолжить с последней сохраненной игры). Чтобы сообщать ходы во время игры, может быть лучше отправлять отдельные ходы в виде отдельных объектов (которые затем применяются к объекту доски после получения) вместо передачи всего объекта доски, который будет передавать только то, что изменилось если он уже "инициализирован". Таким образом, вы можете сначала проверить ввод и игнорировать недопустимые ходы. В любом случае, я просто хотел упомянуть об этом, давайте двигаться дальше.

Если у вас есть объект, который может быть синхронизирован несколько раз, с данными-членами, которые нужно передать только один раз, позвольте объекту решить, является ли он «инициализированным» или нет (и, следовательно, нужно ли ему передавать все или только подмножество) с помощью флага (который не сериализуется).

Затем вы можете проверить флаг в коде сериализации объекта, как и в опубликованном вами коде (за исключением того, что флаг не является параметром метода сериализации, а является переменной-членом объекта, который вы де/сериализуете). Если флаг установлен, де/сериализуйте все и сбросьте флаг. И клиент, и сервер должны иметь одинаковое состояние флага, иначе сериализация прерывается.

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

Имейте в виду, что десериализация должна соответствовать сериализации; Вы должны извлечь одни и те же объекты в том же порядке, в котором они были сериализованы.

Однако вы можете сериализовать полиморфные классы, если они сериализуются на том же уровне в иерархии классов, что и десериализованные (если есть сомнения, приведите к базовому указателю при отправке и десериализуйте также через базовый указатель).

Что касается вашего второго вопроса, вы ищете ненавязчивая сериализация. Ненавязчивая сериализация вызывает автономные функции и передает сериализуемый объект в качестве параметра (так сериализуются 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