Browse Source

review: iterators (sparse set/view)

Michele Caini 7 years ago
parent
commit
dc4e5ddc3c
6 changed files with 442 additions and 209 deletions
  1. 3 0
      TODO
  2. 11 5
      src/entt/entity/entt_traits.hpp
  3. 148 24
      src/entt/entity/sparse_set.hpp
  4. 45 31
      src/entt/entity/view.hpp
  5. 180 44
      test/entt/entity/sparse_set.cpp
  6. 55 105
      test/entt/entity/view.cpp

+ 3 - 0
TODO

@@ -10,4 +10,7 @@
 * improve CMake interface, see mail from Malte
 * is registry/utility.hpp really required?
 * "singleton mode" for tags (see #66)
+* add a shortcut to destroy all the entities that have components X, Y, Z (view and registry?)
+* add operator[] to views if possible
+* C++17. That's all.
 * AOB

+ 11 - 5
src/entt/entity/entt_traits.hpp

@@ -32,6 +32,8 @@ struct entt_traits<std::uint16_t> {
     using entity_type = std::uint16_t;
     /*! @brief Underlying version type. */
     using version_type = std::uint8_t;
+    /*! @brief Difference type. */
+    using difference_type = std::int32_t;
 
     /*! @brief Mask to use to get the entity number out of an identifier. */
     static constexpr auto entity_mask = 0xFFF;
@@ -56,6 +58,8 @@ struct entt_traits<std::uint32_t> {
     using entity_type = std::uint32_t;
     /*! @brief Underlying version type. */
     using version_type = std::uint16_t;
+    /*! @brief Difference type. */
+    using difference_type = std::int64_t;
 
     /*! @brief Mask to use to get the entity number out of an identifier. */
     static constexpr auto entity_mask = 0xFFFFF;
@@ -71,8 +75,8 @@ struct entt_traits<std::uint32_t> {
  *
  * A 64 bits entity identifier guarantees:
  *
- * * 40 bits for the entity number (an indecently large number).
- * * 24 bit for the version (an indecently large number).
+ * * 32 bits for the entity number (an indecently large number).
+ * * 32 bit for the version (an indecently large number).
  */
 template<>
 struct entt_traits<std::uint64_t> {
@@ -80,13 +84,15 @@ struct entt_traits<std::uint64_t> {
     using entity_type = std::uint64_t;
     /*! @brief Underlying version type. */
     using version_type = std::uint32_t;
+    /*! @brief Difference type. */
+    using difference_type = std::int64_t;
 
     /*! @brief Mask to use to get the entity number out of an identifier. */
-    static constexpr auto entity_mask = 0xFFFFFFFFFF;
+    static constexpr auto entity_mask = 0xFFFFFFFF;
     /*! @brief Mask to use to get the version out of an identifier. */
-    static constexpr auto version_mask = 0xFFFFFF;
+    static constexpr auto version_mask = 0xFFFFFFFF;
     /*! @brief Extent of the entity number within an identifier. */
-    static constexpr auto entity_shift = 40;
+    static constexpr auto entity_shift = 32;
 };
 
 

+ 148 - 24
src/entt/entity/sparse_set.hpp

@@ -59,17 +59,34 @@ template<typename Entity>
 class SparseSet<Entity> {
     using traits_type = entt_traits<Entity>;
 
-    struct Iterator final {
-        using difference_type = std::size_t;
-        using value_type = Entity;
-        using pointer = const value_type *;
-        using reference = value_type;
-        using iterator_category = std::input_iterator_tag;
-
-        Iterator(pointer direct, std::size_t pos)
+    class Iterator final {
+        friend class SparseSet<Entity>;
+
+        using entity_type = Entity;
+        using pos_type = typename traits_type::difference_type;
+
+        Iterator(const entity_type *direct, pos_type pos) ENTT_NOEXCEPT
             : direct{direct}, pos{pos}
         {}
 
+    public:
+        using difference_type = pos_type;
+        using value_type = const entity_type;
+        using pointer = value_type *;
+        using reference = value_type &;
+        using iterator_category = std::random_access_iterator_tag;
+
+        Iterator() ENTT_NOEXCEPT = default;
+
+        Iterator(const Iterator &) ENTT_NOEXCEPT = default;
+        Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
+
+        friend void swap(Iterator &lhs, Iterator &rhs) ENTT_NOEXCEPT {
+            using std::swap;
+            swap(lhs.direct, rhs.direct);
+            swap(lhs.pos, rhs.pos);
+        }
+
         Iterator & operator++() ENTT_NOEXCEPT {
             return --pos, *this;
         }
@@ -79,6 +96,15 @@ class SparseSet<Entity> {
             return ++(*this), orig;
         }
 
+        Iterator & operator--() ENTT_NOEXCEPT {
+            return ++pos, *this;
+        }
+
+        Iterator operator--(int) ENTT_NOEXCEPT {
+            Iterator orig = *this;
+            return --(*this), orig;
+        }
+
         Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
             pos -= value;
             return *this;
@@ -88,6 +114,22 @@ class SparseSet<Entity> {
             return Iterator{direct, pos-value};
         }
 
+        inline Iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
+            return (*this += -value);
+        }
+
+        inline Iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+            return (*this + -value);
+        }
+
+        difference_type operator-(const Iterator &other) const ENTT_NOEXCEPT {
+            return other.pos - pos;
+        }
+
+        reference operator[](const difference_type value) const ENTT_NOEXCEPT {
+            return direct[pos-value-1];
+        }
+
         bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
             return other.pos == pos;
         }
@@ -96,13 +138,33 @@ class SparseSet<Entity> {
             return !(*this == other);
         }
 
-        reference operator*() const ENTT_NOEXCEPT {
-            return direct[pos-1];
+        bool operator<(const Iterator &other) const ENTT_NOEXCEPT {
+            return pos > other.pos;
+        }
+
+        bool operator>(const Iterator &other) const ENTT_NOEXCEPT {
+            return pos < other.pos;
+        }
+
+        inline bool operator<=(const Iterator &other) const ENTT_NOEXCEPT {
+            return !(*this > other);
+        }
+
+        inline bool operator>=(const Iterator &other) const ENTT_NOEXCEPT {
+            return !(*this < other);
+        }
+
+        pointer operator->() const ENTT_NOEXCEPT {
+            return (direct+pos-1);
+        }
+
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return *operator->();
         }
 
     private:
         pointer direct;
-        std::size_t pos;
+        pos_type pos;
     };
 
     static constexpr auto pending = ~typename traits_type::entity_type{};
@@ -215,7 +277,8 @@ public:
      * @return An iterator to the first entity of the internal packed array.
      */
     const_iterator_type cbegin() const ENTT_NOEXCEPT {
-        return const_iterator_type{direct.data(), direct.size()};
+        const typename traits_type::difference_type pos = direct.size();
+        return const_iterator_type{direct.data(), pos};
     }
 
     /**
@@ -500,19 +563,37 @@ private:
 template<typename Entity, typename Type>
 class SparseSet<Entity, Type>: public SparseSet<Entity> {
     using underlying_type = SparseSet<Entity>;
+    using traits_type = entt_traits<Entity>;
 
     template<bool Const>
-    struct Iterator final {
-        using difference_type = std::size_t;
-        using value_type = std::conditional_t<Const, const Type, Type>;
-        using pointer = value_type *;
-        using reference = value_type &;
-        using iterator_category = std::input_iterator_tag;
+    class Iterator final {
+        friend class SparseSet<Entity, Type>;
+
+        using instance_type = std::conditional_t<Const, const Type, Type>;
+        using pos_type = typename traits_type::difference_type;
 
-        Iterator(pointer instances, std::size_t pos)
+        Iterator(instance_type *instances, pos_type pos) ENTT_NOEXCEPT
             : instances{instances}, pos{pos}
         {}
 
+    public:
+        using difference_type = pos_type;
+        using value_type = instance_type;
+        using pointer = value_type *;
+        using reference = value_type &;
+        using iterator_category = std::random_access_iterator_tag;
+
+        Iterator() ENTT_NOEXCEPT = default;
+
+        Iterator(const Iterator &) ENTT_NOEXCEPT = default;
+        Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
+
+        friend void swap(Iterator &lhs, Iterator &rhs) ENTT_NOEXCEPT {
+            using std::swap;
+            swap(lhs.instances, rhs.instances);
+            swap(lhs.pos, rhs.pos);
+        }
+
         Iterator & operator++() ENTT_NOEXCEPT {
             return --pos, *this;
         }
@@ -522,6 +603,15 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
             return ++(*this), orig;
         }
 
+        Iterator & operator--() ENTT_NOEXCEPT {
+            return ++pos, *this;
+        }
+
+        Iterator operator--(int) ENTT_NOEXCEPT {
+            Iterator orig = *this;
+            return --(*this), orig;
+        }
+
         Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
             pos -= value;
             return *this;
@@ -531,6 +621,22 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
             return Iterator{instances, pos-value};
         }
 
+        inline Iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
+            return (*this += -value);
+        }
+
+        inline Iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+            return (*this + -value);
+        }
+
+        difference_type operator-(const Iterator &other) const ENTT_NOEXCEPT {
+            return other.pos - pos;
+        }
+
+        reference operator[](const difference_type value) const ENTT_NOEXCEPT {
+            return instances[pos-value-1];
+        }
+
         bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
             return other.pos == pos;
         }
@@ -539,17 +645,33 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
             return !(*this == other);
         }
 
-        reference operator*() const ENTT_NOEXCEPT {
-            return instances[pos-1];
+        bool operator<(const Iterator &other) const ENTT_NOEXCEPT {
+            return pos > other.pos;
+        }
+
+        bool operator>(const Iterator &other) const ENTT_NOEXCEPT {
+            return pos < other.pos;
+        }
+
+        inline bool operator<=(const Iterator &other) const ENTT_NOEXCEPT {
+            return !(*this > other);
+        }
+
+        inline bool operator>=(const Iterator &other) const ENTT_NOEXCEPT {
+            return !(*this < other);
         }
 
         pointer operator->() const ENTT_NOEXCEPT {
             return (instances+pos-1);
         }
 
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return *operator->();
+        }
+
     private:
         pointer instances;
-        std::size_t pos;
+        difference_type pos;
     };
 
 public:
@@ -643,7 +765,8 @@ public:
      * @return An iterator to the first instance of the given type.
      */
     const_iterator_type cbegin() const ENTT_NOEXCEPT {
-        return const_iterator_type{instances.data(), instances.size()};
+        const typename traits_type::difference_type pos = instances.size();
+        return const_iterator_type{instances.data(), pos};
     }
 
     /**
@@ -675,7 +798,8 @@ public:
      * @return An iterator to the first instance of the given type.
      */
     iterator_type begin() ENTT_NOEXCEPT {
-        return iterator_type{instances.data(), instances.size()};
+        const typename traits_type::difference_type pos = instances.size();
+        return iterator_type{instances.data(), pos};
     }
 
     /**

+ 45 - 31
src/entt/entity/view.hpp

@@ -2,6 +2,7 @@
 #define ENTT_ENTITY_VIEW_HPP
 
 
+#include <iterator>
 #include <cassert>
 #include <array>
 #include <tuple>
@@ -469,8 +470,26 @@ class View final {
     using traits_type = entt_traits<Entity>;
 
     class Iterator {
+        friend class View<Entity, Component...>;
+
         using size_type = typename view_type::size_type;
 
+        Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
+            : unchecked{unchecked},
+              begin{begin},
+              end{end},
+              extent{min(std::make_index_sequence<unchecked.size()>{})}
+        {
+            if(begin != end && !valid()) {
+                ++(*this);
+            }
+        }
+
+        template<std::size_t... Indexes>
+        size_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
+            return std::min({ std::get<Indexes>(unchecked)->extent()... });
+        }
+
         bool valid() const ENTT_NOEXCEPT {
             const auto entity = *begin;
             const auto sz = size_type(entity & traits_type::entity_mask);
@@ -485,17 +504,19 @@ class View final {
         using value_type = typename underlying_iterator_type::value_type;
         using pointer = typename underlying_iterator_type::pointer;
         using reference = typename underlying_iterator_type::reference;
-        using iterator_category = typename underlying_iterator_type::iterator_category;
+        using iterator_category = std::forward_iterator_tag;
 
-        Iterator(unchecked_type unchecked, size_type extent, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
-            : unchecked{unchecked},
-              extent{extent},
-              begin{begin},
-              end{end}
-        {
-            if(begin != end && !valid()) {
-                ++(*this);
-            }
+        Iterator() ENTT_NOEXCEPT = default;
+
+        Iterator(const Iterator &) ENTT_NOEXCEPT = default;
+        Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
+
+        friend void swap(Iterator &lhs, Iterator &rhs) ENTT_NOEXCEPT {
+            using std::swap;
+            swap(lhs.unchecked, rhs.unchecked);
+            swap(lhs.extent, rhs.extent);
+            swap(lhs.begin, rhs.begin);
+            swap(lhs.end, rhs.end);
         }
 
         Iterator & operator++() ENTT_NOEXCEPT {
@@ -507,14 +528,6 @@ class View final {
             return ++(*this), orig;
         }
 
-        Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
-            return ((begin += value) != end && !valid()) ? ++(*this) : *this;
-        }
-
-        Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
-            return Iterator{unchecked, extent, begin+value, end};
-        }
-
         bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
             return other.begin == begin;
         }
@@ -523,15 +536,19 @@ class View final {
             return !(*this == other);
         }
 
-        value_type operator*() const ENTT_NOEXCEPT {
-            return *begin;
+        pointer operator->() const ENTT_NOEXCEPT {
+            return begin.operator->();
+        }
+
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return *operator->();
         }
 
     private:
-        const unchecked_type unchecked;
-        const size_type extent;
+        unchecked_type unchecked;
         underlying_iterator_type begin;
         underlying_iterator_type end;
+        size_type extent;
     };
 
     View(pool_type<Component> &... pools) ENTT_NOEXCEPT
@@ -563,10 +580,6 @@ class View final {
         return other;
     }
 
-    typename view_type::size_type extent() const ENTT_NOEXCEPT {
-        return std::min({ pool<Component>().extent()... });
-    }
-
     template<typename Comp, typename Other>
     inline std::enable_if_t<std::is_same<Comp, Other>::value, const Other &>
     get(const component_iterator_type<Comp> &it, const Entity) const ENTT_NOEXCEPT { return *it; }
@@ -592,15 +605,15 @@ class View final {
             }
         }
 
+        const auto extent = std::min({ pool<Component>().extent()... });
         auto it = std::get<component_iterator_type<Comp>>(raw);
-        const auto ext = extent();
 
         // fallback to visit what remains using indirections
         for(; data[0] != end; ++data[0], ++it) {
             const auto entity = *data[0];
             const auto sz = size_type(entity & traits_type::entity_mask);
 
-            if(sz < ext && std::all_of(other.cbegin(), other.cend(), [entity](const view_type *view) { return view->fast(entity); })) {
+            if(sz < extent && std::all_of(other.cbegin(), other.cend(), [entity](const view_type *view) { return view->fast(entity); })) {
                 // avoided at least the indirection due to the sparse set for the pivot type (see get for more details)
                 func(entity, get<Comp, Component>(it, entity)...);
             }
@@ -649,7 +662,7 @@ public:
      */
     const_iterator_type cbegin() const ENTT_NOEXCEPT {
         const auto *view = candidate();
-        return iterator_type{ unchecked(view), extent(), view->cbegin(), view->cend() };
+        return iterator_type{ unchecked(view), view->cbegin(), view->cend() };
     }
 
     /**
@@ -705,7 +718,7 @@ public:
      */
     const_iterator_type cend() const ENTT_NOEXCEPT {
         const auto *view = candidate();
-        return iterator_type{ unchecked(view), extent(), view->cend(), view->cend() };
+        return iterator_type{ unchecked(view), view->cend(), view->cend() };
     }
 
     /**
@@ -753,7 +766,8 @@ public:
      */
     bool contains(const entity_type entity) const ENTT_NOEXCEPT {
         const auto sz = size_type(entity & traits_type::entity_mask);
-        return sz < extent() && std::min({ (pool<Component>().has(entity) && (pool<Component>().data()[pool<Component>().view_type::get(entity)] == entity))... });
+        const auto extent = std::min({ pool<Component>().extent()... });
+        return sz < extent && std::min({ (pool<Component>().has(entity) && (pool<Component>().data()[pool<Component>().view_type::get(entity)] == entity))... });
     }
 
     /**

+ 180 - 44
test/entt/entity/sparse_set.cpp

@@ -2,6 +2,10 @@
 #include <gtest/gtest.h>
 #include <entt/entity/sparse_set.hpp>
 
+struct Type {
+    int value;
+};
+
 TEST(SparseSetNoType, Functionalities) {
     entt::SparseSet<unsigned int> set;
     const auto &cset = set;
@@ -54,7 +58,97 @@ TEST(SparseSetNoType, Functionalities) {
     other = std::move(set);
 }
 
-TEST(SparseSetNoType, DataBeginEnd) {
+TEST(SparseSetNoType, Iterator) {
+    using iterator_type = typename entt::SparseSet<unsigned int>::iterator_type;
+
+    entt::SparseSet<unsigned int> set;
+    set.construct(3);
+
+    iterator_type end{set.begin()};
+    iterator_type begin{};
+    begin = set.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, set.begin());
+    ASSERT_EQ(end, set.end());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(begin++, set.begin());
+    ASSERT_EQ(begin--, set.end());
+
+    ASSERT_EQ(begin+1, set.end());
+    ASSERT_EQ(end-1, set.begin());
+
+    ASSERT_EQ(++begin, set.end());
+    ASSERT_EQ(--begin, set.begin());
+
+    ASSERT_EQ(begin += 1, set.end());
+    ASSERT_EQ(begin -= 1, set.begin());
+
+    ASSERT_EQ(begin + (end - begin), set.end());
+    ASSERT_EQ(begin - (begin - end), set.end());
+
+    ASSERT_EQ(end - (end - begin), set.begin());
+    ASSERT_EQ(end + (begin - end), set.begin());
+
+    ASSERT_EQ(begin[0], *set.begin());
+
+    ASSERT_LT(begin, end);
+    ASSERT_LE(begin, set.begin());
+
+    ASSERT_GT(end, begin);
+    ASSERT_GE(end, set.end());
+
+    ASSERT_EQ(*begin, 3);
+    ASSERT_EQ(*begin.operator->(), 3);
+}
+
+TEST(SparseSetNoType, ConstIterator) {
+    using iterator_type = typename entt::SparseSet<unsigned int>::const_iterator_type;
+
+    entt::SparseSet<unsigned int> set;
+    set.construct(3);
+
+    iterator_type cend{set.cbegin()};
+    iterator_type cbegin{};
+    cbegin = set.cend();
+    std::swap(cbegin, cend);
+
+    ASSERT_EQ(cbegin, set.cbegin());
+    ASSERT_EQ(cend, set.cend());
+    ASSERT_NE(cbegin, cend);
+
+    ASSERT_EQ(cbegin++, set.cbegin());
+    ASSERT_EQ(cbegin--, set.cend());
+
+    ASSERT_EQ(cbegin+1, set.cend());
+    ASSERT_EQ(cend-1, set.cbegin());
+
+    ASSERT_EQ(++cbegin, set.cend());
+    ASSERT_EQ(--cbegin, set.cbegin());
+
+    ASSERT_EQ(cbegin += 1, set.cend());
+    ASSERT_EQ(cbegin -= 1, set.cbegin());
+
+    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
+    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
+
+    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
+    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
+
+    ASSERT_EQ(cbegin[0], *set.cbegin());
+
+    ASSERT_LT(cbegin, cend);
+    ASSERT_LE(cbegin, set.cbegin());
+
+    ASSERT_GT(cend, cbegin);
+    ASSERT_GE(cend, set.cend());
+
+    ASSERT_EQ(*cbegin, 3);
+    ASSERT_EQ(*cbegin.operator->(), 3);
+}
+
+TEST(SparseSetNoType, Data) {
     entt::SparseSet<unsigned int> set;
 
     set.construct(3);
@@ -68,31 +162,6 @@ TEST(SparseSetNoType, DataBeginEnd) {
     ASSERT_EQ(*(set.data() + 0u), 3u);
     ASSERT_EQ(*(set.data() + 1u), 12u);
     ASSERT_EQ(*(set.data() + 2u), 42u);
-
-    auto it = set.begin();
-
-    ASSERT_EQ(*it, 42u);
-    ASSERT_EQ(*(it+1), 12u);
-    ASSERT_EQ(*(it+2), 3u);
-    ASSERT_EQ(it += 3, set.end());
-
-    auto begin = set.begin();
-    auto end = set.end();
-
-    ASSERT_EQ(*(begin++), 42u);
-    ASSERT_EQ(*(begin++), 12u);
-    ASSERT_EQ(*(begin++), 3u);
-
-    ASSERT_EQ(begin, end);
-
-    auto cbegin = set.cbegin();
-    auto cend = set.cend();
-
-    ASSERT_NE(cbegin, cend);
-    ASSERT_EQ(cbegin+3, cend);
-    ASSERT_NE(cbegin, cend);
-    ASSERT_EQ(cbegin += 3, cend);
-    ASSERT_EQ(cbegin, cend);
 }
 
 TEST(SparseSetNoType, RespectDisjoint) {
@@ -320,7 +389,91 @@ TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
     set.destroy(0);
 }
 
-TEST(SparseSetWithType, RawBeginEnd) {
+TEST(SparseSetWithType, Iterator) {
+    using iterator_type = typename entt::SparseSet<unsigned int, Type>::iterator_type;
+
+    entt::SparseSet<unsigned int, Type> set;
+    set.construct(3, 42);
+
+    iterator_type end{set.begin()};
+    iterator_type begin{};
+    begin = set.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, set.begin());
+    ASSERT_EQ(end, set.end());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(begin++, set.begin());
+    ASSERT_EQ(begin--, set.end());
+
+    ASSERT_EQ(begin+1, set.end());
+    ASSERT_EQ(end-1, set.begin());
+
+    ASSERT_EQ(++begin, set.end());
+    ASSERT_EQ(--begin, set.begin());
+
+    ASSERT_EQ(begin += 1, set.end());
+    ASSERT_EQ(begin -= 1, set.begin());
+
+    ASSERT_EQ(begin + (end - begin), set.end());
+    ASSERT_EQ(begin - (begin - end), set.end());
+
+    ASSERT_EQ(end - (end - begin), set.begin());
+    ASSERT_EQ(end + (begin - end), set.begin());
+
+    ASSERT_EQ(begin[0].value, set.begin()->value);
+
+    ASSERT_LT(begin, end);
+    ASSERT_LE(begin, set.begin());
+
+    ASSERT_GT(end, begin);
+    ASSERT_GE(end, set.end());
+}
+
+TEST(SparseSetWithType, ConstIterator) {
+    using iterator_type = typename entt::SparseSet<unsigned int, Type>::const_iterator_type;
+
+    entt::SparseSet<unsigned int, Type> set;
+    set.construct(3, 42);
+
+    iterator_type cend{set.cbegin()};
+    iterator_type cbegin{};
+    cbegin = set.cend();
+    std::swap(cbegin, cend);
+
+    ASSERT_EQ(cbegin, set.cbegin());
+    ASSERT_EQ(cend, set.cend());
+    ASSERT_NE(cbegin, cend);
+
+    ASSERT_EQ(cbegin++, set.cbegin());
+    ASSERT_EQ(cbegin--, set.cend());
+
+    ASSERT_EQ(cbegin+1, set.cend());
+    ASSERT_EQ(cend-1, set.cbegin());
+
+    ASSERT_EQ(++cbegin, set.cend());
+    ASSERT_EQ(--cbegin, set.cbegin());
+
+    ASSERT_EQ(cbegin += 1, set.cend());
+    ASSERT_EQ(cbegin -= 1, set.cbegin());
+
+    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
+    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
+
+    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
+    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
+
+    ASSERT_EQ(cbegin[0].value, set.cbegin()->value);
+
+    ASSERT_LT(cbegin, cend);
+    ASSERT_LE(cbegin, set.cbegin());
+
+    ASSERT_GT(cend, cbegin);
+    ASSERT_GE(cend, set.cend());
+}
+
+TEST(SparseSetWithType, Raw) {
     entt::SparseSet<unsigned int, int> set;
 
     set.construct(3, 3);
@@ -334,23 +487,6 @@ TEST(SparseSetWithType, RawBeginEnd) {
     ASSERT_EQ(*(set.raw() + 0u), 3);
     ASSERT_EQ(*(set.raw() + 1u), 6);
     ASSERT_EQ(*(set.raw() + 2u), 9);
-
-    auto begin = set.begin();
-    auto end = set.end();
-
-    ASSERT_EQ(*(begin++), 9);
-    ASSERT_EQ(*(begin++), 6);
-    ASSERT_EQ(*(begin++), 3);
-    ASSERT_EQ(begin, end);
-
-    auto cbegin = set.cbegin();
-    auto cend = set.cend();
-
-    ASSERT_NE(cbegin, cend);
-    ASSERT_EQ(cbegin+3, cend);
-    ASSERT_NE(cbegin, cend);
-    ASSERT_EQ(cbegin += 3, cend);
-    ASSERT_EQ(cbegin, cend);
 }
 
 TEST(SparseSetWithType, SortOrdered) {

+ 55 - 105
test/entt/entity/view.cpp

@@ -1,8 +1,9 @@
+#include <utility>
 #include <gtest/gtest.h>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/view.hpp>
 
-TEST(View, SingleComponent) {
+TEST(SingleComponentView, Functionalities) {
     entt::DefaultRegistry registry;
     auto view = registry.view<char>();
 
@@ -43,34 +44,11 @@ TEST(View, SingleComponent) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
 }
 
-TEST(View, SingleComponentBeginEnd) {
-    entt::DefaultRegistry registry;
-    auto view = registry.view<int>();
-    const auto &cview = view;
-
-    for(auto i = 0; i < 3; ++i) {
-        registry.assign<int>(registry.create());
-    }
-
-    auto test = [](auto begin, auto end) {
-        ASSERT_NE(begin, end);
-        ASSERT_NE(++begin, end);
-        ASSERT_NE(begin++, end);
-        ASSERT_EQ(begin+1, end);
-        ASSERT_NE(begin, end);
-        ASSERT_EQ((begin += 1), end);
-        ASSERT_EQ(begin, end);
-    };
-
-    test(view.begin(), view.end());
-    test(cview.begin(), cview.end());
-    test(view.cbegin(), view.cend());
-}
-
-TEST(View, SingleComponentContains) {
+TEST(SingleComponentView, Contains) {
     entt::DefaultRegistry registry;
 
     const auto e0 = registry.create();
@@ -87,7 +65,7 @@ TEST(View, SingleComponentContains) {
     ASSERT_TRUE(view.contains(e1));
 }
 
-TEST(View, SingleComponentEmpty) {
+TEST(SingleComponentView, Empty) {
     entt::DefaultRegistry registry;
 
     const auto e0 = registry.create();
@@ -107,7 +85,7 @@ TEST(View, SingleComponentEmpty) {
     }
 }
 
-TEST(View, SingleComponentEach) {
+TEST(SingleComponentView, Each) {
     entt::DefaultRegistry registry;
 
     registry.assign<int>(registry.create());
@@ -126,7 +104,7 @@ TEST(View, SingleComponentEach) {
     ASSERT_EQ(cnt, std::size_t{0});
 }
 
-TEST(View, MultipleComponent) {
+TEST(MultipleComponentView, Functionalities) {
     entt::DefaultRegistry registry;
     auto view = registry.view<int, char>();
 
@@ -152,7 +130,6 @@ TEST(View, MultipleComponent) {
     ASSERT_NO_THROW((++registry.view<int, char>().begin()));
 
     ASSERT_NE(view.begin(), view.end());
-    ASSERT_EQ(view.begin()+1, view.end());
     ASSERT_EQ(view.size(), decltype(view.size()){1});
 
     registry.get<char>(e0) = '1';
@@ -170,33 +147,53 @@ TEST(View, MultipleComponent) {
     registry.remove<char>(e1);
 }
 
-TEST(View, MultipleComponentBeginEnd) {
+TEST(MultipleComponentView, Iterator) {
+    using iterator_type = typename decltype(std::declval<entt::DefaultRegistry>().view<int, char>())::iterator_type;
+
     entt::DefaultRegistry registry;
-    auto view = registry.view<int, char>();
-    const auto &cview = view;
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
 
-    for(auto i = 0; i < 3; ++i) {
-        const auto entity = registry.create();
-        registry.assign<int>(entity);
-        registry.assign<char>(entity);
-    }
+    const auto view = registry.view<int, char>();
+
+    iterator_type end{view.begin()};
+    iterator_type begin{};
+    begin = view.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, view.begin());
+    ASSERT_EQ(end, view.end());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(view.begin()++, view.begin());
+    ASSERT_EQ(++view.begin(), view.end());
+}
+
+TEST(MultipleComponentView, ConstIterator) {
+    using iterator_type = typename decltype(std::declval<entt::DefaultRegistry>().view<int, char>())::const_iterator_type;
+
+    entt::DefaultRegistry registry;
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
 
-    auto test = [](auto begin, auto end) {
-        ASSERT_NE(begin, end);
-        ASSERT_NE(++begin, end);
-        ASSERT_NE(begin++, end);
-        ASSERT_EQ(begin+1, end);
-        ASSERT_NE(begin, end);
-        ASSERT_EQ((begin += 1), end);
-        ASSERT_EQ(begin, end);
-    };
-
-    test(cview.begin(), cview.end());
-    test(view.begin(), view.end());
-    test(view.cbegin(), view.cend());
+    const auto view = registry.view<int, char>();
+
+    iterator_type cend{view.cbegin()};
+    iterator_type cbegin{};
+    cbegin = view.cend();
+    std::swap(cbegin, cend);
+
+    ASSERT_EQ(cbegin, view.cbegin());
+    ASSERT_EQ(cend, view.cend());
+    ASSERT_NE(cbegin, cend);
+
+    ASSERT_EQ(view.cbegin()++, view.cbegin());
+    ASSERT_EQ(++view.cbegin(), view.cend());
 }
 
-TEST(View, MultipleComponentContains) {
+TEST(MultipleComponentView, Contains) {
     entt::DefaultRegistry registry;
 
     const auto e0 = registry.create();
@@ -215,7 +212,7 @@ TEST(View, MultipleComponentContains) {
     ASSERT_TRUE(view.contains(e1));
 }
 
-TEST(View, MultipleComponentEmpty) {
+TEST(MultipleComponentView, Empty) {
     entt::DefaultRegistry registry;
 
     const auto e0 = registry.create();
@@ -235,7 +232,7 @@ TEST(View, MultipleComponentEmpty) {
     }
 }
 
-TEST(View, MultipleComponentEach) {
+TEST(MultipleComponentView, Each) {
     entt::DefaultRegistry registry;
 
     const auto e0 = registry.create();
@@ -259,7 +256,7 @@ TEST(View, MultipleComponentEach) {
     ASSERT_EQ(cnt, std::size_t{0});
 }
 
-TEST(View, MultipleComponentEachWithHoles) {
+TEST(MultipleComponentView, EachWithHoles) {
     entt::DefaultRegistry registry;
 
     const auto e0 = registry.create();
@@ -330,6 +327,7 @@ TEST(PersistentView, Prepare) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
 }
 
@@ -378,35 +376,10 @@ TEST(PersistentView, NoPrepare) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
 }
 
-TEST(PersistentView, BeginEnd) {
-    entt::DefaultRegistry registry;
-    auto view = registry.view<int, char>(entt::persistent_t{});
-    const auto &cview = view;
-
-    for(auto i = 0; i < 3; ++i) {
-        const auto entity = registry.create();
-        registry.assign<int>(entity);
-        registry.assign<char>(entity);
-    }
-
-    auto test = [](auto begin, auto end) {
-        ASSERT_NE(begin, end);
-        ASSERT_NE(++begin, end);
-        ASSERT_NE(begin++, end);
-        ASSERT_EQ(begin+1, end);
-        ASSERT_NE(begin, end);
-        ASSERT_EQ((begin += 1), end);
-        ASSERT_EQ(begin, end);
-    };
-
-    test(cview.begin(), cview.end());
-    test(view.begin(), view.end());
-    test(view.cbegin(), view.cend());
-}
-
 TEST(PersistentView, Contains) {
     entt::DefaultRegistry registry;
 
@@ -558,33 +531,10 @@ TEST(RawView, Functionalities) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
 }
 
-TEST(RawView, BeginEnd) {
-    entt::DefaultRegistry registry;
-    auto view = registry.view<int>(entt::raw_t{});
-    const auto &cview = view;
-
-    for(auto i = 0; i < 3; ++i) {
-        registry.assign<int>(registry.create());
-    }
-
-    auto test = [](auto begin, auto end) {
-        ASSERT_NE(begin, end);
-        ASSERT_NE(++begin, end);
-        ASSERT_NE(begin++, end);
-        ASSERT_EQ(begin+1, end);
-        ASSERT_NE(begin, end);
-        ASSERT_EQ((begin += 1), end);
-        ASSERT_EQ(begin, end);
-    };
-
-    test(cview.begin(), cview.end());
-    test(view.begin(), view.end());
-    test(view.cbegin(), view.cend());
-}
-
 TEST(RawView, Empty) {
     entt::DefaultRegistry registry;