serialise: add a simple binary serialisation framework
This commit is contained in:
parent
dc46dc7c91
commit
c3866ef632
@ -550,6 +550,9 @@ list (
|
||||
registrar.hpp
|
||||
roots/bisection.hpp
|
||||
scoped.hpp
|
||||
serialise/converter.hpp
|
||||
serialise/ops.hpp
|
||||
serialise/std.hpp
|
||||
set/dset.cpp
|
||||
set/dset.hpp
|
||||
signal.cpp
|
||||
@ -782,6 +785,7 @@ if (TESTS)
|
||||
registrar
|
||||
roots/bisection
|
||||
scoped
|
||||
serialise
|
||||
set/dset
|
||||
signal
|
||||
singleton
|
||||
|
61
serialise/converter.hpp
Normal file
61
serialise/converter.hpp
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cruft/util/view.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace cruft::serialise {
|
||||
template <typename ValueT>
|
||||
struct converter {
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
ValueT const &src
|
||||
);
|
||||
|
||||
static
|
||||
ValueT
|
||||
from_bytes (cruft::view<std::byte*> &src);
|
||||
|
||||
static std::size_t size (ValueT const&);
|
||||
};
|
||||
|
||||
template <typename ValueT> struct converter<ValueT const> : public converter<ValueT> {};
|
||||
template <typename ValueT> struct converter<ValueT &> : public converter<ValueT> {};
|
||||
|
||||
template <typename ValueT>
|
||||
requires (std::is_trivial_v<ValueT>)
|
||||
struct converter<ValueT> {
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
ValueT const &src
|
||||
) {
|
||||
if (dst.size () < sizeof (ValueT))
|
||||
throw std::bad_alloc ();
|
||||
|
||||
return write (dst, src);
|
||||
}
|
||||
|
||||
static
|
||||
ValueT
|
||||
from_bytes (cruft::view<std::byte*> &src)
|
||||
{
|
||||
return read<ValueT> (src);
|
||||
}
|
||||
|
||||
|
||||
static std::size_t size (ValueT const&) { return sizeof (ValueT); }
|
||||
};
|
||||
}
|
50
serialise/ops.hpp
Normal file
50
serialise/ops.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./converter.hpp"
|
||||
|
||||
#include <cruft/util/view.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace cruft::serialise {
|
||||
template <typename ValueT>
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (cruft::view<std::byte*> dst, ValueT const &src)
|
||||
{
|
||||
return converter<ValueT>::to_bytes (dst, src);
|
||||
}
|
||||
|
||||
template <typename HeadT, typename ...TailT>
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (cruft::view<std::byte*> dst, HeadT const &head, TailT&& ...tail)
|
||||
{
|
||||
dst = to_bytes (dst, head);
|
||||
if constexpr (sizeof... (TailT))
|
||||
dst = to_bytes (dst, std::forward<TailT> (tail)...);
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
template <typename ValueT>
|
||||
ValueT
|
||||
from_bytes (cruft::view<std::byte*> &src)
|
||||
{
|
||||
return converter<ValueT>::from_bytes (src);
|
||||
}
|
||||
|
||||
|
||||
template <typename ...ArgsT>
|
||||
std::size_t size (ArgsT &&...args)
|
||||
{
|
||||
return (converter<ArgsT>::size (args) + ... + 0uz);
|
||||
}
|
||||
}
|
193
serialise/std.hpp
Normal file
193
serialise/std.hpp
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./converter.hpp"
|
||||
#include "./ops.hpp"
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename ElementT>
|
||||
struct cruft::serialise::converter<std::vector<ElementT>> {
|
||||
using value_type = std::vector<ElementT>;
|
||||
|
||||
static std::size_t
|
||||
size (value_type const &val)
|
||||
{
|
||||
std::size_t accum = sizeof (typename value_type::size_type);
|
||||
for (auto const &i: val)
|
||||
accum += converter<ElementT>::size (i);
|
||||
return accum;
|
||||
}
|
||||
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
value_type const &src
|
||||
) {
|
||||
dst = ::cruft::serialise::to_bytes (dst, src.size ());
|
||||
|
||||
for (auto const &i: src)
|
||||
dst = ::cruft::serialise::to_bytes (dst, i);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
static
|
||||
value_type
|
||||
from_bytes (cruft::view<std::byte*> &src)
|
||||
{
|
||||
auto const len = ::cruft::serialise::from_bytes<typename std::vector<ElementT>::size_type> (src);
|
||||
|
||||
std::vector<ElementT> res;
|
||||
res.reserve (len);
|
||||
|
||||
for (std::size_t i = 0; i != len; ++i)
|
||||
res.push_back (::cruft::serialise::from_bytes<ElementT> (src));
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <>
|
||||
struct cruft::serialise::converter<std::string> {
|
||||
using value_type = std::string;
|
||||
|
||||
static std::size_t
|
||||
size (value_type const &val)
|
||||
{
|
||||
return sizeof (value_type::size_type) + sizeof (value_type::value_type) * val.size ();
|
||||
}
|
||||
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
value_type const &src
|
||||
) {
|
||||
dst = ::cruft::serialise::to_bytes (dst, src.size ());
|
||||
for (auto const &i: src)
|
||||
dst = ::cruft::serialise::to_bytes (dst, i);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static
|
||||
value_type
|
||||
from_bytes (cruft::view<std::byte*> &src)
|
||||
{
|
||||
std::string res;
|
||||
res.resize (::cruft::serialise::converter<std::string::size_type>::from_bytes (src));
|
||||
for (auto &i: res)
|
||||
i = ::cruft::serialise::converter<std::string::value_type>::from_bytes (src);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename A, typename B>
|
||||
struct cruft::serialise::converter<std::pair<A, B>> {
|
||||
using value_type = std::pair<A, B>;
|
||||
|
||||
static std::size_t
|
||||
size (value_type const &val)
|
||||
{
|
||||
return converter<A>::size (val.first) + converter<B>::size (val.second);
|
||||
}
|
||||
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
value_type const &src
|
||||
) {
|
||||
dst = ::cruft::serialise::to_bytes (dst, src.first);
|
||||
dst = ::cruft::serialise::to_bytes (dst, src.second);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static
|
||||
value_type
|
||||
from_bytes (cruft::view<std::byte*> &src)
|
||||
{
|
||||
return {
|
||||
cruft::serialise::converter<A>::from_bytes (src),
|
||||
cruft::serialise::converter<B>::from_bytes (src),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename ...ElementT>
|
||||
struct cruft::serialise::converter<std::tuple<ElementT...>> {
|
||||
using value_type = std::tuple<ElementT...>;
|
||||
|
||||
static std::size_t
|
||||
size (value_type const &val)
|
||||
{
|
||||
return size (val, std::make_index_sequence<sizeof ...(ElementT)> {});
|
||||
}
|
||||
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
value_type const &src
|
||||
) {
|
||||
return to_bytes (dst, src, std::make_index_sequence<sizeof... (ElementT)> {});
|
||||
}
|
||||
|
||||
static
|
||||
value_type
|
||||
from_bytes (cruft::view<std::byte*> &src)
|
||||
{
|
||||
return from_bytes (src, std::make_index_sequence<sizeof... (ElementT)> {});
|
||||
}
|
||||
|
||||
private:
|
||||
template <std::size_t ...Index>
|
||||
static std::size_t
|
||||
size (value_type const &val, std::index_sequence<Index...> const &)
|
||||
{
|
||||
return (
|
||||
converter<
|
||||
std::tuple_element_t<Index, value_type>
|
||||
>::size (
|
||||
std::get<Index> (val)
|
||||
) + ... + 0uz
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
template <std::size_t ...IndexV>
|
||||
static
|
||||
cruft::view<std::byte*>
|
||||
to_bytes [[nodiscard]] (
|
||||
cruft::view<std::byte*> dst,
|
||||
value_type const &src,
|
||||
std::index_sequence<IndexV...> const&
|
||||
) {
|
||||
return ::cruft::serialise::to_bytes (dst, std::get<IndexV> (src)...);
|
||||
}
|
||||
|
||||
|
||||
template <std::size_t ...IndexV>
|
||||
static
|
||||
value_type
|
||||
from_bytes (cruft::view<std::byte*> &src, std::index_sequence<IndexV...> const&)
|
||||
{
|
||||
return std::tuple {
|
||||
::cruft::serialise::from_bytes<ElementT> (src)...,
|
||||
};
|
||||
}
|
||||
};
|
76
test/serialise.cpp
Normal file
76
test/serialise.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include <cruft/util/tap.hpp>
|
||||
|
||||
#include <cruft/util/serialise/ops.hpp>
|
||||
#include <cruft/util/serialise/std.hpp>
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static void
|
||||
test_scalars (cruft::TAP::logger &tap)
|
||||
{
|
||||
std::vector<std::byte> store (sizeof (f32) + sizeof (i16) + sizeof (char));
|
||||
|
||||
{
|
||||
auto remain = cruft::serialise::to_bytes (store, 2.f, i16 (3), char (4));
|
||||
tap.expect (remain.empty (), "scalar serialise: no remainder");
|
||||
}
|
||||
|
||||
{
|
||||
cruft::view remain (store);
|
||||
tap.expect_eq (cruft::serialise::from_bytes<f32> (remain), 2.f, "scalar extract: f32");
|
||||
tap.expect_eq (cruft::serialise::from_bytes<i16> (remain), 3, "scalar extract: i16");
|
||||
tap.expect_eq (cruft::serialise::from_bytes<char> (remain), 4, "scalar extract: char");
|
||||
tap.expect (remain.empty (), "scalar extract: empty");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename ValueT>
|
||||
static void
|
||||
test_roundtrip (cruft::TAP::logger &tap, char const *label, ValueT const &val)
|
||||
{
|
||||
std::vector<std::byte> raw (cruft::serialise::size (val));
|
||||
{
|
||||
cruft::view remain (raw);
|
||||
remain = cruft::serialise::to_bytes (remain, val);
|
||||
tap.expect (remain.empty (), "round_trip {}: serialise", label);
|
||||
}
|
||||
|
||||
{
|
||||
cruft::view remain (raw);
|
||||
auto const extracted = cruft::serialise::from_bytes<ValueT> (remain);
|
||||
tap.expect_eq (extracted, val, "round_trip {}: extract", label);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static void
|
||||
test_stdlib (cruft::TAP::logger &tap)
|
||||
{
|
||||
test_roundtrip (tap, "string", std::string ("this is a string"));
|
||||
test_roundtrip (tap, "pair", std::pair<int, bool> (1, false));
|
||||
test_roundtrip (tap, "vector", std::vector<i16> { 1, 2, 3 });
|
||||
test_roundtrip (
|
||||
tap,
|
||||
"tuple",
|
||||
std::tuple (
|
||||
std::string ("this is a string"),
|
||||
std::pair<int, bool> { 1, false },
|
||||
std::vector<i16> { 1, 2, 3 }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
int main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
test_scalars (tap);
|
||||
test_stdlib (tap);
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user