Skip to content

Commit

Permalink
view: improved entity view (with tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
skypjack committed Jul 2, 2024
1 parent b7090b1 commit e079724
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 35 deletions.
4 changes: 3 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ TODO:
* storage entity: no emplace/insert, rename and add a fast range-push from above
* view: propagate tombostone check request to iterator
* review the short-circuit model now that all storage are freely available
* more test around views and storage entity - ie view ::back
* table: pop back to support swap and pop, single column access, empty type optimization
* checkout tools workflow
* review constrained noexcept-ness (ie sigh)
Expand All @@ -40,3 +39,6 @@ TODO:
* decouple all non-storage classes from component_traits
* group, runtime view: avoid using registry when testing
* improve front for multi-type view
* cleanup common view from tricks to handle single swap-only and in-place, if constexpr branches
* consider returning subrange for swap-only sparse sets or consider using filtered each for in-place storage (smilar to storage entity), cleanup views
* single-type view specialization for in-place
99 changes: 70 additions & 29 deletions src/entt/entity/view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ class basic_common_view {
* @tparam Exclude Types of storage used to filter the view.
*/
template<typename... Get, typename... Exclude>
class basic_view<get_t<Get...>, exclude_t<Exclude...>, std::enable_if_t<(sizeof...(Get) + sizeof...(Exclude) > 1) || (type_list_element_t<0u, type_list<Get...>>::storage_policy != deletion_policy::swap_and_pop)>>
class basic_view<get_t<Get...>, exclude_t<Exclude...>, std::enable_if_t<(sizeof...(Get) + sizeof...(Exclude) > 1) || (type_list_element_t<0u, type_list<Get...>>::storage_policy == deletion_policy::in_place)>>
: public basic_common_view<std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>, sizeof...(Get), sizeof...(Exclude)> {
using base_type = basic_common_view<std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>, sizeof...(Get), sizeof...(Exclude)>;

Expand Down Expand Up @@ -622,15 +622,20 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>, std::enable_if_t<(sizeof.
* @brief Basic storage view implementation.
* @warning For internal use only, backward compatibility not guaranteed.
* @tparam Type Common type among all storage types.
* @tparam Policy Storage policy.
*/
template<typename Type>
class basic_swap_and_pop_view {
template<typename Type, deletion_policy Policy>
class basic_storage_view {
static_assert(Policy != deletion_policy::in_place, "Not supported yet");

protected:
/*! @cond TURN_OFF_DOXYGEN */
basic_swap_and_pop_view() noexcept = default;
basic_storage_view() noexcept = default;

basic_swap_and_pop_view(const Type *value) noexcept
: leading{value} {}
basic_storage_view(const Type *value) noexcept
: leading{value} {
ENTT_ASSERT(leading->policy() == Policy, "Unexpected storage policy");
}
/*! @endcond */

public:
Expand Down Expand Up @@ -658,15 +663,20 @@ class basic_swap_and_pop_view {
* @return Number of entities that have the given element.
*/
[[nodiscard]] size_type size() const noexcept {
return leading ? leading->size() : size_type{};
if constexpr(Policy == deletion_policy::swap_and_pop) {
return leading ? leading->size() : size_type{};
} else {
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
return leading ? leading->free_list() : size_type{};
}
}

/**
* @brief Checks whether a view is empty.
* @return True if the view is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return !leading || leading->empty();
return (size() == 0u);
}

/**
Expand All @@ -677,7 +687,12 @@ class basic_swap_and_pop_view {
* @return An iterator to the first entity of the view.
*/
[[nodiscard]] iterator begin() const noexcept {
return leading ? leading->begin() : iterator{};
if constexpr(Policy == deletion_policy::swap_and_pop) {
return leading ? leading->begin() : iterator{};
} else {
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
return leading ? (leading->end() - leading->free_list()) : iterator{};
}
}

/**
Expand Down Expand Up @@ -706,7 +721,12 @@ class basic_swap_and_pop_view {
* reversed view.
*/
[[nodiscard]] reverse_iterator rend() const noexcept {
return leading ? leading->rend() : reverse_iterator{};
if constexpr(Policy == deletion_policy::swap_and_pop) {
return leading ? leading->rend() : reverse_iterator{};
} else {
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
return leading ? (leading->rbegin() + leading->free_list()) : reverse_iterator{};
}
}

/**
Expand All @@ -715,7 +735,12 @@ class basic_swap_and_pop_view {
* otherwise.
*/
[[nodiscard]] entity_type front() const noexcept {
return empty() ? null : *leading->begin();
if constexpr(Policy == deletion_policy::swap_and_pop) {
return empty() ? null : *leading->begin();
} else {
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
return empty() ? null : *(leading->end() - leading->free_list());
}
}

/**
Expand All @@ -734,7 +759,17 @@ class basic_swap_and_pop_view {
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
return leading ? leading->find(entt) : iterator{};
if(leading) {
if constexpr(Policy == deletion_policy::swap_and_pop) {
return leading->find(entt);
} else {
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
const auto it = leading->find(entt);
return (static_cast<size_type>(it.index()) < leading->free_list()) ? it : iterator{};
}
}

return iterator{};
}

/**
Expand All @@ -751,7 +786,12 @@ class basic_swap_and_pop_view {
* @return True if the view contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
return leading && leading->contains(entt);
if constexpr(Policy == deletion_policy::swap_and_pop) {
return leading && leading->contains(entt);
} else {
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
return leading && leading->contains(entt) && (leading->index(entt) < leading->free_list());
}
}

private:
Expand All @@ -769,9 +809,9 @@ class basic_swap_and_pop_view {
* @tparam Get Type of storage iterated by the view.
*/
template<typename Get>
class basic_view<get_t<Get>, exclude_t<>, std::enable_if_t<Get::storage_policy == deletion_policy::swap_and_pop>>
: public basic_swap_and_pop_view<typename Get::base_type> {
using base_type = basic_swap_and_pop_view<typename Get::base_type>;
class basic_view<get_t<Get>, exclude_t<>, std::enable_if_t<Get::storage_policy != deletion_policy::in_place>>
: public basic_storage_view<typename Get::base_type, Get::storage_policy> {
using base_type = basic_storage_view<typename Get::base_type, Get::storage_policy>;

public:
/*! @brief Common type among all storage types. */
Expand Down Expand Up @@ -908,20 +948,20 @@ class basic_view<get_t<Get>, exclude_t<>, std::enable_if_t<Get::storage_policy =
*/
template<typename Func>
void each(Func func) const {
if(auto *elem = storage(); elem) {
if constexpr(is_applicable_v<Func, decltype(*elem->each().begin())>) {
for(const auto pack: elem->each()) {
std::apply(func, pack);
}
} else if constexpr(std::is_invocable_v<Func, decltype(*elem->begin())>) {
for(auto &&curr: *elem) {
func(curr);
}
} else {
for(size_type pos = elem->size(); pos; --pos) {
func();
if constexpr(is_applicable_v<Func, decltype(*storage()->each().begin())>) {
for(const auto pack: this->each()) {
std::apply(func, pack);
}
} else if constexpr(std::is_invocable_v<Func, decltype(*storage()->begin())>) {
if(size_type len = this->size(); len != 0u) {
for(auto last = storage()->end(), first = last - len; first != last; ++first) {
func(*first);
}
}
} else {
for(size_type pos = this->size(); pos; --pos) {
func();
}
}
}

Expand All @@ -934,7 +974,8 @@ class basic_view<get_t<Get>, exclude_t<>, std::enable_if_t<Get::storage_policy =
*
* @return An iterable object to use to _visit_ the view.
*/
[[nodiscard]] iterable each() const noexcept {
[[nodiscard]] iterable
each() const noexcept {
auto *elem = storage();
return elem ? elem->each() : iterable{};
}
Expand Down
46 changes: 41 additions & 5 deletions test/entt/entity/view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,17 +521,43 @@ TEST(SingleStorageView, StorageEntity) {
storage.erase(entity[0u]);
storage.bump(entity[0u]);

ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(view->size(), 2u);

ASSERT_FALSE(view.empty());
ASSERT_FALSE(view->empty());

ASSERT_FALSE(view.contains(entity[0u]));
ASSERT_TRUE(view->contains(entity[0u]));
ASSERT_TRUE(view.contains(entity[1u]));

ASSERT_EQ(view.front(), entity[1u]);
ASSERT_EQ(view.back(), entity[1u]);

ASSERT_EQ(view.size_hint(), 2u);
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(view.begin(), view->begin());
ASSERT_EQ(view.end(), view->end());

ASSERT_EQ(std::distance(view.begin(), view.end()), 1);
ASSERT_EQ(std::distance(view->begin(), view->end()), 2);

ASSERT_EQ(*view.begin(), entity[1u]);
ASSERT_EQ(*view->begin(), entity[0u]);

ASSERT_EQ(++view->begin(), view.begin());
ASSERT_EQ(++view.begin(), view.end());

ASSERT_EQ(view.rbegin(), view->rbegin());
ASSERT_NE(view.rend(), view->rend());

ASSERT_EQ(std::distance(view.rbegin(), view.rend()), 1);
ASSERT_EQ(std::distance(view->rbegin(), view->rend()), 2);

ASSERT_EQ(++view.rbegin(), view.rend());
ASSERT_EQ(++view.rend(), view->rend());

ASSERT_EQ(view.front(), entity[1u]);
ASSERT_EQ(view.back(), entity[1u]);

ASSERT_EQ(view.find(entity[0u]), view.end());
ASSERT_NE(view->find(entity[0u]), view.end());
ASSERT_NE(view.find(entity[1u]), view.end());

for(auto elem: view.each()) {
ASSERT_EQ(std::get<0>(elem), entity[1u]);
Expand All @@ -540,6 +566,16 @@ TEST(SingleStorageView, StorageEntity) {
view.each([&entity](auto entt) {
ASSERT_EQ(entt, entity[1u]);
});

view.each([&]() {
storage.erase(entity[1u]);
});

ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(view->size(), 2u);

ASSERT_TRUE(view.empty());
ASSERT_FALSE(view->empty());
}

TEST(MultiStorageView, Functionalities) {
Expand Down

0 comments on commit e079724

Please sign in to comment.