Skip to content

Commit

Permalink
Support persisting immer::array
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-sparus committed Aug 23, 2024
1 parent f0d279c commit ece80df
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 61 deletions.
30 changes: 30 additions & 0 deletions immer/extra/cereal/immer_array.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <cereal/cereal.hpp>
#include <immer/array.hpp>

namespace cereal {

template <typename Archive, typename T, typename MemoryPolicy>
void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::array<T, MemoryPolicy>& m)
{
size_type size;
ar(make_size_tag(size));

for (auto i = size_type{}; i < size; ++i) {
T x;
ar(x);
m = std::move(m).push_back(std::move(x));
}
}

template <typename Archive, typename T, typename MemoryPolicy>
void CEREAL_SAVE_FUNCTION_NAME(Archive& ar,
const immer::array<T, MemoryPolicy>& m)
{
ar(make_size_tag(static_cast<size_type>(m.size())));
for (auto&& v : m)
ar(v);
}

} // namespace cereal
2 changes: 1 addition & 1 deletion immer/extra/persist/cereal/policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct via_get_pools_names_policy_t : value0_serialize_t
{
auto get_pool_types(const T& value) const
{
return boost::hana::to_set(boost::hana::keys(get_pools_names(value)));
return boost::hana::keys(get_pools_names(value));
}

using Map = decltype(get_pools_names(std::declval<T>()));
Expand Down
9 changes: 5 additions & 4 deletions immer/extra/persist/cereal/with_pools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void cereal_save_with_pools(std::ostream& os,
const T& value0,
const Policy& policy = Policy{})
{
const auto types = policy.get_pool_types(value0);
const auto types = boost::hana::to_set(policy.get_pool_types(value0));
auto pools = detail::generate_output_pools(types);
const auto wrap = detail::wrap_known_types(types, detail::wrap_for_saving);
using Pools = std::decay_t<decltype(pools)>;
Expand Down Expand Up @@ -75,8 +75,9 @@ template <class T,
Policy<T> Policy = default_policy>
T cereal_load_with_pools(std::istream& is, const Policy& policy = Policy{})
{
using TypesSet = decltype(policy.get_pool_types(std::declval<T>()));
using Pools = decltype(detail::generate_input_pools(TypesSet{}));
using TypesSet =
decltype(boost::hana::to_set(policy.get_pool_types(std::declval<T>())));
using Pools = decltype(detail::generate_input_pools(TypesSet{}));

auto get_pool_name_fn = [](const auto& value) {
return Policy{}.get_pool_name(value);
Expand Down Expand Up @@ -124,7 +125,7 @@ T cereal_load_with_pools(const std::string& input,
template <typename T, Policy<T> Policy = hana_struct_auto_policy>
auto get_output_pools(const T& value0, const Policy& policy = Policy{})
{
const auto types = policy.get_pool_types(value0);
const auto types = boost::hana::to_set(policy.get_pool_types(value0));
auto pools = detail::generate_output_pools(types);
const auto wrap = detail::wrap_known_types(types, detail::wrap_for_saving);
using Pools = std::decay_t<decltype(pools)>;
Expand Down
195 changes: 195 additions & 0 deletions immer/extra/persist/detail/array/pool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#pragma once

#include <immer/extra/cereal/immer_array.hpp>
#include <immer/extra/cereal/immer_vector.hpp>
#include <immer/extra/persist/detail/common/pool.hpp>

#include <boost/hana/functional/id.hpp>

namespace immer::persist::array {

template <typename T, typename MemoryPolicy>
struct output_pool
{
immer::map<container_id, immer::array<T, MemoryPolicy>> arrays;

immer::map<const void*, container_id> ids;

friend bool operator==(const output_pool& left, const output_pool& right)
{
return left.arrays == right.arrays;
}

template <class Archive>
void save(Archive& ar) const
{
ar(cereal::make_size_tag(
static_cast<cereal::size_type>(arrays.size())));
for_each_ordered([&](const auto& array) { ar(array); });
}

template <typename F>
void for_each_ordered(F&& f) const
{
for (auto index = std::size_t{}; index < arrays.size(); ++index) {
auto* p = arrays.find(container_id{index});
assert(p);
f(*p);
}
}
};

template <typename T, typename MemoryPolicy>
std::pair<output_pool<T, MemoryPolicy>, container_id>
get_id(output_pool<T, MemoryPolicy> pool,
const immer::array<T, MemoryPolicy>& array)
{
auto* ptr_void = static_cast<const void*>(array.identity());
if (auto* maybe_id = pool.ids.find(ptr_void)) {
return {std::move(pool), *maybe_id};
}

const auto id = container_id{pool.ids.size()};
pool.ids = std::move(pool.ids).set(ptr_void, id);
return {std::move(pool), id};
}

template <typename T, typename MemoryPolicy>
std::pair<output_pool<T, MemoryPolicy>, container_id>
add_to_pool(immer::array<T, MemoryPolicy> array,
output_pool<T, MemoryPolicy> pool)
{
auto id = container_id{};
std::tie(pool, id) = get_id(std::move(pool), array);

if (pool.arrays.count(id)) {
// Already been saved
return {std::move(pool), id};
}

pool.arrays = std::move(pool.arrays).set(id, std::move(array));
return {std::move(pool), id};
}

template <typename T, typename MemoryPolicy>
struct input_pool
{
immer::vector<immer::array<T, MemoryPolicy>> arrays;

friend bool operator==(const input_pool& left,
const input_pool& right) = default;

template <class Archive>
void load(Archive& ar)
{
cereal::load(ar, arrays);
}

void merge_previous(const input_pool& other)
{
if (arrays.size() != other.arrays.size()) {
return;
}

auto result = immer::vector<immer::array<T, MemoryPolicy>>{};
for (auto i = std::size_t{}; i < arrays.size(); ++i) {
if (arrays[i] == other.arrays[i]) {
// While it's the same, prefer the old one
result = std::move(result).push_back(other.arrays[i]);
} else {
result = std::move(result).push_back(arrays[i]);
}
}
arrays = std::move(result);
}
};

template <typename T, typename MemoryPolicy>
input_pool<T, MemoryPolicy>
to_input_pool(const output_pool<T, MemoryPolicy>& pool)
{
auto result = input_pool<T, MemoryPolicy>{};
pool.for_each_ordered([&](const auto& array) {
result.arrays = std::move(result.arrays).push_back(array);
});
return result;
}

template <typename T,
typename MemoryPolicy,
typename Pool = input_pool<T, MemoryPolicy>,
typename TransformF = boost::hana::id_t>
class loader
{
public:
explicit loader(Pool pool)
requires std::is_same_v<TransformF, boost::hana::id_t>
: pool_{std::move(pool)}
{
}

explicit loader(Pool pool, TransformF transform)
: pool_{std::move(pool)}
, transform_{std::move(transform)}
{
}

immer::array<T, MemoryPolicy> load(container_id id)
{
if (id.value >= pool_.arrays.size()) {
throw invalid_container_id{id};
}
if constexpr (std::is_same_v<TransformF, boost::hana::id_t>) {
return pool_.arrays[id.value];
} else {
if (auto* b = arrays_.find(id)) {
return *b;
}
const auto& old_array = pool_.arrays[id.value];
auto new_array = immer::array<T, MemoryPolicy>{};
for (const auto& item : old_array) {
new_array = std::move(new_array).push_back(transform_(item));
}
arrays_ = std::move(arrays_).set(id, new_array);
return new_array;
}
}

private:
const Pool pool_;
const TransformF transform_;
immer::map<container_id, immer::array<T, MemoryPolicy>> arrays_;
};

template <typename T, typename MemoryPolicy>
loader<T, MemoryPolicy> make_loader_for(const immer::array<T, MemoryPolicy>&,
input_pool<T, MemoryPolicy> pool)
{
return loader<T, MemoryPolicy>{std::move(pool)};
}

} // namespace immer::persist::array

namespace immer::persist::detail {

template <typename T, typename MemoryPolicy>
struct container_traits<immer::array<T, MemoryPolicy>>
{
using output_pool_t = array::output_pool<T, MemoryPolicy>;
using input_pool_t = array::input_pool<T, MemoryPolicy>;

template <typename Pool = input_pool_t,
typename TransformF = boost::hana::id_t>
using loader_t = array::loader<T, MemoryPolicy, Pool, TransformF>;

using container_id = immer::persist::container_id;

template <class F>
static auto transform(F&& func)
{
using U = std::decay_t<decltype(func(std::declval<T>()))>;
return immer::array<U, MemoryPolicy>{};
}
};

} // namespace immer::persist::detail
50 changes: 6 additions & 44 deletions immer/extra/persist/detail/cereal/wrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,13 @@
#include <immer/extra/persist/detail/type_traverse.hpp>

// Bring in all known pools to be able to wrap all immer types
#include <immer/extra/persist/detail/array/pool.hpp>
#include <immer/extra/persist/detail/box/pool.hpp>
#include <immer/extra/persist/detail/champ/traits.hpp>
#include <immer/extra/persist/detail/rbts/traits.hpp>

namespace immer::persist::detail {

template <class T>
struct is_auto_ignored_type : boost::hana::false_
{};

template <class T>
struct is_auto_ignored_type<immer::map<node_id, values_save<T>>>
: boost::hana::true_
{};

template <class T>
struct is_auto_ignored_type<immer::map<node_id, values_load<T>>>
: boost::hana::true_
{};

template <>
struct is_auto_ignored_type<immer::map<node_id, rbts::inner_node>>
: boost::hana::true_
{};

template <>
struct is_auto_ignored_type<immer::vector<node_id>> : boost::hana::true_
{};

template <>
struct is_auto_ignored_type<immer::vector<rbts::rbts_info>> : boost::hana::true_
{};

/**
* This wrapper is used to load a given container via persistable.
*/
Expand Down Expand Up @@ -69,10 +43,6 @@ constexpr auto is_persistable = boost::hana::is_valid(
[](auto&& obj) ->
typename container_traits<std::decay_t<decltype(obj)>>::output_pool_t {});

constexpr auto is_auto_ignored = [](const auto& value) {
return is_auto_ignored_type<std::decay_t<decltype(value)>>{};
};

/**
* Make a function that operates conditionally on its single argument, based on
* the given predicate. If the predicate is not satisfied, the function forwards
Expand All @@ -85,14 +55,6 @@ constexpr auto make_conditional_func = [](auto pred, auto func) {
};
};

// We must not try to persist types that are actually the pool itself,
// for example, `immer::map<node_id, values_save<T>> leaves` etc.
constexpr auto exclude_internal_pool_types = [](auto wrap) {
namespace hana = boost::hana;
return make_conditional_func(hana::compose(hana::not_, is_auto_ignored),
wrap);
};

constexpr auto to_persistable = [](const auto& x) {
return persistable<std::decay_t<decltype(x)>>(x);
};
Expand All @@ -106,11 +68,11 @@ constexpr auto to_persistable_loader = [](auto& value) {
* This function will wrap a value in persistable if possible or will return a
* reference to its argument.
*/
constexpr auto wrap_for_saving = exclude_internal_pool_types(
make_conditional_func(is_persistable, to_persistable));
constexpr auto wrap_for_saving =
make_conditional_func(is_persistable, to_persistable);

constexpr auto wrap_for_loading = exclude_internal_pool_types(
make_conditional_func(is_persistable, to_persistable_loader));
constexpr auto wrap_for_loading =
make_conditional_func(is_persistable, to_persistable_loader);

/**
* Returns a wrapping function that wraps only known types.
Expand Down Expand Up @@ -149,7 +111,7 @@ auto get_pools_for_hana_type()
using Type = typename decltype(type)::type;
return detail::is_persistable(Type{});
});
return hana::to_set(persistable);
return persistable;
}

/**
Expand Down
1 change: 1 addition & 0 deletions test/extra/persist/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include(CTest)

add_executable(
persist-tests EXCLUDE_FROM_ALL
test_array.cpp
test_vectors.cpp
test_champ.cpp
test_xxhash.cpp
Expand Down
Loading

0 comments on commit ece80df

Please sign in to comment.