我正在尝试为移动设备的联网视频游戏设置多用途序列化。因为它是联网的,所以在初始连接期间我需要为游戏状态序列化所有数据,但是,一旦游戏正在进行中,我只需要序列化某些更改。作为boost序列化库一部分的save和load方法仅具有版本号作为参数。我希望能够做的是拥有更多参数,以便我可以根据不仅仅是版本号更改保存和加载内容的条件。
Boost serialization docs are here, for reference.
这是普通的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序列化允许我以这种方式序列化和反序列化吗?
答案 0 :(得分:3)
编辑:下面添加了新答案,以解决实际问题。
您的问题意味着您反复反序列化到同一个对象。如果它是干净的,它是间接的。例如,如果你有棋盘,你会想要 同步棋子的初始位置(从上次保存的游戏继续)。为了在游戏进行时传达动作,发送个人动作可能是更好的主意 作为单独的对象(一旦被接收然后应用于板对象)而不是传输整个板对象,如果它已经“初始化”,它将仅传输已经改变的对象。 这样您就可以先验证输入并忽略无效移动。无论如何,我只是想提一下,让我们继续前进。
如果您有一个可以多次同步的对象,只需要传输一次的成员数据,让对象决定它是否已“初始化” (并且因此,如果它需要通过使用标志(未被序列化)来传输所有内容或仅仅是子集)。
然后你可以检查对象序列化代码中的标志,就像你发布的代码一样(除了标志不是序列化方法的参数, 但是你要去/序列化的对象的成员变量。如果设置了标志,则取消/序列化所有内容并重置标志。客户端和服务器都必须具有相同的标志状态,或者序列化中断。
或者,您可以首先序列化该标志,告诉接收器必须如何执行反序列化(例如,每个成员数据组一位)。
请记住,反序列化必须与序列化匹配;您必须按照序列化的顺序提取相同的对象。
但是,您可以序列化多态类,因为它们在类层次结构中被序列化,因为它们被反序列化 (如果有疑问,也可以在通过基指针发送和反序列化时转换为基指针。)
关于你的第二个问题,你要找的是non-intrusive serialization。
非侵入式序列化调用独立函数并将要序列化的对象作为参数传递(这就是std :: vector和boost :: shared_ptr如何序列化)。
您可以使用BOOST_SERIALIZATION_SPLIT_FREE
将独立的serialize()
功能拆分为save()
和load()
。对于侵入式序列化,它是BOOST_SERIALIZATION_SPLIT_MEMBER
。
要编写通用的de / serialization函数(例如,通过网络传输对象),您可以使用模板:
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) );
}
UPDATE :如果您需要控制(自定义)序列化方法/函数的行为方式,基于序列化类型未知的状态,您可以实现自己的归档类,该类保存状态。然后,序列化函数可以查询状态并采取相应的行动。
此状态(或适当的替换)也必须序列化,以指示数据必须如何反序列化。例如,序列化函数的这种“不同行为”可能是某种压缩,而状态就是使用的压缩类型。
以下是自定义输出存档的最小示例。有关详细信息,请阅读Derivation from an Existing Archive并深入了解提升源。
鉴于您无法修改课程:
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();
}
这个例子的目的只是为了说明原理。这对于生产代码来说太基础了。
答案 1 :(得分:0)
我确实想出了一个解决方案,虽然不太理想,但我认为无论如何都可能值得发布。基本上我设置了一个单例类来管理发送所有序列化请求,并且该类将跟踪用于该请求的最新位标志。因此,在序列化或反序列化期间,这些方法可以查询这些标志。这让我可以让Boost的save
和load
方法调用更强大的方法集,这些方法可以使用这些标志来选择性地序列化某些成员。
// 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
}
答案 2 :(得分: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
}