Michele Caini 7 лет назад
Родитель
Сommit
47e9330646

+ 5 - 6
TODO

@@ -23,9 +23,8 @@
 * allow to replace std:: with custom implementations
 * allow curried function and lambdas on sigh/dispatcher (mixed approach with sinks that accept also delegates?)
 * allow user to use also uint32 for hashed strings type if possible
-
-Ready to go:
-* policy based views
-* preferred approach (hints): registry.view<A, B, C>(exclude<D>, policy<A, B>)?
-* previous model used for persistent views as default view, query as old fashioned views
-* is it enough to store a view as <direct set, length>?
+* allow to sort groups (::respect can already work with begin/end instead of a whole sparse set)
+* views' iterators should return a tuple entity+components, not only entities
+* delegate/sigh "benign" UB: memcpy to work around the fact that types don't pop in existence when the storage is copied (even if all the compilers allow it)
+  see https://stackoverflow.com/questions/54512451/about-aligned-storage-and-trivially-copyable-destructible-types/54518339?noredirect=1
+  see https://stackoverflow.com/questions/54531009/copy-trivially-copyable-types-using-temporary-storage-areas-is-it-allowed

+ 279 - 279
docs/entity.md

@@ -25,18 +25,19 @@
     * [Dependency function](#dependency-function)
     * [Labels](#labels)
   * [Null entity](#null-entity)
-* [Views: pay for what you use](#views-pay-for-what-you-use)
-  * [Standard View](#standard-view)
-    * [Single component standard view](#single-component-standard-view)
-    * [Multi component standard view](#multi-component-standard-view)
-  * [Persistent View](#persistent-view)
-  * [Runtime View](#runtime-view)
+* [Views and Groups](#views-and-groups)
+  * [Views](#views)
+  * [Runtime views](#runtime-views)
+  * [Groups](#groups)
+    * [Full-owning groups](#full-owning-groups)
+    * [Partial-owning groups](#partial-owning-groups)
+    * [Non-owning groups](#non-owning-groups)
   * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
   * [Give me everything](#give-me-everything)
-* [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
+  * [What is allowed and what is not](#what-is-allowed-and-what-is-not)
 * [Empty type optimization](#empty-type-optimization)
 * [Multithreading](#multithreading)
-  * [Views and Iterators](#views-and-iterators)
+  * [Iterators](#iterators)
 <!--
 @endcond TURN_OFF_DOXYGEN
 -->
@@ -73,10 +74,13 @@ what they want.
 
 When it comes to using an entity-component system, the tradeoff is usually
 between performance and memory usage. The faster it is, the more memory it uses.
-However, slightly worse performance along non-critical paths are the right price
-to pay to reduce memory usage and I've always wondered why this kind of tools do
-not leave me the choice.<br/>
-`EnTT` follows a completely different approach. It squeezes the best from the
+Even worse, some approaches tend to heavily affect other functionalities like
+the construction and destruction of components to favor iterations, even when it
+isn't strictly required. In fact, slightly worse performance along non-critical
+paths are the right price to pay to reduce memory usage and have overall better
+perfomance sometimes and I've always wondered why this kind of tools do not
+leave me the choice.<br/>
+`EnTT` follows a completely different approach. It gets the best out from the
 basic data structures and gives users the possibility to pay more for higher
 performance where needed.<br/>
 The disadvantage of this approach is that users need to know the systems they
@@ -88,7 +92,7 @@ many others besides me.
 
 # Vademecum
 
-The registry to store, the views to iterate. That's all.
+The registry to store, the views and the groups to iterate. That's all.
 
 An entity (the _E_ of an _ECS_) is an opaque identifier that users should just
 use as-is and store around if needed. Do not try to inspect an entity
@@ -104,9 +108,9 @@ and move assignable. They are list initialized by using the parameters provided
 to construct the component itself. No need to register components or their types
 neither with the registry nor with the entity-component system at all.<br/>
 Systems (the _S_ of an _ECS_) are just plain functions, functors, lambdas or
-whatever users want. They can accept a `registry` or a view of any type and use
-them the way they prefer. No need to register systems or their types neither
-with the registry nor with the entity-component system at all.
+whatever users want. They can accept a `registry`, a view or a group of any type
+and use them the way they prefer. No need to register systems or their types
+neither with the registry nor with the entity-component system at all.
 
 The following sections will explain in short how to use the entity-component
 system, the core part of the whole library.<br/>
@@ -115,8 +119,8 @@ describe below. For more details, please refer to the inline documentation.
 
 # The Registry, the Entity and the Component
 
-A registry can store and manage entities, as well as create views to iterate the
-underlying data structures.<br/>
+A registry can store and manage entities, as well as create views and groups to
+iterate the underlying data structures.<br/>
 The class template `registry` lets users decide what's the preferred type to
 represent an entity. Because `std::uint32_t` is large enough for almost all the
 cases, `registry` is also an _alias_ for `registry<std::uint32_t>`.
@@ -799,137 +803,99 @@ const auto entity = registry.create();
 const bool null = (entity == entt::null);
 ```
 
-# Views: pay for what you use
+# Views and Groups
 
-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
-system that has access to a registry can create and destroy entities, as well as
-assign and remove components. On the other side, a system that has access to a
-view can only iterate entities and their components, then read or update the
+First of all, it is worth answering an obvious question: why views and
+groups?<br/>
+Briefly, they are a good tool to enforce single responsibility. A system that
+has access to a registry can create and destroy entities, as well as assign and
+remove components. On the other side, a system that has access to a view or a
+group can only iterate entities and their components, then read or update the
 data members of the latter.<br/>
 It is a subtle difference that can help designing a better software sometimes.
 
-There are mainly three kinds of views: standard (also known as `view`),
-persistent (also known as `persistent_view`), and runtime (also known as
-`runtime_view`).<br/>
-All of them have pros and cons to take in consideration. In particular:
-
-* Standard views:
-
-  Pros:
-
-  * They work out-of-the-box and don't require any dedicated data structure.
-  * Creating and destroying them isn't expensive at all because they don't have
-    any type of initialization.
-  * They are the best tool for iterating a single component.
-  * They are the best tool for iterating multiple components when one of them is
-    assigned to a significantly low number of entities.
-  * They don't affect any other operations of the registry.
-
-  Cons:
-
-  * Their performance tend to degenerate when the number of components to
-    iterate grows up and the most of the entities have all of them.
-
-* Persistent views:
-
-  Pros:
-
-  * Once prepared, creating and destroying them isn't expensive at all because
-    they don't have any type of initialization.
-  * They are the best tool for iterating multiple components when most entities
-    have them all.
-  * They are also the only type of views that supports filters without incurring
-    in a loss of performance during iterations.
-
-  Cons:
-
-  * They have dedicated data structures and thus affect the memory usage to a
-    minimal extent.
-  * If not previously initialized, the first time they are used they go through
-    an initialization step that is slightly more expensive.
-  * They affect to a minimum the creation and destruction of entities and
-    components, as well as the sort functionalities. In other terms: the more
-    persistent views there will be, the less performing will be creating and
-    destroying entities and components or sorting a pool.
-
-* Runtime views:
-
-  Pros:
-
-  * Their lists of components are defined at runtime and not at compile-time.
-  * Creating and destroying them isn't expensive at all because they don't have
-    any type of initialization.
-  * They are the best tool for things like plugin systems and mods in general.
-  * They don't affect any other operations of the registry.
-
-  Cons:
-
-  * Their performances are definitely lower than those of all the other views,
-    although they are still usable and sufficient for most of the purposes.
-
-To sum up and as a rule of thumb:
-
-* Use a standard view to iterate entities and components for a single type.
-* Use a standard view to iterate entities and components for multiple types when
-  a significantly low number of entities have one of the components, persistent
-  views won't add much in this case.
-* Use a standard view in all those cases where a persistent view would give a
-  boost to performance but the iteration isn't performed frequently or isn't on
-  a critical path.
-* Use a persistent view when you want to iterate multiple components and each
-  component is assigned to a great number of entities but the intersection
-  between the sets of entities is small.
-* Use a persistent view when you want to set more complex filters that would
-  translate otherwise in a bunch of `if`s within a loop.
-* Use a persistent view in all the cases where a standard view doesn't fit well.
-* Finally, in case you don't know at compile-time what are the components to
-  use, choose a runtime view and set them during execution.
-
-To easily iterate entities and components, all the views offer the common
-`begin` and `end` member functions that allow users to use a view in a typical
-range-for loop. Almost all the views offer also a *more functional* `each`
-member function that accepts a callback for convenience.<br/>
+More in details, views are a non-intrusive tool to access entities and
+components without affecting other functionalities or increasing the memory
+consumption. On the other side, groups are an intrusive tool that allows to
+reach higher performance along critical paths but has also a price to pay for
+that.
+
+There are mainly two kinds of views: _compile-time_ (also known as `view`) and
+runtime (also known as `runtime_view`).<br/>
+The former require that users indicate at compile-time what are the components
+involved and can make several optimizations because of that. The latter can be
+constructed at runtime instead and are a bit slower to iterate entities and
+components.<br/>
+In both cases, creating and destroying a view isn't expensive at all because
+views don't have any type of initialization. Moreover, views don't affect any
+other functionality of the registry and keep memory usage at a minimum.
+
+Groups come in three different flavors: _full-owning groups_, _partial-owning
+groups_ and _non-owning groups_. The main difference between them is in terms of
+performance.<br/>
+Groups can literally _own_ one or more types of components. It means that they
+will be allowed to rearrange pools so as to speed up iterations. Roughly
+speaking: the more components a group owns, the faster it is to iterate them.
+On the other side, a given component can belong only to one group, so users have
+to define groups carefully to get the best out of them.
+
 Continue reading for more details or refer to the inline documentation.
 
-## Standard View
+## Views
+
+A view behaves differently if it's constructed for a single component or if it
+has been created to iterate multiple components. Even the API is slightly
+different in the two cases.
+
+Single component views are specialized in order to give a boost in terms of
+performance in all the situations. This kind of views can access the underlying
+data structures directly and avoid superfluous checks. There is nothing as fast
+as a single component view. In fact, they walk through a packed array of
+components and return them one at a time.<br/>
+Single component views offer a bunch of functionalities to get the number of
+entities they are going to return and a raw access to the entity list as well as
+to the component list. It's also possible to ask a view if it contains a
+given entity.<br/>
+Refer to the inline documentation for all the details.
+
+Multi component views iterate entities that have at least all the given
+components in their bags. During construction, these views look at the number of
+entities available for each component and pick up a reference to the smallest
+set of candidates in order to speed up iterations.<br/>
+They offer fewer functionalities than their companion views for single
+component. In particular, a multi component view exposes utility functions to
+get the estimated number of entities it is going to return and to know whether
+it's empty or not. It's also possible to ask a view if it contains a given
+entity.<br/>
+Refer to the inline documentation for all the details.
+
+There is no need to store views around for they are extremely cheap to
+construct, even though they can be copied without problems and reused freely.
+Views also return newly created and correctly initialized iterators whenever
+`begin` or `end` are invoked.
 
-A standard view behaves differently if it's constructed for a single component
-or if it has been requested to iterate multiple components. Even the API is
-different in the two cases.<br/>
-All that they share is the way they are created by means of a registry:
+Views share the way they are created by means of a registry:
 
 ```cpp
-// single component standard view
+// single component view
 auto single = registry.view<position>();
 
-// multi component standard view
+// multi component view
 auto multi = registry.view<position, velocity>();
 ```
 
-For all that remains, it's worth discussing them separately.<br/>
-
-### Single component standard view
-
-Single component standard views are specialized in order to give a boost in
-terms of performance in all the situation. This kind of views can access the
-underlying data structures directly and avoid superfluous checks.<br/>
-They offer a bunch of functionalities to get the number of entities they are
-going to return and a raw access to the entity list as well as to the component
-list. It's also possible to ask a view if it contains a given entity.<br/>
-Refer to the inline documentation for all the details.
-
-There is no need to store views around for they are extremely cheap to
-construct, even though they can be copied without problems and reused freely. In
-fact, they return newly created and correctly initialized iterators whenever
-`begin` or `end` are invoked.<br/>
-To iterate a single component standard view, either use it in a range-for loop:
+To iterate a view, either use it in a range-for loop:
 
 ```cpp
-auto view = registry.view<renderable>();
+auto view = registry.view<position, velocity>();
 
 for(auto entity: view) {
-    renderable &renderable = view.get(entity);
+    // a component at a time ...
+    auto &position = view.get<position>(entity);
+    auto &velocity = view.get<velocity>(entity);
+
+    // ... or multiple components at once
+    auto &[pos, vel] = view.get<position, velocity>(entity);
 
     // ...
 }
@@ -939,124 +905,132 @@ Or rely on the `each` member function to iterate entities and get all their
 components at once:
 
 ```cpp
-registry.view<renderable>().each([](auto entity, auto &renderable) {
+registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
     // ...
 });
 ```
 
 The `each` member function is highly optimized. Unless users want to iterate
-only entities, using `each` should be the preferred approach.
+only entities or get only some of the components, this should be the preferred
+approach. Note that the entity can also be excluded from the parameter list if
+not required, but this won't improve performance for multi component views.
 
 **Note**: prefer the `get` member function of a view instead of the `get` member
 function template of a registry during iterations, if possible. However, keep in
 mind that it works only with the components of the view itself.
 
-### Multi component standard view
+## Runtime views
 
-Multi component standard views iterate entities that have at least all the given
-components in their bags. During construction, these views look at the number of
-entities available for each component and pick up a reference to the smallest
+Runtime views iterate entities that have at least all the given components in
+their bags. During construction, these views look at the number of entities
+available for each component and pick up a reference to the smallest
 set of candidates in order to speed up iterations.<br/>
-They offer fewer functionalities than their companion views for single
-component. In particular, a multi component standard view exposes utility
-functions to get the estimated number of entities it is going to return and to
-know whether it's empty or not. It's also possible to ask a view if it contains
-a given entity.<br/>
+They offer more or less the same functionalities of a multi component view.
+However, they don't expose a `get` member function and users should refer to the
+registry that generated the view to access components. In particular, a runtime
+view exposes utility functions to get the estimated number of entities it is
+going to return and to know whether it's empty or not. It's also possible to ask
+a runtime view if it contains a given entity.<br/>
 Refer to the inline documentation for all the details.
 
-There is no need to store views around for they are extremely cheap to
-construct, even though they can be copied without problems and reused freely. In
-fact, they return newly created and correctly initialized iterators whenever
-`begin` or `end` are invoked.<br/>
-To iterate a multi component standard view, either use it in a range-for loop:
+Runtime view are extremely cheap to construct and should not be stored around in
+any case. They should be used immediately after creation and then they should be
+thrown away. The reasons for this go far beyond the scope of this document.<br/>
+To iterate a runtime view, either use it in a range-for loop:
 
 ```cpp
-auto view = registry.view<position, velocity>();
+using component_type = typename decltype(registry)::component_type;
+component_type types[] = { registry.type<position>(), registry.type<velocity>() };
+
+auto view = registry.runtime_view(std::cbegin(types), std::cend(types));
 
 for(auto entity: view) {
     // a component at a time ...
-    auto &position = view.get<position>(entity);
-    auto &velocity = view.get<velocity>(entity);
+    auto &position = registry.get<position>(entity);
+    auto &velocity = registry.get<velocity>(entity);
 
     // ... or multiple components at once
-    auto &[pos, vel] = view.get<position, velocity>(entity);
+    auto &[pos, vel] = registry.get<position, velocity>(entity);
 
     // ...
 }
 ```
 
-Or rely on the `each` member function to iterate entities and get all their
-components at once:
+Or rely on the `each` member function to iterate entities:
 
 ```cpp
-registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
+using component_type = typename decltype(registry)::component_type;
+component_type types[] = { registry.type<position>(), registry.type<velocity>() };
+
+auto view = registry.runtime_view(std::cbegin(types), std::cend(types)).each([](auto entity) {
     // ...
 });
 ```
 
-The `each` member function is highly optimized. Unless users want to iterate
-only entities or get only some of the components, using `each` should be the
-preferred approach.
-
-**Note**: prefer the `get` member function of a view instead of the `get` member
-function template of a registry during iterations, if possible. However, keep in
-mind that it works only with the components of the view itself.
-
-## Persistent View
-
-A persistent view returns all the entities and only the entities that have at
-least the given components and respect the given filters. Moreover, it's
-guaranteed that the entity list is tightly packed in memory for fast
-iterations.<br/>
-In general, persistent views don't stay true to the order of any set of
-components unless users explicitly sort them.
-
-Persistent views are used mainly to iterate multiple components at once:
-
-```cpp
-auto view = registry.persistent_view<position, velocity>();
-```
-
-Filtering entities by components is also supported:
-
-```cpp
-auto view = registry.persistent_view<position, velocity>(entt::exclude<renderable>);
-```
+Performance are exactly the same in both cases.
 
-In this case, the view will return all the entities that have both components
-`position` and `velocity` but don't have component `renderable`.<br/>
-Exclusive filters (ie the entities that have either `position` or `velocity`)
-aren't directly supported for performance reasons. Similarly, a filter cannot be
-applied to a persistent view with an empty template parameters list.
+**Note**: runtime views are meant for all those cases where users don't know at
+compile-time what components to use to iterate entities. This is particularly
+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
+views.
 
-There is no need to store views around for they are extremely cheap to
-construct, even though they can be copied without problems and reused freely. In
-fact, they return newly created and correctly initialized iterators whenever
-`begin` or `end` are invoked.<br/>
-That being said, persistent views perform an initialization step the very first
-time they are constructed and this could be quite costly. To avoid it, consider
-creating them when no components have been assigned yet. If the registry is
-empty, preparation is extremely fast.
-
-A persistent view offers a bunch of functionalities to get the number of
-entities it's going to return, a raw access to the entity list and the
-possibility to sort the underlying data structures according to the order of one
-of the components for which it has been constructed. It's also possible to ask a
-view if it contains a given entity.<br/>
+## Groups
+
+Groups are meant to iterate multiple components at once and offer a faster
+alternative to views. Roughly speaking, they just play in another league when
+compared to views.<br/>
+Groups overcome the performance of the other tools available but require to get
+the ownership of components and this sets some constraints on pools. On the
+other side, groups aren't an automatism that increases memory consumption,
+affects functionalities and tries to optimize iterations for all the possible
+combinations of components. Users can decide when to pay for groups and to what
+extent.<br/>
+The most interesting aspect of groups is that they fit _usage patterns_. Other
+solutions around usually try to optimize everything, because it is known that
+somewhere within the _everything_ there are also our usage patterns. However
+this has a cost that isn't negligible, both in terms of performance and memory
+usage. Ironically, users pay the price also for things they don't want and this
+isn't something I like much. Even worse, you cannot easily disable such a
+behavior. Groups work differently instead and are designed to optimize only the
+real use cases when users find they need to.<br/>
+Another nice-to-have feature of groups is that they have no impact on memory
+consumption, put aside full non-owning groups that are pretty rare and should be
+avoided as long as possible.
+
+All groups affect to an extent the creation and destruction of their components.
+This is due to the fact that they must _observe_ changes in the pools of
+interest and arrange data _correctly_ when needed for the types they own.<br/>
+That being said, the way groups operate is beyond the scope of this document.
+However, it's unlikely that users will be able to appreciate the impact of
+groups on other functionalities of the registry.
+
+Groups offer a bunch of functionalities to get the number of entities they are
+going to return and a raw access to the entity list as well as to the component
+list for owned components. It's also possible to ask a group if it contains a
+given entity.<br/>
 Refer to the inline documentation for all the details.
 
-To iterate a persistent view, either use it in a range-for loop:
+There is no need to store groups around for they are extremely cheap to
+construct, even though they can be copied without problems and reused freely.
+A group performs an initialization step the very first time it's requested and
+this could be quite costly. To avoid it, consider creating the group when no
+components have been assigned yet. If the registry is empty, preparation is
+extremely fast. Groups also return newly created and correctly initialized
+iterators whenever `begin` or `end` are invoked.
+
+To iterate groups, either use them in a range-for loop:
 
 ```cpp
-auto view = registry.persistent_view<position, velocity>();
+auto group = registry.group<position>(entt::get<velocity>);
 
-for(auto entity: view) {
+for(auto entity: group) {
     // a component at a time ...
-    auto &position = view.get<position>(entity);
-    auto &velocity = view.get<velocity>(entity);
+    auto &position = group.get<position>(entity);
+    auto &velocity = group.get<velocity>(entity);
 
     // ... or multiple components at once
-    auto &[pos, vel] = view.get<position, velocity>(entity);
+    auto &[pos, vel] = group.get<position, velocity>(entity);
 
     // ...
 }
@@ -1066,83 +1040,107 @@ Or rely on the `each` member function to iterate entities and get all their
 components at once:
 
 ```cpp
-registry.persistent_view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
+registry.registry.group<position>(entt::get<velocity>).each([](auto entity, auto &pos, auto &vel) {
     // ...
 });
 ```
 
 The `each` member function is highly optimized. Unless users want to iterate
-only entities, using `each` should be the preferred approach.
+only entities, this should be the preferred approach. Note that the entity can
+also be excluded from the parameter list if not required and it can improve even
+further the performance during iterations.
 
-**Note**: prefer the `get` member function of a view instead of the `get` member
-function template of a registry during iterations, if possible. However, keep in
-mind that it works only with the components of the view itself.
+**Note**: prefer the `get` member function of a group instead of the `get`
+member function template of a registry during iterations, if possible. However,
+keep in mind that it works only with the components of the group itself.
 
-## Runtime View
+Let's go a bit deeper into the different types of groups made available by this
+library to know how they are constructed and what are the differences between
+them.
 
-Runtime views iterate entities that have at least all the given components in
-their bags. During construction, these views look at the number of entities
-available for each component and pick up a reference to the smallest
-set of candidates in order to speed up iterations.<br/>
-They offer more or less the same functionalities of a multi component standard
-view. However, they don't expose a `get` member function and users should refer
-to the registry that generated the view to access components. In particular, a
-runtime view exposes utility functions to get the estimated number of entities
-it is going to return and to know whether it's empty or not. It's also possible
-to ask a view if it contains a given entity.<br/>
-Refer to the inline documentation for all the details.
+### Full-owning groups
 
-Runtime view are extremely cheap to construct and should not be stored around in
-any case. They should be used immediately after creation and then they should be
-thrown away. The reasons for this go far beyond the scope of this document.<br/>
-To iterate a runtime view, either use it in a range-for loop:
+A full-owning group is the fastest tool an user can expect to use to iterate
+multiple components at once. It iterates all the components directly, no
+indirection required. This type of groups performs more or less as if users are
+accessing sequentially a bunch of packed arrays of components all sorted
+identically.
+
+A full-owning group is created as:
 
 ```cpp
-using component_type = typename decltype(registry)::component_type;
-component_type types[] = { registry.type<position>(), registry.type<velocity>() };
+auto group = registry.group<position, velocity>();
+```
 
-auto view = registry.runtime_view(std::cbegin(types), std::cend(types));
+Filtering entities by components is also supported:
 
-for(auto entity: view) {
-    // a component at a time ...
-    auto &position = registry.get<position>(entity);
-    auto &velocity = registry.get<velocity>(entity);
+```cpp
+auto group = registry.group<position, velocity>(entt::exclude<renderable>);
+```
 
-    // ... or multiple components at once
-    auto &[pos, vel] = registry.get<position, velocity>(entity);
+Once created, the group gets the ownership of all the components specified in
+the template parameter list and arranges their pools so as to iterate all of
+them as fast as possible.<br/>
+Sorting owned components is no longer allowed once the group has been created.
 
-    // ...
-}
+### Partial-owning groups
+
+A partial-owning group works similarly to a full-owning group for the components
+it owns, but relies on indirection to get components owned by other groups. This
+isn't as fast as a full-owning group, but it's already much faster than views
+when there are only one or two free components to retrieve (the most common
+cases likely). In the worst case, it's not slower than views anyway.
+
+A partial-owning group is created as:
+
+```cpp
+auto group = registry.group<position>(entt::get<velocity>);
 ```
 
-Or rely on the `each` member function to iterate entities:
+Filtering entities by components is also supported:
 
 ```cpp
-using component_type = typename decltype(registry)::component_type;
-component_type types[] = { registry.type<position>(), registry.type<velocity>() };
+auto group = registry.group<position>(entt::get<velocity>, entt::exclude<renderable>);
+```
 
-auto view = registry.runtime_view(std::cbegin(types), std::cend(types)).each([](auto entity) {
-    // ...
-});
+Once created, the group gets the ownership of all the components specified in
+the template parameter list and arranges their pools so as to iterate all of
+them as fast as possible. The ownership of the types provided via `entt::get`
+doesn't pass to the group instead.<br/>
+Sorting owned components is no longer allowed once the group has been created.
+
+### Non-owning groups
+
+Non-owning groups are usually fast enough, for sure faster than views and well
+suited for most of the cases. However, they require custom data structures to
+work properly and they increase memory consumption. As a rule of thumb, users
+should avoid using non-owning groups, if possible.
+
+A non-owning group is created as:
+
+```cpp
+auto group = registry.group<>(entt::get<position, velocity>);
 ```
 
-Performance are exactly the same in both cases.
+Filtering entities by components is also supported:
 
-**Note**: runtime views are meant for all those cases where users don't know at
-compile-time what components to use to iterate entities. This is particularly
-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
-views.
+```cpp
+auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<renderable>);
+```
+
+The group doesn't receive the ownership of any type of component in this case.
+This has a positive implication, that is, the fact that non-owning groups can be
+sorted by means of the `sort` member function, if required.
 
 # 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 version 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:
+The `registry` class offers two overloads when it comes to constructing views
+and groups: a const version 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 and groups can be constructed also from a const registry and
+they propagate the constness of the registry to the types involved. As an
+example:
 
 ```cpp
 entt::view<const position, const velocity> view = std::as_const(registry).view<const position, const velocity>();
@@ -1189,10 +1187,12 @@ 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.
 
+The same concepts apply to groups as well.
+
 ## Give me everything
 
-Views are narrow windows on the entire list of entities. They work by filtering
-entities according to their components.<br/>
+Views and groups are narrow windows on the entire list of entities. They work by
+filtering entities according to their components.<br/>
 In some cases there may be the need to iterate all the entities still in use
 regardless of their components. The registry offers a specific member function
 to do that:
@@ -1205,9 +1205,9 @@ registry.each([](auto entity) {
 
 It returns to the caller all the entities that are still in use by means of the
 given function.<br/>
-As a rule of thumb, consider using a view if the goal is to iterate entities
-that have a determinate set of components. A view is usually much faster than
-combining this function with a bunch of custom tests.<br/>
+As a rule of thumb, consider using a view or a group if the goal is to iterate
+entities that have a determinate set of components. These tools are usually much
+faster than combining this function with a bunch of custom tests.<br/>
 In all the other cases, this is the way to go.
 
 There exists also another member function to use to retrieve orphans. An orphan
@@ -1229,7 +1229,7 @@ In general, all these functions can result in poor performance.<br/>
 entity. For similar reasons, `orphans` can be even slower. Both functions should
 not be used frequently to avoid the risk of a performance hit.
 
-# Iterations: what is allowed and what is not
+## What is allowed and what is not
 
 Most of the _ECS_ available out there have some annoying limitations (at least
 from my point of view): entities and components cannot be created nor destroyed
@@ -1237,13 +1237,12 @@ during iterations.<br/>
 `EnTT` partially solves the problem with a few limitations:
 
 * Creating entities and components is allowed during iterations.
-* Deleting an entity or removing its components is allowed during iterations if
-  it's the one currently returned by the view. For all the other entities,
-  destroying them or removing their components isn't allowed and it can result
-  in undefined behavior.
+* Deleting the current entity or removing its components is allowed during
+  iterations. For all the other entities, destroying them or removing their
+  components isn't allowed and can result in undefined behavior.
 
 Iterators are invalidated and the behavior is undefined if an entity is modified
-or destroyed and it's not the one currently returned by the view nor a newly
+or destroyed and it's not the one currently returned by the iterator nor a newly
 created one.<br/>
 To work around it, possible approaches are:
 
@@ -1285,10 +1284,10 @@ offered by the library aren't affected, but for the `raw` member function.
 In general, the entire registry isn't thread safe as it is. Thread safety isn't
 something that users should want out of the box for several reasons. Just to
 mention one of them: performance.<br/>
-Views and consequently the approach adopted by `EnTT` are the great exception to
-the rule. It's true that views and thus their iterators aren't thread safe by
-themselves. Because of this users shouldn't try to iterate a set of components
-and modify the same set concurrently. However:
+Views, groups and consequently the approach adopted by `EnTT` are the great
+exception to the rule. It's true that views, groups and iterators in general
+aren't thread safe by themselves. Because of this users shouldn't try to iterate
+a set of components and modify the same set concurrently. However:
 
 * As long as a thread iterates the entities that have the component `X` or
   assign and removes that component from a set of entities, another thread can
@@ -1314,17 +1313,18 @@ are completely responsible for synchronization whether required. On the other
 hand, they could get away with it without having to resort to particular
 expedients.
 
-## Views and Iterators
+## Iterators
 
-A special mention is needed for the iterators returned by the views. Most of the
-time they meet the requirements of **random access iterators**, in all cases
-they meet at least the requirements of **forward iterators**.<br/>
+A special mention is needed for the iterators returned by the views and the
+groups. Most of the time they meet the requirements of **random access
+iterators**, in all cases they meet at least the requirements of **forward
+iterators**.<br/>
 In other terms, they are suitable for use with the **parallel algorithms** of
 the standard library. If it's not clear, this is a great thing.
 
 As an example, this kind of iterators can be used in combination with
 `std::for_each` and `std::execution::par` to parallelize the visit and therefore
-the update of the components returned by a view, as long as the constraints
-previously discussed are respected.<br/>
+the update of the components returned by a view or a group, as long as the
+constraints previously discussed are respected.<br/>
 This can increase the throughput considerably, even without resorting to who
 knows what artifacts that are difficult to maintain over time.

+ 0 - 8
src/entt/core/type_traits.hpp

@@ -34,14 +34,6 @@ constexpr auto type_list_cat(type_list<Type...>, type_list<Other...>, List...) {
 }
 
 
-/**
- * @brief Variable template for exclusion lists.
- * @tparam Type List of types.
- */
-template<typename... Type>
-constexpr type_list<Type...> exclude{};
-
-
 }
 
 

+ 586 - 0
src/entt/entity/group.hpp

@@ -0,0 +1,586 @@
+#ifndef ENTT_ENTITY_GROUP_HPP
+#define ENTT_ENTITY_GROUP_HPP
+
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+#include <algorithm>
+#include <type_traits>
+#include "../config/config.h"
+#include "../core/type_traits.hpp"
+#include "sparse_set.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @brief Forward declaration of the registry class.
+ */
+template<typename>
+class registry;
+
+
+/**
+ * @brief Alias for lists of observed components.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+struct get_t: type_list<Type...> {};
+
+
+/**
+ * @brief Variable template for lists of observed components.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+constexpr get_t<Type...> get{};
+
+
+/**
+ * @brief Group.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error, but for a few reasonable cases.
+ */
+template<typename...>
+class group;
+
+
+/**
+ * @brief Detached group.
+ *
+ * A detached group returns all the entities and only the entities that have at
+ * least the given components. Moreover, it's guaranteed that the entity list is
+ * tightly packed in memory for fast iterations.<br/>
+ * In general, detached groups don't stay true to the order of any set of
+ * components unless users explicitly sort them.
+ *
+ * @b Important
+ *
+ * Iterators aren't invalidated if:
+ *
+ * * New instances of the given components are created and assigned to entities.
+ * * The entity currently pointed is modified (as an example, if one of the
+ *   given components is removed from the entity to which the iterator points).
+ *
+ * In all the other cases, modifying the pools of the given components in any
+ * way invalidates all the iterators and using them results in undefined
+ * behavior.
+ *
+ * @note
+ * Groups share references to the underlying data structures of the registry
+ * that generated them. Therefore any change to the entities and to the
+ * components made by means of the registry are immediately reflected by all the
+ * groups.<br/>
+ * Moreover, sorting a detached group affects all the other groups of the same
+ * type (it means that users don't have to call `sort` on each group to sort all
+ * of them because they share the set of entities).
+ *
+ * @warning
+ * Lifetime of a group must overcome the one of the registry that generated it.
+ * In any other case, attempting to use a group results in undefined behavior.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam Get Types of components iterated by the group.
+ */
+template<typename Entity, typename... Get>
+class group<Entity, get_t<Get...>> {
+    static_assert(sizeof...(Get));
+
+    /*! @brief A registry is allowed to create groups. */
+    friend class registry<Entity>;
+
+    template<typename Component>
+    using pool_type = std::conditional_t<std::is_const_v<Component>, const sparse_set<Entity, std::remove_const_t<Component>>, sparse_set<Entity, Component>>;
+
+    // we could use pool_type<Get> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
+    group(sparse_set<Entity> *handler, sparse_set<Entity, std::remove_const_t<Get>> *... pools) ENTT_NOEXCEPT
+        : handler{handler},
+          pools{pools...}
+    {}
+
+public:
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename sparse_set<Entity>::entity_type;
+    /*! @brief Unsigned integer type. */
+    using size_type = typename sparse_set<Entity>::size_type;
+    /*! @brief Input iterator type. */
+    using iterator_type = typename sparse_set<Entity>::iterator_type;
+
+    /*! @brief Default copy constructor. */
+    group(const group &) = default;
+    /*! @brief Default move constructor. */
+    group(group &&) = default;
+
+    /*! @brief Default copy assignment operator. @return This group. */
+    group & operator=(const group &) = default;
+    /*! @brief Default move assignment operator. @return This group. */
+    group & operator=(group &&) = default;
+
+    /**
+     * @brief Returns the number of entities that have the given components.
+     * @return Number of entities that have the given components.
+     */
+    size_type size() const ENTT_NOEXCEPT {
+        return handler->size();
+    }
+
+    /**
+     * @brief Checks whether the group is empty.
+     * @return True if the group is empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return handler->empty();
+    }
+
+    /**
+     * @brief Direct access to the list of entities.
+     *
+     * The returned pointer is such that range `[data(), data() + size()]` is
+     * always a valid range, even if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the entities. Use `begin` and
+     * `end` if you want to iterate the group in the expected order.
+     *
+     * @return A pointer to the array of entities.
+     */
+    const entity_type * data() const ENTT_NOEXCEPT {
+        return handler->data();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the group is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    iterator_type begin() const ENTT_NOEXCEPT {
+        return handler->begin();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    iterator_type end() const ENTT_NOEXCEPT {
+        return handler->end();
+    }
+
+    /**
+     * @brief Finds an entity.
+     * @param entity A valid entity identifier.
+     * @return An iterator to the given entity if it's found, past the end
+     * iterator otherwise.
+     */
+    iterator_type find(const entity_type entity) const ENTT_NOEXCEPT {
+        const auto it = handler->find(entity);
+        return it != end() && *it == entity ? it : end();
+    }
+
+    /**
+     * @brief Returns the identifier that occupies the given position.
+     * @param pos Position of the element to return.
+     * @return The identifier that occupies the given position.
+     */
+    entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
+        return begin()[pos];
+    }
+
+    /**
+     * @brief Checks if a group contains an entity.
+     * @param entity A valid entity identifier.
+     * @return True if the group contains the given entity, false otherwise.
+     */
+    bool contains(const entity_type entity) const ENTT_NOEXCEPT {
+        return find(entity) != end();
+    }
+
+    /**
+     * @brief Returns the components assigned to the given entity.
+     *
+     * Prefer this function instead of `registry::get` during iterations. It has
+     * far better performance than its companion function.
+     *
+     * @warning
+     * Attempting to use an invalid component type results in a compilation
+     * error. Attempting to use an entity that doesn't belong to the group
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * group doesn't contain the given entity.
+     *
+     * @tparam Component Types of components to get.
+     * @param entity A valid entity identifier.
+     * @return The components assigned to the entity.
+     */
+    template<typename... Component>
+    std::conditional_t<sizeof...(Component) == 1, std::tuple_element_t<0, std::tuple<Component &...>>, std::tuple<Component &...>>
+    get([[maybe_unused]] const entity_type entity) const ENTT_NOEXCEPT {
+        assert(contains(entity));
+
+        if constexpr(sizeof...(Component) == 1) {
+            static_assert(std::disjunction_v<std::is_same<Component..., Get>..., std::is_same<std::remove_const_t<Component>..., Get>...>);
+            return (std::get<pool_type<Component> *>(pools)->get(entity), ...);
+        } else {
+            return std::tuple<Component &...>{get<Component>(entity)...};
+        }
+    }
+
+    /**
+     * @brief Iterates entities and components and applies the given function
+     * object to them.
+     *
+     * The function object is invoked for each entity. It is provided with the
+     * entity itself and a set of references to all its components. The
+     * _constness_ of the components is as requested.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Get &...);
+     * void(Get &...);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    inline void each(Func func) const {
+        if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Get>...>) {
+            std::for_each(handler->begin(), handler->end(), [func = std::move(func), this](const auto entity) mutable {
+                func(std::get<pool_type<Get> *>(pools)->get(entity)...);
+            });
+        } else {
+            std::for_each(handler->begin(), handler->end(), [func = std::move(func), this](const auto entity) mutable {
+                func(entity, std::get<pool_type<Get> *>(pools)->get(entity)...);
+            });
+        }
+    }
+
+    /**
+     * @brief Sort the shared pool of entities according to the given component.
+     *
+     * Detached groups of the same type share with the registry a pool of
+     * entities with  its own order that doesn't depend on the order of any pool
+     * of components. Users can order the underlying data structure so that it
+     * respects the order of the pool of the given component.
+     *
+     * @note
+     * The shared pool of entities and thus its order is affected by the changes
+     * to each and every pool that it tracks. Therefore changes to those pools
+     * can quickly ruin the order imposed to the pool of entities shared between
+     * the detached groups.
+     *
+     * @tparam Component Type of component to use to impose the order.
+     */
+    template<typename Component>
+    void sort() const {
+        handler->respect(*std::get<pool_type<Component> *>(pools));
+    }
+
+private:
+    sparse_set<entity_type> *handler;
+    const std::tuple<pool_type<Get> *...> pools;
+};
+
+
+/**
+ * @brief Owning group.
+ *
+ * Owning groups return all the entities and only the entities that have at
+ * least the given components. Moreover:
+ *
+ * * It's guaranteed that the entity list is tightly packed in memory for fast
+ *   iterations.
+ * * It's guaranteed that the lists of owned components are tightly packed in
+ *   memory for even faster iterations and to allow direct access.
+ * * They stay true to the order of the owned components and all the owned
+ *   components have the same order in memory.
+ *
+ * The more types of components are owned by a group, the faster it is to
+ * iterate them.
+ *
+ * @b Important
+ *
+ * Iterators aren't invalidated if:
+ *
+ * * New instances of the given components are created and assigned to entities.
+ * * The entity currently pointed is modified (as an example, if one of the
+ *   given components is removed from the entity to which the iterator points).
+ *
+ * In all the other cases, modifying the pools of the given components in any
+ * way invalidates all the iterators and using them results in undefined
+ * behavior.
+ *
+ * @note
+ * Groups share references to the underlying data structures of the registry
+ * that generated them. Therefore any change to the entities and to the
+ * components made by means of the registry are immediately reflected by all the
+ * groups.
+ *
+ * @warning
+ * Lifetime of a group must overcome the one of the registry that generated it.
+ * In any other case, attempting to use a group results in undefined behavior.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam Get Types of components observed by the group.
+ * @tparam Owned Types of components owned by the group.
+ */
+template<typename Entity, typename... Get, typename... Owned>
+class group<Entity, get_t<Get...>, Owned...> {
+    static_assert(sizeof...(Get) + sizeof...(Owned));
+
+    /*! @brief A registry is allowed to create groups. */
+    friend class registry<Entity>;
+
+    template<typename Component>
+    using pool_type = std::conditional_t<std::is_const_v<Component>, const sparse_set<Entity, std::remove_const_t<Component>>, sparse_set<Entity, Component>>;
+
+    template<typename Component>
+    using component_iterator_type = decltype(std::declval<pool_type<Component>>().begin());
+
+    // we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
+    group(typename sparse_set<Entity>::size_type *length, sparse_set<Entity, std::remove_const_t<Owned>> *... owned, sparse_set<Entity, std::remove_const_t<Get>> *... others) ENTT_NOEXCEPT
+        : length{length},
+          pools{owned..., others...}
+    {}
+
+public:
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename sparse_set<Entity>::entity_type;
+    /*! @brief Unsigned integer type. */
+    using size_type = typename sparse_set<Entity>::size_type;
+    /*! @brief Input iterator type. */
+    using iterator_type = typename sparse_set<Entity>::iterator_type;
+
+    /*! @brief Default copy constructor. */
+    group(const group &) = default;
+    /*! @brief Default move constructor. */
+    group(group &&) = default;
+
+    /*! @brief Default copy assignment operator. @return This group. */
+    group & operator=(const group &) = default;
+    /*! @brief Default move assignment operator. @return This group. */
+    group & operator=(group &&) = default;
+
+    /**
+     * @brief Returns the number of entities that have the given components.
+     * @return Number of entities that have the given components.
+     */
+    size_type size() const ENTT_NOEXCEPT {
+        return *length;
+    }
+
+    /**
+     * @brief Checks whether the group is empty.
+     * @return True if the group is empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return !*length;
+    }
+
+    /**
+     * @brief Direct access to the list of components of an owned type.
+     *
+     * The returned pointer is such that range `[raw(), raw() + size()]` is
+     * always a valid range, even if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the components. Use `each` if you
+     * want to iterate the group in the expected order.
+     *
+     * @warning
+     * Empty components aren't explicitly instantiated. Therefore, this function
+     * always returns `nullptr` for them.
+     *
+     * @return A pointer to the array of components.
+     */
+    template<typename Component>
+    Component * raw() const ENTT_NOEXCEPT {
+        static_assert(std::disjunction_v<std::is_same<Component, Owned>..., std::is_same<std::remove_const_t<Component>, Owned>...>);
+        return std::get<pool_type<Component> *>(pools)->raw();
+    }
+
+    /**
+     * @brief Direct access to the list of entities.
+     *
+     * The returned pointer is such that range `[data(), data() + size()]` is
+     * always a valid range, even if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the entities. Use `begin` and
+     * `end` if you want to iterate the view in the expected order.
+     *
+     * @return A pointer to the array of entities.
+     */
+    const entity_type * data() const ENTT_NOEXCEPT {
+        return std::get<0>(pools)->data();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the group is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    iterator_type begin() const ENTT_NOEXCEPT {
+        return std::get<0>(pools)->sparse_set<entity_type>::end() - *length;
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    iterator_type end() const ENTT_NOEXCEPT {
+        return std::get<0>(pools)->sparse_set<entity_type>::end();
+    }
+
+    /**
+     * @brief Finds an entity.
+     * @param entity A valid entity identifier.
+     * @return An iterator to the given entity if it's found, past the end
+     * iterator otherwise.
+     */
+    iterator_type find(const entity_type entity) const ENTT_NOEXCEPT {
+        const auto it = std::get<0>(pools)->find(entity);
+        return it != end() && it >= begin() && *it == entity ? it : end();
+    }
+
+    /**
+     * @brief Returns the identifier that occupies the given position.
+     * @param pos Position of the element to return.
+     * @return The identifier that occupies the given position.
+     */
+    entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
+        return begin()[pos];
+    }
+
+    /**
+     * @brief Checks if a group contains an entity.
+     * @param entity A valid entity identifier.
+     * @return True if the group contains the given entity, false otherwise.
+     */
+    bool contains(const entity_type entity) const ENTT_NOEXCEPT {
+        return find(entity) != end();
+    }
+
+    /**
+     * @brief Returns the components assigned to the given entity.
+     *
+     * Prefer this function instead of `registry::get` during iterations. It has
+     * far better performance than its companion function.
+     *
+     * @warning
+     * Attempting to use an invalid component type results in a compilation
+     * error. Attempting to use an entity that doesn't belong to the group
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * group doesn't contain the given entity.
+     *
+     * @tparam Component Types of components to get.
+     * @param entity A valid entity identifier.
+     * @return The components assigned to the entity.
+     */
+    template<typename... Component>
+    std::conditional_t<sizeof...(Component) == 1, std::tuple_element_t<0, std::tuple<Component &...>>, std::tuple<Component &...>>
+    get([[maybe_unused]] const entity_type entity) const ENTT_NOEXCEPT {
+        assert(contains(entity));
+
+        if constexpr(sizeof...(Component) == 1) {
+            static_assert(std::disjunction_v<std::is_same<Component..., Owned>..., std::is_same<std::remove_const_t<Component>..., Owned>...,
+                          std::is_same<Component..., Get>..., std::is_same<std::remove_const_t<Component>..., Get>...>);
+            return (std::get<pool_type<Component> *>(pools)->get(entity), ...);
+        } else {
+            return std::tuple<Component &...>{get<Component>(entity)...};
+        }
+    }
+
+    /**
+     * @brief Iterates entities and components and applies the given function
+     * object to them.
+     *
+     * The function object is invoked for each entity. It is provided with the
+     * entity itself and a set of references to all its components. The
+     * _constness_ of the components is as requested.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Owned &..., Get &...);
+     * void(Owned &..., Get &...);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    inline void each(Func func) const {
+        auto raw = std::make_tuple((std::get<pool_type<Owned> *>(pools)->end() - *length)...);
+        [[maybe_unused]] auto data = std::get<0>(pools)->sparse_set<entity_type>::end() - *length;
+        const auto cend = std::get<0>(pools)->end();
+
+        while(std::get<0>(raw) != cend) {
+            if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Owned>..., std::add_lvalue_reference_t<Get>...>) {
+                if constexpr(sizeof...(Get)) {
+                    const auto entity = *(data++);
+                    func(*(std::get<component_iterator_type<Owned>>(raw)++)..., std::get<pool_type<Get> *>(pools)->get(entity)...);
+                } else {
+                    func(*(std::get<component_iterator_type<Owned>>(raw)++)...);
+                }
+            } else {
+                const auto entity = *(data++);
+                func(entity, *(std::get<component_iterator_type<Owned>>(raw)++)..., std::get<pool_type<Get> *>(pools)->get(entity)...);
+            }
+        }
+    }
+
+private:
+    const size_type *length;
+    const std::tuple<pool_type<Owned> *..., pool_type<Get> *...> pools;
+};
+
+
+}
+
+
+#endif // ENTT_ENTITY_GROUP_HPP

+ 53 - 10
src/entt/entity/helper.hpp

@@ -30,21 +30,64 @@ struct as_view {
     /**
      * @brief Conversion function from a registry to a view.
      * @tparam Component Type of components used to construct the view.
-     * @return A newly created standard view.
+     * @return A newly created view.
      */
     template<typename... Component>
     inline operator entt::view<Entity, Component...>() const {
         return reg.template view<Component...>();
     }
 
+private:
+    registry_type &reg;
+};
+
+
+/**
+ * @brief Deduction guideline.
+ *
+ * It allows to deduce the constness of a registry directly from the instance
+ * provided to the constructor.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+as_view(registry<Entity> &) ENTT_NOEXCEPT -> as_view<false, Entity>;
+
+
+/*! @copydoc as_view */
+template<typename Entity>
+as_view(const registry<Entity> &) ENTT_NOEXCEPT -> as_view<true, Entity>;
+
+
+/**
+ * @brief Converts a registry to a group.
+ * @tparam Const Constness of the accepted registry.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<bool Const, typename Entity>
+struct as_group {
+    /*! @brief Type of registry to convert. */
+    using registry_type = std::conditional_t<Const, const entt::registry<Entity>, entt::registry<Entity>>;
+
     /**
-     * @brief Conversion function from a registry to a persistent view.
-     * @tparam Component Types of components used to construct the view.
-     * @return A newly created persistent view.
+     * @brief Constructs a converter for a given registry.
+     * @param reg A valid reference to a registry.
      */
-    template<typename... Component>
-    inline operator entt::persistent_view<Entity, Component...>() const {
-        return reg.template persistent_view<Component...>();
+    as_group(registry_type &reg) ENTT_NOEXCEPT: reg{reg} {}
+
+    /**
+     * @brief Conversion function from a registry to a group.
+     *
+     * @note
+     * Unfortunately, only full owning groups are supported because of an issue
+     * with msvc that doesn't manage to correctly deduce types.
+     *
+     * @tparam Owned Types of components owned by the group.
+     * @return A newly created group.
+     */
+    template<typename... Owned>
+    inline operator entt::group<Entity, get_t<>, Owned...>() const {
+        return reg.template group<Owned...>();
     }
 
 private:
@@ -61,12 +104,12 @@ private:
  * @tparam Entity A valid entity type (see entt_traits for more details).
  */
 template<typename Entity>
-as_view(registry<Entity> &) ENTT_NOEXCEPT -> as_view<false, Entity>;
+as_group(registry<Entity> &) ENTT_NOEXCEPT -> as_group<false, Entity>;
 
 
-/*! @copydoc as_view */
+/*! @copydoc as_group */
 template<typename Entity>
-as_view(const registry<Entity> &) ENTT_NOEXCEPT -> as_view<true, Entity>;
+as_group(const registry<Entity> &) ENTT_NOEXCEPT -> as_group<true, Entity>;
 
 
 /**

+ 262 - 121
src/entt/entity/registry.hpp

@@ -21,6 +21,7 @@
 #include "../signal/sigh.hpp"
 #include "entity.hpp"
 #include "entt_traits.hpp"
+#include "group.hpp"
 #include "snapshot.hpp"
 #include "sparse_set.hpp"
 #include "view.hpp"
@@ -29,6 +30,22 @@
 namespace entt {
 
 
+/**
+ * @brief Alias for exclusion lists.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+struct exclude_t: type_list<Type...> {};
+
+
+/**
+ * @brief Variable template for exclusion lists.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+constexpr exclude_t<Type...> exclude{};
+
+
 /**
  * @brief Fast and reliable entity-component system.
  *
@@ -43,12 +60,52 @@ template<typename Entity = std::uint32_t>
 class registry {
     using component_family = family<struct internal_registry_component_family>;
     using handler_family = family<struct internal_registry_handler_family>;
+    using group_family = family<struct internal_registry_group_family>;
     using signal_type = sigh<void(registry &, const Entity)>;
     using traits_type = entt_traits<Entity>;
 
-    template<typename Type, auto Has, auto Any>
+    struct signals {
+        signal_type construction;
+        signal_type destruction;
+    };
+
+    struct basic_descriptor {
+        virtual ~basic_descriptor() = default;
+        virtual bool contains(typename component_family::family_type) = 0;
+        typename sparse_set<Entity>::size_type last;
+    };
+
+    template<typename... Owned>
+    struct descriptor: basic_descriptor {
+        bool contains(typename component_family::family_type ctype) override {
+            return ((ctype == type<Owned>()) || ...);
+        }
+    };
+
+    template<auto Has, auto Accept, typename... Owned>
+    static void induce_if(registry &reg, const Entity entity) {
+        if((reg.*Has)(entity) && (reg.*Accept)(entity)) {
+            const auto curr = reg.descriptors[handler_family::type<Owned...>]->last++;
+            (std::swap(reg.pool<Owned>().get(entity), reg.pool<Owned>().raw()[curr]), ...);
+            (reg.pool<Owned>().swap(reg.pools[reg.type<Owned>()]->get(entity), curr), ...);
+        }
+    }
+
+    template<typename... Owned>
+    static void discard_if(registry &reg, const Entity entity) {
+        const auto ctype = type<std::tuple_element_t<0, std::tuple<Owned...>>>();
+        const auto dtype = handler_family::type<Owned...>;
+
+        if(reg.pools[ctype]->has(entity) && reg.pools[ctype]->get(entity) < reg.descriptors[dtype]->last) {
+            const auto curr = --reg.descriptors[dtype]->last;
+            (std::swap(reg.pool<Owned>().get(entity), reg.pool<Owned>().raw()[curr]), ...);
+            (reg.pool<Owned>().swap(reg.pools[reg.type<Owned>()]->get(entity), curr), ...);
+        }
+    }
+
+    template<auto Has, auto Accept, typename Type>
     static void construct_if(registry &reg, const Entity entity) {
-        if((reg.*Has)(entity) && !(reg.*Any)(entity)) {
+        if((reg.*Has)(entity) && (reg.*Accept)(entity)) {
             reg.handlers[handler_family::type<Type>]->construct(entity);
         }
     }
@@ -63,25 +120,40 @@ class registry {
     }
 
     template<typename Component>
-    auto & assure() const {
-        const auto ctype = component_family::type<Component>;
+    inline const auto & pool() const ENTT_NOEXCEPT {
+        assert(type<Component>() < pools.size() && pools[type<Component>()]);
+        return static_cast<const sparse_set<Entity, std::decay_t<Component>> &>(*pools[type<Component>()]);
+    }
+
+    template<typename Component>
+    inline auto & pool() ENTT_NOEXCEPT {
+        return const_cast<sparse_set<Entity, std::decay_t<Component>> &>(std::as_const(*this).template pool<Component>());
+    }
 
-        if(!(ctype < pools.size())) {
-            pools.resize(ctype+1);
-            sighs.resize(ctype+1);
+    template<typename Component>
+    auto & assure() const {
+        if(!(type<Component>() < pools.size())) {
+            pools.resize(type<Component>()+1);
+            sighs.resize(type<Component>()+1);
         }
 
-        if(!pools[ctype]) {
-            pools[ctype] = std::make_unique<sparse_set<Entity, std::decay_t<Component>>>();
+        if(!pools[type<Component>()]) {
+            pools[type<Component>()] = std::make_unique<sparse_set<Entity, std::decay_t<Component>>>();
         }
 
-        return static_cast<sparse_set<Entity, std::decay_t<Component>> &>(*pools[ctype]);
+        return pool<Component>();
     }
 
-    template<typename... Component>
-    bool any_of(const Entity entity) const ENTT_NOEXCEPT {
+    template<typename Component>
+    inline auto & assure() {
+        return const_cast<sparse_set<Entity, std::decay_t<Component>> &>(std::as_const(*this).template assure<Component>());
+    }
+
+    template<auto Auth, typename... Component>
+    bool accept(const Entity entity) const ENTT_NOEXCEPT {
         assert(valid(entity));
-        return (assure<Component>().has(entity) || ...);
+        // internal function, pools are guaranteed to exist
+        return (0 + ... + pool<Component>().has(entity)) == Auth;
     }
 
 public:
@@ -90,7 +162,7 @@ public:
     /*! @brief Underlying version type. */
     using version_type = typename traits_type::version_type;
     /*! @brief Unsigned integer type. */
-    using size_type = std::size_t;
+    using size_type = typename sparse_set<Entity>::size_type;
     /*! @brief Unsigned integer type. */
     using component_type = typename component_family::family_type;
     /*! @brief Type of sink for the given component. */
@@ -198,7 +270,7 @@ public:
     }
 
     /**
-     * @brief Checks whether the pool of the given component is empty.
+     * @brief Checks whether the pool of a given component is empty.
      * @tparam Component Type of component in which one is interested.
      * @return True if the pool of the given component is empty, false
      * otherwise.
@@ -442,7 +514,7 @@ public:
             auto &cpool = pools[pos-1];
 
             if(cpool && cpool->has(entity)) {
-                sighs[pos-1].second.publish(*this, entity);
+                sighs[pos-1].destruction.publish(*this, entity);
                 cpool->destroy(entity);
             }
         };
@@ -504,7 +576,7 @@ public:
     Component & assign(const entity_type entity, Args &&... args) {
         assert(valid(entity));
         auto &component = assure<Component>().construct(entity, std::forward<Args>(args)...);
-        sighs[component_family::type<Component>].first.publish(*this, entity);
+        sighs[type<Component>()].construction.publish(*this, entity);
         return component;
     }
 
@@ -524,8 +596,8 @@ public:
     template<typename Component>
     void remove(const entity_type entity) {
         assert(valid(entity));
-        sighs[component_family::type<Component>].second.publish(*this, entity);
-        assure<Component>().destroy(entity);
+        sighs[type<Component>()].destruction.publish(*this, entity);
+        pool<Component>().destroy(entity);
     }
 
     /**
@@ -565,7 +637,7 @@ public:
         assert(valid(entity));
 
         if constexpr(sizeof...(Component) == 1) {
-            return assure<Component...>().get(entity);
+            return (pool<Component>().get(entity), ...);
         } else {
             return std::tuple<std::add_const_t<Component> &...>{get<Component>(entity)...};
         }
@@ -607,12 +679,12 @@ public:
     template<typename Component>
     Component & get(const entity_type entity, Component &&component) ENTT_NOEXCEPT {
         assert(valid(entity));
-        auto &cpool = assure<Component>();
+        auto &cpool = pool<Component>();
         auto *comp = cpool.try_get(entity);
 
         if(!comp) {
             comp = &cpool.construct(entity, std::forward<Component>(component));
-            sighs[component_family::type<Component>].first.publish(*this, entity);
+            sighs[type<Component>()].construction.publish(*this, entity);
         }
 
         return *comp;
@@ -635,7 +707,7 @@ public:
         assert(valid(entity));
 
         if constexpr(sizeof...(Component) == 1) {
-            return assure<Component...>().try_get(entity);
+            return (assure<Component>().try_get(entity), ...);
         } else {
             return std::tuple<std::add_const_t<Component> *...>{try_get<Component>(entity)...};
         }
@@ -673,7 +745,7 @@ public:
      */
     template<typename Component, typename... Args>
     Component & replace(const entity_type entity, Args &&... args) {
-        return (assure<Component>().get(entity) = std::decay_t<Component>{std::forward<Args>(args)...});
+        return (pool<Component>().get(entity) = std::decay_t<Component>{std::forward<Args>(args)...});
     }
 
     /**
@@ -711,7 +783,7 @@ public:
             *comp = std::decay_t<Component>{std::forward<Args>(args)...};
         } else {
             comp = &cpool.construct(entity, std::forward<Args>(args)...);
-            sighs[component_family::type<Component>].first.publish(*this, entity);
+            sighs[type<Component>()].construction.publish(*this, entity);
         }
 
         return *comp;
@@ -743,7 +815,7 @@ public:
     template<typename Component>
     sink_type construction() ENTT_NOEXCEPT {
         assure<Component>();
-        return sighs[component_family::type<Component>].first.sink();
+        return sighs[type<Component>()].construction.sink();
     }
 
     /**
@@ -772,7 +844,7 @@ public:
     template<typename Component>
     sink_type destruction() ENTT_NOEXCEPT {
         assure<Component>();
-        return sighs[component_family::type<Component>].second.sink();
+        return sighs[type<Component>()].destruction.sink();
     }
 
     /**
@@ -808,6 +880,13 @@ public:
      * necessarily the type of the one passed along with the other parameters to
      * this member function.
      *
+     * @warning
+     * Pools of components that are owned by a group cannot be sorted anymore.
+     * The group takes the ownership of the pools and arrange components so as
+     * to iterate them as fast as possible.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the pool is owned by a group.
+     *
      * @tparam Component Type of components to sort.
      * @tparam Compare Type of comparison function object.
      * @tparam Sort Type of sort function object.
@@ -818,7 +897,9 @@ public:
      */
     template<typename Component, typename Compare, typename Sort = std_sort, typename... Args>
     void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
-        assure<Component>().sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
+        assert(!owned<Component>());
+        auto &cpool = assure<Component>();
+        cpool.sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
     }
 
     /**
@@ -848,12 +929,21 @@ public:
      *
      * Any subsequent change to `B` won't affect the order in `A`.
      *
+     * @warning
+     * Pools of components that are owned by a group cannot be sorted anymore.
+     * The group takes the ownership of the pools and arrange components so as
+     * to iterate them as fast as possible.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the pool is owned by a group.
+     *
      * @tparam To Type of components to sort.
      * @tparam From Type of components to use to sort.
      */
     template<typename To, typename From>
     void sort() {
-        assure<To>().respect(assure<From>());
+        assert(!owned<To>());
+        auto &cpool = assure<To>();
+        cpool.respect(assure<From>());
     }
 
     /**
@@ -876,7 +966,7 @@ public:
         auto &cpool = assure<Component>();
 
         if(cpool.has(entity)) {
-            sighs[component_family::type<Component>].second.publish(*this, entity);
+            sighs[type<Component>()].destruction.publish(*this, entity);
             cpool.destroy(entity);
         }
     }
@@ -891,13 +981,14 @@ public:
      */
     template<typename Component>
     void reset() {
-        sparse_set<Entity> &cpool = assure<Component>();
-        auto &sigh = sighs[component_family::type<Component>].second;
+        auto &cpool = assure<Component>();
+        auto &sigh = sighs[type<Component>()].destruction;
 
         if(sigh.empty()) {
+            // no group set, otherwise the signal wouldn't be empty
             cpool.reset();
         } else {
-            for(const auto entity: cpool) {
+            for(const auto entity: static_cast<const sparse_set<entity_type> &>(cpool)) {
                 sigh.publish(*this, entity);
                 cpool.destroy(entity);
             }
@@ -1008,17 +1099,17 @@ public:
     }
 
     /**
-     * @brief Returns a standard view for the given components.
+     * @brief Returns a view for the given components.
      *
-     * This kind of views are created on the fly and share with the registry its
-     * internal data structures.<br/>
+     * This kind of objects 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:
+     * 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.
@@ -1026,19 +1117,17 @@ public:
      *   component and pick up a reference to the smallest set of candidates to
      *   test for the given components.
      *
+     * Views in no way affect the functionalities of the registry nor those of
+     * the underlying pools.
+     *
      * @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 runtime_view
+     * To get a performance boost, consider using a group instead.
      *
      * @tparam Component Type of components used to construct the view.
-     * @return A newly created standard view.
+     * @return A newly created view.
      */
     template<typename... Component>
     entt::view<Entity, Component...> view() {
@@ -1053,88 +1142,138 @@ public:
     }
 
     /**
-     * @brief Returns a persistent view for the given components.
+     * @brief Checks whether a given component belongs to a group.
+     * @tparam Component Type of component in which one is interested.
+     * @return True if the component belongs to a group, false otherwise.
+     */
+    template<typename Component>
+    bool owned() const ENTT_NOEXCEPT {
+        return std::any_of(descriptors.cbegin(), descriptors.cend(), [](const auto &descriptor) {
+            return descriptor && descriptor->contains(type<Component>());
+        });
+    }
+
+    /**
+     * @brief Returns a group 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, but for the first time they are used. That's mainly
-     * because of the internal data structures of the registry that are
-     * dedicated to this kind of views and that don't exist yet the very first
-     * time they are requested.<br/>
-     * As a rule of thumb, storing a view should never be an option.
+     * This kind of objects are created on the fly and share with the registry
+     * its internal data structures.<br/>
+     * Feel free to discard a group after the use. Creating and destroying a
+     * group is an incredibly cheap operation because they do not require any
+     * type of initialization, but for the first time they are requested.<br/>
+     * As a rule of thumb, storing a group 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. They are also the only type of views that supports filters
-     * without incurring in a loss of performance during iterations.<br/>
-     * However, persistent views 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, as well as sort functionalities.
-     *
-     * That being said, persistent views are an incredibly powerful tool if used
-     * with care and offer a boost of performance undoubtedly.
-     *
-     * @sa view
-     * @sa view<Entity, Component>
-     * @sa persistent_view
-     * @sa runtime_view
-     *
-     * @tparam Component Types of components used to construct the view.
-     * @tparam Exclude Types of components used to filter the view.
-     * @return A newly created persistent view.
+     * Groups support exclusion lists and can own types of components. The more
+     * types are owned by a group, the faster it is to iterate entities and
+     * components.<br/>
+     * However, groups also affect some features of the registry such as the
+     * creation and destruction of components, which will consequently be
+     * slightly slower (nothing that can be noticed in most cases).
+     *
+     * @note
+     * Pools of components that are owned by a group cannot be sorted anymore.
+     * The group takes the ownership of the pools and arrange components so as
+     * to iterate them as fast as possible.
+     *
+     * @tparam Owned Types of components owned by the group.
+     * @tparam Get Types of components observed by the group.
+     * @tparam Exclude Types of components used to filter the group.
+     * @return A newly created group.
      */
-    template<typename... Component, typename... Exclude>
-    entt::persistent_view<Entity, Component...> persistent_view(type_list<Exclude...> = {}) {
-        using handler_type = type_list<Component..., type_list<Exclude...>>;
-        const auto htype = handler_family::type<handler_type>;
+    template<typename... Owned, typename... Get, typename... Exclude>
+    entt::group<Entity, get_t<Get...>, Owned...> group(get_t<Get...>, exclude_t<Exclude...> = {}) {
+        static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1);
+        static_assert(sizeof...(Owned) + sizeof...(Get));
 
-        if(!(htype < handlers.size())) {
-            handlers.resize(htype + 1);
-        }
+        (assure<Owned>(), ...);
+        (assure<Get>(), ...);
+        (assure<Exclude>(), ...);
 
-        if(!handlers[htype]) {
-            (assure<Component>(), ...);
-            (assure<Exclude>(), ...);
+        if constexpr(sizeof...(Owned) == 0) {
+            using handler_type = type_list<Get..., type_list<Exclude...>>;
+            const auto htype = handler_family::type<handler_type>;
+
+            if(!(htype < handlers.size())) {
+                handlers.resize(htype + 1);
+            }
 
-            handlers[htype] = std::make_unique<sparse_set<entity_type>>();
-            auto *direct = handlers[htype].get();
+            if(!handlers[htype]) {
+                handlers[htype] = std::make_unique<sparse_set<entity_type>>();
+                auto *direct = handlers[htype].get();
 
-            ((sighs[component_family::type<Component>].first.sink().template connect<&construct_if<handler_type, &registry::has<Component...>, &registry::any_of<Exclude...>>>()), ...);
-            ((sighs[component_family::type<Exclude>].second.sink().template connect<&construct_if<handler_type, &registry::has<Component...>, &registry::any_of<Exclude...>>>()), ...);
-            ((sighs[component_family::type<Exclude>].first.sink().template connect<&registry::destroy_if<handler_type>>()), ...);
-            ((sighs[component_family::type<Component>].second.sink().template connect<&registry::destroy_if<handler_type>>()), ...);
+                ((sighs[type<Get>()].destruction.sink().template connect<&registry::destroy_if<handler_type>>()), ...);
+                ((sighs[type<Get>()].construction.sink().template connect<&construct_if<&registry::has<Get...>, &registry::accept<0, Exclude...>, handler_type>>()), ...);
+                ((sighs[type<Exclude>()].destruction.sink().template connect<&construct_if<&registry::has<Get...>, &registry::accept<1, Exclude...>, handler_type>>()), ...);
+                ((sighs[type<Exclude>()].construction.sink().template connect<&registry::destroy_if<handler_type>>()), ...);
 
-            for(const auto entity: view<Component...>()) {
-                if(!any_of<Exclude...>(entity)) {
-                    direct->construct(entity);
+                for(const auto entity: view<Get...>()) {
+                    if(accept<0, Exclude...>(entity)) {
+                        direct->construct(entity);
+                    }
                 }
             }
+
+            return { handlers[htype].get(), &pool<Get>()... };
+        } else {
+            const auto dtype = handler_family::type<Owned...>;
+
+            if(!(dtype < descriptors.size())) {
+                descriptors.resize(dtype + 1);
+            }
+
+            if(!descriptors[dtype]) {
+                assert((!owned<Owned>() && ...));
+                descriptors[dtype] = std::make_unique<descriptor<Owned...>>();
+
+                (sighs[type<Owned>()].construction.sink().template connect<&induce_if<&registry::has<Owned..., Get...>, &registry::accept<0, Exclude...>, Owned...>>(), ...);
+                (sighs[type<Get>()].construction.sink().template connect<&induce_if<&registry::has<Owned..., Get...>, &registry::accept<0, Exclude...>, Owned...>>(), ...);
+
+                (sighs[type<Owned>()].destruction.sink().template connect<&discard_if<Owned...>>(), ...);
+                (sighs[type<Get>()].destruction.sink().template connect<&discard_if<Owned...>>(), ...);
+
+                (sighs[type<Exclude>()].destruction.sink().template connect<&induce_if<&registry::has<Owned..., Get...>, &registry::accept<1, Exclude...>, Owned...>>(), ...);
+                (sighs[type<Exclude>()].construction.sink().template connect<&discard_if<Owned...>>(), ...);
+
+                const auto *cpool = std::min({ pools[type<Owned>()].get()... }, [](const auto *lhs, const auto *rhs) {
+                    return lhs->size() < rhs->size();
+                });
+
+                std::for_each(cpool->data(), cpool->data() + cpool->size(), [this](const auto entity) {
+                    induce_if<&registry::has<Owned..., Get...>, &registry::accept<0, Exclude...>, Owned...>(*this, entity);
+                });
+            }
+
+            return { &descriptors[dtype]->last, &pool<Owned>()..., &pool<Get>()... };
         }
+    }
 
-        return { handlers[htype].get(), &assure<Component>()... };
+    /*! @copydoc group */
+    template<typename... Owned, typename... Get, typename... Exclude>
+    inline entt::group<Entity, get_t<Get...>, Owned...> group(get_t<Get...>, exclude_t<Exclude...> = {}) const {
+        static_assert(std::conjunction_v<std::is_const<Owned>..., std::is_const<Get>...>);
+        return const_cast<registry *>(this)->group<Owned...>(entt::get<Get...>, exclude<Exclude...>);
     }
 
-    /*! @copydoc persistent_view */
-    template<typename... Component, typename... Exclude>
-    inline entt::persistent_view<Entity, Component...> persistent_view(type_list<Exclude...> = {}) const {
-        static_assert(std::conjunction_v<std::is_const<Component>...>);
-        return const_cast<registry *>(this)->persistent_view<Component...>(type_list<Exclude...>{});
+    /*! @copydoc group */
+    template<typename... Owned, typename... Exclude>
+    inline entt::group<Entity, get_t<>, Owned...> group(exclude_t<Exclude...> = {}) {
+        return group<Owned...>(entt::get<>, exclude<Exclude...>);
+    }
+
+    /*! @copydoc group */
+    template<typename... Owned, typename... Exclude>
+    inline entt::group<Entity, get_t<>, Owned...> group(exclude_t<Exclude...> = {}) const {
+        return group<Owned...>(entt::get<>, exclude<Exclude...>);
     }
 
     /**
      * @brief Returns a runtime view for the given components.
      *
-     * This kind of views are created on the fly and share with the registry its
-     * internal data structures.<br/>
+     * This kind of objects are created on the fly and share with the registry
+     * its internal data structures.<br/>
      * Users should throw away the view after use. Fortunately, creating and
-     * destroying a view is an incredibly cheap operation because they do not
-     * require any type of initialization.<br/>
+     * destroying a runtime 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.
      *
      * Runtime views are well suited when users want to construct a view from
@@ -1142,11 +1281,6 @@ public:
      * components.<br/>
      * This is particularly well suited to plugin systems and mods in general.
      *
-     * @sa view
-     * @sa view<Entity, Component>
-     * @sa persistent_view
-     * @sa runtime_view
-     *
      * @tparam It Type of forward iterator.
      * @param first An iterator to the first element of the range of components.
      * @param last An iterator past the last element of the range of components.
@@ -1172,6 +1306,15 @@ public:
      * If no components are provided, the registry will try to clone all the
      * existing pools.
      *
+     * @note
+     * There isn't an efficient way to know if all the entities are assigned at
+     * least one component once copied. Therefore, there may be orphans. It is
+     * up to the caller to clean up the registry if necessary.
+     *
+     * @note
+     * Listeners and groups aren't copied. It is up to the caller to connect the
+     * listeners of interest to the new registry and to set up groups.
+     *
      * @warning
      * Attempting to clone components that aren't copyable can result in
      * unexpected behaviors.<br/>
@@ -1180,22 +1323,20 @@ public:
      * execution at runtime in debug mode in case one or more pools cannot be
      * cloned.
      *
-     * @note
-     * There isn't an efficient way to know if all the entities are assigned at
-     * least one component once copied. Therefore, there may be orphans. It is
-     * up to the caller to clean up the registry if necessary.
-     *
      * @tparam Component Types of components to clone.
      * @return A fresh copy of the registry.
      */
     template<typename... Component>
     registry clone() const {
         registry other;
+
         other.pools.resize(pools.size());
+        other.sighs.resize(pools.size());
 
         if(sizeof...(Component)) {
             static_assert(std::conjunction_v<std::is_copy_constructible<Component>...>);
-            ((other.pools[component_family::type<Component>] = assure<Component>().clone()), ...);
+            ((other.pools[type<Component>()] = assure<Component>().clone()), ...);
+            assert((other.pools[type<Component>()] && ...));
         } else {
             for(auto pos = pools.size(); pos; --pos) {
                 auto &cpool = pools[pos-1];
@@ -1209,8 +1350,7 @@ public:
 
         other.next = next;
         other.available = available;
-        other.entities.resize(entities.size());
-        std::copy(entities.cbegin(), entities.cend(), other.entities.begin());
+        other.entities = entities;
 
         return other;
     }
@@ -1283,8 +1423,9 @@ public:
 
 private:
     std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
+    std::vector<std::unique_ptr<basic_descriptor>> descriptors;
     mutable std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
-    mutable std::vector<std::pair<signal_type, signal_type>> sighs;
+    mutable std::vector<signals> sighs;
     std::vector<entity_type> entities;
     size_type available{};
     entity_type next{};

+ 4 - 4
src/entt/entity/sparse_set.hpp

@@ -187,7 +187,7 @@ public:
      *
      * @param cap Desired capacity.
      */
-    void reserve(const size_type cap) {
+    virtual void reserve(const size_type cap) {
         direct.reserve(cap);
     }
 
@@ -441,7 +441,7 @@ public:
      *
      * @param other The sparse sets that imposes the order of the entities.
      */
-    void respect(const sparse_set &other) ENTT_NOEXCEPT {
+    virtual void respect(const sparse_set &other) ENTT_NOEXCEPT {
         const auto to = other.end();
         auto from = other.begin();
 
@@ -747,7 +747,7 @@ public:
      *
      * @param cap Desired capacity.
      */
-    void reserve(const size_type cap) {
+    void reserve(const size_type cap) override {
         underlying_type::reserve(cap);
 
         if constexpr(!std::is_empty_v<object_type>) {
@@ -1043,7 +1043,7 @@ public:
      *
      * @param other The sparse sets that imposes the order of the entities.
      */
-    void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT {
+    void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override {
         if constexpr(std::is_empty_v<object_type>) {
             underlying_type::respect(other);
         } else {

+ 15 - 288
src/entt/entity/view.hpp

@@ -4,7 +4,6 @@
 
 #include <iterator>
 #include <cassert>
-#include <cstddef>
 #include <array>
 #include <tuple>
 #include <vector>
@@ -12,6 +11,7 @@
 #include <algorithm>
 #include <type_traits>
 #include "../config/config.h"
+#include "../core/type_traits.hpp"
 #include "entt_traits.hpp"
 #include "sparse_set.hpp"
 
@@ -26,267 +26,6 @@ template<typename>
 class registry;
 
 
-/**
- * @brief Persistent view.
- *
- * A persistent view returns all the entities and only the entities that have
- * at least the given components. Moreover, it's guaranteed that the entity list
- * is tightly packed in memory for fast iterations.<br/>
- * In general, persistent views don't stay true to the order of any set of
- * components unless users explicitly sort them.
- *
- * @b Important
- *
- * Iterators aren't invalidated if:
- *
- * * New instances of the given components are created and assigned to entities.
- * * The entity currently pointed is modified (as an example, if one of the
- *   given components is removed from the entity to which the iterator points).
- *
- * In all the other cases, modifying the pools of the given components in any
- * way invalidates all the iterators and using them results in undefined
- * behavior.
- *
- * @note
- * Views share references to the underlying data structures with the registry
- * that generated them. Therefore any change to the entities and to the
- * components made by means of the registry are immediately reflected by all the
- * views.<br/>
- * Moreover, sorting a persistent view affects all the other views of the same
- * type (it means that users don't have to call `sort` on each view to sort all
- * of them because they share the set of entities).
- *
- * @warning
- * Lifetime of a view must overcome the one of the registry that generated it.
- * In any other case, attempting to use a view results in undefined behavior.
- *
- * @sa view
- * @sa view<Entity, Component>
- * @sa runtime_view
- *
- * @tparam Entity A valid entity type (see entt_traits for more details).
- * @tparam Component Types of components iterated by the view.
- */
-template<typename Entity, typename... Component>
-class persistent_view {
-    static_assert(sizeof...(Component));
-
-    /*! @brief A registry is allowed to create views. */
-    friend class registry<Entity>;
-
-    template<typename Comp>
-    using pool_type = std::conditional_t<std::is_const_v<Comp>, const sparse_set<Entity, std::remove_const_t<Comp>>, sparse_set<Entity, Comp>>;
-
-    // we could use pool_type<Component> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
-    persistent_view(sparse_set<Entity> *handler, sparse_set<Entity, std::remove_const_t<Component>> *... pools) ENTT_NOEXCEPT
-        : handler{handler},
-          pools{pools...}
-    {}
-
-public:
-    /*! @brief Underlying entity identifier. */
-    using entity_type = typename sparse_set<Entity>::entity_type;
-    /*! @brief Unsigned integer type. */
-    using size_type = typename sparse_set<Entity>::size_type;
-    /*! @brief Input iterator type. */
-    using iterator_type = typename sparse_set<Entity>::iterator_type;
-
-    /*! @brief Default copy constructor. */
-    persistent_view(const persistent_view &) = default;
-    /*! @brief Default move constructor. */
-    persistent_view(persistent_view &&) = default;
-
-    /*! @brief Default copy assignment operator. @return This view. */
-    persistent_view & operator=(const persistent_view &) = default;
-    /*! @brief Default move assignment operator. @return This view. */
-    persistent_view & operator=(persistent_view &&) = default;
-
-    /**
-     * @brief Returns the number of entities that have the given components.
-     * @return Number of entities that have the given components.
-     */
-    size_type size() const ENTT_NOEXCEPT {
-        return handler->size();
-    }
-
-    /**
-     * @brief Checks whether the view is empty.
-     * @return True if the view is empty, false otherwise.
-     */
-    bool empty() const ENTT_NOEXCEPT {
-        return handler->empty();
-    }
-
-    /**
-     * @brief Direct access to the list of entities.
-     *
-     * The returned pointer is such that range `[data(), data() + size()]` is
-     * always a valid range, even if the container is empty.
-     *
-     * @note
-     * There are no guarantees on the order of the entities. Use `begin` and
-     * `end` if you want to iterate the view in the expected order.
-     *
-     * @return A pointer to the array of entities.
-     */
-    const entity_type * data() const ENTT_NOEXCEPT {
-        return handler->data();
-    }
-
-    /**
-     * @brief Returns an iterator to the first entity that has the given
-     * components.
-     *
-     * The returned iterator points to the first entity that has the given
-     * components. If the view is empty, the returned iterator will be equal to
-     * `end()`.
-     *
-     * @note
-     * Input iterators stay true to the order imposed to the underlying data
-     * structures.
-     *
-     * @return An iterator to the first entity that has the given components.
-     */
-    iterator_type begin() const ENTT_NOEXCEPT {
-        return handler->begin();
-    }
-
-    /**
-     * @brief Returns an iterator that is past the last entity that has the
-     * given components.
-     *
-     * The returned iterator points to the entity following the last entity that
-     * has the given components. Attempting to dereference the returned iterator
-     * results in undefined behavior.
-     *
-     * @note
-     * Input iterators stay true to the order imposed to the underlying data
-     * structures.
-     *
-     * @return An iterator to the entity following the last entity that has the
-     * given components.
-     */
-    iterator_type end() const ENTT_NOEXCEPT {
-        return handler->end();
-    }
-
-    /**
-     * @brief Finds an entity.
-     * @param entity A valid entity identifier.
-     * @return An iterator to the given entity if it's found, past the end
-     * iterator otherwise.
-     */
-    iterator_type find(const entity_type entity) const ENTT_NOEXCEPT {
-        const auto it = handler->find(entity);
-        return it != end() && *it == entity ? it : end();
-    }
-
-    /**
-     * @brief Returns the identifier that occupies the given position.
-     * @param pos Position of the element to return.
-     * @return The identifier that occupies the given position.
-     */
-    entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
-        return begin()[pos];
-    }
-
-    /**
-     * @brief Checks if a view contains an entity.
-     * @param entity A valid entity identifier.
-     * @return True if the view contains the given entity, false otherwise.
-     */
-    bool contains(const entity_type entity) const ENTT_NOEXCEPT {
-        return find(entity) != end();
-    }
-
-    /**
-     * @brief Returns the components assigned to the given entity.
-     *
-     * Prefer this function instead of `registry::get` during iterations. It has
-     * far better performance than its companion function.
-     *
-     * @warning
-     * Attempting to use an invalid component type results in a compilation
-     * error. Attempting to use an entity that doesn't belong to the view
-     * results in undefined behavior.<br/>
-     * An assertion will abort the execution at runtime in debug mode if the
-     * view doesn't contain the given entity.
-     *
-     * @tparam Comp Types of components to get.
-     * @param entity A valid entity identifier.
-     * @return The components assigned to the entity.
-     */
-    template<typename... Comp>
-    std::conditional_t<sizeof...(Comp) == 1, std::tuple_element_t<0, std::tuple<Comp &...>>, std::tuple<Comp &...>>
-    get([[maybe_unused]] const entity_type entity) const ENTT_NOEXCEPT {
-        assert(contains(entity));
-
-        if constexpr(sizeof...(Comp) == 1) {
-            static_assert(std::disjunction_v<std::is_same<Comp..., Component>..., std::is_same<std::remove_const_t<Comp>..., Component>...>);
-            return (std::get<pool_type<Comp> *>(pools)->get(entity), ...);
-        } else {
-            return std::tuple<Comp &...>{get<Comp>(entity)...};
-        }
-    }
-
-    /**
-     * @brief Iterates entities and components and applies the given function
-     * object to them.
-     *
-     * The function object is invoked for each entity. It is provided with the
-     * entity itself and a set of references to all its components. The
-     * _constness_ of the components is as requested.<br/>
-     * The signature of the function must be equivalent to one of the following
-     * forms:
-     *
-     * @code{.cpp}
-     * void(const entity_type, Component &...);
-     * void(Component &...);
-     * @endcode
-     *
-     * @tparam Func Type of the function object to invoke.
-     * @param func A valid function object.
-     */
-    template<typename Func>
-    inline void each(Func func) const {
-        if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Component>...>) {
-            std::for_each(handler->begin(), handler->end(), [func = std::move(func), this](const auto entity) mutable {
-                func(std::get<pool_type<Component> *>(pools)->get(entity)...);
-            });
-        } else {
-            std::for_each(handler->begin(), handler->end(), [func = std::move(func), this](const auto entity) mutable {
-                func(entity, std::get<pool_type<Component> *>(pools)->get(entity)...);
-            });
-        }
-    }
-
-    /**
-     * @brief Sort the shared pool of entities according to the given component.
-     *
-     * Persistent views of the same type share with the registry a pool of
-     * entities with its own order that doesn't depend on the order of any pool
-     * of components. Users can order the underlying data structure so that it
-     * respects the order of the pool of the given component.
-     *
-     * @note
-     * The shared pool of entities and thus its order is affected by the changes
-     * to each and every pool that it tracks. Therefore changes to those pools
-     * can quickly ruin the order imposed to the pool of entities shared between
-     * the persistent views.
-     *
-     * @tparam Comp Type of component to use to impose the order.
-     */
-    template<typename Comp>
-    void sort() const {
-        handler->respect(*std::get<pool_type<Comp> *>(pools));
-    }
-
-private:
-    sparse_set<entity_type> *handler;
-    const std::tuple<pool_type<Component> *...> pools;
-};
-
-
 /**
  * @brief Multi component view.
  *
@@ -312,18 +51,14 @@ private:
  * behavior.
  *
  * @note
- * Views share references to the underlying data structures with the registry
- * that generated them. Therefore any change to the entities and to the
- * components made by means of the registry are immediately reflected by views.
+ * Views share references to the underlying data structures of the registry that
+ * generated them. Therefore any change to the entities and to the components
+ * made by means of the registry are immediately reflected by views.
  *
  * @warning
  * Lifetime of a view must overcome the one of the registry that generated it.
  * In any other case, attempting to use a view results in undefined behavior.
  *
- * @sa view<Entity, Component>
- * @sa persistent_view
- * @sa runtime_view
- *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Types of components iterated by the view.
  */
@@ -360,7 +95,7 @@ class view {
             }
         }
 
-        template<std::size_t... Indexes>
+        template<auto... Indexes>
         extent_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
             return std::min({ std::get<Indexes>(unchecked)->extent()... });
         }
@@ -431,7 +166,7 @@ class view {
 
     unchecked_type unchecked(const sparse_set<Entity> *view) const ENTT_NOEXCEPT {
         unchecked_type other{};
-        std::size_t pos{};
+        typename unchecked_type::size_type pos{};
         ((std::get<pool_type<Component> *>(pools) == view ? nullptr : (other[pos++] = std::get<pool_type<Component> *>(pools))), ...);
         return other;
     }
@@ -445,7 +180,7 @@ class view {
         }
     }
 
-    template<typename Comp, typename Func, std::size_t... Indexes>
+    template<typename Comp, typename Func, auto... Indexes>
     void each(pool_type<Comp> *cpool, Func func, std::index_sequence<Indexes...>) const {
         const auto other = unchecked(cpool);
         std::array<underlying_iterator_type, sizeof...(Indexes)> data{{std::get<Indexes>(other)->begin()...}};
@@ -657,18 +392,14 @@ private:
  * invalidates all the iterators and using them results in undefined behavior.
  *
  * @note
- * Views share a reference to the underlying data structure with the registry
- * that generated them. Therefore any change to the entities and to the
- * components made by means of the registry are immediately reflected by views.
+ * Views share a reference to the underlying data structure of the registry that
+ * generated them. Therefore any change to the entities and to the components
+ * made by means of the registry are immediately reflected by views.
  *
  * @warning
  * Lifetime of a view must overcome the one of the registry that generated it.
  * In any other case, attempting to use a view results in undefined behavior.
  *
- * @sa view
- * @sa persistent_view
- * @sa runtime_view
- *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Type of component iterated by the view.
  */
@@ -900,20 +631,16 @@ private:
  * behavior.
  *
  * @note
- * Views share references to the underlying data structures with the registry
- * that generated them. Therefore any change to the entities and to the
- * components made by means of the registry are immediately reflected by views,
- * unless a pool wasn't missing when the view was built (in this case, the view
- * won't have a valid reference and won't be updated accordingly).
+ * Views share references to the underlying data structures of the registry that
+ * generated them. Therefore any change to the entities and to the components
+ * made by means of the registry are immediately reflected by the views, unless
+ * a pool was missing when the view was built (in this case, the view won't
+ * have a valid reference and won't be updated accordingly).
  *
  * @warning
  * Lifetime of a view must overcome the one of the registry that generated it.
  * In any other case, attempting to use a view results in undefined behavior.
  *
- * @sa view
- * @sa view<Entity, Component>
- * @sa persistent_view
- *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  */
 template<typename Entity>

+ 1 - 0
src/entt/entt.hpp

@@ -8,6 +8,7 @@
 #include "entity/actor.hpp"
 #include "entity/entity.hpp"
 #include "entity/entt_traits.hpp"
+#include "entity/group.hpp"
 #include "entity/helper.hpp"
 #include "entity/prototype.hpp"
 #include "entity/registry.hpp"

+ 8 - 0
src/entt/signal/sigh.hpp

@@ -134,6 +134,14 @@ class sink<Ret(Args...)> {
     {}
 
 public:
+    /**
+     * @brief Returns false if at least a listener is connected to the sink.
+     * @return True if the sink has no listeners connected, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return calls->empty();
+    }
+
     /**
      * @brief Connects a free function to a signal.
      *

+ 1 - 0
test/CMakeLists.txt

@@ -70,6 +70,7 @@ SETUP_AND_ADD_TEST(utility entt/core/utility.cpp)
 
 SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
 SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
+SETUP_AND_ADD_TEST(group entt/entity/group.cpp)
 SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
 SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)
 SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp)

+ 235 - 242
test/benchmark/benchmark.cpp

@@ -221,11 +221,11 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
     });
 }
 
-TEST(Benchmark, IterateTwoComponentsPersistent1M) {
+TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) {
     entt::registry<> registry;
-    registry.persistent_view<position, velocity>();
+    registry.group<>(entt::get<position, velocity>);
 
-    std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl;
+    std::cout << "Iterating over 1000000 entities, two components, non owning group" << std::endl;
 
     for(std::uint64_t i = 0; i < 1000000L; i++) {
         const auto entity = registry.create();
@@ -235,7 +235,55 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.persistent_view<position, velocity>().each(func);
+        registry.group<>(entt::get<position, velocity>).each(func);
+        timer.elapsed();
+    };
+
+    test([](const auto &...) {});
+    test([](auto &... comp) {
+        ((comp.x = {}), ...);
+    });
+}
+
+TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) {
+    entt::registry<> registry;
+    registry.group<position, velocity>();
+
+    std::cout << "Iterating over 1000000 entities, two components, full owning group" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<position>(entity);
+        registry.assign<velocity>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        timer timer;
+        registry.group<position, velocity>().each(func);
+        timer.elapsed();
+    };
+
+    test([](const auto &...) {});
+    test([](auto &... comp) {
+        ((comp.x = {}), ...);
+    });
+}
+
+TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) {
+    entt::registry<> registry;
+    registry.group<position>(entt::get<velocity>);
+
+    std::cout << "Iterating over 1000000 entities, two components, partial owning group" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<position>(entity);
+        registry.assign<velocity>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        timer timer;
+        registry.group<position>(entt::get<velocity>).each(func);
         timer.elapsed();
     };
 
@@ -341,14 +389,14 @@ TEST(Benchmark, IterateFiveComponents1M) {
         const auto entity = registry.create();
         registry.assign<position>(entity);
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
     }
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.view<position, velocity, comp<1>, comp<2>, comp<3>>().each(func);
+        registry.view<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
         timer.elapsed();
     };
 
@@ -366,9 +414,9 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
     for(std::uint64_t i = 0; i < 1000000L; i++) {
         const auto entity = registry.create();
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
 
         if(i % 2) {
             registry.assign<position>(entity);
@@ -377,7 +425,7 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.view<position, velocity, comp<1>, comp<2>, comp<3>>().each(func);
+        registry.view<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
         timer.elapsed();
     };
 
@@ -395,9 +443,9 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
     for(std::uint64_t i = 0; i < 1000000L; i++) {
         const auto entity = registry.create();
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
 
         if(i == 5000000L) {
             registry.assign<position>(entity);
@@ -406,7 +454,61 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.view<position, velocity, comp<1>, comp<2>, comp<3>>().each(func);
+        registry.view<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
+        timer.elapsed();
+    };
+
+    test([](const auto &...) {});
+    test([](auto &... comp) {
+        ((comp.x = {}), ...);
+    });
+}
+
+TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) {
+    entt::registry<> registry;
+    registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>);
+
+    std::cout << "Iterating over 1000000 entities, five components, non owning group" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<position>(entity);
+        registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
+        registry.assign<comp<1>>(entity);
+        registry.assign<comp<2>>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        timer timer;
+        registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>).each(func);
+        timer.elapsed();
+    };
+
+    test([](const auto &...) {});
+    test([](auto &... comp) {
+        ((comp.x = {}), ...);
+    });
+}
+
+TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) {
+    entt::registry<> registry;
+    registry.group<position, velocity, comp<0>, comp<1>, comp<2>>();
+
+    std::cout << "Iterating over 1000000 entities, five components, full owning group" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<position>(entity);
+        registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
+        registry.assign<comp<1>>(entity);
+        registry.assign<comp<2>>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        timer timer;
+        registry.group<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
         timer.elapsed();
     };
 
@@ -416,24 +518,51 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
     });
 }
 
-TEST(Benchmark, IterateFiveComponentsPersistent1M) {
+TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) {
     entt::registry<> registry;
-    registry.persistent_view<position, velocity, comp<1>, comp<2>, comp<3>>();
+    registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>);
 
-    std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl;
+    std::cout << "Iterating over 1000000 entities, five components, partial (4 of 5) owning group" << std::endl;
 
     for(std::uint64_t i = 0; i < 1000000L; i++) {
         const auto entity = registry.create();
         registry.assign<position>(entity);
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
     }
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.persistent_view<position, velocity, comp<1>, comp<2>, comp<3>>().each(func);
+        registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>).each(func);
+        timer.elapsed();
+    };
+
+    test([](const auto &...) {});
+    test([](auto &... comp) {
+        ((comp.x = {}), ...);
+    });
+}
+
+TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) {
+    entt::registry<> registry;
+    registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>);
+
+    std::cout << "Iterating over 1000000 entities, five components, partial (3 of 5) owning group" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<position>(entity);
+        registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
+        registry.assign<comp<1>>(entity);
+        registry.assign<comp<2>>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        timer timer;
+        registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>).each(func);
         timer.elapsed();
     };
 
@@ -452,9 +581,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
         const auto entity = registry.create();
         registry.assign<position>(entity);
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
     }
 
     auto test = [&registry](auto func) {
@@ -462,9 +591,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
         component_type types[] = {
             registry.type<position>(),
             registry.type<velocity>(),
+            registry.type<comp<0>>(),
             registry.type<comp<1>>(),
-            registry.type<comp<2>>(),
-            registry.type<comp<3>>()
+            registry.type<comp<2>>()
         };
 
         timer timer;
@@ -476,9 +605,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
     test([&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
+        registry.get<comp<0>>(entity).x = {};
         registry.get<comp<1>>(entity).x = {};
         registry.get<comp<2>>(entity).x = {};
-        registry.get<comp<3>>(entity).x = {};
     });
 }
 
@@ -490,9 +619,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
     for(std::uint64_t i = 0; i < 1000000L; i++) {
         const auto entity = registry.create();
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
 
         if(i % 2) {
             registry.assign<position>(entity);
@@ -504,9 +633,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
         component_type types[] = {
             registry.type<position>(),
             registry.type<velocity>(),
+            registry.type<comp<0>>(),
             registry.type<comp<1>>(),
-            registry.type<comp<2>>(),
-            registry.type<comp<3>>()
+            registry.type<comp<2>>()
         };
 
         timer timer;
@@ -518,9 +647,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
     test([&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
+        registry.get<comp<0>>(entity).x = {};
         registry.get<comp<1>>(entity).x = {};
         registry.get<comp<2>>(entity).x = {};
-        registry.get<comp<3>>(entity).x = {};
     });
 }
 
@@ -532,9 +661,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
     for(std::uint64_t i = 0; i < 1000000L; i++) {
         const auto entity = registry.create();
         registry.assign<velocity>(entity);
+        registry.assign<comp<0>>(entity);
         registry.assign<comp<1>>(entity);
         registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
 
         if(i == 5000000L) {
             registry.assign<position>(entity);
@@ -546,9 +675,9 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
         component_type types[] = {
             registry.type<position>(),
             registry.type<velocity>(),
+            registry.type<comp<0>>(),
             registry.type<comp<1>>(),
-            registry.type<comp<2>>(),
-            registry.type<comp<3>>()
+            registry.type<comp<2>>()
         };
 
         timer timer;
@@ -560,102 +689,84 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
     test([&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
+        registry.get<comp<0>>(entity).x = {};
         registry.get<comp<1>>(entity).x = {};
         registry.get<comp<2>>(entity).x = {};
-        registry.get<comp<3>>(entity).x = {};
     });
 }
 
-TEST(Benchmark, IterateTenComponents1M) {
+TEST(Benchmark, IteratePathological) {
     entt::registry<> registry;
 
-    std::cout << "Iterating over 1000000 entities, ten components" << std::endl;
+    std::cout << "Pathological case" << std::endl;
 
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
+    for(std::uint64_t i = 0; i < 500000L; i++) {
         const auto entity = registry.create();
         registry.assign<position>(entity);
         registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
+        registry.assign<comp<0>>(entity);
     }
 
-    auto test = [&registry](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](const auto &...) {});
-    test([](auto &... comp) {
-        ((comp.x = {}), ...);
-    });
-}
-
-TEST(Benchmark, IterateTenComponents1MHalf) {
-    entt::registry<> registry;
-
-    std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components" << std::endl;
+    for(auto i = 0; i < 10; ++i) {
+        registry.each([i = 0, &registry](const auto entity) mutable {
+            if(i % 7) { registry.remove<position>(entity); }
+            if(i % 11) { registry.remove<velocity>(entity); }
+            if(i % 13) { registry.remove<comp<0>>(entity); }
+            if(i % 17) { registry.destroy(entity); }
+        });
 
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
-        const auto entity = registry.create();
-        registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
-
-        if(i % 2) {
+        for(std::uint64_t i = 0; i < 50000L; i++) {
+            const auto entity = registry.create();
             registry.assign<position>(entity);
+            registry.assign<velocity>(entity);
+            registry.assign<comp<0>>(entity);
         }
     }
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>().each(func);
+        registry.view<position, velocity, comp<0>>().each(func);
         timer.elapsed();
     };
 
-    test([](auto &...) {});
+    test([](const auto &...) {});
     test([](auto &... comp) {
         ((comp.x = {}), ...);
     });
 }
 
-TEST(Benchmark, IterateTenComponents1MOne) {
+TEST(Benchmark, IteratePathologicalNonOwningGroup) {
     entt::registry<> registry;
+    registry.group<>(entt::get<position, velocity, comp<0>>);
 
-    std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components" << std::endl;
+    std::cout << "Pathological case" << std::endl;
 
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
+    for(std::uint64_t i = 0; i < 500000L; i++) {
         const auto entity = registry.create();
+        registry.assign<position>(entity);
         registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
+        registry.assign<comp<0>>(entity);
+    }
 
-        if(i == 5000000L) {
+    for(auto i = 0; i < 10; ++i) {
+        registry.each([i = 0, &registry](const auto entity) mutable {
+            if(i % 7) { registry.remove<position>(entity); }
+            if(i % 11) { registry.remove<velocity>(entity); }
+            if(i % 13) { registry.remove<comp<0>>(entity); }
+            if(i % 17) { registry.destroy(entity); }
+        });
+
+        for(std::uint64_t i = 0; i < 50000L; i++) {
+            const auto entity = registry.create();
             registry.assign<position>(entity);
+            registry.assign<velocity>(entity);
+            registry.assign<comp<0>>(entity);
         }
     }
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>().each(func);
+        registry.group<>(entt::get<position, velocity, comp<0>>).each(func);
         timer.elapsed();
     };
 
@@ -665,29 +776,38 @@ TEST(Benchmark, IterateTenComponents1MOne) {
     });
 }
 
-TEST(Benchmark, IterateTenComponentsPersistent1M) {
+TEST(Benchmark, IteratePathologicalFullOwningGroup) {
     entt::registry<> registry;
-    registry.persistent_view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>();
+    registry.group<position, velocity, comp<0>>();
 
-    std::cout << "Iterating over 1000000 entities, ten components, persistent view" << std::endl;
+    std::cout << "Pathological case" << std::endl;
 
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
+    for(std::uint64_t i = 0; i < 500000L; i++) {
         const auto entity = registry.create();
         registry.assign<position>(entity);
         registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
+        registry.assign<comp<0>>(entity);
+    }
+
+    for(auto i = 0; i < 10; ++i) {
+        registry.each([i = 0, &registry](const auto entity) mutable {
+            if(i % 7) { registry.remove<position>(entity); }
+            if(i % 11) { registry.remove<velocity>(entity); }
+            if(i % 13) { registry.remove<comp<0>>(entity); }
+            if(i % 17) { registry.destroy(entity); }
+        });
+
+        for(std::uint64_t i = 0; i < 50000L; i++) {
+            const auto entity = registry.create();
+            registry.assign<position>(entity);
+            registry.assign<velocity>(entity);
+            registry.assign<comp<0>>(entity);
+        }
     }
 
     auto test = [&registry](auto func) {
         timer timer;
-        registry.persistent_view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>().each(func);
+        registry.group<position, velocity, comp<0>>().each(func);
         timer.elapsed();
     };
 
@@ -697,171 +817,44 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
     });
 }
 
-TEST(Benchmark, IterateTenComponentsRuntime1M) {
+TEST(Benchmark, IteratePathologicalPartialOwningGroup) {
     entt::registry<> registry;
+    registry.group<position, velocity>(entt::get<comp<0>>);
 
-    std::cout << "Iterating over 1000000 entities, ten components, runtime view" << std::endl;
+    std::cout << "Pathological case" << std::endl;
 
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
+    for(std::uint64_t i = 0; i < 500000L; i++) {
         const auto entity = registry.create();
         registry.assign<position>(entity);
         registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
-    }
-
-    auto test = [&registry](auto func) {
-        using component_type = typename entt::registry<>::component_type;
-        component_type types[] = {
-            registry.type<position>(),
-            registry.type<velocity>(),
-            registry.type<comp<1>>(),
-            registry.type<comp<2>>(),
-            registry.type<comp<3>>(),
-            registry.type<comp<4>>(),
-            registry.type<comp<5>>(),
-            registry.type<comp<6>>(),
-            registry.type<comp<7>>(),
-            registry.type<comp<8>>()
-        };
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
-
-    test([](auto) {});
-    test([&registry](auto entity) {
-        registry.get<position>(entity).x = {};
-        registry.get<velocity>(entity).x = {};
-        registry.get<comp<1>>(entity).x = {};
-        registry.get<comp<2>>(entity).x = {};
-        registry.get<comp<3>>(entity).x = {};
-        registry.get<comp<4>>(entity).x = {};
-        registry.get<comp<5>>(entity).x = {};
-        registry.get<comp<6>>(entity).x = {};
-        registry.get<comp<7>>(entity).x = {};
-        registry.get<comp<8>>(entity).x = {};
-    });
-}
-
-TEST(Benchmark, IterateTenComponentsRuntime1MHalf) {
-    entt::registry<> registry;
-
-    std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components, runtime view" << std::endl;
-
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
-        const auto entity = registry.create();
-        registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
-
-        if(i % 2) {
-            registry.assign<position>(entity);
-        }
+        registry.assign<comp<0>>(entity);
     }
 
-    auto test = [&registry](auto func) {
-        using component_type = typename entt::registry<>::component_type;
-        component_type types[] = {
-            registry.type<position>(),
-            registry.type<velocity>(),
-            registry.type<comp<1>>(),
-            registry.type<comp<2>>(),
-            registry.type<comp<3>>(),
-            registry.type<comp<4>>(),
-            registry.type<comp<5>>(),
-            registry.type<comp<6>>(),
-            registry.type<comp<7>>(),
-            registry.type<comp<8>>()
-        };
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    for(auto i = 0; i < 10; ++i) {
+        registry.each([i = 0, &registry](const auto entity) mutable {
+            if(i % 7) { registry.remove<position>(entity); }
+            if(i % 11) { registry.remove<velocity>(entity); }
+            if(i % 13) { registry.remove<comp<0>>(entity); }
+            if(i % 17) { registry.destroy(entity); }
+        });
 
-    test([](auto) {});
-    test([&registry](auto entity) {
-        registry.get<position>(entity).x = {};
-        registry.get<velocity>(entity).x = {};
-        registry.get<comp<1>>(entity).x = {};
-        registry.get<comp<2>>(entity).x = {};
-        registry.get<comp<3>>(entity).x = {};
-        registry.get<comp<4>>(entity).x = {};
-        registry.get<comp<5>>(entity).x = {};
-        registry.get<comp<6>>(entity).x = {};
-        registry.get<comp<7>>(entity).x = {};
-        registry.get<comp<8>>(entity).x = {};
-    });
-}
-
-TEST(Benchmark, IterateTenComponentsRuntime1MOne) {
-    entt::registry<> registry;
-
-    std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components, runtime view" << std::endl;
-
-    for(std::uint64_t i = 0; i < 1000000L; i++) {
-        const auto entity = registry.create();
-        registry.assign<velocity>(entity);
-        registry.assign<comp<1>>(entity);
-        registry.assign<comp<2>>(entity);
-        registry.assign<comp<3>>(entity);
-        registry.assign<comp<4>>(entity);
-        registry.assign<comp<5>>(entity);
-        registry.assign<comp<6>>(entity);
-        registry.assign<comp<7>>(entity);
-        registry.assign<comp<8>>(entity);
-
-        if(i == 5000000L) {
+        for(std::uint64_t i = 0; i < 50000L; i++) {
+            const auto entity = registry.create();
             registry.assign<position>(entity);
+            registry.assign<velocity>(entity);
+            registry.assign<comp<0>>(entity);
         }
     }
 
     auto test = [&registry](auto func) {
-        using component_type = typename entt::registry<>::component_type;
-        component_type types[] = {
-            registry.type<position>(),
-            registry.type<velocity>(),
-            registry.type<comp<1>>(),
-            registry.type<comp<2>>(),
-            registry.type<comp<3>>(),
-            registry.type<comp<4>>(),
-            registry.type<comp<5>>(),
-            registry.type<comp<6>>(),
-            registry.type<comp<7>>(),
-            registry.type<comp<8>>()
-        };
-
         timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
+        registry.group<position, velocity>(entt::get<comp<0>>).each(func);
         timer.elapsed();
     };
 
-    test([](auto) {});
-    test([&registry](auto entity) {
-        registry.get<position>(entity).x = {};
-        registry.get<velocity>(entity).x = {};
-        registry.get<comp<1>>(entity).x = {};
-        registry.get<comp<2>>(entity).x = {};
-        registry.get<comp<3>>(entity).x = {};
-        registry.get<comp<4>>(entity).x = {};
-        registry.get<comp<5>>(entity).x = {};
-        registry.get<comp<6>>(entity).x = {};
-        registry.get<comp<7>>(entity).x = {};
-        registry.get<comp<8>>(entity).x = {};
+    test([](const auto &...) {});
+    test([](auto &... comp) {
+        ((comp.x = {}), ...);
     });
 }
 

+ 685 - 0
test/entt/entity/group.cpp

@@ -0,0 +1,685 @@
+#include <utility>
+#include <iterator>
+#include <algorithm>
+#include <gtest/gtest.h>
+#include <entt/entity/registry.hpp>
+#include <entt/entity/group.hpp>
+
+TEST(NonOwningGroup, Functionalities) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, char>);
+    auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
+
+    ASSERT_TRUE(group.empty());
+
+    const auto e0 = registry.create();
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    ASSERT_FALSE(group.empty());
+    ASSERT_NO_THROW((group.begin()++));
+    ASSERT_NO_THROW((++cgroup.begin()));
+
+    ASSERT_NE(group.begin(), group.end());
+    ASSERT_NE(cgroup.begin(), cgroup.end());
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+
+    registry.assign<int>(e0);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+
+    registry.remove<int>(e0);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+
+    registry.get<char>(e0) = '1';
+    registry.get<char>(e1) = '2';
+    registry.get<int>(e1) = 42;
+
+    for(auto entity: group) {
+        ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
+        ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
+        ASSERT_EQ(cgroup.get<const char>(entity), '2');
+    }
+
+    ASSERT_EQ(*(group.data() + 0), e1);
+
+    registry.remove<char>(e0);
+    registry.remove<char>(e1);
+
+    ASSERT_EQ(group.begin(), group.end());
+    ASSERT_EQ(cgroup.begin(), cgroup.end());
+    ASSERT_TRUE(group.empty());
+}
+
+TEST(OwningGroup, Functionalities) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<char>);
+    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
+
+    ASSERT_TRUE(group.empty());
+
+    const auto e0 = registry.create();
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    ASSERT_FALSE(group.empty());
+    ASSERT_NO_THROW((group.begin()++));
+    ASSERT_NO_THROW((++cgroup.begin()));
+
+    ASSERT_NE(group.begin(), group.end());
+    ASSERT_NE(cgroup.begin(), cgroup.end());
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+
+    registry.assign<int>(e0);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+
+    registry.remove<int>(e0);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+
+    registry.get<char>(e0) = '1';
+    registry.get<char>(e1) = '2';
+    registry.get<int>(e1) = 42;
+
+    ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
+    ASSERT_EQ(*(group.raw<int>() + 0), 42);
+
+    for(auto entity: group) {
+        ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
+        ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
+        ASSERT_EQ(cgroup.get<const char>(entity), '2');
+    }
+
+    ASSERT_EQ(*(group.data() + 0), e1);
+
+    registry.remove<char>(e0);
+    registry.remove<char>(e1);
+
+    ASSERT_EQ(group.begin(), group.end());
+    ASSERT_EQ(cgroup.begin(), cgroup.end());
+    ASSERT_TRUE(group.empty());
+}
+
+TEST(NonOwningGroup, ElementAccess) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, char>);
+    auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    for(typename decltype(group)::size_type i{}; i < group.size(); ++i) {
+        ASSERT_EQ(group[i], i ? e0 : e1);
+        ASSERT_EQ(cgroup[i], i ? e0 : e1);
+    }
+}
+
+TEST(OwningGroup, ElementAccess) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<char>);
+    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    for(typename decltype(group)::size_type i{}; i < group.size(); ++i) {
+        ASSERT_EQ(group[i], i ? e0 : e1);
+        ASSERT_EQ(cgroup[i], i ? e0 : e1);
+    }
+}
+
+TEST(NonOwningGroup, Contains) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    registry.destroy(e0);
+
+    ASSERT_FALSE(group.contains(e0));
+    ASSERT_TRUE(group.contains(e1));
+}
+
+TEST(OwningGroup, Contains) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    registry.destroy(e0);
+
+    ASSERT_FALSE(group.contains(e0));
+    ASSERT_TRUE(group.contains(e1));
+}
+
+TEST(NonOwningGroup, Empty) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<double>(e0);
+    registry.assign<int>(e0);
+    registry.assign<float>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<char>(e1);
+    registry.assign<float>(e1);
+
+    for(auto entity: registry.group<>(entt::get<char, int, float>)) {
+        (void)entity;
+        FAIL();
+    }
+
+    for(auto entity: registry.group<>(entt::get<double, char, int, float>)) {
+        (void)entity;
+        FAIL();
+    }
+}
+
+TEST(OwningGroup, Empty) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<double>(e0);
+    registry.assign<int>(e0);
+    registry.assign<float>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<char>(e1);
+    registry.assign<float>(e1);
+
+    for(auto entity: registry.group<char, int>(entt::get<float>)) {
+        (void)entity;
+        FAIL();
+    }
+
+    for(auto entity: registry.group<char, int>(entt::get<double, float>)) {
+        (void)entity;
+        FAIL();
+    }
+}
+
+TEST(NonOwningGroup, Each) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
+    std::size_t cnt = 0;
+
+    group.each([&cnt](auto, int &, char &) { ++cnt; });
+    group.each([&cnt](int &, char &) { ++cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{4});
+
+    cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
+    cgroup.each([&cnt](const int &, const char &) { --cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{0});
+}
+
+TEST(OwningGroup, Each) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
+    std::size_t cnt = 0;
+
+    group.each([&cnt](auto, int &, char &) { ++cnt; });
+    group.each([&cnt](int &, char &) { ++cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{4});
+
+    cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
+    cgroup.each([&cnt](const int &, const char &) { --cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{0});
+}
+
+TEST(NonOwningGroup, Sort) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<const int, unsigned int>);
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+    const auto e2 = registry.create();
+
+    auto uval = 0u;
+    auto ival = 0;
+
+    registry.assign<unsigned int>(e0, uval++);
+    registry.assign<unsigned int>(e1, uval++);
+    registry.assign<unsigned int>(e2, uval++);
+
+    registry.assign<int>(e0, ival++);
+    registry.assign<int>(e1, ival++);
+    registry.assign<int>(e2, ival++);
+
+    for(auto entity: group) {
+        ASSERT_EQ(group.get<unsigned int>(entity), --uval);
+        ASSERT_EQ(group.get<const int>(entity), --ival);
+    }
+
+    registry.sort<unsigned int>(std::less<unsigned int>{});
+    group.sort<unsigned int>();
+
+    for(auto entity: group) {
+        ASSERT_EQ(group.get<unsigned int>(entity), uval++);
+        ASSERT_EQ(group.get<const int>(entity), ival++);
+    }
+}
+
+TEST(NonOwningGroup, IndexRebuiltOnDestroy) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, unsigned int>);
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+
+    registry.assign<unsigned int>(e0, 0u);
+    registry.assign<unsigned int>(e1, 1u);
+
+    registry.assign<int>(e0, 0);
+    registry.assign<int>(e1, 1);
+
+    registry.destroy(e0);
+    registry.assign<int>(registry.create(), 42);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group[{}], e1);
+    ASSERT_EQ(group.get<int>(e1), 1);
+    ASSERT_EQ(group.get<unsigned int>(e1), 1u);
+
+    group.each([e1](auto entity, auto ivalue, auto uivalue) {
+        ASSERT_EQ(entity, e1);
+        ASSERT_EQ(ivalue, 1);
+        ASSERT_EQ(uivalue, 1u);
+    });
+}
+
+TEST(OwningGroup, IndexRebuiltOnDestroy) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<unsigned int>);
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+
+    registry.assign<unsigned int>(e0, 0u);
+    registry.assign<unsigned int>(e1, 1u);
+
+    registry.assign<int>(e0, 0);
+    registry.assign<int>(e1, 1);
+
+    registry.destroy(e0);
+    registry.assign<int>(registry.create(), 42);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group[{}], e1);
+    ASSERT_EQ(group.get<int>(e1), 1);
+    ASSERT_EQ(group.get<unsigned int>(e1), 1u);
+
+    group.each([e1](auto entity, auto ivalue, auto uivalue) {
+        ASSERT_EQ(entity, e1);
+        ASSERT_EQ(ivalue, 1);
+        ASSERT_EQ(uivalue, 1u);
+    });
+}
+
+TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, const char>);
+
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char>(0)), std::tuple<int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+
+    group.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(OwningGroup, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto group = registry.group<int, const char>(entt::get<double, const float>);
+
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<double>(0)), double &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const double>(0)), const double &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const float>(0)), const float &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, double, const float>(0)), std::tuple<int &, const char &, double &, const float &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int, const char, const double, const float>(0)), std::tuple<const int &, const char &, const double &, const float &>>));
+
+    group.each([](auto, auto &&i, auto &&c, auto &&d, auto &&f) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
+        ASSERT_TRUE((std::is_same_v<decltype(d), double &>));
+        ASSERT_TRUE((std::is_same_v<decltype(f), const float &>));
+    });
+}
+
+TEST(NonOwningGroup, Find) {
+    entt::registry<> registry;
+    auto group = registry.group<>(entt::get<int, const char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2);
+    registry.assign<char>(e2);
+
+    const auto e3 = registry.create();
+    registry.assign<int>(e3);
+    registry.assign<char>(e3);
+
+    registry.remove<int>(e1);
+
+    ASSERT_NE(group.find(e0), group.end());
+    ASSERT_EQ(group.find(e1), group.end());
+    ASSERT_NE(group.find(e2), group.end());
+    ASSERT_NE(group.find(e3), group.end());
+
+    auto it = group.find(e2);
+
+    ASSERT_EQ(*it, e2);
+    ASSERT_EQ(*(++it), e3);
+    ASSERT_EQ(*(++it), e0);
+    ASSERT_EQ(++it, group.end());
+    ASSERT_EQ(++group.find(e0), group.end());
+
+    const auto e4 = registry.create();
+    registry.destroy(e4);
+    const auto e5 = registry.create();
+    registry.assign<int>(e5);
+    registry.assign<char>(e5);
+
+    ASSERT_NE(group.find(e5), group.end());
+    ASSERT_EQ(group.find(e4), group.end());
+}
+
+TEST(OwningGroup, Find) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<const char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2);
+    registry.assign<char>(e2);
+
+    const auto e3 = registry.create();
+    registry.assign<int>(e3);
+    registry.assign<char>(e3);
+
+    registry.remove<int>(e1);
+
+    ASSERT_NE(group.find(e0), group.end());
+    ASSERT_EQ(group.find(e1), group.end());
+    ASSERT_NE(group.find(e2), group.end());
+    ASSERT_NE(group.find(e3), group.end());
+
+    auto it = group.find(e2);
+
+    ASSERT_EQ(*it, e2);
+    ASSERT_EQ(*(++it), e3);
+    ASSERT_EQ(*(++it), e0);
+    ASSERT_EQ(++it, group.end());
+    ASSERT_EQ(++group.find(e0), group.end());
+
+    const auto e4 = registry.create();
+    registry.destroy(e4);
+    const auto e5 = registry.create();
+    registry.assign<int>(e5);
+    registry.assign<char>(e5);
+
+    ASSERT_NE(group.find(e5), group.end());
+    ASSERT_EQ(group.find(e4), group.end());
+}
+
+TEST(NonOwningGroup, ExcludedComponents) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 1);
+    registry.assign<char>(e1);
+
+    const auto group = registry.group<>(entt::get<int>, entt::exclude<char>);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 2);
+
+    const auto e3 = registry.create();
+    registry.assign<int>(e3, 3);
+    registry.assign<char>(e3);
+
+    for(const auto entity: group) {
+        if(entity == e0) {
+            ASSERT_EQ(group.get<int>(e0), 0);
+        } else if(entity == e2) {
+            ASSERT_EQ(group.get<int>(e2), 2);
+        } else {
+            FAIL();
+        }
+    }
+
+    registry.assign<char>(e0);
+    registry.assign<char>(e2);
+
+    ASSERT_TRUE(group.empty());
+
+    registry.remove<char>(e1);
+    registry.remove<char>(e3);
+
+    for(const auto entity: group) {
+        if(entity == e1) {
+            ASSERT_EQ(group.get<int>(e1), 1);
+        } else if(entity == e3) {
+            ASSERT_EQ(group.get<int>(e3), 3);
+        } else {
+            FAIL();
+        }
+    }
+}
+
+TEST(OwningGroup, ExcludedComponents) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 1);
+    registry.assign<char>(e1);
+
+    const auto group = registry.group<int>(entt::exclude<char>);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 2);
+
+    const auto e3 = registry.create();
+    registry.assign<int>(e3, 3);
+    registry.assign<char>(e3);
+
+    for(const auto entity: group) {
+        if(entity == e0) {
+            ASSERT_EQ(group.get<int>(e0), 0);
+        } else if(entity == e2) {
+            ASSERT_EQ(group.get<int>(e2), 2);
+        } else {
+            FAIL();
+        }
+    }
+
+    registry.assign<char>(e0);
+    registry.assign<char>(e2);
+
+    ASSERT_TRUE(group.empty());
+
+    registry.remove<char>(e1);
+    registry.remove<char>(e3);
+
+    for(const auto entity: group) {
+        if(entity == e1) {
+            ASSERT_EQ(group.get<int>(e1), 1);
+        } else if(entity == e3) {
+            ASSERT_EQ(group.get<int>(e3), 3);
+        } else {
+            FAIL();
+        }
+    }
+}
+
+TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
+    struct empty_type {};
+    entt::registry<> registry;
+    const auto group = registry.group<>(entt::get<int, empty_type>);
+
+    const auto e0 = registry.create();
+    registry.assign<empty_type>(e0);
+    registry.assign<int>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<empty_type>(e1);
+    registry.assign<int>(e1);
+
+    registry.assign<int>(registry.create());
+
+    for(const auto entity: group) {
+        ASSERT_TRUE(entity == e0 || entity == e1);
+    }
+
+    group.each([e0, e1](const auto entity, const int &, const empty_type &) {
+        ASSERT_TRUE(entity == e0 || entity == e1);
+    });
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
+}
+
+TEST(OwningGroup, EmptyAndNonEmptyTypes) {
+    struct empty_type {};
+    entt::registry<> registry;
+    const auto group = registry.group<empty_type>(entt::get<int>);
+
+    const auto e0 = registry.create();
+    registry.assign<empty_type>(e0);
+    registry.assign<int>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<empty_type>(e1);
+    registry.assign<int>(e1);
+
+    registry.assign<int>(registry.create());
+
+    for(const auto entity: group) {
+        ASSERT_TRUE(entity == e0 || entity == e1);
+    }
+
+    group.each([e0, e1](const auto entity, const empty_type &, const int &) {
+        ASSERT_TRUE(entity == e0 || entity == e1);
+    });
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
+}
+
+TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
+    entt::registry<> registry;
+    const entt::registry<> &cregistry = registry;
+    const auto group = registry.group<>(entt::get<int>, entt::exclude<char>);
+    const auto cgroup = cregistry.group<>(entt::get<const int>, entt::exclude<char>);
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    ASSERT_TRUE(group.empty());
+    ASSERT_TRUE(cgroup.empty());
+
+    registry.remove<char>(entity);
+
+    ASSERT_FALSE(group.empty());
+    ASSERT_FALSE(cgroup.empty());
+}
+
+TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
+    entt::registry<> registry;
+    const entt::registry<> &cregistry = registry;
+    const auto group = registry.group<int>(entt::exclude<char>);
+    const auto cgroup = cregistry.group<const int>(entt::exclude<char>);
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    ASSERT_TRUE(group.empty());
+    ASSERT_TRUE(cgroup.empty());
+
+    registry.remove<char>(entity);
+
+    ASSERT_FALSE(group.empty());
+    ASSERT_FALSE(cgroup.empty());
+}

+ 11 - 2
test/entt/entity/helper.cpp

@@ -10,10 +10,19 @@ TEST(Helper, AsView) {
     const entt::registry<> cregistry;
 
     ([](entt::view<entity_type, int, char>) {})(entt::as_view{registry});
+    ([](entt::view<entity_type, const int, char>) {})(entt::as_view{registry});
     ([](entt::view<entity_type, const double>) {})(entt::as_view{cregistry});
+}
+
+TEST(Helper, AsGroup) {
+    using entity_type = typename entt::registry<>::entity_type;
+
+    entt::registry<> registry;
+    const entt::registry<> cregistry;
 
-    ([](entt::persistent_view<entity_type, int, char>) {})(entt::as_view{registry});
-    ([](entt::persistent_view<entity_type, const double, const float>) {})(entt::as_view{cregistry});
+    ([](entt::group<entity_type, entt::get_t<>, double, float>) {})(entt::as_group{registry});
+    ([](entt::group<entity_type, entt::get_t<>, const double, float>) {})(entt::as_group{registry});
+    ([](entt::group<entity_type, entt::get_t<>, const double, const float>) {})(entt::as_group{cregistry});
 }
 
 TEST(Helper, Dependency) {

+ 296 - 23
test/entt/entity/registry.cpp

@@ -370,7 +370,7 @@ TEST(Registry, CreateDestroyEntities) {
     ASSERT_EQ(registry.current(pre), registry.current(post));
 }
 
-TEST(Registry, StandardView) {
+TEST(Registry, View) {
     entt::registry<> registry;
     auto mview = registry.view<int, char>();
     auto iview = registry.view<int>();
@@ -396,7 +396,7 @@ TEST(Registry, StandardView) {
     ASSERT_EQ(cnt, decltype(mview)::size_type{2});
 }
 
-TEST(Registry, PersistentView) {
+TEST(Registry, NonOwningGroupInitOnFirstUse) {
     entt::registry<> registry;
 
     const auto e0 = registry.create();
@@ -410,17 +410,172 @@ TEST(Registry, PersistentView) {
     registry.assign<int>(e2, 0);
     registry.assign<char>(e2, 'c');
 
-    auto view = registry.persistent_view<int, char>();
-    decltype(view)::size_type cnt{0};
-    view.each([&cnt](auto...) { ++cnt; });
+    ASSERT_FALSE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
 
-    ASSERT_EQ(cnt, decltype(view)::size_type{2});
+    auto group = registry.group<>(entt::get<int, char>);
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_FALSE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
 }
 
-TEST(Registry, CleanStandardViewAfterReset) {
+TEST(Registry, NonOwningGroupInitOnAssign) {
     entt::registry<> registry;
-    auto view = registry.view<int>();
-    registry.assign<int>(registry.create(), 0);
+    auto group = registry.group<>(entt::get<int, char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+    registry.assign<char>(e0, 'c');
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 0);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 0);
+    registry.assign<char>(e2, 'c');
+
+    ASSERT_FALSE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_FALSE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+}
+
+TEST(Registry, FullOwningGroupInitOnFirstUse) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+    registry.assign<char>(e0, 'c');
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 0);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 0);
+    registry.assign<char>(e2, 'c');
+
+    ASSERT_FALSE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+
+    auto group = registry.group<int, char>();
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_TRUE(registry.owned<int>());
+    ASSERT_TRUE(registry.owned<char>());
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+}
+
+TEST(Registry, FullOwningGroupInitOnAssign) {
+    entt::registry<> registry;
+    auto group = registry.group<int, char>();
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+    registry.assign<char>(e0, 'c');
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 0);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 0);
+    registry.assign<char>(e2, 'c');
+
+    ASSERT_TRUE(registry.owned<int>());
+    ASSERT_TRUE(registry.owned<char>());
+
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_TRUE(registry.owned<int>());
+    ASSERT_TRUE(registry.owned<char>());
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+}
+
+TEST(Registry, PartialOwningGroupInitOnFirstUse) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+    registry.assign<char>(e0, 'c');
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 0);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 0);
+    registry.assign<char>(e2, 'c');
+
+    ASSERT_FALSE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+
+    auto group = registry.group<int>(entt::get<char>);
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_TRUE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+
+}
+
+TEST(Registry, PartialOwningGroupInitOnAssign) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+    registry.assign<char>(e0, 'c');
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 0);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 0);
+    registry.assign<char>(e2, 'c');
+
+    ASSERT_TRUE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_TRUE(registry.owned<int>());
+    ASSERT_FALSE(registry.owned<char>());
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+}
+
+TEST(Registry, CleanViewAfterReset) {
+    entt::registry<> registry;
+    auto view = registry.view<int, char>();
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity, 0);
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(view.size(), entt::registry<>::size_type{1});
+
+    registry.reset<char>(entity);
+
+    ASSERT_EQ(view.size(), entt::registry<>::size_type{0});
+
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(view.size(), entt::registry<>::size_type{1});
+
+    registry.reset<int>();
+
+    ASSERT_EQ(view.size(), entt::registry<>::size_type{0});
+
+    registry.assign<int>(entity, 0);
 
     ASSERT_EQ(view.size(), entt::registry<>::size_type{1});
 
@@ -429,19 +584,97 @@ TEST(Registry, CleanStandardViewAfterReset) {
     ASSERT_EQ(view.size(), entt::registry<>::size_type{0});
 }
 
-TEST(Registry, CleanPersistentViewAfterReset) {
+TEST(Registry, CleanNonOwningGroupViewAfterReset) {
     entt::registry<> registry;
-    auto view = registry.persistent_view<int, char>();
+    auto group = registry.group<>(entt::get<int, char>);
 
     const auto entity = registry.create();
     registry.assign<int>(entity, 0);
     registry.assign<char>(entity, 'c');
 
-    ASSERT_EQ(view.size(), entt::registry<>::size_type{1});
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset<char>(entity);
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset<int>();
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+
+    registry.assign<int>(entity, 0);
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
 
     registry.reset();
 
-    ASSERT_EQ(view.size(), entt::registry<>::size_type{0});
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+}
+
+TEST(Registry, CleanFullOwningGroupViewAfterReset) {
+    entt::registry<> registry;
+    auto group = registry.group<int, char>();
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity, 0);
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset<char>(entity);
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset<int>();
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+
+    registry.assign<int>(entity, 0);
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset();
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+}
+
+TEST(Registry, CleanPartialOwningGroupViewAfterReset) {
+    entt::registry<> registry;
+    auto group = registry.group<int>(entt::get<char>);
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity, 0);
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset<char>(entity);
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+
+    registry.assign<char>(entity, 'c');
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset<int>();
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
+
+    registry.assign<int>(entity, 0);
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{1});
+
+    registry.reset();
+
+    ASSERT_EQ(group.size(), entt::registry<>::size_type{0});
 }
 
 TEST(Registry, SortSingle) {
@@ -662,12 +895,12 @@ TEST(Registry, DestroyByComponents) {
 TEST(Registry, SignalsOnAccommodate) {
     entt::registry<> registry;
     const auto entity = registry.create();
-    const auto view = registry.persistent_view<int, char>();
+    const auto group = registry.group<>(entt::get<int, char>);
 
     registry.assign<int>(entity);
     registry.assign_or_replace<char>(entity);
 
-    ASSERT_FALSE((view.empty()));
+    ASSERT_FALSE((group.empty()));
 }
 
 TEST(Registry, CreateManyEntitiesAtOnce) {
@@ -695,7 +928,47 @@ TEST(Registry, CreateManyEntitiesAtOnce) {
     ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
 }
 
-TEST(Registry, PersistentViewInterleaved) {
+TEST(Registry, NonOwningGroupInterleaved) {
+    entt::registry<> registry;
+    typename entt::registry<>::entity_type entity = entt::null;
+
+    entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    const auto group = registry.group<>(entt::get<int, char>);
+
+    entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+}
+
+TEST(Registry, FullOwningGroupInterleaved) {
+    entt::registry<> registry;
+    typename entt::registry<>::entity_type entity = entt::null;
+
+    entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    const auto group = registry.group<int, char>();
+
+    entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
+}
+
+TEST(Registry, PartialOwningGroupInterleaved) {
     entt::registry<> registry;
     typename entt::registry<>::entity_type entity = entt::null;
 
@@ -703,21 +976,21 @@ TEST(Registry, PersistentViewInterleaved) {
     registry.assign<int>(entity);
     registry.assign<char>(entity);
 
-    const auto view = registry.persistent_view<int, char>();
+    const auto group = registry.group<int>(entt::get<char>);
 
     entity = registry.create();
     registry.assign<int>(entity);
     registry.assign<char>(entity);
 
-    decltype(view)::size_type cnt{0};
-    view.each([&cnt](auto...) { ++cnt; });
+    decltype(group)::size_type cnt{0};
+    group.each([&cnt](auto...) { ++cnt; });
 
-    ASSERT_EQ(cnt, decltype(view)::size_type{2});
+    ASSERT_EQ(cnt, decltype(group)::size_type{2});
 }
 
-TEST(Registry, PersistentViewSortInterleaved) {
+TEST(Registry, NonOwningGroupSortInterleaved) {
     entt::registry<> registry;
-    const auto view = registry.persistent_view<int, char>();
+    const auto group = registry.group<>(entt::get<int, char>);
 
     const auto e0 = registry.create();
     registry.assign<int>(e0, 0);
@@ -734,7 +1007,7 @@ TEST(Registry, PersistentViewSortInterleaved) {
     registry.assign<int>(e2, 2);
     registry.assign<char>(e2, '2');
 
-    view.each([e0, e1, e2](const auto entity, const auto &i, const auto &c) {
+    group.each([e0, e1, e2](const auto entity, const auto &i, const auto &c) {
         if(entity == e0) {
             ASSERT_EQ(i, 0);
             ASSERT_EQ(c, '0');

+ 0 - 350
test/entt/entity/view.cpp

@@ -5,356 +5,6 @@
 #include <entt/entity/registry.hpp>
 #include <entt/entity/view.hpp>
 
-TEST(PersistentView, Functionalities) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<int, char>();
-    auto cview = std::as_const(registry).persistent_view<const int, const char>();
-
-    ASSERT_TRUE(view.empty());
-
-    const auto e0 = registry.create();
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW((view.begin()++));
-    ASSERT_NO_THROW((++cview.begin()));
-
-    ASSERT_NE(view.begin(), view.end());
-    ASSERT_NE(cview.begin(), cview.end());
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
-
-    registry.assign<int>(e0);
-
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
-
-    registry.remove<int>(e0);
-
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
-
-    registry.get<char>(e0) = '1';
-    registry.get<char>(e1) = '2';
-    registry.get<int>(e1) = 42;
-
-    for(auto entity: view) {
-        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(cview.get<const char>(entity), '2');
-    }
-
-    ASSERT_EQ(*(view.data() + 0), e1);
-
-    registry.remove<char>(e0);
-    registry.remove<char>(e1);
-
-    ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(cview.begin(), cview.end());
-    ASSERT_TRUE(view.empty());
-}
-
-TEST(PersistentView, ElementAccess) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<int, char>();
-    auto cview = std::as_const(registry).persistent_view<const int, const char>();
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
-        ASSERT_EQ(view[i], i ? e0 : e1);
-        ASSERT_EQ(cview[i], i ? e0 : e1);
-    }
-}
-
-TEST(PersistentView, Contains) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<int, char>();
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    registry.destroy(e0);
-
-    ASSERT_FALSE(view.contains(e0));
-    ASSERT_TRUE(view.contains(e1));
-}
-
-TEST(PersistentView, Empty) {
-    entt::registry<> registry;
-
-    const auto e0 = registry.create();
-    registry.assign<double>(e0);
-    registry.assign<int>(e0);
-    registry.assign<float>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<char>(e1);
-    registry.assign<float>(e1);
-
-    for(auto entity: registry.persistent_view<char, int, float>()) {
-        (void)entity;
-        FAIL();
-    }
-
-    for(auto entity: registry.persistent_view<double, char, int, float>()) {
-        (void)entity;
-        FAIL();
-    }
-}
-
-TEST(PersistentView, Each) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<int, char>();
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    auto cview = std::as_const(registry).persistent_view<const int, const char>();
-    std::size_t cnt = 0;
-
-    view.each([&cnt](auto, int &, char &) { ++cnt; });
-    view.each([&cnt](int &, char &) { ++cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{4});
-
-    cview.each([&cnt](auto, const int &, const char &) { --cnt; });
-    cview.each([&cnt](const int &, const char &) { --cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{0});
-}
-
-TEST(PersistentView, Sort) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<const int, unsigned int>();
-
-    const auto e0 = registry.create();
-    const auto e1 = registry.create();
-    const auto e2 = registry.create();
-
-    auto uval = 0u;
-    auto ival = 0;
-
-    registry.assign<unsigned int>(e0, uval++);
-    registry.assign<unsigned int>(e1, uval++);
-    registry.assign<unsigned int>(e2, uval++);
-
-    registry.assign<int>(e0, ival++);
-    registry.assign<int>(e1, ival++);
-    registry.assign<int>(e2, ival++);
-
-    for(auto entity: view) {
-        ASSERT_EQ(view.get<unsigned int>(entity), --uval);
-        ASSERT_EQ(view.get<const int>(entity), --ival);
-    }
-
-    registry.sort<unsigned int>(std::less<unsigned int>{});
-    view.sort<unsigned int>();
-
-    for(auto entity: view) {
-        ASSERT_EQ(view.get<unsigned int>(entity), uval++);
-        ASSERT_EQ(view.get<const int>(entity), ival++);
-    }
-}
-
-TEST(PersistentView, IndexRebuiltOnDestroy) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<int, unsigned int>();
-
-    const auto e0 = registry.create();
-    const auto e1 = registry.create();
-
-    registry.assign<unsigned int>(e0, 0u);
-    registry.assign<unsigned int>(e1, 1u);
-
-    registry.assign<int>(e0, 0);
-    registry.assign<int>(e1, 1);
-
-    registry.destroy(e0);
-    registry.assign<int>(registry.create(), 42);
-
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
-    ASSERT_EQ(view[{}], e1);
-    ASSERT_EQ(view.get<int>(e1), 1);
-    ASSERT_EQ(view.get<unsigned int>(e1), 1u);
-
-    view.each([e1](auto entity, auto ivalue, auto uivalue) {
-        ASSERT_EQ(entity, e1);
-        ASSERT_EQ(ivalue, 1);
-        ASSERT_EQ(uivalue, 1u);
-    });
-}
-
-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(PersistentView, Find) {
-    entt::registry<> registry;
-    auto view = registry.persistent_view<int, const char>();
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    const auto e2 = registry.create();
-    registry.assign<int>(e2);
-    registry.assign<char>(e2);
-
-    const auto e3 = registry.create();
-    registry.assign<int>(e3);
-    registry.assign<char>(e3);
-
-    registry.remove<int>(e1);
-
-    ASSERT_NE(view.find(e0), view.end());
-    ASSERT_EQ(view.find(e1), view.end());
-    ASSERT_NE(view.find(e2), view.end());
-    ASSERT_NE(view.find(e3), view.end());
-
-    auto it = view.find(e2);
-
-    ASSERT_EQ(*it, e2);
-    ASSERT_EQ(*(++it), e3);
-    ASSERT_EQ(*(++it), e0);
-    ASSERT_EQ(++it, view.end());
-    ASSERT_EQ(++view.find(e0), view.end());
-
-    const auto e4 = registry.create();
-    registry.destroy(e4);
-    const auto e5 = registry.create();
-    registry.assign<int>(e5);
-    registry.assign<char>(e5);
-
-    ASSERT_NE(view.find(e5), view.end());
-    ASSERT_EQ(view.find(e4), view.end());
-}
-
-TEST(PersistentView, SingleComponent) {
-    entt::registry<> registry;
-    const auto view = registry.persistent_view<const int>();
-
-    registry.assign<int>(registry.create());
-
-    const auto entity = registry.create();
-    registry.assign<int>(entity);
-
-    registry.assign<int>(registry.create());
-
-    registry.destroy(entity);
-    registry.assign<int>(registry.create());
-
-    ASSERT_TRUE(std::equal(view.begin(), view.end(), registry.view<int>().begin()));
-}
-
-TEST(PersistentView, ExcludedComponents) {
-    entt::registry<> registry;
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0, 0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1, 1);
-    registry.assign<char>(e1);
-
-    const auto view = registry.persistent_view<int>(entt::exclude<char>);
-
-    const auto e2 = registry.create();
-    registry.assign<int>(e2, 2);
-
-    const auto e3 = registry.create();
-    registry.assign<int>(e3, 3);
-    registry.assign<char>(e3);
-
-    for(const auto entity: view) {
-        if(entity == e0) {
-            ASSERT_EQ(view.get<int>(e0), 0);
-        } else if(entity == e2) {
-            ASSERT_EQ(view.get<int>(e2), 2);
-        } else {
-            FAIL();
-        }
-    }
-
-    registry.assign<char>(e0);
-    registry.assign<char>(e2);
-
-    ASSERT_TRUE(view.empty());
-
-    registry.remove<char>(e1);
-    registry.remove<char>(e3);
-
-    for(const auto entity: view) {
-        if(entity == e1) {
-            ASSERT_EQ(view.get<int>(e1), 1);
-        } else if(entity == e3) {
-            ASSERT_EQ(view.get<int>(e3), 3);
-        } else {
-            FAIL();
-        }
-    }
-}
-
-TEST(PersistentView, EmptyAndNonEmptyTypes) {
-    struct empty_type {};
-    entt::registry<> registry;
-    const auto view = registry.persistent_view<int, empty_type>();
-
-    const auto e0 = registry.create();
-    registry.assign<empty_type>(e0);
-    registry.assign<int>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<empty_type>(e1);
-    registry.assign<int>(e1);
-
-    registry.assign<int>(registry.create());
-
-    for(const auto entity: view) {
-        ASSERT_TRUE(entity == e0 || entity == e1);
-    }
-
-    view.each([e0, e1](const auto entity, const int &, const empty_type &) {
-        ASSERT_TRUE(entity == e0 || entity == e1);
-    });
-
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
-    ASSERT_EQ(&view.get<empty_type>(e0), &view.get<empty_type>(e1));
-}
-
 TEST(SingleComponentView, Functionalities) {
     entt::registry<> registry;
     auto view = registry.view<char>();