Skip to content

Commit

Permalink
wip getting read and write macros working
Browse files Browse the repository at this point in the history
  • Loading branch information
gafferongames committed Nov 14, 2024
1 parent ae94583 commit 0d6c07c
Showing 1 changed file with 153 additions and 42 deletions.
195 changes: 153 additions & 42 deletions serialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@
#ifdef _MSC_VER
#pragma warning( disable : 4127 )
#pragma warning( disable : 4244 )
#include <malloc.h>
#endif // #ifdef _MSC_VER

#include <stdint.h>
Expand Down Expand Up @@ -348,14 +347,32 @@ namespace serialize
{
public:

BitWriter()
{
memset( (void*) this, 0, sizeof(BitWriter) );
}

void Initialize( void * __restrict__ data, int bytes )
{
serialize_assert( data );
serialize_assert( ( bytes % 4 ) == 0 );
m_data = (uint32_t*) data;
m_numWords = bytes / 4;
m_numBits = m_numWords * 32;
m_bitsWritten = 0;
m_wordIndex = 0;
m_scratch = 0;
m_scratchBits = 0;
}

/**
Bit writer constructor.
Creates a bit writer object to write to the specified buffer.
@param data The pointer to the buffer to fill with bitpacked data.
@param bytes The size of the buffer in bytes. Must be a multiple of 4, because the bitpacker reads and writes memory as dwords, not bytes.
*/

BitWriter( void * data, int bytes ) : m_data( (uint32_t*) data ), m_numWords( bytes / 4 )
BitWriter( void * __restrict__ data, int bytes ) : m_data( (uint32_t*) data ), m_numWords( bytes / 4 )
{
serialize_assert( data );
serialize_assert( ( bytes % 4 ) == 0 );
Expand Down Expand Up @@ -427,7 +444,7 @@ namespace serialize
@see BitReader::ReadBytes
*/

void WriteBytes( const uint8_t * data, int bytes )
void WriteBytes( const uint8_t * __restrict__ data, int bytes )
{
serialize_assert( GetAlignBits() == 0 );
serialize_assert( m_bitsWritten + bytes * 8 <= m_numBits );
Expand Down Expand Up @@ -572,9 +589,9 @@ namespace serialize
*/

#ifdef SERIALIZE_DEBUG
BitReader( const void * data, int bytes ) : m_data( (const uint32_t*) data ), m_numBytes( bytes ), m_numWords( ( bytes + 3 ) / 4)
BitReader( const void * __restrict__ data, int bytes ) : m_data( (const uint32_t*) data ), m_numBytes( bytes ), m_numWords( ( bytes + 3 ) / 4)
#else // #ifdef SERIALIZE_DEBUG
BitReader( const void * data, int bytes ) : m_data( (const uint32_t*) data ), m_numBytes( bytes )
BitReader( const void * __restrict__ data, int bytes ) : m_data( (const uint32_t*) data ), m_numBytes( bytes )
#endif // #ifdef SERIALIZE_DEBUG
{
serialize_assert( data );
Expand Down Expand Up @@ -663,7 +680,7 @@ namespace serialize
@see BitWriter::WriteBytes
*/

void ReadBytes( uint8_t * data, int bytes )
void ReadBytes( uint8_t * __restrict__ data, int bytes )
{
serialize_assert( GetAlignBits() == 0 );
serialize_assert( m_bitsRead + bytes * 8 <= m_numBits );
Expand Down Expand Up @@ -735,16 +752,16 @@ namespace serialize

private:

const uint32_t * m_data; ///< The bitpacked data we're reading as a dword array.
uint64_t m_scratch; ///< The scratch value. New data is read in 32 bits at a top to the left of this buffer, and data is read off to the right.
int m_numBits; ///< Number of bits to read in the buffer. Of course, we can't *really* know this so it's actually m_numBytes * 8.
int m_numBytes; ///< Number of bytes to read in the buffer. We know this, and this is the non-rounded up version.
const uint32_t * __restrict__ m_data; ///< The bitpacked data we're reading as a dword array.
uint64_t m_scratch; ///< The scratch value. New data is read in 32 bits at a top to the left of this buffer, and data is read off to the right.
int m_numBits; ///< Number of bits to read in the buffer. Of course, we can't *really* know this so it's actually m_numBytes * 8.
int m_numBytes; ///< Number of bytes to read in the buffer. We know this, and this is the non-rounded up version.
#ifdef SERIALIZE_DEBUG
int m_numWords; ///< Number of words to read in the buffer. This is rounded up to the next word if necessary.
int m_numWords; ///< Number of words to read in the buffer. This is rounded up to the next word if necessary.
#endif // #ifdef SERIALIZE_DEBUG
int m_bitsRead; ///< Number of bits read from the buffer so far.
int m_scratchBits; ///< Number of bits currently in the scratch value. If the user wants to read more bits than this, we have to go fetch another dword from memory.
int m_wordIndex; ///< Index of the next word to read from memory.
int m_bitsRead; ///< Number of bits read from the buffer so far.
int m_scratchBits; ///< Number of bits currently in the scratch value. If the user wants to read more bits than this, we have to go fetch another dword from memory.
int m_wordIndex; ///< Index of the next word to read from memory.
};

/**
Expand Down Expand Up @@ -826,6 +843,13 @@ namespace serialize
enum { IsWriting = 1 };
enum { IsReading = 0 };

WriteStream() : m_writer() {}

void Initialize( uint8_t * buffer, int bytes )
{
m_writer.Initialize( buffer, bytes );
}

/**
Write stream constructor.
@param buffer The buffer to write to.
Expand Down Expand Up @@ -1307,6 +1331,7 @@ namespace serialize
} \
} while (0)


/**
Serialize a boolean value to the stream (read/write/measure).
This is a helper macro to make writing unified serialize functions easier.
Expand Down Expand Up @@ -1460,6 +1485,24 @@ namespace serialize
return stream.SerializeBytes( data, bytes );
}

/**
Serialize unsigned 32 bit integer (read/write/measure).
IMPORTANT: This macro must be called inside a templated serialize function with template \<typename Stream\>. The serialize method must have a bool return value.
@param stream The stream object. May be a read, write or measure stream.
@param value The unsigned 32 bit integer value.
*/

#define serialize_uint32( stream, value ) serialize_bits( stream, value, 32 );

/**
Serialize unsigned 64 bit integer (read/write/measure).
IMPORTANT: This macro must be called inside a templated serialize function with template \<typename Stream\>. The serialize method must have a bool return value.
@param stream The stream object. May be a read, write or measure stream.
@param value The unsigned 64 bit integer value.
*/

#define serialize_uint64( stream, value ) serialize_bits( stream, value, 64 );

/**
Serialize an array of bytes to the stream (read/write/measure).
This is a helper macro to make unified serialize functions easier.
Expand Down Expand Up @@ -1721,17 +1764,34 @@ namespace serialize
// read macros corresponding to each serialize_*. useful when you want separate read and write functions.

#define read_bits( stream, value, bits ) \
do \
{ \
serialize_assert( bits > 0 ); \
serialize_assert( bits <= 32 ); \
uint32_t uint32_value= 0; \
if ( !stream.SerializeBits( uint32_value, bits ) ) \
do \
{ \
return false; \
} \
value = uint32_value; \
} while (0)
serialize_assert( bits > 0 ); \
serialize_assert( bits <= 64 ); \
if ( bits <= 32 ) \
{ \
uint32_t uint32_value; \
if ( !stream.SerializeBits( uint32_value, bits ) ) \
{ \
return false; \
} \
value = uint32_value; \
} \
else \
{ \
uint32_t lo = 0; \
uint32_t hi = 0; \
if ( !stream.SerializeBits( lo, 32 ) ) \
{ \
return false; \
} \
if ( !stream.SerializeBits( hi, bits - 32 ) ) \
{ \
return false; \
} \
value = ( uint64_t(hi) << 32 ) | lo; \
} \
} while (0)

#define read_int( stream, value, min, max ) \
do \
Expand All @@ -1749,11 +1809,13 @@ namespace serialize
} \
} while (0)

#define read_bool( stream, value ) read_bits( stream, value, 1 )
#define read_bool( stream, value ) read_bits( stream, value, 1 )
#define read_uint8( stream, value ) read_bits( stream, value, 8 )
#define read_uint16( stream, value ) read_bits( stream, value, 16 )
#define read_uint32( stream, value ) read_bits( stream, value, 32 )
#define read_uint64( stream, value ) read_bits( stream, value, 64 )

#define read_float serialize_float
#define read_uint32 serialize_uint32
#define read_uint64 serialize_uint64
#define read_double serialize_double
#define read_bytes serialize_bytes
#define read_string serialize_string
Expand All @@ -1762,34 +1824,42 @@ namespace serialize
#define read_object serialize_object
#define read_int_relative serialize_int_relative

// write macros corresponding to each serialize_*. useful when you want separate read and write functions for some reason.
// write macros corresponding to each serialize_*. useful when you want separate read and write functions.

#define write_bits( stream, value, bits ) \
do \
{ \
serialize_assert( bits > 0 ); \
serialize_assert( bits <= 32 ); \
uint32_t uint32_value = (uint32_t) value; \
if ( !stream.SerializeBits( uint32_value, bits ) ) \
if ( bits <= 32 ) \
{ \
return false; \
uint32_t uint32_value = (uint32_t) value; \
stream.SerializeBits( uint32_value, bits ); \
} \
else \
{ \
uint32_t lo = uint32_t( uint64_t(value) & 0xFFFFFFFF ); \
uint32_t hi = uint32_t( uint64_t(value) >> 32 ); \
stream.SerializeBits( lo, 32 ); \
stream.SerializeBits( hi, bits - 32 ); \
} \
} while (0)

#define write_int( stream, value, min, max ) \
do \
{ \
serialize_assert( min < max ); \
serialize_assert( value >= min ); \
serialize_assert( value <= max ); \
serialize_assert( (int32_t) min < (int32_t) max ); \
serialize_assert( (int32_t) value >= (int32_t) min ); \
serialize_assert( (int32_t) value <= (int32_t) max ); \
int32_t int32_value = (int32_t) value; \
if ( !stream.SerializeInteger( int32_value, min, max ) ) \
return false; \
stream.SerializeInteger( int32_value, min, max ); \
} while (0)

#define write_bool( stream, value ) write_bits( stream, value, 1 )
#define write_uint8( stream, value ) write_bits( stream, value, 8 )
#define write_uint16( stream, value ) write_bits( stream, value, 16 )
#define write_uint32( stream, value ) write_bits( stream, value, 32 )
#define write_uint64( stream, value ) write_bits( stream, value, 64 )

#define write_float serialize_float
#define write_uint32 serialize_uint32
#define write_uint64 serialize_uint64
#define write_double serialize_double
#define write_bytes serialize_bytes
#define write_string serialize_string
Expand Down Expand Up @@ -2051,7 +2121,7 @@ struct TestObject
}
};

inline void test_stream()
inline void test_serialize()
{
const int BufferSize = 1024;

Expand Down Expand Up @@ -2081,6 +2151,46 @@ inline void test_stream()
serialize_check( readObject == writeObject );
}

inline void test_read_write()
{
const int BufferSize = 1024;

uint8_t buffer[BufferSize];

// write to the buffer
{
serialize::WriteStream writeStream;
writeStream.Initialize( buffer, BufferSize );

write_bits( writeStream, 13, 4 );
write_bool( writeStream, true );
write_uint8( writeStream, 255 );
write_uint16( writeStream, 65535 );
write_uint32( writeStream, 0xFFFFFFFF );
write_uint32( writeStream, 0xFFFFFFFFFFFFFFFFULL );
/*
write_float( writeStream, 1.0f );
write_double( writeStream, 1.0f );
char * data = { 1, 2, 3, 4, 5 };
write_bytes( writeStream, data, 5 );
write_string
write_align
write_object
write_int_relative
*/
// ...

writeStream.Flush();

const int bytesWritten = writeStream.GetBytesProcessed();

memset( buffer + bytesWritten, 0, BufferSize - bytesWritten );
}
}

#define SERIALIZE_RUN_TEST( test_function ) \
do \
{ \
Expand All @@ -2096,7 +2206,8 @@ inline void serialize_test()
SERIALIZE_RUN_TEST( test_endian );
SERIALIZE_RUN_TEST( test_bitpacker );
SERIALIZE_RUN_TEST( test_bits_required );
SERIALIZE_RUN_TEST( test_stream );
SERIALIZE_RUN_TEST( test_serialize );
SERIALIZE_RUN_TEST( test_read_write );
}
}

Expand Down

0 comments on commit 0d6c07c

Please sign in to comment.