One thing that I get really tired of doing is defining how Objects need to serialize (read/write) themselves to file; be it xml, binary, whatever. It's also error prone if you add something new to the Serialize() method, but forget to do the same (or wrongly do it) to the Deserialize() method. Worse is that we need to duplicate this code for each file type we want. In case you're new to files, you can't just say fwrite(f, this, sizeof*this)); because 90+% of the time the members are not POD type.
The solution I came up with is very small, a single file, uses simple buffers and arrays, and is actually O(1) time. Considering what it does this is pretty good I think. (Looks at boost::serializer bloated nonsense)..
Here it is:
Code:
struct AttributeAccessorInfo
{
public:
AttributeAccessorVariableType type;
const char* name;
int offset;
AttributeAccessorInfo() : type(VAR_TYPE_NONE), name(0), offset(0) {}
AttributeAccessorInfo( AttributeAccessorVariableType type, const char* name, int offset )
: type(type), name(name), offset(offset)
{}
};
class AttributeAccessorObjectTypeBase
{
public:
typedef fc::vector<AttributeAccessorInfo> vec_type;
AttributeAccessorObjectTypeBase( const char* className ) : m_className(className) {}
virtual ~AttributeAccessorObjectTypeBase()
{}
void RegisterAttributeAccessor( const AttributeAccessorInfo& attrInfo );
NO_INLINE void SerializeObjectAttributesXml( const void* obj, XmlWriter* xml );
NO_INLINE void DeserializeObjectAttributesXml( void* obj, XmlReader* xml );
int OnReadAttributeInfoXml( void* obj, XmlReader* xml, const AttributeAccessorInfo& attr );
int OnWriteAttributeInfoXml( const void* obj, XmlWriter* xml, const AttributeAccessorInfo& attr );
protected:
static int m_typeIdGenerator;
static int GenerateTypeId()
{
int currentVal = m_typeIdGenerator++;
return currentVal;
}
const char* m_className;
vec_type m_attributes;
};
template<class T>
class AttributeAccessorObjectTypeInfo : public AttributeAccessorObjectTypeBase
{
public:
AttributeAccessorObjectTypeInfo( const char* className )
: AttributeAccessorObjectTypeBase(className)
{}
static int GetTypeId()
{
static int typeId = AttributeAccessorObjectTypeBase::GenerateTypeId();
return typeId;
}
};
class ObjectAttributeSerializerFactory
{
public:
enum : size_t
{
MaxBufferBytes = 512
};
typedef fc::fixed_tiny_vector<AttributeAccessorObjectTypeBase*, MaxBufferBytes / sizeof(AttributeAccessorObjectTypeBase*)> vec_type;
typedef fc::aligned_buffer<MaxBufferBytes, FC_ALIGNOF(AttributeAccessorObjectTypeBase*)> buffer_type;
ObjectAttributeSerializerFactory() :
m_factories(),
m_buffer(),
m_offset(0)
{
}
static ObjectAttributeSerializerFactory* GetInstance()
{
return &m_instance;
}
template <class T>
void RegisterAttribute( const AttributeAccessorInfo& attrInfo )
{
int registeredTypeId = AttributeAccessorObjectTypeInfo<T>::GetTypeId();
m_instance.m_factories[registeredTypeId]->RegisterAttributeAccessor(attrInfo);
}
template <class T>
void RegisterFactoryType( const char* className )
{
int registeredTypeId = AttributeAccessorObjectTypeInfo<T>::GetTypeId();
ASSERT((size_t)registeredTypeId <= m_factories.size());
// only register a new factory if typeId is a valid type.
if( (size_t)registeredTypeId == m_factories.size() )
{
ASSERT(m_offset <= MaxBufferBytes - sizeof(AttributeAccessorObjectTypeInfo<T>));
AttributeAccessorObjectTypeInfo<T>* p = new (m_buffer.buffer + m_offset) AttributeAccessorObjectTypeInfo<T>(className);
m_offset += sizeof(AttributeAccessorObjectTypeInfo<T>);
m_factories.push_back(p);
}
}
template <class T>
void SerializeObjectAttributesXml( const T* obj, XmlWriter* xml )
{
int registeredTypeId = AttributeAccessorObjectTypeInfo<T>::GetTypeId();
m_instance.m_factories[registeredTypeId]->SerializeObjectAttributesXml(obj, xml);
}
template <class T>
void DeserializeObjectAttributesXml( T* obj, XmlReader* xml )
{
int registeredTypeId = AttributeAccessorObjectTypeInfo<T>::GetTypeId();
m_instance.m_factories[registeredTypeId]->DeserializeObjectAttributesXml(obj, xml);
}
private:
// There's zero point in having more than one instance
// of a factory that depends on compile-time constants
// and static template typeid generation.
static ObjectAttributeSerializerFactory m_instance;
vec_type m_factories;
buffer_type m_buffer;
size_t m_offset;
};
#define REGISTER_ATTRIBUTE_FACTORY_TYPE(class_) \
ObjectAttributeSerializerFactory::GetInstance()->RegisterFactoryType<class_>(#class_)
#define PUSH_ATTRIBUTE_NODE(class_, nodeName) \
ObjectAttributeSerializerFactory::GetInstance()->RegisterAttribute<class_> \
(AttributeAccessorInfo(VAR_TYPE_PUSH_NODE, nodeName, 0))
#define POP_ATTRIBUTE_NODE(class_) \
ObjectAttributeSerializerFactory::GetInstance()->RegisterAttribute<class_> \
(AttributeAccessorInfo(VAR_TYPE_POP_NODE, "", 0))
#define REGISTER_ATTRIBUTE(class_, type, name, var) \
ObjectAttributeSerializerFactory::GetInstance()->RegisterAttribute<class_> \
(AttributeAccessorInfo(type, name, offsetof(class_, var)))
#define SERIALIZE_OBJECT_ATTRIBUTES_XML(obj, xml) \
ObjectAttributeSerializerFactory::GetInstance()->SerializeObjectAttributesXml(obj, xml)
#define DESERIALIZE_OBJECT_ATTRIBUTES_XML(obj, xml) \
ObjectAttributeSerializerFactory::GetInstance()->DeserializeObjectAttributesXml(obj, xml)
Now any classes we want to write to file we add a static "Register" method that looks like this:
Code:
void CharacterClass::RegisterObject()
{
REGISTER_ATTRIBUTE_FACTORY_TYPE(CharacterClass);
REGISTER_ATTRIBUTE(CharacterClass, VAR_TYPE_STRING, "name", name);
REGISTER_ATTRIBUTE(CharacterClass, VAR_TYPE_STRING, "script", script);
REGISTER_ATTRIBUTE(CharacterClass, VAR_TYPE_STRING, "description", description);
//etc..
}
And that's how babies are made.
...yeah, I guess I haven't actually done anything worthwhile this week. :\