Binary Serialization
Now let’s have look at serialization. Assume that we’re handing over a preallocated buffer and a buffer size where the data is to be serialized to, how would you know how big the buffer should be? You could use a std::vector
internally instead which keeps growing with the serialized data you create, but this would lead to an unnecessary continuous buffer reallocation whenever the vector
has to resize.
A much simpler idea is to create a Serializer
which does nothing but count the amount of memory required.
#ifndef VTE_CORE_BINARYSERIALIZATIONSIZE_H #define VTE_CORE_BINARYSERIALIZATIONSIZE_H #include "Serializer.h" namespace vte { namespace core { class BinarySerializationSize : public Serializer { public: BinarySerializationSize() : length( 0 ) { } void beginAttribute( const std::string& name ) { } void writeNumberOfElements( u32 numElements ) { length += 4; } void writeParameter( const std::string& name, const std::string& value ) { // Four bytes for the length, plus string data length += 4 + (u32) value.length(); } void writeParameter( const std::string& name, s32 value, const std::string& readable ) { length += 4; } void writeParameter( const std::string& name, const bool& value ) { // Bools are saved as one byte length++; } ... void endAttribute() { } u32 getSize() const { return length; } private: u32 length; }; } // namespace core } // namespace vte #endif
Using an instance of BinarySerializationSize
you would simple trigger the serialization process once, read the required size from getSize
, allocate a chunk of memory and then do the actual serialization using the BinarySerializer
.
#ifndef VTE_CORE_BINARYSERIALIZER_H #define VTE_CORE_BINARYSERIALIZER_H #include "Serializer.h" namespace vte { namespace core { class BinarySerializer : public Serializer { public: BinarySerializer( u8* buffer, u32 length ); virtual ~BinarySerializer(); void beginAttribute( const std::string& name ) { } void writeNumberOfElements( u32 numElements ); void writeParameter( const std::string& name, const std::string& value ); void writeParameter( const std::string& name, s32 value, const std::string& readable ); void writeParameter( const std::string& name, const bool& value ); void writeParameter( const std::string& name, const u32& value ); void writeParameter( const std::string& name, const f32& value ); void writeParameter( const std::string& name, const vec3& value ); void writeParameter( const std::string& name, const irr::video::SColorf& value ); void endAttribute() { } bool isOverflowed() const { return overflow; } protected: u8* buffer; u32 bufferSize; u32 pointer; bool overflow; }; } // namespace core } // namespace vte #endif
This looks pretty much like the BinaryDeserializer
, so I won’t get into the details here. Like the declaration, the definition is also quite similar to the BinaryDeserializer
.
vte::core::BinarySerializer::BinarySerializer( u8* buffer, u32 bufferSize ) : buffer( buffer ), bufferSize( bufferSize ), pointer( 0 ), overflow( false ) { } vte::core::BinarySerializer::~BinarySerializer() { }
We have our buffer passed in again, remember the buffer
and its size plus our current position in the data and an overflow
flag.
void vte::core::BinarySerializer::writeNumberOfElements( u32 numElements ) { VTE_ASSERT( !overflow ); if( overflow || pointer + 4 > bufferSize ) { overflow = true; return; } memcpy( (void*)( buffer + pointer ), &numElements, 4 ); pointer += 4; } void vte::core::BinarySerializer::writeParameter( const std::string& name, const std::string& value ) { VTE_ASSERT( !overflow ); u32 len = (u32) value.length(); writeNumberOfElements( len ); if( overflow || pointer + len > bufferSize ) { overflow = true; return; } memcpy( (void*)( buffer + pointer ), value.c_str(), len ); pointer += len; }
Writing values into the binary buffer works like reading, but with memcpy
‘ing from the source value
into the buffer
instead of the other way around. Whenever an overflow is detected, the methods leave to avoid memory corruption.
4 Comments