From bfaa821085ad13ad37fb65fc341463bde4d552d7 Mon Sep 17 00:00:00 2001 From: Yan-Tong Lin Date: Fri, 28 Jun 2024 12:21:39 -0700 Subject: [PATCH] Support recursion for sub_struct_view::sub_struct_view_of --- BUILD.bazel | 5 + include/fixed_containers/sub_struct_view.hpp | 412 +++++++++++-- test/sub_struct_view_test.cpp | 574 ++++++++++++++++--- 3 files changed, 854 insertions(+), 137 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 9c471c56..d7875323 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -862,12 +862,16 @@ cc_library( strip_include_prefix = strip_include_prefix_config(), deps = [ ":assert_or_abort", + ":fixed_vector", ":fixed_map", + ":fixed_set", ":iterator_utils", ":memory", + ":in_out", ":out", ":random_access_iterator", ":reflection", + ":type_name" ], copts = ["-std=c++20"], ) @@ -1621,6 +1625,7 @@ cc_test( ":fixed_vector", ":out", ":sub_struct_view", + ":source_location", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], diff --git a/include/fixed_containers/sub_struct_view.hpp b/include/fixed_containers/sub_struct_view.hpp index b529debf..6a447b0f 100644 --- a/include/fixed_containers/sub_struct_view.hpp +++ b/include/fixed_containers/sub_struct_view.hpp @@ -2,6 +2,9 @@ #include "fixed_containers/assert_or_abort.hpp" #include "fixed_containers/fixed_map.hpp" +#include "fixed_containers/fixed_set.hpp" +#include "fixed_containers/fixed_vector.hpp" +#include "fixed_containers/in_out.hpp" #include "fixed_containers/iterator_utils.hpp" #include "fixed_containers/memory.hpp" #include "fixed_containers/out.hpp" @@ -9,78 +12,389 @@ #include "fixed_containers/reflection.hpp" #include +#include #include +#include +#include +#include #include +#include +#include + +/** + * Terminologies + * + * A Path: + * A `PathNameChain` is a sequence of struct field names that leads to a terminal field in the + * struct, with a caveat that when encountering an iterable, we do not include the index as part of + * the path, instead we use `data` to represent all the elements in the iterable. + * `for_each_path_dfs` is a recursive function that iterates over all the paths in the struct. + * + * Accessing a field by path: + * We defer all the indexing to the end of the path, + * where we can then use the `offset.get_offset(Indices indices)` function to get the offset of the + * field. + * + * Sub struct view: + * `sub_struct_view_of` create a view of the super struct object in the sub struct object. + * Currently, `sub_struct_view_of` employs a greedy strategy and updates all the indices for all + * paths at once. This can be improved for some usecases by using a lazy evaluation strategy, where + * we only update the indices when we need to. Users will specify a `ContiguousRangeSubStructView` + * instead of an array to denote the need of lazy evaluation. + * + * TODO: `ContiguousRangeSubStructView` currently only supports flat structs. + * To support partial lazy evaluation, use `PathPropertiesTree` instead of `PathPropertiesMap` + */ + +namespace fixed_containers::sub_struct_view_detail +{ -namespace fixed_containers::sub_struct_view +inline constexpr std::size_t MAX_PATH_LENGTH = 16; +inline constexpr std::size_t MAX_DIM = 5; +inline constexpr std::string_view ITERABLE_PATH_NAME = "data[:]"; +inline constexpr std::string_view PATH_DELIMITER = "."; + +using PathNameChain = FixedVector; + +struct Dimension { -struct FieldProperties + std::size_t stride{}; + std::size_t size{}; +}; + +template +using Dimensions = FixedVector; + +template +using Indices = FixedVector; + +template +struct Offset { - std::ptrdiff_t offset{}; - bool is_pointer{}; + using Dimensions = Dimensions; + using Indices = Indices; + + std::size_t base_offset{}; + Dimensions dimensions{}; + + [[nodiscard]] auto get_offset(Indices indices) const + { + auto stride_view = dimensions | std::views::transform(&Dimension::stride); + return std::inner_product( + std::begin(indices), std::end(indices), std::begin(stride_view), base_offset); + } - constexpr bool operator==(const FieldProperties&) const = default; + constexpr bool operator==(const Offset&) const = default; }; -template -using FieldPropertiesMap = - FixedMap()>; +// Recursion Strategy Concepts +template +concept Iterable = std::ranges::sized_range && std::ranges::contiguous_range; + +template +concept NonTerminal = reflection::Reflectable || Iterable; + +template +concept Terminal = !NonTerminal; + +template +concept Branch = reflection::Reflectable && !Iterable; + +template + requires(Iterable>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); +template + requires(Branch>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); +template + requires(Terminal>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); +template +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); + +template + requires(Iterable>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain) +{ + pre_fn(std::as_const(*chain), instance); + chain->push_back(ITERABLE_PATH_NAME); + for_each_path_dfs_helper(*instance.data(), pre_fn, post_fn, fixed_containers::in_out{*chain}); + chain->pop_back(); + post_fn(std::as_const(*chain), instance); +} -template -auto extract_field_properties_of(const S& instance = {}) +template + requires(Branch>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain) { - FieldPropertiesMap field_properties_map{}; + pre_fn(std::as_const(*chain), instance); reflection::for_each_field( instance, - [&](const std::string_view& name, F& field) + [&pre_fn, &post_fn, &chain](const std::string_view& name, T& field) { - const std::byte* base_pointer = memory::addressof_as_const_byte_ptr(instance); - const std::byte* field_pointer = memory::addressof_as_const_byte_ptr(field); - const bool is_pointer = std::is_pointer_v; - field_properties_map.try_emplace( - name, std::distance(base_pointer, field_pointer), is_pointer); + chain->push_back(name); + for_each_path_dfs_helper(field, pre_fn, post_fn, fixed_containers::in_out{*chain}); + chain->pop_back(); }); + post_fn(std::as_const(*chain), instance); +} - return field_properties_map; +template + requires(Terminal>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain) +{ + pre_fn(std::as_const(*chain), instance); + post_fn(std::as_const(*chain), instance); } -template -void sub_struct_view_of(std::byte* base_super_struct_pointer, - const SuperProperties& super_struct_field_properties, - std::byte* base_sub_struct_pointer, - const SubProperties& sub_struct_field_properties) +template +constexpr void for_each_path_dfs_helper(S&& /*instance*/, + PreFunction&& /*pre_fn*/, + PostFunction&& /*post_fn*/, + fixed_containers::in_out /*chain*/) { - for (const auto& [name, field_properties] : sub_struct_field_properties) + static_assert(std::is_same_v, "Unreachable Fallback"); +} + +// template function that expands to MAX_DIM nested loops for iterating all indices +template +constexpr void for_each_index_helper(const Offset& offset, + auto&& func, + Indices& indices) +{ + // DIM == std::size(indices) + if (DIM == std::size(offset.dimensions)) + { + func(indices); + return; + } + + for (std::size_t i = 0; i < offset.dimensions[DIM].size; ++i) { - if (!field_properties.is_pointer) + indices.push_back(i); + if constexpr (DIM < MAXIMUM_SIZE) { - continue; + for_each_index_helper(offset, func, indices); } + indices.pop_back(); + } +} + +// runtime version of offsetof in +template +std::size_t get_pointer_distance(Instance&& instance, Field&& field) +{ + const std::byte* instance_ptr = memory::addressof_as_const_byte_ptr(instance); + const std::byte* field_ptr = memory::addressof_as_const_byte_ptr(field); + assert_or_abort(instance_ptr <= field_ptr); + return static_cast(std::distance(instance_ptr, field_ptr)); +} + +} // namespace fixed_containers::sub_struct_view_detail + +namespace fixed_containers::sub_struct_view +{ + +using PathNameChain = sub_struct_view_detail::PathNameChain; +using Dimension = sub_struct_view_detail::Dimension; +using Dimensions = sub_struct_view_detail::Dimensions; +using Indices = sub_struct_view_detail::Indices; +using Offset = sub_struct_view_detail::Offset; + +enum class StructTreeNodeType +{ + BRANCH, + TERMINAL, + ITERABLE, +}; + +struct PathProperties +{ + StructTreeNodeType type{}; + Offset offset{}; + + constexpr bool operator==(const PathProperties&) const = default; +}; + +// This function iterates over all paths of a given struct and calls a pre and post function for +// each field. +template + requires(reflection::Reflectable>) +constexpr void for_each_path_dfs(S&& instance, PreFunction&& pre_fn, PostFunction&& post_fn) +{ + PathNameChain chain{}; + sub_struct_view_detail::for_each_path_dfs_helper( + instance, pre_fn, post_fn, fixed_containers::in_out{chain}); +} + +template +constexpr std::size_t path_count_of() +{ + std::size_t count = 0; + for_each_path_dfs(S{}, [&count](const auto&, auto&) { ++count; }, [](const auto&, auto&) {}); + return count; +} + +template +using PathPropertiesMap = FixedMap()>; +template +using PathSet = FixedSet()>; + +inline PathNameChain path_from_string(const std::string_view& path_name_chain_string) +{ + auto view_of_string_view = + path_name_chain_string | std::views::split(sub_struct_view_detail::PATH_DELIMITER) | + std::views::transform( + [](auto&& name) + { return std::string_view(std::ranges::begin(name), std::ranges::size(name)); }); + return PathNameChain(std::ranges::begin(view_of_string_view), + std::ranges::end(view_of_string_view)); +} + +template +auto extract_paths_of(const S& instance = {}) +{ + PathSet paths{}; + + for_each_path_dfs( + instance, + [&](const PathNameChain& chain, const F& /*field*/) { paths.insert(chain); }, + [&](const PathNameChain&, const F&) {}); + return paths; +} + +template +auto extract_path_properties_of_filtered( + const S& instance, const std::optional& registered_set = std::nullopt) +{ + PathPropertiesMap paths{}; + Dimensions dimensions{}; + + for_each_path_dfs( + instance, + [&](const PathNameChain& chain, const F& field) + { + if (registered_set.has_value() && !registered_set.value().contains(chain)) + { + return; + } + if constexpr (sub_struct_view_detail::Terminal) + { + auto [_, was_inserted] = paths.try_emplace( + chain, + PathProperties{ + .type = StructTreeNodeType::TERMINAL, + .offset = {.base_offset = sub_struct_view_detail::get_pointer_distance( + instance, field), + .dimensions = dimensions}, + }); + assert_or_abort(was_inserted); + } + else if constexpr (sub_struct_view_detail::Iterable) + { + dimensions.push_back({ + .stride = sizeof(std::ranges::range_value_t), + .size = std::size(field), + }); + auto [_, was_inserted] = paths.try_emplace( + chain, + PathProperties{ + .type = StructTreeNodeType::ITERABLE, + .offset = {.base_offset = sub_struct_view_detail::get_pointer_distance( + instance, field), + .dimensions = dimensions}, + }); + assert_or_abort(was_inserted); + } + else if constexpr (sub_struct_view_detail::Branch) + { + // Branch nodes will not be part of path properties. + // They can be used naturally inside of the sub struct. + } + }, + [&](const PathNameChain& /*chain*/, const F& /*field*/) + { + if constexpr (sub_struct_view_detail::Iterable) + { + dimensions.pop_back(); + } + }); + return paths; +} - const std::ptrdiff_t super_struct_offset = super_struct_field_properties.at(name).offset; - std::byte* super_struct_field_ptr = - std::next(base_super_struct_pointer, super_struct_offset); - std::byte* sub_struct_field_ptr = - std::next(base_sub_struct_pointer, field_properties.offset); +template +auto extract_path_properties_of(const S& instance = {}) +{ + return extract_path_properties_of_filtered>(instance, std::nullopt); +} + +void for_each_index(const Offset& offset, auto&& func) +{ + Indices indices; + for_each_index_helper<0>(offset, func, indices); +} - *reinterpret_cast(sub_struct_field_ptr) = - reinterpret_cast(super_struct_field_ptr); +template +void sub_struct_view_of(const std::byte* base_super_struct_pointer, + const SuperProperties& super_struct_path_properties, + std::byte* base_sub_struct_pointer, + const SubProperties& sub_struct_path_properties) +{ + for (const auto& [path, path_properties] : sub_struct_path_properties) + { + Offset super_struct_offset = super_struct_path_properties.at(path).offset; + Offset sub_struct_offset = sub_struct_path_properties.at(path).offset; + + for_each_index( + sub_struct_offset, + [&](const auto& indices) + { + const std::byte* super_struct_field_ptr = + std::next(base_super_struct_pointer, + static_cast(super_struct_offset.get_offset(indices))); + std::byte* sub_struct_field_ptr = + std::next(base_sub_struct_pointer, + static_cast(sub_struct_offset.get_offset(indices))); + *reinterpret_cast(sub_struct_field_ptr) = + reinterpret_cast(super_struct_field_ptr); + }); } } template -void sub_struct_view_of(Super& super_struct, - const SuperProperties& super_struct_field_properties, +void sub_struct_view_of(const Super& super_struct, + const SuperProperties& super_struct_path_properties, out out_sub_struct, - const SubProperties& sub_struct_field_properties) + const SubProperties& sub_struct_path_properties) { - std::byte* base_super_struct_pointer = memory::addressof_as_mutable_byte_ptr(super_struct); + const std::byte* base_super_struct_pointer = memory::addressof_as_const_byte_ptr(super_struct); std::byte* base_sub_struct_pointer = memory::addressof_as_mutable_byte_ptr(*out_sub_struct); return sub_struct_view_of(base_super_struct_pointer, - super_struct_field_properties, + super_struct_path_properties, base_sub_struct_pointer, - sub_struct_field_properties); + sub_struct_path_properties); } template @@ -88,8 +402,8 @@ class ContiguousRangeSubStructView { struct AccessingInfo { - FieldPropertiesMap sub_struct_field_properties{}; - FieldPropertiesMap super_struct_field_properties{}; + PathPropertiesMap sub_struct_path_properties{}; + PathPropertiesMap super_struct_path_properties{}; std::byte* base_array_super_struct_ptr{}; std::size_t stride{}; std::size_t size{}; @@ -104,9 +418,9 @@ class ContiguousRangeSubStructView std::next(accessing_info.base_array_super_struct_ptr, static_cast(index * accessing_info.stride)); sub_struct_view::sub_struct_view_of(base_of_ith_entry, - accessing_info.super_struct_field_properties, + accessing_info.super_struct_path_properties, memory::addressof_as_mutable_byte_ptr(instance), - accessing_info.sub_struct_field_properties); + accessing_info.sub_struct_path_properties); return instance; } @@ -178,8 +492,8 @@ class ContiguousRangeSubStructView template ContiguousRangeSubStructView(SuperStructContainer& super_struct_container) : accessing_info_{ - .sub_struct_field_properties = extract_field_properties_of(), - .super_struct_field_properties = {}, + .sub_struct_path_properties = extract_path_properties_of(), + .super_struct_path_properties = {}, .base_array_super_struct_ptr = memory::addressof_as_mutable_byte_ptr(*super_struct_container.data()), .stride = {}, @@ -188,11 +502,11 @@ class ContiguousRangeSubStructView { using SuperStruct = typename SuperStructContainer::value_type; - auto super_struct_field_properties_all = extract_field_properties_of(); - for (const auto& [name, _] : accessing_info_.sub_struct_field_properties) + auto super_struct_path_properties_all = extract_path_properties_of(); + for (const auto& [name, _] : accessing_info_.sub_struct_path_properties) { - accessing_info_.super_struct_field_properties[name] = - super_struct_field_properties_all.at(name); + accessing_info_.super_struct_path_properties[name] = + super_struct_path_properties_all.at(name); } accessing_info_.stride = sizeof(SuperStruct); diff --git a/test/sub_struct_view_test.cpp b/test/sub_struct_view_test.cpp index 7221cdf1..a06080fd 100644 --- a/test/sub_struct_view_test.cpp +++ b/test/sub_struct_view_test.cpp @@ -9,72 +9,127 @@ #include #include +#include +#include +#include namespace fixed_containers::sub_struct_view { namespace { + +[[maybe_unused]] std::ostream& operator<<(std::ostream& out_stream, const PathNameChain& chain) +{ + out_stream << '['; + if (!std::empty(chain)) + { + for (auto it = std::begin(chain); it != std::end(chain) - 1; ++it) + { + out_stream << *it << '.'; + } + out_stream << std::rbegin(chain)->data(); + } + out_stream << ']'; + return out_stream; +} + struct FlatSuperStruct1 { - bool ignore1_dont_forget_alignment{}; - double retain1{}; - float ignore2{}; - float retain2{}; - int ignore3{}; + std::int8_t ignore1_dont_forget_alignment{}; + std::int64_t retain1{}; + std::int32_t ignore2{}; + std::int32_t retain2{}; + std::int16_t ignore3{}; }; struct FlatSubStruct1 { - const double* retain1; - const float* retain2; + const std::int64_t* retain1; + const std::int32_t* retain2; }; } // namespace -TEST(SubStructView, ExtractFieldPropertiesOf) +TEST(SubStructView, GetPointerDistanceFlat) +{ + const FlatSuperStruct1 flat_super_struct_1{}; + EXPECT_EQ(8, + sub_struct_view_detail::get_pointer_distance(flat_super_struct_1, + flat_super_struct_1.retain1)); + EXPECT_EQ(20, + sub_struct_view_detail::get_pointer_distance(flat_super_struct_1, + flat_super_struct_1.retain2)); +} + +TEST(SubStructView, ExtractPathsOfFlat) { { - const FlatSuperStruct1 instance{}; - - auto field_properties = extract_field_properties_of(instance); - - EXPECT_EQ(5, field_properties.size()); - EXPECT_EQ((FieldProperties{.offset = 0, .is_pointer = false}), - field_properties.at("ignore1_dont_forget_alignment")); - EXPECT_EQ((FieldProperties{.offset = 8, .is_pointer = false}), - field_properties.at("retain1")); - EXPECT_EQ((FieldProperties{.offset = 16, .is_pointer = false}), - field_properties.at("ignore2")); - EXPECT_EQ((FieldProperties{.offset = 20, .is_pointer = false}), - field_properties.at("retain2")); - EXPECT_EQ((FieldProperties{.offset = 24, .is_pointer = false}), - field_properties.at("ignore3")); + auto paths = extract_paths_of(); + EXPECT_EQ(3, path_count_of()); + EXPECT_EQ(std::size(paths), path_count_of()); + EXPECT_TRUE(paths.contains(path_from_string(""))); + EXPECT_TRUE(paths.contains(path_from_string("retain1"))); + EXPECT_TRUE(paths.contains(path_from_string("retain2"))); + } +} + +TEST(SubStructView, ExtractPathPropertiesOfFlat) +{ + { + auto path_properties = extract_path_properties_of(); + + EXPECT_EQ(5, std::size(path_properties)); + EXPECT_EQ(0, + path_properties.at(path_from_string("ignore1_dont_forget_alignment")) + .offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("ignore1_dont_forget_alignment")).type); + + EXPECT_EQ(8, path_properties.at(path_from_string("retain1")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain1")).type); + + EXPECT_EQ(16, path_properties.at(path_from_string("ignore2")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("ignore2")).type); + + EXPECT_EQ(20, path_properties.at(path_from_string("retain2")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain2")).type); + + EXPECT_EQ(24, path_properties.at(path_from_string("ignore3")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("ignore3")).type); } { const FlatSubStruct1 instance{}; - auto field_properties = extract_field_properties_of(instance); + auto path_properties = extract_path_properties_of(instance); + + EXPECT_EQ(2, path_properties.size()); + + EXPECT_EQ(0, path_properties.at(path_from_string("retain1")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain1")).type); - EXPECT_EQ(2, field_properties.size()); - EXPECT_EQ((FieldProperties{.offset = 0, .is_pointer = true}), - field_properties.at("retain1")); - EXPECT_EQ((FieldProperties{.offset = 8, .is_pointer = true}), - field_properties.at("retain2")); + EXPECT_EQ(8, path_properties.at(path_from_string("retain2")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain2")).type); } } -TEST(SubStructView, SubStructViewOf) +TEST(SubStructView, SubStructViewOfFlat) { - FlatSuperStruct1 flat_super_struct_1{}; + const FlatSuperStruct1 flat_super_struct_1{}; FlatSubStruct1 flat_sub_struct_1{}; - auto super_struct_field_properties = extract_field_properties_of(flat_super_struct_1); - auto sub_struct_field_properties = extract_field_properties_of(flat_sub_struct_1); + auto super_struct_path_properties = extract_path_properties_of(flat_super_struct_1); + auto sub_struct_path_properties = extract_path_properties_of(flat_sub_struct_1); - sub_struct_view::sub_struct_view_of(flat_super_struct_1, - super_struct_field_properties, - out{flat_sub_struct_1}, - sub_struct_field_properties); + sub_struct_view_of(flat_super_struct_1, + super_struct_path_properties, + out{flat_sub_struct_1}, + sub_struct_path_properties); ASSERT_EQ(flat_sub_struct_1.retain1, &flat_super_struct_1.retain1); ASSERT_EQ(flat_sub_struct_1.retain2, &flat_super_struct_1.retain2); @@ -82,25 +137,27 @@ TEST(SubStructView, SubStructViewOf) namespace { +inline constexpr std::size_t TEST_ARRAY_SIZE = 3; + struct PointXYZ { - double x{}; - double y{}; - double z{}; + std::int64_t x{}; + std::int64_t y{}; + std::int64_t z{}; }; struct FlatSuperStruct2 { - int ignore1{}; - std::array retain_array_1{}; - FixedVector retain_vec_2{}; - float ignore2{}; + std::int16_t ignore1{}; + std::array retain_array_1{}; + FixedVector retain_vec_2{}; + std::int32_t ignore2{}; }; struct PointXZ { - const double* z{}; - const double* x{}; + const std::int64_t* z{}; + const std::int64_t* x{}; }; struct FlatSubStruct2 @@ -111,69 +168,53 @@ struct FlatSubStruct2 } // namespace -TEST(ContiguousRangeSubStructView, OperatorAt) +TEST(ContiguousRangeSubStructView, OperatorAtFlat) { FlatSuperStruct2 flat_super_struct_2{}; FlatSubStruct2 flat_sub_struct_2{}; - flat_super_struct_2.retain_vec_2.resize(3); + flat_super_struct_2.retain_vec_2.resize(TEST_ARRAY_SIZE); flat_sub_struct_2.retain_array_1 = flat_super_struct_2.retain_array_1; flat_sub_struct_2.retain_vec_2 = flat_super_struct_2.retain_vec_2; { - ASSERT_EQ(3, flat_sub_struct_2.retain_array_1.size()); - - ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(0).x, - &flat_super_struct_2.retain_array_1.at(0).x); - ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(0).z, - &flat_super_struct_2.retain_array_1.at(0).z); - - ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(1).x, - &flat_super_struct_2.retain_array_1.at(1).x); - ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(1).z, - &flat_super_struct_2.retain_array_1.at(1).z); + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_array_1.size()); - ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(2).x, - &flat_super_struct_2.retain_array_1.at(2).x); - ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(2).z, - &flat_super_struct_2.retain_array_1.at(2).z); - - ASSERT_DEATH((void)flat_sub_struct_2.retain_array_1.at(3), ""); + for (std::size_t i = 0; i < TEST_ARRAY_SIZE; ++i) + { + ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(i).x, + &flat_super_struct_2.retain_array_1.at(i).x); + ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(i).z, + &flat_super_struct_2.retain_array_1.at(i).z); + } + ASSERT_DEATH((void)flat_sub_struct_2.retain_array_1.at(TEST_ARRAY_SIZE), ""); } { - ASSERT_EQ(3, flat_sub_struct_2.retain_vec_2.size()); - - ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(0).x, - &flat_super_struct_2.retain_vec_2.at(0).x); - ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(0).z, - &flat_super_struct_2.retain_vec_2.at(0).z); - - ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(1).x, - &flat_super_struct_2.retain_vec_2.at(1).x); - ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(1).z, - &flat_super_struct_2.retain_vec_2.at(1).z); - - ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(2).x, - &flat_super_struct_2.retain_vec_2.at(2).x); - ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(2).z, - &flat_super_struct_2.retain_vec_2.at(2).z); + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_vec_2.size()); - ASSERT_DEATH((void)flat_sub_struct_2.retain_vec_2.at(3), ""); + for (std::size_t i = 0; i < TEST_ARRAY_SIZE; ++i) + { + ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(i).x, + &flat_super_struct_2.retain_vec_2.at(i).x); + ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(i).z, + &flat_super_struct_2.retain_vec_2.at(i).z); + } + ASSERT_DEATH((void)flat_sub_struct_2.retain_vec_2.at(TEST_ARRAY_SIZE), ""); } } -TEST(ContiguousRangeSubStructView, Iteration) +TEST(ContiguousRangeSubStructView, IterationFlat) { FlatSuperStruct2 flat_super_struct_2{}; FlatSubStruct2 flat_sub_struct_2{}; - flat_super_struct_2.retain_vec_2.resize(3); + flat_super_struct_2.retain_vec_2.resize(TEST_ARRAY_SIZE); flat_sub_struct_2.retain_array_1 = flat_super_struct_2.retain_array_1; flat_sub_struct_2.retain_vec_2 = flat_super_struct_2.retain_vec_2; { - ASSERT_EQ(3, flat_sub_struct_2.retain_array_1.size()); + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_array_1.size()); std::size_t counter = 0; for (auto&& sub_struct : flat_sub_struct_2.retain_array_1) @@ -182,10 +223,10 @@ TEST(ContiguousRangeSubStructView, Iteration) ASSERT_EQ(sub_struct.z, &flat_super_struct_2.retain_array_1.at(counter).z); counter++; } - ASSERT_EQ(3, counter); + ASSERT_EQ(TEST_ARRAY_SIZE, counter); } { - ASSERT_EQ(3, flat_sub_struct_2.retain_vec_2.size()); + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_vec_2.size()); std::size_t counter = 0; for (auto&& sub_struct : flat_sub_struct_2.retain_vec_2) @@ -194,7 +235,364 @@ TEST(ContiguousRangeSubStructView, Iteration) ASSERT_EQ(sub_struct.z, &flat_super_struct_2.retain_vec_2.at(counter).z); counter++; } - ASSERT_EQ(3, counter); + ASSERT_EQ(TEST_ARRAY_SIZE, counter); + } +} + +namespace +{ + +struct NestedSuperStructLayer2 +{ + std::int16_t retain1{}; + std::int64_t retain2{}; +}; + +struct NestedSuperStructLayer1 +{ + std::int8_t alignment_check_1{}; + std::int64_t retain1{}; + NestedSuperStructLayer2 nested1{}; + std::int32_t ignore2{}; + NestedSuperStructLayer2 nested2{}; +}; + +struct NestedSubStructLayer2Usage1 +{ + const std::int16_t* retain1{}; +}; + +struct NestedSubStructLayer2Usage2 +{ + const std::int64_t* retain2{}; +}; + +struct NestedSubStructLayer1 +{ + const std::int64_t* retain1{}; + NestedSubStructLayer2Usage1 nested1{}; + NestedSubStructLayer2Usage2 nested2{}; +}; + +} // namespace + +TEST(SubStructView, GetPointerDistanceRecursive) +{ + const NestedSuperStructLayer1 nested_super_struct_1{}; + EXPECT_EQ(8, + sub_struct_view_detail::get_pointer_distance(nested_super_struct_1, + nested_super_struct_1.retain1)); + EXPECT_EQ(16, + sub_struct_view_detail::get_pointer_distance(nested_super_struct_1, + nested_super_struct_1.nested1.retain1)); + EXPECT_EQ(48, + sub_struct_view_detail::get_pointer_distance(nested_super_struct_1, + nested_super_struct_1.nested2.retain2)); +} + +TEST(SubStructView, ExtractPathsOfRecursive) +{ + { + auto paths = extract_paths_of(); + EXPECT_EQ(path_count_of(), std::size(paths)); + EXPECT_EQ(6, path_count_of()); + EXPECT_TRUE(paths.contains(path_from_string(""))); + EXPECT_TRUE(paths.contains(path_from_string("retain1"))); + EXPECT_TRUE(paths.contains(path_from_string("nested1"))); + EXPECT_TRUE(paths.contains(path_from_string("nested1.retain1"))); + EXPECT_TRUE(paths.contains(path_from_string("nested2"))); + EXPECT_TRUE(paths.contains(path_from_string("nested2.retain2"))); + } +} + +TEST(SubStructView, ExtractPathPropertiesOfRecursive) +{ + { + auto nested_sub_struct_1 = NestedSubStructLayer1{}; + auto path_properties = extract_path_properties_of(); + + EXPECT_EQ(3, path_properties.size()); + + EXPECT_EQ(0, path_properties.at(path_from_string("retain1")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain1")).type); + + EXPECT_EQ(path_properties.at(path_from_string("nested1.retain1")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + nested_sub_struct_1, nested_sub_struct_1.nested1.retain1)); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("nested1.retain1")).type); + + EXPECT_EQ(path_properties.at(path_from_string("nested2.retain2")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + nested_sub_struct_1, nested_sub_struct_1.nested2.retain2)); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("nested2.retain2")).type); + } +} + +TEST(SubStructView, SubStructViewOfRecursive) +{ + const NestedSuperStructLayer1 nested_super_struct_1{}; + NestedSubStructLayer1 nested_sub_struct_1{}; + + auto super_struct_path_properties = extract_path_properties_of(nested_super_struct_1); + auto sub_struct_path_properties = extract_path_properties_of(nested_sub_struct_1); + + sub_struct_view_of(nested_super_struct_1, + super_struct_path_properties, + out{nested_sub_struct_1}, + sub_struct_path_properties); + + ASSERT_EQ(nested_sub_struct_1.retain1, &nested_super_struct_1.retain1); + ASSERT_EQ(nested_sub_struct_1.nested1.retain1, &nested_super_struct_1.nested1.retain1); + ASSERT_EQ(nested_sub_struct_1.nested2.retain2, &nested_super_struct_1.nested2.retain2); +} + +namespace +{ + +struct ArrayTestSuperStructLayer2 +{ + std::int8_t alignment_check_1{}; + std::array arr{}; + FixedVector vec{ + FixedVector(TEST_ARRAY_SIZE)}; + std::int8_t alignment_check_2{}; +}; + +struct ArrayTestSuperStructLayer1 +{ + std::int8_t alignment_check_1{}; + std::int64_t ignored{}; + std::array arr{}; + std::int8_t alignment_check_2{}; + FixedVector vec{ + FixedVector(TEST_ARRAY_SIZE)}; + std::array, TEST_ARRAY_SIZE> matrix{}; +}; + +struct ArrayTestSubStructLayer2 +{ + std::array arr{}; + // use std::array for accessing FixedVector + std::array vec{}; +}; + +struct ArrayTestSubStructLayer1 +{ + std::array arr{}; + std::array vec{}; + std::array, TEST_ARRAY_SIZE> matrix{}; +}; + +} // namespace + +TEST(SubStructView, GetPointerDistanceRecursiveWithArray) +{ + const ArrayTestSuperStructLayer1 array_test_super_struct_1{}; + EXPECT_EQ(8 + 8, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.arr)); + EXPECT_EQ(8 + 8 + TEST_ARRAY_SIZE * sizeof(ArrayTestSuperStructLayer2) + 8, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.vec)); + EXPECT_EQ(8 + 8 + TEST_ARRAY_SIZE * sizeof(ArrayTestSuperStructLayer2) + 8 + + (8 + TEST_ARRAY_SIZE * sizeof(ArrayTestSuperStructLayer2)), + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.matrix)); +} + +TEST(SubStructView, ExtractPathsOfRecursiveWithArray) +{ + { + auto paths = extract_paths_of(); + EXPECT_EQ(path_count_of(), std::size(paths)); + EXPECT_EQ(16, path_count_of()); + EXPECT_TRUE(paths.contains(path_from_string(""))); + EXPECT_TRUE(paths.contains(path_from_string("arr.data[:].vec.data[:]"))); + EXPECT_TRUE(paths.contains(path_from_string("vec.data[:].arr.data[:]"))); + EXPECT_TRUE(paths.contains(path_from_string("matrix.data[:].data[:]"))); + } +} + +TEST(SubStructView, ExtractPathPropertiesOfRecursiveWithArray) +{ + { + auto array_test_super_struct_1 = ArrayTestSuperStructLayer1{}; + auto path_properties = extract_path_properties_of(array_test_super_struct_1); + + EXPECT_EQ(path_properties.size(), 20); + + // std::array + + // 1st dimension + EXPECT_EQ(path_properties.at(path_from_string("arr")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.arr)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, path_properties.at(path_from_string("arr")).type); + EXPECT_EQ(1, path_properties.at(path_from_string("arr")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("arr")).offset.dimensions[0].size); + EXPECT_EQ(sizeof(ArrayTestSuperStructLayer2), + path_properties.at(path_from_string("arr")).offset.dimensions[0].stride); + + EXPECT_DEATH((void)path_properties.at(path_from_string("arr.data[:]")), ""); + + // 2nd dimension + EXPECT_EQ(path_properties.at(path_from_string("arr.data[:].arr")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.arr[0].arr)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("arr.data[:].arr")).type); + EXPECT_EQ(2, + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions.size()); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[0].size); + EXPECT_EQ( + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[0].stride, + sizeof(ArrayTestSuperStructLayer2)); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[1].size); + EXPECT_EQ( + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[1].stride, + sizeof(std::int16_t)); + + // terminal + EXPECT_EQ( + path_properties.at(path_from_string("arr.data[:].arr.data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.arr[0].arr[0])); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("arr.data[:].arr.data[:]")).type); + EXPECT_EQ(path_properties.at(path_from_string("arr.data[:].arr.data[:]")) + .offset.dimensions.size(), + 2); + + // FixedVector + + // 1st dimension + EXPECT_EQ(path_properties.at(path_from_string("vec")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.vec)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, path_properties.at(path_from_string("vec")).type); + EXPECT_EQ(1, path_properties.at(path_from_string("vec")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("vec")).offset.dimensions[0].size); + EXPECT_EQ(sizeof(ArrayTestSuperStructLayer2), + path_properties.at(path_from_string("vec")).offset.dimensions[0].stride); + + EXPECT_DEATH((void)path_properties.at(path_from_string("vec.data[:]")), ""); + + // 2nd dimension + EXPECT_EQ(path_properties.at(path_from_string("vec.data[:].arr")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.vec[0].arr)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("vec.data[:].arr")).type); + EXPECT_EQ(2, + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions.size()); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[0].size); + EXPECT_EQ( + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[0].stride, + sizeof(ArrayTestSuperStructLayer2)); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[1].size); + EXPECT_EQ( + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[1].stride, + sizeof(std::int16_t)); + + // terminal + EXPECT_EQ( + path_properties.at(path_from_string("vec.data[:].arr.data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.vec[0].arr[0])); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("vec.data[:].arr.data[:]")).type); + EXPECT_EQ(path_properties.at(path_from_string("vec.data[:].arr.data[:]")) + .offset.dimensions.size(), + 2); + + // matrix (2d std::array) + + // 1st dimension + EXPECT_EQ(path_properties.at(path_from_string("matrix")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.matrix)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("matrix")).type); + EXPECT_EQ(1, path_properties.at(path_from_string("matrix")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("matrix")).offset.dimensions[0].size); + EXPECT_EQ(TEST_ARRAY_SIZE * sizeof(std::int64_t), + path_properties.at(path_from_string("matrix")).offset.dimensions[0].stride); + + // 2nd dimension + EXPECT_EQ(path_properties.at(path_from_string("matrix.data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.matrix[0])); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("matrix.data[:]")).type); + EXPECT_EQ(2, + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[0].size); + EXPECT_EQ( + TEST_ARRAY_SIZE * sizeof(std::int64_t), + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[0].stride); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[1].size); + EXPECT_EQ( + sizeof(std::int64_t), + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[1].stride); + + // terminal + EXPECT_EQ(path_properties.at(path_from_string("matrix.data[:].data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.matrix[0][0])); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("matrix.data[:].data[:]")).type); + EXPECT_EQ( + path_properties.at(path_from_string("matrix.data[:].data[:]")).offset.dimensions.size(), + 2); + } +} + +TEST(SubStructView, SubStructViewOfRecursiveWithArray) +{ + ArrayTestSuperStructLayer1 array_test_super_struct_1{}; + ArrayTestSubStructLayer1 array_test_sub_struct_1{}; + + auto paths = extract_paths_of(array_test_sub_struct_1); + auto super_struct_path_properties = extract_path_properties_of_filtered( + array_test_super_struct_1, std::optional>{paths}); + auto sub_struct_path_properties = extract_path_properties_of(array_test_sub_struct_1); + + sub_struct_view_of(array_test_super_struct_1, + super_struct_path_properties, + out{array_test_sub_struct_1}, + sub_struct_path_properties); + + for (std::size_t i = 0; i < TEST_ARRAY_SIZE; ++i) + { + for (std::size_t j = 0; j < TEST_ARRAY_SIZE; ++j) + { + ASSERT_TRUE(array_test_sub_struct_1.arr[i].arr[j] == + &array_test_super_struct_1.arr[i].arr[j]); + ASSERT_TRUE(array_test_sub_struct_1.arr[i].vec[j] == + &array_test_super_struct_1.arr[i].vec[j]); + ASSERT_TRUE(array_test_sub_struct_1.vec[i].arr[j] == + &array_test_super_struct_1.vec[i].arr[j]); + ASSERT_TRUE(array_test_sub_struct_1.vec[i].vec[j] == + &array_test_super_struct_1.vec[i].vec[j]); + ASSERT_TRUE(array_test_sub_struct_1.matrix[i][j] == + &array_test_super_struct_1.matrix[i][j]); + } } }