Skip to content

Commit

Permalink
Try auto-archive with type conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-sparus committed Mar 19, 2024
1 parent 48fa5ad commit 6d3d2b2
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 20 deletions.
9 changes: 8 additions & 1 deletion immer/extra/archive/champ/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ template <typename T,
immer::detail::hamts::bits_t B>
struct container_traits<immer::table<T, KeyFn, Hash, Equal, MemoryPolicy, B>>
: champ_traits<immer::table<T, KeyFn, Hash, Equal, MemoryPolicy, B>>
{};
{
template <class F>
static auto transform(F&& func)
{
using U = std::decay_t<decltype(func(std::declval<T>()))>;
return immer::table<U, KeyFn, Hash, Equal, MemoryPolicy, B>{};
}
};

} // namespace immer::archive
10 changes: 10 additions & 0 deletions immer/extra/archive/json/json_with_archive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class error_no_archive_for_the_given_type_check_get_archives_types_function;
template <class T>
class error_duplicate_archive_name_found;

template <class T>
class error_missing_archive_for_type;

/**
* Archives and functions to serialize types that contain archivable data
* structures.
Expand Down Expand Up @@ -112,6 +115,13 @@ struct archives_load
template <class Container>
auto& get_loader()
{
using Contains =
decltype(hana::contains(storage, hana::type_c<Container>));
constexpr bool contains = hana::value<Contains>();
if constexpr (!contains) {
auto err = error_missing_archive_for_type<Container>{};
}

auto& load = storage[hana::type_c<Container>];
if (!load.loader) {
load.loader.emplace(load.archive);
Expand Down
85 changes: 66 additions & 19 deletions immer/extra/archive/json/json_with_archive_auto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,22 +226,13 @@ auto load_initial_auto_archives(std::istream& is, WrapF wrap)
return archives;
}

template <typename T, class ArchivesTypes>
T from_json_with_auto_archive(std::istream& is,
const ArchivesTypes& archives_types)
{
namespace hana = boost::hana;
constexpr auto wrap = wrap_for_loading;
using WrapF = std::decay_t<decltype(wrap)>;

using Archives =
std::decay_t<decltype(detail::generate_archives_load(archives_types))>;

constexpr auto reload_archive_auto = [wrap](std::istream& is,
auto archives) {
auto restore = util::istream_snapshot{is};
auto previous = immer::archive::json_immer_input_archive<Archives>{
std::move(archives), is};
constexpr auto reload_archive_auto = [](auto wrap) {
return [wrap](std::istream& is, auto archives) {
using Archives = std::decay_t<decltype(archives)>;
using WrapF = std::decay_t<decltype(wrap)>;
auto restore = util::istream_snapshot{is};
auto previous =
json_immer_input_archive<Archives>{std::move(archives), is};
auto ar = json_immer_auto_input_archive<decltype(previous), WrapF>{
previous, wrap};
/**
Expand All @@ -253,14 +244,25 @@ T from_json_with_auto_archive(std::istream& is,
ar(CEREAL_NVP(archives));
return archives;
};
};

template <typename T, class ArchivesTypes>
T from_json_with_auto_archive(std::istream& is,
const ArchivesTypes& archives_types)
{
namespace hana = boost::hana;
constexpr auto wrap = wrap_for_loading;
using WrapF = std::decay_t<decltype(wrap)>;

using Archives =
std::decay_t<decltype(detail::generate_archives_load(archives_types))>;

auto archives =
load_archives(is,
load_initial_auto_archives<Archives>(is, wrap),
reload_archive_auto);
reload_archive_auto(wrap));

auto previous = immer::archive::json_immer_input_archive<Archives>{
std::move(archives), is};
auto previous = json_immer_input_archive<Archives>{std::move(archives), is};
auto ar = json_immer_auto_input_archive<decltype(previous), WrapF>{previous,
wrap};
auto r = T{};
Expand All @@ -276,4 +278,49 @@ T from_json_with_auto_archive(const std::string& input,
return from_json_with_auto_archive<T>(is, archives_types);
}

template <typename T,
typename OldType,
typename ConversionsMap,
class ArchivesTypes>
T from_json_with_auto_archive_with_conversion(
std::istream& is,
const ConversionsMap& map,
const ArchivesTypes& archives_types)
{
constexpr auto wrap = wrap_for_loading;
using WrapF = std::decay_t<decltype(wrap)>;

// Load the archives part for the old type
using OldArchives =
std::decay_t<decltype(detail::generate_archives_load(archives_types))>;
auto archives_old =
load_archives(is,
load_initial_auto_archives<OldArchives>(is, wrap),
reload_archive_auto(wrap));

auto archives = archives_old.transform(map);
using Archives = decltype(archives);

auto previous = json_immer_input_archive<Archives>{std::move(archives), is};
auto ar = json_immer_auto_input_archive<decltype(previous), WrapF>{previous,
wrap};
auto r = T{};
ar(r);
return r;
}

template <typename T,
typename OldType,
typename ConversionsMap,
class ArchivesTypes>
T from_json_with_auto_archive_with_conversion(
const std::string& input,
const ConversionsMap& map,
const ArchivesTypes& archives_types)
{
auto is = std::istringstream{input};
return from_json_with_auto_archive_with_conversion<T, OldType>(
is, map, archives_types);
}

} // namespace immer::archive
120 changes: 120 additions & 0 deletions test/extra/archive/test_special_archive_auto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,123 @@ TEST_CASE("Test save and load small type")
REQUIRE(loaded == value);
}
}

namespace {

using test::new_type;
using test::old_type;

template <class V>
using map_t = immer::map<std::string, V, immer::archive::xx_hash<std::string>>;

template <class T>
using table_t =
immer::table<T, immer::table_key_fn, immer::archive::xx_hash<std::string>>;

// Some type that an application would serialize. Contains multiple vectors and
// maps to demonstrate structural sharing.
struct old_app_type
{
BOOST_HANA_DEFINE_STRUCT(old_app_type,
(test::vector_one<old_type>, vec),
(test::vector_one<old_type>, vec2),
(map_t<old_type>, map),
(map_t<old_type>, map2),
(table_t<old_type>, table)

);

template <class Archive>
void serialize(Archive& ar)
{
serialize_members(ar, *this);
}
};

struct new_app_type
{
BOOST_HANA_DEFINE_STRUCT(new_app_type,
(test::vector_one<new_type>, vec),
(test::vector_one<new_type>, vec2),
(map_t<new_type>, map),
(map_t<new_type>, map2),
(table_t<new_type>, table)

);

template <class Archive>
void serialize(Archive& ar)
{
serialize_members(ar, *this);
}
};

} // namespace

TEST_CASE("Test conversion with auto-archive")
{
const auto vec1 = test::vector_one<old_type>{
old_type{.data = 123},
old_type{.data = 234},
};
const auto vec2 = vec1.push_back(old_type{.data = 345});

const auto map1 = [] {
auto map = map_t<old_type>{};
for (auto i = 0; i < 30; ++i) {
map =
std::move(map).set(fmt::format("x{}x", i), old_type{.data = i});
}
return map;
}();
const auto map2 = map1.set("345", old_type{.data = 345});

// Prepare a value of the old type that uses some structural sharing
// internally.
const auto value = old_app_type{
.vec = vec1,
.vec2 = vec2,
.map = map1,
.map2 = map2,
.table =
{
old_type{"_51_", 51},
old_type{"_52_", 52},
old_type{"_53_", 53},
},
};

constexpr auto old_names = [] {
return immer::archive::get_archives_for_types(
hana::tuple_t<old_app_type>, hana::make_map());
};

using OldArchiveTypes = decltype(old_names());
constexpr auto old_archive_types = OldArchiveTypes{};
const auto [json_str, archives] =
immer::archive::to_json_with_auto_archive(value, old_archive_types);
// REQUIRE(json_str == "");

// Describe how to go from the old archive to the desired new archive.
// Convert all old archives with convert_old_type.
const auto archives_conversions = hana::unpack(
hana::transform(hana::keys(old_archive_types),
[&](auto key) {
return hana::make_pair(key, test::convert_old_type);
}),
hana::make_map);

// Having a JSON from serializing old_app_type and a conversion function,
// we need to somehow load new_app_type.
const new_app_type full_load = immer::archive::
from_json_with_auto_archive_with_conversion<new_app_type, old_app_type>(
json_str, archives_conversions, old_archive_types);

{
REQUIRE(full_load.vec == transform_vec(value.vec));
REQUIRE(full_load.vec2 == transform_vec(value.vec2));
REQUIRE(full_load.map == transform_map(value.map));
REQUIRE(full_load.map2 == transform_map(value.map2));
REQUIRE(full_load.table == transform_table(value.table));
}
}
13 changes: 13 additions & 0 deletions test/extra/archive/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <immer/extra/archive/rbts/load.hpp>
#include <immer/extra/archive/xxhash/xxhash.hpp>

#include <immer/table.hpp>

#include <sstream>

#include <cereal/archives/json.hpp>
Expand Down Expand Up @@ -151,6 +153,17 @@ inline auto transform_map(const auto& map)
return result;
}

inline auto transform_table(const auto& table)
{
auto result = immer::table<new_type,
immer::table_key_fn,
immer::archive::xx_hash<std::string>>{};
for (const auto& item : table) {
result = std::move(result).insert(convert_old_type(item));
}
return result;
}

} // namespace test

template <>
Expand Down

0 comments on commit 6d3d2b2

Please sign in to comment.