mirror of
https://github.com/RPCS3/cereal.git
synced 2025-03-04 13:37:05 +00:00
Finish up endian support for portable binary
-added more tests -should resolve #115
This commit is contained in:
parent
1ef6415f15
commit
20778fe4b8
@ -62,8 +62,8 @@ namespace cereal
|
||||
/*! This archive outputs data to a stream in an extremely compact binary
|
||||
representation with as little extra metadata as possible.
|
||||
|
||||
This archive will record the endianness of the data and assuming that
|
||||
the user takes care of ensuring serialized types are the same size
|
||||
This archive will record the endianness of the data as well as the desired in/out endianness
|
||||
and assuming that the user takes care of ensuring serialized types are the same size
|
||||
across machines, is portable over different architectures.
|
||||
|
||||
When using a binary archive and a file stream, you must use the
|
||||
@ -78,20 +78,64 @@ namespace cereal
|
||||
class PortableBinaryOutputArchive : public OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>
|
||||
{
|
||||
public:
|
||||
//! Construct, outputting to the provided stream
|
||||
/*! @param stream The stream to output to. Can be a stringstream, a file stream, or
|
||||
even cout! */
|
||||
PortableBinaryOutputArchive(std::ostream & stream) :
|
||||
OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream)
|
||||
//! A class containing various advanced options for the PortableBinaryOutput archive
|
||||
class Options
|
||||
{
|
||||
this->operator()( portable_binary_detail::is_little_endian() );
|
||||
public:
|
||||
//! Represents desired endianness
|
||||
enum class Endianness : std::uint8_t
|
||||
{ big, little };
|
||||
|
||||
//! Default options, preserve system endianness
|
||||
static Options Default(){ return Options(); }
|
||||
|
||||
//! Save as little endian
|
||||
static Options LittleEndian(){ return Options( Endianness::little ); }
|
||||
|
||||
//! Save as big endian
|
||||
static Options BigEndian(){ return Options( Endianness::big ); }
|
||||
|
||||
//! Specify specific options for the PortableBinaryOutputArchive
|
||||
/*! @param outputEndian The desired endianness of saved (output) data */
|
||||
explicit Options( Endianness outputEndian = getEndianness() ) :
|
||||
itsOutputEndianness( outputEndian ) { }
|
||||
|
||||
private:
|
||||
//! Gets the endianness of the system
|
||||
inline static Endianness getEndianness()
|
||||
{ return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
|
||||
|
||||
//! Checks if Options is set for little endian
|
||||
inline bool is_little_endian() const
|
||||
{ return itsOutputEndianness == Endianness::little; }
|
||||
|
||||
friend class PortableBinaryOutputArchive;
|
||||
Endianness itsOutputEndianness;
|
||||
};
|
||||
|
||||
//! Construct, outputting to the provided stream
|
||||
/*! @param stream The stream to output to. Should be opened with std::ios::binary flag.
|
||||
@param options The PortableBinary specific options to use. See the Options struct
|
||||
for the values of default parameters */
|
||||
PortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) :
|
||||
OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream),
|
||||
itsConvertEndianness( portable_binary_detail::is_little_endian() ^ options.is_little_endian() )
|
||||
{
|
||||
this->operator()( options.is_little_endian() );
|
||||
}
|
||||
|
||||
//! Writes size bytes of data to the output stream
|
||||
template <std::size_t DataSize> inline
|
||||
void saveBinary( const void * data, std::size_t size )
|
||||
{
|
||||
auto const writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) );
|
||||
std::size_t writtenSize = 0;
|
||||
|
||||
if( itsConvertEndianness )
|
||||
for( std::size_t i = 0; i < DataSize; ++i )
|
||||
writtenSize += static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ) + DataSize - i - 1, 1 ) );
|
||||
else
|
||||
writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) );
|
||||
|
||||
if(writtenSize != size)
|
||||
throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
|
||||
@ -99,6 +143,7 @@ namespace cereal
|
||||
|
||||
private:
|
||||
std::ostream & itsStream;
|
||||
const bool itsConvertEndianness; //!< If set to true, we will need to swap bytes upon saving
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
@ -130,23 +175,60 @@ namespace cereal
|
||||
class PortableBinaryInputArchive : public InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>
|
||||
{
|
||||
public:
|
||||
//! A class containing various advanced options for the PortableBinaryInput archive
|
||||
class Options
|
||||
{
|
||||
public:
|
||||
//! Represents desired endianness
|
||||
enum class Endianness : std::uint8_t
|
||||
{ big, little };
|
||||
|
||||
//! Default options, preserve system endianness
|
||||
static Options Default(){ return Options(); }
|
||||
|
||||
//! Load into little endian
|
||||
static Options LittleEndian(){ return Options( Endianness::little ); }
|
||||
|
||||
//! Load into big endian
|
||||
static Options BigEndian(){ return Options( Endianness::big ); }
|
||||
|
||||
//! Specify specific options for the PortableBinaryInputArchive
|
||||
/*! @param inputEndian The desired endianness of loaded (input) data */
|
||||
explicit Options( Endianness inputEndian = getEndianness() ) :
|
||||
itsInputEndianness( inputEndian ) { }
|
||||
|
||||
private:
|
||||
//! Gets the endianness of the system
|
||||
inline static Endianness getEndianness()
|
||||
{ return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
|
||||
|
||||
//! Checks if Options is set for little endian
|
||||
inline bool is_little_endian() const
|
||||
{ return itsInputEndianness == Endianness::little; }
|
||||
|
||||
friend class PortableBinaryInputArchive;
|
||||
Endianness itsInputEndianness;
|
||||
};
|
||||
|
||||
//! Construct, loading from the provided stream
|
||||
/*! @param stream The stream to read from. */
|
||||
PortableBinaryInputArchive(std::istream & stream) :
|
||||
/*! @param stream The stream to read from. Should be opened with std::ios::binary flag.
|
||||
@param options The PortableBinary specific options to use. See the Options struct
|
||||
for the values of default parameters */
|
||||
PortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) :
|
||||
InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream),
|
||||
itsConvertEndianness( false )
|
||||
{
|
||||
bool streamLittleEndian;
|
||||
this->operator()( streamLittleEndian );
|
||||
itsConvertEndianness = portable_binary_detail::is_little_endian() ^ streamLittleEndian;
|
||||
itsConvertEndianness = options.is_little_endian() ^ streamLittleEndian;
|
||||
}
|
||||
|
||||
//! Reads size bytes of data from the input stream
|
||||
/*! @param data The data to save
|
||||
@param size The number of bytes in the data
|
||||
@tparam DataSize T The size of the actual type of the data elements being loaded */
|
||||
template <std::size_t DataSize>
|
||||
template <std::size_t DataSize> inline
|
||||
void loadBinary( void * const data, std::size_t size )
|
||||
{
|
||||
// load data
|
||||
@ -180,7 +262,7 @@ namespace cereal
|
||||
static_assert( !std::is_floating_point<T>::value ||
|
||||
(std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
|
||||
"Portable binary only supports IEEE 754 standardized floating point" );
|
||||
ar.saveBinary(std::addressof(t), sizeof(t));
|
||||
ar.template saveBinary<sizeof(T)>(std::addressof(t), sizeof(t));
|
||||
}
|
||||
|
||||
//! Loading for POD types from portable binary
|
||||
@ -219,7 +301,7 @@ namespace cereal
|
||||
(std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
|
||||
"Portable binary only supports IEEE 754 standardized floating point" );
|
||||
|
||||
ar.saveBinary( bd.data, static_cast<std::size_t>( bd.size ) );
|
||||
ar.template saveBinary<sizeof(TT)>( bd.data, static_cast<std::size_t>( bd.size ) );
|
||||
}
|
||||
|
||||
//! Loading binary data from portable binary
|
||||
|
@ -26,6 +26,7 @@
|
||||
*/
|
||||
#include "common.hpp"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace mynamespace { struct MyCustomClass {}; }
|
||||
|
||||
@ -47,7 +48,123 @@ inline void swapBytes( T & t )
|
||||
cereal::portable_binary_detail::swap_bytes<sizeof(T)>( reinterpret_cast<std::uint8_t*>(&t) );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( portable_binary_archive )
|
||||
// swaps all output data
|
||||
#define CEREAL_TEST_SWAP_OUTPUT \
|
||||
swapBytes(o_bool); \
|
||||
swapBytes(o_uint8); \
|
||||
swapBytes(o_int8); \
|
||||
swapBytes(o_uint16); \
|
||||
swapBytes(o_int16); \
|
||||
swapBytes(o_uint32); \
|
||||
swapBytes(o_int32); \
|
||||
swapBytes(o_uint64); \
|
||||
swapBytes(o_int64); \
|
||||
swapBytes(o_float); \
|
||||
swapBytes(o_double);
|
||||
|
||||
#define CEREAL_TEST_CHECK_EQUAL \
|
||||
BOOST_CHECK_EQUAL(i_bool , o_bool); \
|
||||
BOOST_CHECK_EQUAL(i_uint8 , o_uint8); \
|
||||
BOOST_CHECK_EQUAL(i_int8 , o_int8); \
|
||||
BOOST_CHECK_EQUAL(i_uint16 , o_uint16); \
|
||||
BOOST_CHECK_EQUAL(i_int16 , o_int16); \
|
||||
BOOST_CHECK_EQUAL(i_uint32 , o_uint32); \
|
||||
BOOST_CHECK_EQUAL(i_int32 , o_int32); \
|
||||
BOOST_CHECK_EQUAL(i_uint64 , o_uint64); \
|
||||
BOOST_CHECK_EQUAL(i_int64 , o_int64); \
|
||||
if( !std::isnan(i_float) && !std::isnan(o_float) ) BOOST_CHECK_CLOSE(i_float , o_float, (float)1e-5); \
|
||||
if( !std::isnan(i_float) && !std::isnan(o_float) ) BOOST_CHECK_CLOSE(i_double , o_double, 1e-5);
|
||||
|
||||
// Last parameter exists to keep everything hidden in options
|
||||
template <class IArchive, class OArchive>
|
||||
void test_endian_serialization( typename IArchive::Options const & iOptions, typename OArchive::Options const & oOptions, const bool inputLittleEndian )
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
for(size_t i=0; i<100; ++i)
|
||||
{
|
||||
bool o_bool = random_value<uint8_t>(gen) % 2 ? true : false;
|
||||
uint8_t o_uint8 = random_value<uint8_t>(gen);
|
||||
int8_t o_int8 = random_value<int8_t>(gen);
|
||||
uint16_t o_uint16 = random_value<uint16_t>(gen);
|
||||
int16_t o_int16 = random_value<int16_t>(gen);
|
||||
uint32_t o_uint32 = random_value<uint32_t>(gen);
|
||||
int32_t o_int32 = random_value<int32_t>(gen);
|
||||
uint64_t o_uint64 = random_value<uint64_t>(gen);
|
||||
int64_t o_int64 = random_value<int64_t>(gen);
|
||||
float o_float = random_value<float>(gen);
|
||||
double o_double = random_value<double>(gen);
|
||||
|
||||
std::ostringstream os;
|
||||
{
|
||||
OArchive oar(os, oOptions);
|
||||
oar(o_bool);
|
||||
oar(o_uint8);
|
||||
oar(o_int8);
|
||||
oar(o_uint16);
|
||||
oar(o_int16);
|
||||
oar(o_uint32);
|
||||
oar(o_int32);
|
||||
oar(o_uint64);
|
||||
oar(o_int64);
|
||||
oar(o_float);
|
||||
oar(o_double);
|
||||
}
|
||||
|
||||
bool i_bool = false;
|
||||
uint8_t i_uint8 = 0;
|
||||
int8_t i_int8 = 0;
|
||||
uint16_t i_uint16 = 0;
|
||||
int16_t i_int16 = 0;
|
||||
uint32_t i_uint32 = 0;
|
||||
int32_t i_int32 = 0;
|
||||
uint64_t i_uint64 = 0;
|
||||
int64_t i_int64 = 0;
|
||||
float i_float = 0;
|
||||
double i_double = 0;
|
||||
|
||||
std::istringstream is(os.str());
|
||||
{
|
||||
IArchive iar(is, iOptions);
|
||||
iar(i_bool);
|
||||
iar(i_uint8);
|
||||
iar(i_int8);
|
||||
iar(i_uint16);
|
||||
iar(i_int16);
|
||||
iar(i_uint32);
|
||||
iar(i_int32);
|
||||
iar(i_uint64);
|
||||
iar(i_int64);
|
||||
iar(i_float);
|
||||
iar(i_double);
|
||||
}
|
||||
|
||||
// Convert to big endian if we expect to read big and didn't start big
|
||||
if( cereal::portable_binary_detail::is_little_endian() ^ inputLittleEndian ) // Convert to little endian if
|
||||
{
|
||||
CEREAL_TEST_SWAP_OUTPUT
|
||||
}
|
||||
|
||||
CEREAL_TEST_CHECK_EQUAL
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( portable_binary_archive_endian_conversions )
|
||||
{
|
||||
// (last parameter is whether we load as little endian)
|
||||
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
|
||||
cereal::PortableBinaryInputArchive::Options::BigEndian(), cereal::PortableBinaryOutputArchive::Options::BigEndian(), false );
|
||||
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
|
||||
cereal::PortableBinaryInputArchive::Options::LittleEndian(), cereal::PortableBinaryOutputArchive::Options::BigEndian(), true );
|
||||
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
|
||||
cereal::PortableBinaryInputArchive::Options::BigEndian(), cereal::PortableBinaryOutputArchive::Options::LittleEndian(), false );
|
||||
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
|
||||
cereal::PortableBinaryInputArchive::Options::LittleEndian(), cereal::PortableBinaryOutputArchive::Options::LittleEndian(), true );
|
||||
}
|
||||
|
||||
// Tests the default behavior to swap bytes to current machine's endianness
|
||||
BOOST_AUTO_TEST_CASE( portable_binary_archive_default_behavior )
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
@ -67,17 +184,7 @@ BOOST_AUTO_TEST_CASE( portable_binary_archive )
|
||||
double o_double = random_value<double>(gen);
|
||||
|
||||
// swap the bytes on all of the data
|
||||
swapBytes(o_bool);
|
||||
swapBytes(o_uint8);
|
||||
swapBytes(o_int8);
|
||||
swapBytes(o_uint16);
|
||||
swapBytes(o_int16);
|
||||
swapBytes(o_uint32);
|
||||
swapBytes(o_int32);
|
||||
swapBytes(o_uint64);
|
||||
swapBytes(o_int64);
|
||||
swapBytes(o_float);
|
||||
swapBytes(o_double);
|
||||
CEREAL_TEST_SWAP_OUTPUT
|
||||
|
||||
std::ostringstream os;
|
||||
{
|
||||
@ -99,17 +206,7 @@ BOOST_AUTO_TEST_CASE( portable_binary_archive )
|
||||
}
|
||||
|
||||
// swap back to original values
|
||||
swapBytes(o_bool);
|
||||
swapBytes(o_uint8);
|
||||
swapBytes(o_int8);
|
||||
swapBytes(o_uint16);
|
||||
swapBytes(o_int16);
|
||||
swapBytes(o_uint32);
|
||||
swapBytes(o_int32);
|
||||
swapBytes(o_uint64);
|
||||
swapBytes(o_int64);
|
||||
swapBytes(o_float);
|
||||
swapBytes(o_double);
|
||||
CEREAL_TEST_SWAP_OUTPUT
|
||||
|
||||
bool i_bool = false;
|
||||
uint8_t i_uint8 = 0;
|
||||
@ -139,17 +236,9 @@ BOOST_AUTO_TEST_CASE( portable_binary_archive )
|
||||
iar(i_double);
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(i_bool , o_bool);
|
||||
BOOST_CHECK_EQUAL(i_uint8 , o_uint8);
|
||||
BOOST_CHECK_EQUAL(i_int8 , o_int8);
|
||||
BOOST_CHECK_EQUAL(i_uint16 , o_uint16);
|
||||
BOOST_CHECK_EQUAL(i_int16 , o_int16);
|
||||
BOOST_CHECK_EQUAL(i_uint32 , o_uint32);
|
||||
BOOST_CHECK_EQUAL(i_int32 , o_int32);
|
||||
BOOST_CHECK_EQUAL(i_uint64 , o_uint64);
|
||||
BOOST_CHECK_EQUAL(i_int64 , o_int64);
|
||||
BOOST_CHECK_CLOSE(i_float , o_float, (float)1e-5);
|
||||
BOOST_CHECK_CLOSE(i_double , o_double, 1e-5);
|
||||
CEREAL_TEST_CHECK_EQUAL
|
||||
}
|
||||
}
|
||||
|
||||
#undef CEREAl_TEST_SWAP_DATA
|
||||
#undef CEREAL_TEST_CHECK_EQUAL
|
||||
|
Loading…
x
Reference in New Issue
Block a user