Michele Caini 8 лет назад
Родитель
Сommit
d295c88474
7 измененных файлов с 730 добавлено и 28 удалено
  1. 149 14
      README.md
  2. 1 2
      TODO
  3. 268 0
      src/entt/entity/space.hpp
  4. 1 0
      src/entt/entt.hpp
  5. 1 0
      test/CMakeLists.txt
  6. 156 12
      test/benchmark/benchmark.cpp
  7. 154 0
      test/entt/entity/space.cpp

+ 149 - 14
README.md

@@ -25,6 +25,7 @@
             * [Multi component standard view](#multi-component-standard-view)
             * [Multi component standard view](#multi-component-standard-view)
          * [Persistent View](#persistent-view)
          * [Persistent View](#persistent-view)
          * [Give me everything](#give-me-everything)
          * [Give me everything](#give-me-everything)
+      * [Spaces](#spaces)
       * [Side notes](#side-notes)
       * [Side notes](#side-notes)
    * [Crash Course: core functionalities](#crash-course-core-functionalities)
    * [Crash Course: core functionalities](#crash-course-core-functionalities)
       * [Compile-time identifiers](#compile-time-identifiers)
       * [Compile-time identifiers](#compile-time-identifiers)
@@ -76,7 +77,7 @@ Requests for feature, PR, suggestions ad feedback are highly appreciated.
 
 
 If you find you can help me and want to contribute to the `EnTT` framework with
 If you find you can help me and want to contribute to the `EnTT` framework with
 your experience or you do want to get part of the project for some other
 your experience or you do want to get part of the project for some other
-reason, feel free to contact me directly (you can find the mail in the
+reasons, feel free to contact me directly (you can find the mail in the
 [profile](https://github.com/skypjack)).<br/>
 [profile](https://github.com/skypjack)).<br/>
 I can't promise that each and every contribution will be accepted, but I can
 I can't promise that each and every contribution will be accepted, but I can
 assure that I'll do my best to take them all seriously.
 assure that I'll do my best to take them all seriously.
@@ -91,6 +92,7 @@ compile-time or at runtime).
 * An incredibly fast entity-component system based on sparse sets, with its own
 * An incredibly fast entity-component system based on sparse sets, with its own
 views and a _pay for what you use_ policy to adjust performance and memory usage
 views and a _pay for what you use_ policy to adjust performance and memory usage
 according to the users' requirements.
 according to the users' requirements.
+* Spaces, a nice and easy way to create partitions between entities.
 * Actor class for those who aren't confident with entity-component systems.
 * Actor class for those who aren't confident with entity-component systems.
 * The smallest and most basic implementation of a service locator ever seen.
 * The smallest and most basic implementation of a service locator ever seen.
 * A cooperative scheduler for processes of any type.
 * A cooperative scheduler for processes of any type.
@@ -175,8 +177,7 @@ case.<br/>
 In the end, I did it, but it wasn't much satisfying. Actually it wasn't
 In the end, I did it, but it wasn't much satisfying. Actually it wasn't
 satisfying at all. The fastest and nothing more, fairly little indeed. When I
 satisfying at all. The fastest and nothing more, fairly little indeed. When I
 realized it, I tried hard to keep intact the great performance of `EnTT` and to
 realized it, I tried hard to keep intact the great performance of `EnTT` and to
-add all the features I wanted to see in *my* entity-component system at the same
-time.
+add all the features I wanted to see in *my own library* at the same time.
 
 
 Today `EnTT` is finally what I was looking for: still faster than its
 Today `EnTT` is finally what I was looking for: still faster than its
 _competitors_, lower memory usage in the average case, a really good API and an
 _competitors_, lower memory usage in the average case, a really good API and an
@@ -194,21 +195,32 @@ Dell XPS 13 out of the mid 2014):
 | Create 1M entities | 0.0167s | **0.0046s** |
 | Create 1M entities | 0.0167s | **0.0046s** |
 | Destroy 1M entities | 0.0053s | **0.0022s** |
 | Destroy 1M entities | 0.0053s | **0.0022s** |
 | Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
 | Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
-| Standard view, 1M entities, two components | 0.0012s | **0.0010s** |
-| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **0.0006s** |
+| Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
+| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
 | Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
 | Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
 | Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
 | Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
-| Standard view, 1M entities, five components | **0.0010s** | 0.0024s |
+| Standard view, 1M entities, five components | 0.0010s | **7.0e-07s** |
 | Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
 | Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
-| Standard view, 1M entities, ten components | **0.0011s** | 0.0058s |
-| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | **0.0010s** | 0.0032s |
-| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.7e-06s** |
+| Standard view, 1M entities, ten components | 0.0011s | **1.2e-06s** |
+| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
+| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
 | Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
 | Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
 | Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
 | Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
 | Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
 | Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
 
 
 Note: The default version of `EntityX` (`master` branch) wasn't added to the
 Note: The default version of `EntityX` (`master` branch) wasn't added to the
-comparison because it's already much slower than its compile-time counterpart.
+comparison because it's already much slower than its compile-time
+counterpart.
+
+Pretty interesting, aren't them? In fact, these benchmarks are the same used by
+`EntityX` to show _how good it is_. To be honest, they aren't so good and these
+results shouldn't be taken much seriously.<br/>
+The proposed entity-component system is incredibly fast to iterate entities and
+the compiler can make a lot of optimizations as long as components aren't used.
+Similarly, its extra level of indirection pulls in a lot of interesting features
+(as an example, it's possible to create/destroy entities and components during
+iterations) with the risk of slowing down everything if users do not use it
+carefully and choose the right tool (namely the best _view_) in each case.
 
 
 `EnTT` includes its own tests and benchmarks. See
 `EnTT` includes its own tests and benchmarks. See
 [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
 [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
@@ -333,7 +345,7 @@ The `Registry` to store, the `View` to iterate. That's all.
 
 
 An entity (the _E_ of an _ECS_) is an opaque identifier that users should just
 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
 use as-is and store around if needed. Do not try to inspect an entity
-identifier, its type can change in future and a registry offers all the
+identifier, its format can change in future and a registry offers all the
 functionalities to query them out-of-the-box. The underlying type of an entity
 functionalities to query them out-of-the-box. The underlying type of an entity
 (either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified
 (either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified
 when defining a registry (actually the `DefaultRegistry` is nothing more than a
 when defining a registry (actually the `DefaultRegistry` is nothing more than a
@@ -652,6 +664,14 @@ In fact, there are two functions that respond to slightly different needs:
 
 
 ## View: to persist or not to persist?
 ## View: to persist or not to persist?
 
 
+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 as well as modify their data
+members.<br/>
+It is a subtle difference that can help designing a better software sometimes.
+
 There are mainly two kinds of views: standard (also known as `View`) and
 There are mainly two kinds of views: standard (also known as `View`) and
 persistent (also known as `PersistentView`).<br/>
 persistent (also known as `PersistentView`).<br/>
 Both of them have pros and cons to take in consideration. In particular:
 Both of them have pros and cons to take in consideration. In particular:
@@ -729,7 +749,7 @@ terms of performance in all the situation. This kind of views can access the
 underlying data structures directly and avoid superfluous checks.<br/>
 underlying data structures directly and avoid superfluous checks.<br/>
 They offer a bunch of functionalities to get the number of entities they are
 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
 going to return and a raw access to the entity list as well as to the component
-list.<br/>
+list. It's also possible to ask a view if it contains a given entity.<br/>
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 the details.
 the details.
 
 
@@ -774,7 +794,8 @@ set of candidates in order to speed up iterations.<br/>
 They offer fewer functionalities than their companion views for single
 They offer fewer functionalities than their companion views for single
 component. In particular, a multi component standard view exposes utility
 component. In particular, a multi component standard view exposes utility
 functions to reset its internal state (optimization purposes) and to get the
 functions to reset its internal state (optimization purposes) and to get the
-estimated number of entities it is going to return.<br/>
+estimated number of entities it is going to return. It's also possible to ask a
+view if it contains a given entity.<br/>
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 the details.
 the details.
 
 
@@ -850,7 +871,8 @@ immediately and does nothing.
 A persistent view offers a bunch of functionalities to get the number of
 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
 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
 possibility to sort the underlying data structures according to the order of one
-of the components for which it has been constructed.<br/>
+of the components for which it has been constructed. It's also possible to ask a
+view if it contains a given entity.<br/>
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 the details.
 the details.
 
 
@@ -928,6 +950,119 @@ In general, all these functions can result in poor performance.<br/>
 entity. For similar reasons, `orphans` can be even slower. Both functions should
 entity. For similar reasons, `orphans` can be even slower. Both functions should
 not be used frequently to avoid the risk of a performance hit.
 not be used frequently to avoid the risk of a performance hit.
 
 
+## Spaces
+
+Spaces are sort of partitions of a registry. They can be used to easily get a
+subset of the entities of a view or a registry without recurring to multiple
+registries to separate them explicitly.<br/>
+To learn more about their intended use,
+[here](https://gamedevelopment.tutsplus.com/tutorials/spaces-useful-game-object-containers--gamedev-14091)
+is an interesting article that goes deep into the topic.
+
+Spaces aren't for free. In most of the cases, the cost isn't relevant. However,
+keep in mind that they add an extra check during iterations and it could slow
+down a bit the whole thing.<br/>
+Alternatives to spaces exist, but they have their own problems:
+
+* Multiple registries: memory usage tends to grow up and some tasks are just
+  more difficult to accomplish (as an example, putting an entity logically in
+  more than one registry requires syncing them and it can quickly become a
+  problem).
+
+* Dedicated components: memory usage tends to grow up and the number of spaces
+  is fixed and defined at compile-time (at least, it ought to be for performance
+  reasons), moreover the solution is much more error-prone.
+
+Another benefit of spaces defined as an external class is that users of a space
+do not have access to the whole registry, thus separation of responsibility is
+automatically enforced. In both the alternatives described above, systems have
+access to the whole set of entities instead and can easily break the contract
+with the callers.
+
+The `EnTT` framework offers support to spaces out of the box. Spaces are
+constructed using a registry to which they refer:
+
+```cpp
+entt::DefaultRegistry registry;
+entt::Space<typename entt::DefaultRegistry::entity_type> space{registry};
+```
+
+They offer the classical set of member functions to know the estimated number of
+entities and to check if a space has a given entity.<br/>
+Refer to the [official documentation](https://skypjack.github.io/entt/) for all
+the details.
+
+In addition, they expose two member functions to create an entity through a
+space or to assign to a space an already existent entity, other than member
+functions to remove entities from a space:
+
+```cpp
+// creates an entity using a space
+auto entity = space.create();
+
+// assigns an already existent entity to a space
+space.assign(registry.create());
+
+// removes an entity from the given space
+space.remove(entity);
+
+// removes all the entities from a space
+space.reset();
+```
+
+Entities returned through the `create` member function are created directly into
+the underlying registry and assigned immediately to the space.<br/>
+Removing an entity from a space doesn't mean that it's destroyed within the
+underlying registry in any case.
+
+Spaces and thus the entities they contain can be easily iterated in a range-for
+loop:
+
+```cpp
+for(auto entity: space) {
+    // ...
+}
+```
+
+However, this isn't the best way to iterate entities in a space, mainly because
+this member function returns all the entities it contains, no matter what are
+their components. To iterate entities that have specific components, spaces
+expose two dedicated member functions that differ for the view they use under
+the hood:
+
+```cpp
+// uses a standard view internally
+space.view<AComponent, AnotherComponent>([](auto entity, auto &aComponent, auto &anotherComponent) {
+    // ...
+});
+
+// uses a persistent view internally
+space.persisten<AComponent, AnotherComponent>([](auto entity, auto &aComponent, auto &anotherComponent) {
+    // ...
+});
+```
+
+Spaces get rid of entities that are no longer in use during iterations. They
+aren't kept in sync with a registry each and every time an entity is destroyed
+so as to avoid penalties in terms of performance. Instead, spaces remove invalid
+entities as soon as they are detected during iterations.
+
+Because of the _lazy clean_ policy, the size of a space could grow up if
+destroyed entities are never detected for some reasons. To avoid it, spaces has
+a member function named `shrink` that forces a clean up and reduce the size to a
+minimum:
+
+```cpp
+// gets rid of all the invalid entities still tracked by a space
+space.shrink();
+```
+
+Note that the size of a space isn't a problem in terms of performance. Views
+rule during iterations, mainly because of the order which may have been imposed
+by the user for some reasons and must be respected. Therefore unused entities
+are never visited and thus they don't affect iterations. However, memory usage
+can be reduced by shrinking spaces every so often.
+
 ## Side notes
 ## Side notes
 
 
 * Entity identifiers are numbers and nothing more. They are not classes and they
 * Entity identifiers are numbers and nothing more. They are not classes and they

+ 1 - 2
TODO

@@ -3,8 +3,7 @@
 * to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
 * to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
 * save/restore functionalities - see #27
 * save/restore functionalities - see #27
 * parent-child relationships between entities directly managed by the registry. is it possible to do that in a clean and safe way?
 * parent-child relationships between entities directly managed by the registry. is it possible to do that in a clean and safe way?
-* blueprint registry - external tool, kind of factory to create entitites template for initialization
-* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
+* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
 * raw view (affects sparse sets - custom iterator in derived one - and introduces a new kind of view): single component view to iterate components only instead of entities (in the right order!!)
 * raw view (affects sparse sets - custom iterator in derived one - and introduces a new kind of view): single component view to iterate components only instead of entities (in the right order!!)
   it should speed up systems like rendering or whatever requires a single component and isn't interested in the entity, for it avoids the double check of the get
   it should speed up systems like rendering or whatever requires a single component and isn't interested in the entity, for it avoids the double check of the get
 * AOB
 * AOB

+ 268 - 0
src/entt/entity/space.hpp

@@ -0,0 +1,268 @@
+#ifndef ENTT_ENTITY_SPACE_HPP
+#define ENTT_ENTITY_SPACE_HPP
+
+
+#include <utility>
+#include "sparse_set.hpp"
+#include "registry.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @brief A space is a sort of partition of a registry.
+ *
+ * Spaces can be used to create partitions of a registry. They can be useful for
+ * logically separating menus, world and any other type of scene, while still
+ * using only one registry.<br/>
+ * Similar results are obtained either using multiple registries or using
+ * dedicated components, even though in both cases the memory usage isn't the
+ * same. On the other side, spaces can introduce performance hits that are
+ * sometimes unacceptable (mainly if you are working on AAA games or kind of).
+ *
+ * For more details about spaces and their use, take a look at this article:
+ * https://gamedevelopment.tutsplus.com/tutorials/spaces-useful-game-object-containers--gamedev-14091
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+class Space: private SparseSet<Entity> {
+    using view_type = SparseSet<Entity>;
+
+    template<typename View, typename Func>
+    inline void each(View view, Func func) {
+        // use the view to iterate so as to respect order of components if any
+        view.each([func = std::move(func), this](auto entity, auto &&... components) {
+            if(this->has(entity)) {
+                if(this->data()[this->get(entity)] == entity) {
+                    func(entity, std::forward<decltype(components)>(components)...);
+                } else {
+                    // lazy destroy to avoid keeping a space in sync
+                    this->destroy(entity);
+                }
+            }
+        });
+    }
+
+public:
+    /*! @brief Type of registry to which the space refers. */
+    using registry_type = Registry<Entity>;
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename registry_type::entity_type;
+    /*! @brief Input iterator type. */
+    using iterator_type = typename view_type::iterator_type;
+    /*! @brief Unsigned integer type. */
+    using size_type = typename view_type::size_type;
+
+    /**
+     * @brief Constructs a space by using the given registry.
+     * @param registry An entity-component system properly initialized.
+     */
+    Space(registry_type &registry)
+        : registry{registry}
+    {}
+
+    /**
+     * @brief Returns the number of entities tracked by a space.
+     * @return Number of entities tracked by the space.
+     */
+    size_type size() const noexcept {
+        return SparseSet<Entity>::size();
+    }
+
+    /**
+     * @brief Checks if there exists at least an entity tracked by a space.
+     * @return True if the space tracks at least an entity, false otherwise.
+     */
+    bool empty() const noexcept {
+        return SparseSet<Entity>::empty();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity tracked by a space.
+     *
+     * The returned iterator points to the first entity tracked by the space. If
+     * the space is empty, the returned iterator will be equal to `end()`.
+     *
+     * @return An iterator to the first entity tracked by a space.
+     */
+    iterator_type begin() const noexcept {
+        return SparseSet<Entity>::begin();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity tracked by a
+     * space.
+     *
+     * The returned iterator points to the entity following the last entity
+     * tracked by the space. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @return An iterator to the entity following the last entity tracked by a
+     * space.
+     */
+    iterator_type end() const noexcept {
+        return SparseSet<Entity>::end();
+    }
+
+    /**
+     * @brief Checks if a space contains an entity.
+     * @param entity A valid entity identifier.
+     * @return True if the space contains the given entity, false otherwise.
+     */
+    bool contains(entity_type entity) const noexcept {
+        return this->has(entity) && this->data()[this->get(entity)] == entity;
+    }
+
+    /**
+     * @brief Creates a new entity and returns it.
+     *
+     * The space creates an entity from the underlying registry and registers it
+     * immediately before to return the identifier. Use the `assign` member
+     * function to register an already existent entity created at a different
+     * time.
+     *
+     * The returned entity has no components assigned.
+     *
+     * @return A valid entity identifier.
+     */
+    entity_type create() {
+        const auto entity = registry.create();
+        assign(entity);
+        return entity;
+    }
+
+    /**
+     * @brief Assigns an entity to a space.
+     *
+     * The space starts tracking the given entity and will return it during
+     * iterations whenever required.<br/>
+     * Entities can be assigned to more than one space at the same time.
+     *
+     * @warning
+     * Attempting to use an invalid entity or to assign an entity that doesn't
+     * belong to the underlying registry results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity or if the registry doesn't own the entity.
+     *
+     * @param entity A valid entity identifier.
+     */
+    void assign(entity_type entity) {
+        assert(registry.valid(entity));
+
+        if(this->has(entity)) {
+            this->destroy(entity);
+        }
+
+        this->construct(entity);
+    }
+
+    /**
+     * @brief Removes an entity from a space.
+     *
+     * The space stops tracking the given entity and won't return it anymore
+     * during iterations.<br/>
+     * In case the entity belongs to more than one space, it won't be removed
+     * automatically from all the other ones as a consequence of invoking this
+     * function.
+     *
+     * @param entity A valid entity identifier.
+     */
+    void remove(entity_type entity) {
+        if(this->has(entity)) {
+            this->destroy(entity);
+        }
+    }
+
+    /**
+     * @brief Iterates entities using a standard view under the hood.
+     *
+     * A space does not return directly views to iterate entities because it
+     * requires to apply a filter to those sets. Instead, it uses a view
+     * internally and returns only those entities that are tracked by the space
+     * itself.<br/>
+     * This member function can be used to iterate a space by means of a
+     * standard view. Naming the function the same as the type of view used to
+     * perform the task proved to be a good choice so as not to tricky users.
+     *
+     * @note
+     * 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 the `persistent` member
+     * function instead.
+     *
+     * @tparam Component Type of components used to construct the view.
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename... Component, typename Func>
+    void view(Func func) {
+        each(registry.template view<Component...>(), std::move(func));
+    }
+
+    /**
+     * @brief Iterates entities using a persistent view under the hood.
+     *
+     * A space does not return directly views to iterate entities because it
+     * requires to apply a filter to those sets. Instead, it uses a view
+     * internally and returns only those entities that are tracked by the space
+     * itself.<br/>
+     * This member function can be used to iterate a space by means of a
+     * persistent view. Naming the function the same as the type of view used to
+     * perform the task proved to be a good choice so as not to tricky users.
+     *
+     * @tparam Component Type of components used to construct the view.
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename... Component, typename Func>
+    void persistent(Func func) {
+        each(registry.template persistent<Component...>(), std::move(func));
+    }
+
+    /**
+     * @brief Performs a clean up step.
+     *
+     * Spaces do a lazy cleanup during iterations to avoid introducing
+     * performance hits when entities are destroyed.<br/>
+     * This function can be used to force a clean up step and to get rid of all
+     * those entities that are still tracked by a space but have been destroyed
+     * in the underlying registry.
+     */
+    void shrink() {
+        for(auto entity: *this) {
+            if(!registry.fast(entity)) {
+                this->destroy(entity);
+            }
+        }
+    }
+
+    /**
+     * @brief Resets a whole space.
+     *
+     * The space stops tracking all the entities assigned to it so far. After
+     * calling this function, iterations won't return any entity.
+     */
+    void reset() {
+        SparseSet<Entity>::reset();
+    }
+
+private:
+    Registry<Entity> &registry;
+};
+
+
+/**
+ * @brief Default space class.
+ *
+ * The default space is the best choice for almost all the applications.<br/>
+ * Users should have a really good reason to choose something different.
+ */
+using DefaultSpace = Space<DefaultRegistry::entity_type>;
+
+
+}
+
+
+#endif // ENTT_ENTITY_SPACE_HPP

+ 1 - 0
src/entt/entt.hpp

@@ -4,6 +4,7 @@
 #include "entity/actor.hpp"
 #include "entity/actor.hpp"
 #include "entity/entt_traits.hpp"
 #include "entity/entt_traits.hpp"
 #include "entity/registry.hpp"
 #include "entity/registry.hpp"
+#include "entity/space.hpp"
 #include "entity/sparse_set.hpp"
 #include "entity/sparse_set.hpp"
 #include "entity/view.hpp"
 #include "entity/view.hpp"
 #include "locator/locator.hpp"
 #include "locator/locator.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -55,6 +55,7 @@ add_executable(
     $<TARGET_OBJECTS:odr>
     $<TARGET_OBJECTS:odr>
     entt/entity/actor.cpp
     entt/entity/actor.cpp
     entt/entity/registry.cpp
     entt/entity/registry.cpp
+    entt/entity/space.cpp
     entt/entity/sparse_set.cpp
     entt/entity/sparse_set.cpp
     entt/entity/view.cpp
     entt/entity/view.cpp
 )
 )

+ 156 - 12
test/benchmark/benchmark.cpp

@@ -1,10 +1,10 @@
-#include <gtest/gtest.h>
 #include <iostream>
 #include <iostream>
 #include <cstddef>
 #include <cstddef>
 #include <cstdint>
 #include <cstdint>
 #include <chrono>
 #include <chrono>
-#include <vector>
+#include <gtest/gtest.h>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/registry.hpp>
+#include <entt/entity/space.hpp>
 
 
 struct Position {
 struct Position {
     std::uint64_t x;
     std::uint64_t x;
@@ -17,7 +17,7 @@ struct Velocity {
 };
 };
 
 
 template<std::size_t>
 template<std::size_t>
-struct Comp {};
+struct Comp { int x; };
 
 
 struct Timer final {
 struct Timer final {
     Timer(): start{std::chrono::system_clock::now()} {}
     Timer(): start{std::chrono::system_clock::now()} {}
@@ -47,17 +47,16 @@ TEST(Benchmark, Construct) {
 
 
 TEST(Benchmark, Destroy) {
 TEST(Benchmark, Destroy) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;
-    std::vector<entt::DefaultRegistry::entity_type> entities{};
 
 
     std::cout << "Destroying 1000000 entities" << std::endl;
     std::cout << "Destroying 1000000 entities" << std::endl;
 
 
     for(std::uint64_t i = 0; i < 1000000L; i++) {
     for(std::uint64_t i = 0; i < 1000000L; i++) {
-        entities.push_back(registry.create());
+        registry.create<int>();
     }
     }
 
 
     Timer timer;
     Timer timer;
 
 
-    for(auto entity: entities) {
+    for(auto entity: registry.view<int>()) {
         registry.destroy(entity);
         registry.destroy(entity);
     }
     }
 
 
@@ -281,13 +280,11 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
 
 
 TEST(Benchmark, SortSingle) {
 TEST(Benchmark, SortSingle) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;
-    std::vector<entt::DefaultRegistry::entity_type> entities{};
 
 
     std::cout << "Sort 150000 entities, one component" << std::endl;
     std::cout << "Sort 150000 entities, one component" << std::endl;
 
 
     for(std::uint64_t i = 0; i < 150000L; i++) {
     for(std::uint64_t i = 0; i < 150000L; i++) {
-        auto entity = registry.create<Position>({ i, i });
-        entities.push_back(entity);
+        registry.create<Position>({ i, i });
     }
     }
 
 
     Timer timer;
     Timer timer;
@@ -301,13 +298,11 @@ TEST(Benchmark, SortSingle) {
 
 
 TEST(Benchmark, SortMulti) {
 TEST(Benchmark, SortMulti) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;
-    std::vector<entt::DefaultRegistry::entity_type> entities{};
 
 
     std::cout << "Sort 150000 entities, two components" << std::endl;
     std::cout << "Sort 150000 entities, two components" << std::endl;
 
 
     for(std::uint64_t i = 0; i < 150000L; i++) {
     for(std::uint64_t i = 0; i < 150000L; i++) {
-        auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
-        entities.push_back(entity);
+        registry.create<Position, Velocity>({ i, i }, { i, i });
     }
     }
 
 
     registry.sort<Position>([](const auto &lhs, const auto &rhs) {
     registry.sort<Position>([](const auto &lhs, const auto &rhs) {
@@ -320,3 +315,152 @@ TEST(Benchmark, SortMulti) {
 
 
     timer.elapsed();
     timer.elapsed();
 }
 }
+
+TEST(Benchmark, SpaceConstruct) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    std::cout << "Constructing 1000000 entities" << std::endl;
+
+    Timer timer;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        space.create();
+    }
+
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceAssign) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    std::cout << "Assigning 1000000 entities" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        registry.create<int>();
+    }
+
+    Timer timer;
+
+    for(auto entity: registry.view<int>()) {
+        space.assign(entity);
+    }
+
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateSingleComponent1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    std::cout << "Iterating over 1000000 entities, one component" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.view<Position>([](auto, auto &) {});
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateTwoComponents1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    std::cout << "Iterating over 1000000 entities, two components" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position, Velocity>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.view<Position, Velocity>([](auto, auto &...) {});
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateTwoComponentsPersistent1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+    registry.prepare<Position, Velocity>();
+
+    std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position, Velocity>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.persistent<Position, Velocity>([](auto, auto &...) {});
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateFiveComponents1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    std::cout << "Iterating over 1000000 entities, five components" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>([](auto, auto &...) {});
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateFiveComponentsPersistent1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+    registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
+
+    std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>([](auto, auto &...) {});
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateTenComponents1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    std::cout << "Iterating over 1000000 entities, ten components" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>([](auto, auto &...) {});
+    timer.elapsed();
+}
+
+TEST(Benchmark, SpaceIterateTenComponentsPersistent1M) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+    registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
+
+    std::cout << "Iterating over 1000000 entities, ten components, persistent view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
+        space.assign(entity);
+    }
+
+    Timer timer;
+    space.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>([](auto, auto &...) {});
+    timer.elapsed();
+}

+ 154 - 0
test/entt/entity/space.cpp

@@ -0,0 +1,154 @@
+#include <gtest/gtest.h>
+#include <entt/entity/registry.hpp>
+#include <entt/entity/space.hpp>
+
+TEST(Space, SpaceContainsView) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    auto e0 = space.create();
+    auto e1 = registry.create();
+
+    space.assign(e1);
+    registry.assign<int>(e1);
+
+    ASSERT_TRUE(space.contains(e0));
+    ASSERT_TRUE(space.contains(e1));
+
+    space.view<int>([e0, e1](auto entity, auto&&...) {
+        ASSERT_NE(entity, e0);
+        ASSERT_EQ(entity, e1);
+    });
+
+    space.view<double>([e0, e1](auto, auto&&...) {
+        FAIL();
+    });
+
+    auto count = 0;
+
+    for(auto entity: space) {
+        (void)entity;
+        ++count;
+    }
+
+    ASSERT_EQ(count, 2);
+
+    const auto view = registry.view<int>();
+
+    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{2});
+    ASSERT_FALSE(space.empty());
+
+    registry.reset();
+    space.reset();
+
+    ASSERT_TRUE(space.empty());
+
+    for(auto i = 0; i < 5; ++i) {
+        registry.destroy(space.create());
+        registry.create<int>();
+    }
+
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5});
+    ASSERT_FALSE(space.empty());
+
+    space.view<int>([](auto, auto&&...) {
+        FAIL();
+    });
+
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{0});
+    ASSERT_TRUE(space.empty());
+}
+
+TEST(Space, ViewContainsSpace) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    auto e0 = registry.create();
+    auto e1 = space.create();
+
+    registry.assign<int>(e0);
+    registry.assign<int>(e1);
+
+    ASSERT_FALSE(space.contains(e0));
+    ASSERT_TRUE(space.contains(e1));
+
+    space.view<int>([e0, e1](auto entity, auto&&...) {
+        ASSERT_NE(entity, e0);
+        ASSERT_EQ(entity, e1);
+    });
+
+    space.view<double>([e0, e1](auto, auto&&...) {
+        FAIL();
+    });
+
+    auto count = 0;
+
+    for(auto entity: space) {
+        (void)entity;
+        ++count;
+    }
+
+    ASSERT_EQ(count, 1);
+
+    const auto view = registry.view<int>();
+
+    ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{1});
+    ASSERT_FALSE(space.empty());
+
+    registry.reset();
+    space.reset();
+
+    ASSERT_TRUE(space.empty());
+
+    for(auto i = 0; i < 5; ++i) {
+        registry.destroy(space.create());
+        registry.create<int>();
+        registry.create<int>();
+    }
+
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5});
+    ASSERT_FALSE(space.empty());
+
+    space.view<int>([](auto, auto&&...) {
+        FAIL();
+    });
+
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{0});
+    ASSERT_TRUE(space.empty());
+}
+
+TEST(Space, AssignRemove) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    ASSERT_TRUE(space.empty());
+
+    space.remove(space.create());
+
+    ASSERT_TRUE(space.empty());
+}
+
+TEST(Space, Shrink) {
+    entt::DefaultRegistry registry;
+    entt::DefaultSpace space{registry};
+
+    for(auto i = 0; i < 5; ++i) {
+        space.create();
+    }
+
+    for(auto entity: space) {
+        registry.destroy(entity);
+    }
+
+    space.create();
+
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5});
+    ASSERT_FALSE(space.empty());
+
+    space.shrink();
+
+    ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{1});
+    ASSERT_FALSE(space.empty());
+}