Browse Source

views: const, non-const and all in between (fix #152)

Michele Caini 7 years ago
parent
commit
c13fe3feb6

+ 57 - 2
docs/entity.md

@@ -25,13 +25,14 @@
     * [Dependency function](#dependency-function)
     * [Dependency function](#dependency-function)
     * [Labels](#labels)
     * [Labels](#labels)
   * [Null entity](#null-entity)
   * [Null entity](#null-entity)
-* [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
+* [Views: pay for what you use](#views-pay-for-what-you-use)
   * [Standard View](#standard-view)
   * [Standard View](#standard-view)
     * [Single component standard view](#single-component-standard-view)
     * [Single component standard view](#single-component-standard-view)
     * [Multi component standard view](#multi-component-standard-view)
     * [Multi component standard view](#multi-component-standard-view)
   * [Persistent View](#persistent-view)
   * [Persistent View](#persistent-view)
   * [Raw View](#raw-view)
   * [Raw View](#raw-view)
   * [Runtime View](#runtime-view)
   * [Runtime View](#runtime-view)
+  * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
   * [Give me everything](#give-me-everything)
   * [Give me everything](#give-me-everything)
 * [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
 * [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
 * [Multithreading](#multithreading)
 * [Multithreading](#multithreading)
@@ -798,7 +799,7 @@ const auto entity = registry.create();
 const bool null = (entity == entt::null);
 const bool null = (entity == entt::null);
 ```
 ```
 
 
-# View: to persist or not to persist?
+# Views: pay for what you use
 
 
 First of all, it is worth answering an obvious question: why views?<br/>
 First of all, it is worth answering an obvious question: why views?<br/>
 Roughly speaking, they are a good tool to enforce single responsibility. A
 Roughly speaking, they are a good tool to enforce single responsibility. A
@@ -1185,6 +1186,60 @@ well suited to plugin systems and mods in general. Where possible, don't use
 runtime views, as their performance are slightly inferior to those of the other
 runtime views, as their performance are slightly inferior to those of the other
 views.
 views.
 
 
+# Types: const, non-const and all in between
+
+The `registry` class offers two overloads for most of the member functions used
+to construct views: a const one and a non-const one. The former accepts both
+const and non-const types as template parameters, the latter accepts only const
+types instead.<br/>
+It means that views can be constructed also from a const registry and they
+require to propagate the constness of the registry to the types used to
+construct the views themselves:
+
+```cpp
+entt::view<const position, const velocity> view = std::as_const(registry).view<const position, const velocity>();
+```
+
+Consider the following definition for a non-const view instead:
+
+```cpp
+entt::view<position, const velocity> view = registry.view<position, const velocity>();
+```
+
+In the example above, `view` can be used to access either read-only or writable
+`position` components while `velocity` components are read-only in all
+cases.<br/>
+In other terms, these statements are all valid:
+
+```cpp
+position &pos = view.get<position>(entity);
+const position &cpos = view.get<const position>(entity);
+const velocity &cpos = view.get<const velocity>(entity);
+std::tuple<position &, const velocity &> tup = view.get<position, const velocity>(entity);
+std::tuple<const position &, const velocity &> ctup = view.get<const position, const velocity>(entity);
+```
+
+It's not possible to get non-const references to `velocity` components from the
+same view instead and these will result in compilation errors:
+
+```cpp
+velocity &cpos = view.get<velocity>(entity);
+std::tuple<position &, velocity &> tup = view.get<position, velocity>(entity);
+std::tuple<const position &, velocity &> ctup = view.get<const position, velocity>(entity);
+```
+
+Similarly, the `each` member functions will propagate constness to the type of
+the components returned during iterations:
+
+```cpp
+view.each([](const auto entity, position &pos, const velocity &vel) {
+    // ...
+});
+```
+
+Obviously, a caller can still refer to the `position` components through a const
+reference because of the rules of the language that fortunately already allow
+it.
 
 
 ## Give me everything
 ## Give me everything
 
 

+ 2 - 2
src/entt/entity/prototype.hpp

@@ -186,7 +186,7 @@ public:
         if constexpr(sizeof...(Component) == 1) {
         if constexpr(sizeof...(Component) == 1) {
             return (std::as_const(*reg).template get<component_wrapper<Component...>>(entity).component);
             return (std::as_const(*reg).template get<component_wrapper<Component...>>(entity).component);
         } else {
         } else {
-            return std::tuple<const Component &...>{get<Component>()...};
+            return std::tuple<std::add_const_t<Component> &...>{get<Component>()...};
         }
         }
     }
     }
 
 
@@ -222,7 +222,7 @@ public:
             const auto *wrapper = reg->template try_get<component_wrapper<Component...>>(entity);
             const auto *wrapper = reg->template try_get<component_wrapper<Component...>>(entity);
             return wrapper ? &wrapper->component : nullptr;
             return wrapper ? &wrapper->component : nullptr;
         } else {
         } else {
-            return std::tuple<const Component *...>{try_get<Component>()...};
+            return std::tuple<std::add_const_t<Component> *...>{try_get<Component>()...};
         }
         }
     }
     }
 
 

+ 145 - 33
src/entt/entity/registry.hpp

@@ -110,42 +110,37 @@ class registry {
     }
     }
 
 
     template<typename Component>
     template<typename Component>
-    inline const component_pool<Component> & pool() const ENTT_NOEXCEPT {
+    inline auto & pool() const ENTT_NOEXCEPT {
         assert(managed<Component>());
         assert(managed<Component>());
-        return static_cast<const component_pool<Component> &>(*pools[component_family::type<Component>]);
-    }
-
-    template<typename Component>
-    inline component_pool<Component> & pool() ENTT_NOEXCEPT {
-        return const_cast<component_pool<Component> &>(std::as_const(*this).template pool<Component>());
+        return static_cast<component_pool<std::decay_t<Component>> &>(*pools[component_family::type<Component>]);
     }
     }
 
 
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
-    void connect(std::index_sequence<Indexes...>) {
+    void connect(std::index_sequence<Indexes...>) const {
         pool<Comp>().construction().template connect<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().construction().template connect<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().destruction().template connect<&registry::destroying<Comp, Index, Component...>>();
         pool<Comp>().destruction().template connect<&registry::destroying<Comp, Index, Component...>>();
     }
     }
 
 
     template<typename... Component, std::size_t... Indexes>
     template<typename... Component, std::size_t... Indexes>
-    void connect(std::index_sequence<Indexes...>) {
+    void connect(std::index_sequence<Indexes...>) const {
         (assure<Component>(), ...);
         (assure<Component>(), ...);
         (connect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
         (connect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
     }
     }
 
 
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
-    void disconnect(std::index_sequence<Indexes...>) {
+    void disconnect(std::index_sequence<Indexes...>) const {
         pool<Comp>().construction().template disconnect<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().construction().template disconnect<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().destruction().template disconnect<&registry::destroying<Comp, Index, Component...>>();
         pool<Comp>().destruction().template disconnect<&registry::destroying<Comp, Index, Component...>>();
     }
     }
 
 
     template<typename... Component, std::size_t... Indexes>
     template<typename... Component, std::size_t... Indexes>
-    void disconnect(std::index_sequence<Indexes...>) {
+    void disconnect(std::index_sequence<Indexes...>) const {
         // if a set exists, pools have already been created for it
         // if a set exists, pools have already been created for it
         (disconnect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
         (disconnect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
     }
     }
 
 
     template<typename Component>
     template<typename Component>
-    void assure() {
+    void assure() const {
         const auto ctype = component_family::type<Component>;
         const auto ctype = component_family::type<Component>;
 
 
         if(!(ctype < pools.size())) {
         if(!(ctype < pools.size())) {
@@ -153,7 +148,7 @@ class registry {
         }
         }
 
 
         if(!pools[ctype]) {
         if(!pools[ctype]) {
-            pools[ctype] = std::make_unique<component_pool<Component>>(this);
+            pools[ctype] = std::make_unique<component_pool<std::decay_t<Component>>>(const_cast<registry *>(this));
         }
         }
     }
     }
 
 
@@ -305,7 +300,7 @@ public:
      * @return A pointer to the array of components of the given type.
      * @return A pointer to the array of components of the given type.
      */
      */
     template<typename Component>
     template<typename Component>
-    const Component * raw() const ENTT_NOEXCEPT {
+    std::add_const_t<Component> * raw() const ENTT_NOEXCEPT {
         return managed<Component>() ? pool<Component>().raw() : nullptr;
         return managed<Component>() ? pool<Component>().raw() : nullptr;
     }
     }
 
 
@@ -478,6 +473,7 @@ public:
      */
      */
     template<typename It>
     template<typename It>
     void create(It first, It last) {
     void create(It first, It last) {
+        static_assert(std::is_convertible_v<entity_type, typename std::iterator_traits<It>::value_type>);
         const auto length = size_type(last - first);
         const auto length = size_type(last - first);
         const auto sz = std::min(available, length);
         const auto sz = std::min(available, length);
 
 
@@ -646,9 +642,9 @@ public:
         assert((managed<Component>() && ...));
         assert((managed<Component>() && ...));
 
 
         if constexpr(sizeof...(Component) == 1) {
         if constexpr(sizeof...(Component) == 1) {
-            return pool<Component...>().get(entity);
+            return std::as_const(pool<Component...>()).get(entity);
         } else {
         } else {
-            return std::tuple<const Component &...>{get<Component>(entity)...};
+            return std::tuple<std::add_const_t<Component> &...>{get<Component>(entity)...};
         }
         }
     }
     }
 
 
@@ -724,9 +720,9 @@ public:
         assert(valid(entity));
         assert(valid(entity));
 
 
         if constexpr(sizeof...(Component) == 1) {
         if constexpr(sizeof...(Component) == 1) {
-            return managed<Component...>() ? pool<Component...>().try_get(entity) : nullptr;
+            return managed<Component...>() ? std::as_const(pool<Component...>()).try_get(entity) : nullptr;
         } else {
         } else {
-            return std::tuple<const Component *...>{try_get<Component>(entity)...};
+            return std::tuple<std::add_const_t<Component> *...>{try_get<Component>(entity)...};
         }
         }
     }
     }
 
 
@@ -773,7 +769,7 @@ public:
      */
      */
     template<typename Component, typename... Args>
     template<typename Component, typename... Args>
     Component & replace(const entity_type entity, Args &&... args) {
     Component & replace(const entity_type entity, Args &&... args) {
-        return (get<Component>(entity) = Component{std::forward<Args>(args)...});
+        return (pool<Component>().get(entity) = std::decay_t<Component>{std::forward<Args>(args)...});
     }
     }
 
 
     /**
     /**
@@ -808,7 +804,7 @@ public:
         auto &cpool = pool<Component>();
         auto &cpool = pool<Component>();
 
 
         return cpool.has(entity)
         return cpool.has(entity)
-                ? cpool.get(entity) = Component{std::forward<Args>(args)...}
+                ? cpool.get(entity) = std::decay_t<Component>{std::forward<Args>(args)...}
                 : cpool.construct(entity, std::forward<Args>(args)...);
                 : cpool.construct(entity, std::forward<Args>(args)...);
     }
     }
 
 
@@ -1042,6 +1038,8 @@ public:
      */
      */
     template<typename Func>
     template<typename Func>
     void each(Func func) const {
     void each(Func func) const {
+        static_assert(std::is_invocable_v<Func, entity_type>);
+
         if(available) {
         if(available) {
             for(auto pos = entities.size(); pos; --pos) {
             for(auto pos = entities.size(); pos; --pos) {
                 const auto curr = entity_type(pos - 1);
                 const auto curr = entity_type(pos - 1);
@@ -1097,6 +1095,8 @@ public:
      */
      */
     template<typename Func>
     template<typename Func>
     void orphans(Func func) const {
     void orphans(Func func) const {
+        static_assert(std::is_invocable_v<Func, entity_type>);
+
         each([func = std::move(func), this](const auto entity) {
         each([func = std::move(func), this](const auto entity) {
             if(orphan(entity)) {
             if(orphan(entity)) {
                 func(entity);
                 func(entity);
@@ -1144,6 +1144,46 @@ public:
         return { &pool<Component>()... };
         return { &pool<Component>()... };
     }
     }
 
 
+    /**
+     * @brief Returns a standard view for the given components.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Feel free to discard a view after the use. Creating and destroying a view
+     * is an incredibly cheap operation because they do not require any type of
+     * initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Standard views do their best to iterate the smallest set of candidate
+     * entities. In particular:
+     *
+     * * Single component views are incredibly fast and iterate a packed array
+     *   of entities, all of which has the given component.
+     * * Multi component views look at the number of entities available for each
+     *   component and pick up a reference to the smallest set of candidates to
+     *   test for the given components.
+     *
+     * @note
+     * Multi component views are pretty fast. However their performance tend to
+     * degenerate when the number of components to iterate grows up and the most
+     * of the entities have all the given components.<br/>
+     * To get a performance boost, consider using a persistent_view instead.
+     *
+     * @sa view
+     * @sa view<Entity, Component>
+     * @sa persistent_view
+     * @sa raw_view
+     * @sa runtime_view
+     *
+     * @tparam Component Type of components used to construct the view.
+     * @return A newly created standard view.
+     */
+    template<typename... Component>
+    inline entt::view<Entity, Component...> view() const {
+        static_assert(std::conjunction_v<std::is_const<Component>...>);
+        return const_cast<registry *>(this)->view<Component...>();
+    }
+
     /**
     /**
      * @brief Returns a persistent view for the given components.
      * @brief Returns a persistent view for the given components.
      *
      *
@@ -1188,24 +1228,67 @@ public:
         static_assert(sizeof...(Component) > 1);
         static_assert(sizeof...(Component) > 1);
         const auto htype = handler_family::type<Component...>;
         const auto htype = handler_family::type<Component...>;
 
 
-        if(!(htype < handlers.size() && handlers[htype])) {
-            if(!(htype < handlers.size())) {
-                handlers.resize(htype + 1);
-            }
+        if(!(htype < handlers.size())) {
+            handlers.resize(htype + 1);
+        }
 
 
-            if(!handlers[htype]) {
-                (assure<Component>(), ...);
-                connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
-                handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
-            }
+        if(!handlers[htype]) {
+            (assure<Component>(), ...);
+            connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
+            handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
         }
         }
 
 
         return {
         return {
-            static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get()),
+            static_cast<handler_type<sizeof...(Component)> *>(handlers[handler_family::type<Component...>].get()),
             &pool<Component>()...
             &pool<Component>()...
         };
         };
     }
     }
 
 
+    /**
+     * @brief Returns a persistent view for the given components.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Feel free to discard a view after the use. Creating and destroying a view
+     * is an incredibly cheap operation because they do not require any type of
+     * initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Persistent views are the right choice to iterate entities when the number
+     * of components grows up and the most of the entities have all the given
+     * components.<br/>
+     * However they have also drawbacks:
+     *
+     * * Each kind of persistent view requires a dedicated data structure that
+     *   is allocated within the registry and it increases memory pressure.
+     * * Internal data structures used to construct persistent views must be
+     *   kept updated and it affects slightly construction and destruction of
+     *   entities and components.
+     *
+     * That being said, persistent views are an incredibly powerful tool if used
+     * with care and offer a boost of performance undoubtedly.
+     *
+     * @note
+     * Consider to use the `prepare` member function to initialize the internal
+     * data structures used by persistent views when the registry is still
+     * empty. Initialization could be a costly operation otherwise and it will
+     * be performed the very first time each view is created.
+     *
+     * @sa view
+     * @sa view<Entity, Component>
+     * @sa persistent_view
+     * @sa raw_view
+     * @sa runtime_view
+     *
+     * @tparam Component Types of components used to construct the view.
+     * @return A newly created persistent view.
+     */
+    template<typename... Component>
+    inline entt::persistent_view<Entity, Component...> persistent_view() const {
+        static_assert(std::conjunction_v<std::is_const<Component>...>);
+        return const_cast<registry *>(this)->persistent_view<Component...>();
+    }
+
     /**
     /**
      * @brief Returns a raw view for the given component.
      * @brief Returns a raw view for the given component.
      *
      *
@@ -1235,6 +1318,35 @@ public:
         return { &pool<Component>() };
         return { &pool<Component>() };
     }
     }
 
 
+    /**
+     * @brief Returns a raw view for the given component.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Feel free to discard a view after the use. Creating and destroying a view
+     * is an incredibly cheap operation because they do not require any type of
+     * initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Raw views are incredibly fast and must be considered the best tool to
+     * iterate components whenever knowing the entities to which they belong
+     * isn't required.
+     *
+     * @sa view
+     * @sa view<Entity, Component>
+     * @sa persistent_view
+     * @sa raw_view
+     * @sa runtime_view
+     *
+     * @tparam Component Type of component used to construct the view.
+     * @return A newly created raw view.
+     */
+    template<typename Component>
+    inline entt::raw_view<Entity, Component> raw_view() const {
+        static_assert(std::is_const_v<Component>);
+        return const_cast<registry *>(this)->raw_view<Component>();
+    }
+
     /**
     /**
      * @brief Returns a runtime view for the given components.
      * @brief Returns a runtime view for the given components.
      *
      *
@@ -1262,7 +1374,7 @@ public:
      * @return A newly created runtime view.
      * @return A newly created runtime view.
      */
      */
     template<typename It>
     template<typename It>
-    entt::runtime_view<Entity> runtime_view(It first, It last) {
+    entt::runtime_view<Entity> runtime_view(It first, It last) const {
         static_assert(std::is_convertible_v<typename std::iterator_traits<It>::value_type, component_type>);
         static_assert(std::is_convertible_v<typename std::iterator_traits<It>::value_type, component_type>);
         std::vector<const sparse_set<Entity> *> set(last - first);
         std::vector<const sparse_set<Entity> *> set(last - first);
 
 
@@ -1340,8 +1452,8 @@ public:
     }
     }
 
 
 private:
 private:
-    std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
-    std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
+    mutable std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
+    mutable std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
     std::vector<entity_type> entities;
     std::vector<entity_type> entities;
     size_type available{};
     size_type available{};
     entity_type next{};
     entity_type next{};

File diff suppressed because it is too large
+ 57 - 627
src/entt/entity/view.hpp


+ 1 - 1
test/entt/entity/snapshot.cpp

@@ -225,7 +225,7 @@ TEST(Snapshot, Iterator) {
     const auto view = registry.view<a_component>();
     const auto view = registry.view<a_component>();
     const auto size = view.size();
     const auto size = view.size();
 
 
-    registry.snapshot().component<another_component>(output, view.cbegin(), view.cend());
+    registry.snapshot().component<another_component>(output, view.begin(), view.end());
     registry.reset();
     registry.reset();
     registry.loader().component<another_component>(input);
     registry.loader().component<another_component>(input);
 
 

+ 109 - 79
test/entt/entity/view.cpp

@@ -7,7 +7,7 @@
 TEST(PersistentView, Functionalities) {
 TEST(PersistentView, Functionalities) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.persistent_view<int, char>();
     auto view = registry.persistent_view<int, char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).persistent_view<const int, const char>();
 
 
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 
 
@@ -19,8 +19,8 @@ TEST(PersistentView, Functionalities) {
     registry.assign<char>(e1);
     registry.assign<char>(e1);
 
 
     ASSERT_FALSE(view.empty());
     ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW((registry.persistent_view<int, char>().begin()++));
-    ASSERT_NO_THROW((++registry.persistent_view<int, char>().begin()));
+    ASSERT_NO_THROW((view.begin()++));
+    ASSERT_NO_THROW((++cview.begin()));
 
 
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(cview.begin(), cview.end());
     ASSERT_NE(cview.begin(), cview.end());
@@ -39,10 +39,9 @@ TEST(PersistentView, Functionalities) {
     registry.get<int>(e1) = 42;
     registry.get<int>(e1) = 42;
 
 
     for(auto entity: view) {
     for(auto entity: view) {
-        const auto &cview = static_cast<const decltype(view) &>(view);
-        ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
+        ASSERT_EQ(std::get<0>(cview.get<const int, const char>(entity)), 42);
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
-        ASSERT_EQ(cview.get<char>(entity), '2');
+        ASSERT_EQ(cview.get<const char>(entity), '2');
     }
     }
 
 
     ASSERT_EQ(*(view.data() + 0), e1);
     ASSERT_EQ(*(view.data() + 0), e1);
@@ -51,14 +50,14 @@ TEST(PersistentView, Functionalities) {
     registry.remove<char>(e1);
     registry.remove<char>(e1);
 
 
     ASSERT_EQ(view.begin(), view.end());
     ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
+    ASSERT_EQ(cview.begin(), cview.end());
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 }
 }
 
 
 TEST(PersistentView, ElementAccess) {
 TEST(PersistentView, ElementAccess) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.persistent_view<int, char>();
     auto view = registry.persistent_view<int, char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).persistent_view<const int, const char>();
 
 
     const auto e0 = registry.create();
     const auto e0 = registry.create();
     registry.assign<int>(e0);
     registry.assign<int>(e0);
@@ -127,7 +126,7 @@ TEST(PersistentView, Each) {
     registry.assign<int>(e1);
     registry.assign<int>(e1);
     registry.assign<char>(e1);
     registry.assign<char>(e1);
 
 
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    auto cview = std::as_const(registry).persistent_view<const int, const char>();
     std::size_t cnt = 0;
     std::size_t cnt = 0;
 
 
     view.each([&cnt](auto, int &, char &) { ++cnt; });
     view.each([&cnt](auto, int &, char &) { ++cnt; });
@@ -141,7 +140,7 @@ TEST(PersistentView, Each) {
 
 
 TEST(PersistentView, Sort) {
 TEST(PersistentView, Sort) {
     entt::registry<> registry;
     entt::registry<> registry;
-    auto view = registry.persistent_view<int, unsigned int>();
+    auto view = registry.persistent_view<const int, unsigned int>();
 
 
     const auto e0 = registry.create();
     const auto e0 = registry.create();
     const auto e1 = registry.create();
     const auto e1 = registry.create();
@@ -160,7 +159,7 @@ TEST(PersistentView, Sort) {
 
 
     for(auto entity: view) {
     for(auto entity: view) {
         ASSERT_EQ(view.get<unsigned int>(entity), --uval);
         ASSERT_EQ(view.get<unsigned int>(entity), --uval);
-        ASSERT_EQ(view.get<int>(entity), --ival);
+        ASSERT_EQ(view.get<const int>(entity), --ival);
     }
     }
 
 
     registry.sort<unsigned int>(std::less<unsigned int>{});
     registry.sort<unsigned int>(std::less<unsigned int>{});
@@ -168,7 +167,7 @@ TEST(PersistentView, Sort) {
 
 
     for(auto entity: view) {
     for(auto entity: view) {
         ASSERT_EQ(view.get<unsigned int>(entity), uval++);
         ASSERT_EQ(view.get<unsigned int>(entity), uval++);
-        ASSERT_EQ(view.get<int>(entity), ival++);
+        ASSERT_EQ(view.get<const int>(entity), ival++);
     }
     }
 }
 }
 
 
@@ -183,7 +182,7 @@ TEST(PersistentView, Initialize) {
 
 
     registry.assign<int>(registry.create());
     registry.assign<int>(registry.create());
 
 
-    auto view = registry.persistent_view<int, char>();
+    auto view = std::as_const(registry).persistent_view<const int, const char>();
 
 
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
@@ -322,10 +321,26 @@ TEST(PersistentView, IndexRebuiltOnDestroy) {
     });
     });
 }
 }
 
 
+TEST(PersistentView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.persistent_view<int, const char>();
+
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int, const char>(0)), std::tuple<int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+
+    view.each([](auto, auto &&i, auto &&c) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
+    });
+}
+
 TEST(SingleComponentView, Functionalities) {
 TEST(SingleComponentView, Functionalities) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.view<char>();
     auto view = registry.view<char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).view<const char>();
 
 
     const auto e0 = registry.create();
     const auto e0 = registry.create();
     const auto e1 = registry.create();
     const auto e1 = registry.create();
@@ -351,7 +366,6 @@ TEST(SingleComponentView, Functionalities) {
     view.get(e1) = '2';
     view.get(e1) = '2';
 
 
     for(auto entity: view) {
     for(auto entity: view) {
-        const auto &cview = static_cast<const decltype(view) &>(view);
         ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
         ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
     }
     }
 
 
@@ -365,14 +379,13 @@ TEST(SingleComponentView, Functionalities) {
     registry.remove<char>(e1);
     registry.remove<char>(e1);
 
 
     ASSERT_EQ(view.begin(), view.end());
     ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 }
 }
 
 
 TEST(SingleComponentView, ElementAccess) {
 TEST(SingleComponentView, ElementAccess) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.view<int>();
     auto view = registry.view<int>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).view<const int>();
 
 
     const auto e0 = registry.create();
     const auto e0 = registry.create();
     registry.assign<int>(e0);
     registry.assign<int>(e0);
@@ -442,10 +455,32 @@ TEST(SingleComponentView, Each) {
     ASSERT_EQ(cnt, std::size_t{0});
     ASSERT_EQ(cnt, std::size_t{0});
 }
 }
 
 
+TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.view<int>();
+    auto cview = registry.view<const int>();
+
+    ASSERT_TRUE((std::is_same_v<typename decltype(view)::raw_type, int>));
+    ASSERT_TRUE((std::is_same_v<typename decltype(cview)::raw_type, const int>));
+
+    ASSERT_TRUE((std::is_same_v<decltype(view.get(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.raw()), int *>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview.get(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview.raw()), const int *>));
+
+    view.each([](auto, auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+    });
+
+    cview.each([](auto, auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), const int &>));
+    });
+}
+
 TEST(MultipleComponentView, Functionalities) {
 TEST(MultipleComponentView, Functionalities) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.view<int, char>();
     auto view = registry.view<int, char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).view<const int, const char>();
 
 
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 
 
@@ -476,10 +511,9 @@ TEST(MultipleComponentView, Functionalities) {
     registry.get<int>(e1) = 42;
     registry.get<int>(e1) = 42;
 
 
     for(auto entity: view) {
     for(auto entity: view) {
-        const auto &cview = static_cast<const decltype(view) &>(view);
-        ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
+        ASSERT_EQ(std::get<0>(cview.get<const int, const char>(entity)), 42);
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
-        ASSERT_EQ(cview.get<char>(entity), '2');
+        ASSERT_EQ(cview.get<const char>(entity), '2');
     }
     }
 }
 }
 
 
@@ -505,28 +539,6 @@ TEST(MultipleComponentView, Iterator) {
     ASSERT_EQ(++view.begin(), view.end());
     ASSERT_EQ(++view.begin(), view.end());
 }
 }
 
 
-TEST(MultipleComponentView, ConstIterator) {
-    entt::registry<> registry;
-    const auto entity = registry.create();
-    registry.assign<int>(entity);
-    registry.assign<char>(entity);
-
-    const auto view = registry.view<int, char>();
-    using iterator_type = typename decltype(view)::iterator_type;
-
-    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(MultipleComponentView, Contains) {
 TEST(MultipleComponentView, Contains) {
     entt::registry<> registry;
     entt::registry<> registry;
 
 
@@ -578,7 +590,7 @@ TEST(MultipleComponentView, Each) {
     registry.assign<char>(e1);
     registry.assign<char>(e1);
 
 
     auto view = registry.view<int, char>();
     auto view = registry.view<int, char>();
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    auto cview = std::as_const(registry).view<const int, const char>();
     std::size_t cnt = 0;
     std::size_t cnt = 0;
 
 
     view.each([&cnt](auto, int &, char &) { ++cnt; });
     view.each([&cnt](auto, int &, char &) { ++cnt; });
@@ -615,10 +627,26 @@ TEST(MultipleComponentView, EachWithHoles) {
     });
     });
 }
 }
 
 
+TEST(MultipleComponentView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.view<int, const char>();
+
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int, const char>(0)), std::tuple<int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+
+    view.each([](auto, auto &&i, auto &&c) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
+    });
+}
+
 TEST(RawView, Functionalities) {
 TEST(RawView, Functionalities) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.raw_view<char>();
     auto view = registry.raw_view<char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).raw_view<const char>();
 
 
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 
 
@@ -629,8 +657,8 @@ TEST(RawView, Functionalities) {
     registry.assign<char>(e1);
     registry.assign<char>(e1);
 
 
     ASSERT_FALSE(view.empty());
     ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW(registry.raw_view<char>().begin()++);
-    ASSERT_NO_THROW(++registry.raw_view<char>().begin());
+    ASSERT_NO_THROW(view.begin()++);
+    ASSERT_NO_THROW(++cview.begin());
 
 
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(cview.begin(), cview.end());
     ASSERT_NE(cview.begin(), cview.end());
@@ -651,14 +679,14 @@ TEST(RawView, Functionalities) {
     ASSERT_EQ(*(view.data() + 1), e0);
     ASSERT_EQ(*(view.data() + 1), e0);
 
 
     ASSERT_EQ(*(view.raw() + 0), '2');
     ASSERT_EQ(*(view.raw() + 0), '2');
-    ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
+    ASSERT_EQ(*(cview.raw() + 1), '1');
 
 
     for(auto &&component: view) {
     for(auto &&component: view) {
         // verifies that iterators return references to components
         // verifies that iterators return references to components
         component = '0';
         component = '0';
     }
     }
 
 
-    for(auto &&component: view) {
+    for(auto &&component: cview) {
         ASSERT_TRUE(component == '0');
         ASSERT_TRUE(component == '0');
     }
     }
 
 
@@ -666,14 +694,13 @@ TEST(RawView, Functionalities) {
     registry.remove<char>(e1);
     registry.remove<char>(e1);
 
 
     ASSERT_EQ(view.begin(), view.end());
     ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 }
 }
 
 
 TEST(RawView, ElementAccess) {
 TEST(RawView, ElementAccess) {
     entt::registry<> registry;
     entt::registry<> registry;
     auto view = registry.raw_view<int>();
     auto view = registry.raw_view<int>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).raw_view<const int>();
 
 
     const auto e0 = registry.create();
     const auto e0 = registry.create();
     registry.assign<int>(e0, 42);
     registry.assign<int>(e0, 42);
@@ -714,7 +741,7 @@ TEST(RawView, Each) {
     registry.assign<int>(registry.create(), 3);
     registry.assign<int>(registry.create(), 3);
 
 
     auto view = registry.raw_view<int>();
     auto view = registry.raw_view<int>();
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    auto cview = std::as_const(registry).raw_view<const int>();
     std::size_t cnt = 0;
     std::size_t cnt = 0;
 
 
     view.each([&cnt](int &v) { cnt += (v % 2); });
     view.each([&cnt](int &v) { cnt += (v % 2); });
@@ -726,6 +753,36 @@ TEST(RawView, Each) {
     ASSERT_EQ(cnt, std::size_t{0});
     ASSERT_EQ(cnt, std::size_t{0});
 }
 }
 
 
+TEST(RawView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.raw_view<int>();
+    auto cview = registry.raw_view<const int>();
+
+    ASSERT_TRUE((std::is_same_v<typename decltype(view)::raw_type, int>));
+    ASSERT_TRUE((std::is_same_v<typename decltype(cview)::raw_type, const int>));
+
+    ASSERT_TRUE((std::is_same_v<decltype(view[0]), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.raw()), int *>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview[0]), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview.raw()), const int *>));
+
+    view.each([](auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+    });
+
+    cview.each([](auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), const int &>));
+    });
+
+    for(auto &&i: view) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+    }
+
+    for(auto &&i: cview) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), const int &>));
+    }
+}
+
 TEST(RuntimeView, Functionalities) {
 TEST(RuntimeView, Functionalities) {
     entt::registry<> registry;
     entt::registry<> registry;
     using component_type = typename decltype(registry)::component_type;
     using component_type = typename decltype(registry)::component_type;
@@ -736,7 +793,6 @@ TEST(RuntimeView, Functionalities) {
 
 
     component_type types[] = { registry.type<int>(), registry.type<char>() };
     component_type types[] = { registry.type<int>(), registry.type<char>() };
     auto view = registry.runtime_view(std::begin(types), std::end(types));
     auto view = registry.runtime_view(std::begin(types), std::end(types));
-    const auto &cview = view;
 
 
     ASSERT_TRUE(view.empty());
     ASSERT_TRUE(view.empty());
 
 
@@ -759,7 +815,6 @@ TEST(RuntimeView, Functionalities) {
     ASSERT_NO_THROW((++registry.runtime_view(std::begin(types), std::end(types)).begin()));
     ASSERT_NO_THROW((++registry.runtime_view(std::begin(types), std::end(types)).begin()));
 
 
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(view.begin(), view.end());
-    ASSERT_NE(cview.begin(), cview.end());
     ASSERT_EQ(view.size(), decltype(view.size()){1});
     ASSERT_EQ(view.size(), decltype(view.size()){1});
 
 
     registry.get<char>(e0) = '1';
     registry.get<char>(e0) = '1';
@@ -797,31 +852,6 @@ TEST(RuntimeView, Iterator) {
     ASSERT_EQ(++view.begin(), view.end());
     ASSERT_EQ(++view.begin(), view.end());
 }
 }
 
 
-TEST(RuntimeView, ConstIterator) {
-    entt::registry<> registry;
-    using component_type = typename decltype(registry)::component_type;
-
-    const auto entity = registry.create();
-    registry.assign<int>(entity);
-    registry.assign<char>(entity);
-
-    component_type types[] = { registry.type<int>(), registry.type<char>() };
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
-    using iterator_type = typename decltype(view)::iterator_type;
-
-    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(RuntimeView, Contains) {
 TEST(RuntimeView, Contains) {
     entt::registry<> registry;
     entt::registry<> registry;
     using component_type = typename decltype(registry)::component_type;
     using component_type = typename decltype(registry)::component_type;

Some files were not shown because too many files changed in this diff