Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions include/bitstream/stream/bit_reader.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
#pragma once
#include "../utility/assert.h"
#include "../utility/crc.h"
#include "../utility/endian.h"
#include "../utility/meta.h"

#include "byte_buffer.h"
#include "multi.h"
#include "serialize_traits.h"
#include "stream_traits.h"

#include <cstdint>
#include <cstring>
#include <string>
#include <type_traits>

namespace bitstream
Expand Down Expand Up @@ -272,6 +271,20 @@ namespace bitstream
return true;
}

/**
* @brief Reads from the buffer into mulitple variables.
* @note Pass multi<T>(...) to this in place of multiple calls to the regular serialize functions.
* @tparam ...Args The types of the arguments to pass to the serialize function
* @param ...args The arguments to pass to the serialize function
* @return Whether successful or not
*/
template<typename... Args, typename = std::enable_if_t<(utility::has_instance_serialize_v<Args, bit_reader> && ...)>>
[[nodiscard]] bool serialize(Args&&... args)
noexcept((noexcept(std::declval<Args&>().serialize(std::declval<bit_reader&>())) && ...))
{
return (std::forward<Args>(args).serialize(*this) && ...);
}

/**
* @brief Reads from the buffer, using the given @p Trait.
* @note The Trait type in this function must always be explicitly declared
Expand Down
16 changes: 15 additions & 1 deletion include/bitstream/stream/bit_writer.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#pragma once
#include "../utility/assert.h"
#include "../utility/crc.h"
#include "../utility/endian.h"
#include "../utility/meta.h"

#include "byte_buffer.h"
#include "multi.h"
#include "serialize_traits.h"
#include "stream_traits.h"

Expand Down Expand Up @@ -316,6 +316,20 @@ namespace bitstream
return true;
}

/**
* @brief Writes to the buffer from multiple variables.
* @note Pass multi<T>(...) to this in place of multiple calls to the regular serialize functions.
* @tparam ...Args The types of the arguments to pass to the serialize function
* @param ...args The arguments to pass to the serialize function
* @return Whether successful or not
*/
template<typename... Args, typename = std::enable_if_t<(utility::has_instance_serialize_v<Args, bit_writer> && ...)>>
[[nodiscard]] bool serialize(Args&&... args)
noexcept((noexcept(std::declval<Args&>().serialize(std::declval<bit_writer&>())) && ...))
{
return (std::forward<Args>(args).serialize(*this) && ...);
}

/**
* @brief Writes to the buffer, using the given @p Trait.
* @note The Trait type in this function must always be explicitly declared
Expand Down
28 changes: 28 additions & 0 deletions include/bitstream/stream/multi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include "../utility/meta.h"
#include "../utility/parameter.h"

#include "serialize_traits.h"

#include <tuple>

namespace bitstream
{
template<typename T, typename... Ts>
struct multi_args
{
std::tuple<Ts...> Args;

template<typename Stream, typename = utility::has_serialize_t<T, Stream, Ts...>>
bool serialize(Stream& stream) noexcept(utility::is_serialize_noexcept_v<T, Stream, Ts...>)
{
return std::apply([&](auto&&... args) { return serialize_traits<T>::serialize(stream, args ...); }, std::move(Args));
}
};

template<typename T, typename... Args>
multi_args<T, Args&&...> multi(Args&&... args) noexcept
{
return multi_args<T, Args&&...>{ std::forward_as_tuple(std::forward<Args>(args) ...) };
}
}
11 changes: 11 additions & 0 deletions include/bitstream/utility/meta.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@

namespace bitstream::utility
{
// Check if instance has a serializable trait
template<typename T, typename Stream, typename Void = void>
struct has_instance_serialize : std::false_type {};

template<typename T, typename Stream>
struct has_instance_serialize<T, Stream, std::void_t<decltype(std::declval<T&>().serialize(std::declval<Stream&>()))>> : std::true_type {};

template<typename T, typename Stream>
constexpr bool has_instance_serialize_v = has_instance_serialize<T, Stream>::value;


// Check if type has a serializable trait
template<typename Void, typename T, typename Stream, typename... Args>
struct has_serialize : std::false_type {};
Expand Down
46 changes: 46 additions & 0 deletions src/test/serialize_multi_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "../shared/assert.h"
#include "../shared/test.h"

#include <bitstream/stream/bit_reader.h>
#include <bitstream/stream/bit_writer.h>

#include <bitstream/traits/integral_traits.h>
#include <bitstream/traits/quantization_traits.h>

namespace bitstream::test::multi_serialize
{
BS_ADD_TEST(test_serialize_multi)
{
// Test serializing multiple values at once
uint32_t in_value1 = 511;
float in_value2 = 99.12345f;

bounded_range range(-1000.0f, 1000.0f, 0.001f);

// Write some values
byte_buffer<16> buffer;
fixed_bit_writer writer(buffer);

BS_TEST_ASSERT(writer.serialize(
multi<uint32_t>(in_value1, 328, 611),
multi<bounded_range>(range, in_value2)
));

uint32_t num_bits = writer.flush();

BS_TEST_ASSERT(num_bits == 30);

// Read the values back and validate
uint32_t out_value1;
float out_value2;
fixed_bit_reader reader(buffer, num_bits);

BS_TEST_ASSERT(reader.serialize(
multi<uint32_t>(out_value1, 328U, 611U),
multi<bounded_range>(range, out_value2)
));

BS_TEST_ASSERT(out_value1 == in_value1);
BS_TEST_ASSERT(std::abs(in_value2 - out_value2) <= range.get_precision());
}
}