5

我正在尝试为移动设备的网络视频游戏设置多用途序列化。因为它是联网的,所以在初始连接期间,我需要序列化游戏状态的所有数据,但是,一旦游戏进行中,我只需要序列化某些更改。作为 boost 序列化库一部分的 save 和 load 方法只有一个版本号作为参数。我想做的是有更多的参数,这样我就可以改变保存和加载的条件,而不仅仅是一个版本号。

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的调用中调用我自己的版本,并且可能从单独的位置获取。是否有人对如何干净地完成此操作或任何好的替代方案有任何想法?savestate_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 序列化是否允许我以这种方式进行序列化和反序列化?

4

3 回答 3

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

这个例子的目的只是为了说明原理。它对于生产代码来说太基础了。

于 2012-12-28T04:15:53.467 回答
0

我确实想出了一个解决方案,虽然不太理想,但我认为它可能值得发布。基本上,我设置了一个单例类来管理发送所有序列化请求,并且该类将跟踪用于该请求的最新位标志。因此,在序列化或反序列化期间,这些方法可以查询这些标志。这使我可以让 Boostsaveload方法调用一组更强大的方法,这些方法可以使用这些标志来选择性地仅序列化某些成员。

// 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
}
于 2012-12-28T17:59:02.590 回答
0

这是一个更简单的方法:

// 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
}
于 2015-11-25T00:06:51.940 回答