Browse Source

entity:
* allocator support for sparse set and storage
* pointer stability when assigning components
* removed ::raw functions (registry, view, group)
* to_entity returns null for invalid components

Michele Caini 4 years ago
parent
commit
6194e12616

+ 49 - 55
docs/md/entity.md

@@ -7,10 +7,9 @@
 
 
 * [Introduction](#introduction)
 * [Introduction](#introduction)
 * [Design decisions](#design-decisions)
 * [Design decisions](#design-decisions)
-  * [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
+  * [Type-less and bitset-free](#type-less-and-bitset-free)
+  * [Build your own](#build-your-own)
   * [Pay per use](#pay-per-use)
   * [Pay per use](#pay-per-use)
-  * [All or nothing](#all-or-nothing)
-  * [Stateless systems](#stateless-systems)
 * [Vademecum](#vademecum)
 * [Vademecum](#vademecum)
 * [Pools](#pools)
 * [Pools](#pools)
 * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
 * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
@@ -63,7 +62,7 @@ used mostly in game development.
 
 
 # Design decisions
 # Design decisions
 
 
-## A bitset-free entity-component system
+## Type-less and bitset-free
 
 
 `EnTT` offers a _bitset-free_ entity-component system that doesn't require users
 `EnTT` offers a _bitset-free_ entity-component system that doesn't require users
 to specify the set of components neither at compile-time nor at runtime.<br/>
 to specify the set of components neither at compile-time nor at runtime.<br/>
@@ -82,6 +81,23 @@ entt::registry<comp_0, comp_1, ..., comp_n> registry;
 Furthermore, it isn't necessary to announce the existence of a component type.
 Furthermore, it isn't necessary to announce the existence of a component type.
 When the time comes, just use it and that's all.
 When the time comes, just use it and that's all.
 
 
+## Build your own
+
+`EnTT` is designed as a container that can be used at any time just as a vector
+or any other tool would be used. It doesn't attempt in any way to take over on
+the user code base, nor to control its main loop or process scheduling.<br/>
+Unlike other more or less known models, it makes use of independent pools. This
+has some advantages and disadvantages. The main purpose is to provide a fully
+customizable tool, where users have the freedom to define pools and opaque
+proxies for types with specific requirements.
+
+The library provides a default implementation for many things and a mixin model
+that allows users to completely replace or even just enrich the pool dedicated
+to one or more components.<br/>
+The built-in signal support is an example of that: defined as a mixin, it's
+easily disabled if not needed. Similarly, poly storage is another example of how
+everything is customizable down to the smallest detail.
+
 ## Pay per use
 ## Pay per use
 
 
 `EnTT` is entirely designed around the principle that users have to pay only for
 `EnTT` is entirely designed around the principle that users have to pay only for
@@ -102,28 +118,6 @@ performance where needed.
 So far, this choice has proven to be a good one and I really hope it can be for
 So far, this choice has proven to be a good one and I really hope it can be for
 many others besides me.
 many others besides me.
 
 
-## All or nothing
-
-`EnTT` is such that at every moment a pair `(T *, size)` is available to
-directly access all the instances of a given component type `T`.<br/>
-This was a guideline and a design decision that influenced many choices, for
-better and for worse. I cannot say whether it will be useful or not to the
-reader, but it's worth to mention it since it's one of the corner stones of
-this library.
-
-Many of the tools described below give the possibility to get this information
-and have been designed around this need.<br/>
-The rest is experimentation and the desire to invent something new, hoping to
-have succeeded.
-
-## Stateless systems
-
-`EnTT` is designed so that it can work with _stateless systems_. In other words,
-all systems can be free functions and there is no need to define them as classes
-(although nothing prevents users from doing so).<br/>
-This is possible because the main class with which the users will work provides
-all what is needed to act as the sole _source of truth_ of an application.
-
 # Vademecum
 # Vademecum
 
 
 The registry to store, the views and the groups to iterate. That's all.
 The registry to store, the views and the groups to iterate. That's all.
@@ -631,9 +625,7 @@ instance of a component and returns the entity associated with the latter:
 const auto entity = entt::to_entity(registry, position);
 const auto entity = entt::to_entity(registry, position);
 ```
 ```
 
 
-This utility doesn't perform any check on the validity of the component.
-Therefore, trying to take the entity of an invalid element or of an instance
-that isn't associated with the given registry can result in undefined behavior.
+A null entity is returned in case the component doesn't belong to the registry.
 
 
 ### Dependencies
 ### Dependencies
 
 
@@ -947,7 +939,7 @@ copying an entity will be as easy as:
 
 
 ```cpp
 ```cpp
 registry.visit(entity, [&](const auto info) {
 registry.visit(entity, [&](const auto info) {
-    auto storage = registry.storage(info);
+    auto &&storage = registry.storage(info);
     storage->emplace(registry, other, storage->get(entity));
     storage->emplace(registry, other, storage->get(entity));
 });
 });
 ```
 ```
@@ -957,8 +949,7 @@ Similarly, copying entire pools between different registries can look like this:
 
 
 ```cpp
 ```cpp
 registry.visit([&](const auto info) {
 registry.visit([&](const auto info) {
-    auto storage = registry.storage(info);
-    other.storage(info)->insert(other, storage->data(), storage->raw(), storage->size());
+    registry.storage(info)->copy_to(other);
 });
 });
 ```
 ```
 
 
@@ -1249,9 +1240,8 @@ 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
 as a single component view. In fact, they walk through a packed array of
 components and return them one at a time.<br/>
 components and return them one at a time.<br/>
 Single component views offer a bunch of functionalities to get the number of
 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/>
+entities they are going to return and a raw access to the entity 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.
 Refer to the inline documentation for all the details.
 
 
 Multi component views iterate entities that have at least all the given
 Multi component views iterate entities that have at least all the given
@@ -1368,8 +1358,8 @@ the above type order rules apply sequentially.
 
 
 Runtime views iterate entities that have at least all the given components in
 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
 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/>
+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 view.
 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
 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
 registry that generated the view to access components. In particular, a runtime
@@ -1445,9 +1435,8 @@ However, it's unlikely that users will be able to appreciate the impact of
 groups on the other functionalities of a registry.
 groups on the other functionalities of a registry.
 
 
 Groups offer a bunch of functionalities to get the number of entities they are
 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/>
+going to return and a raw access to the entity list. It's also possible to ask a
+group if it contains a given entity.<br/>
 Refer to the inline documentation for all the details.
 Refer to the inline documentation for all the details.
 
 
 There is no need to store groups aside for they are extremely cheap to
 There is no need to store groups aside for they are extremely cheap to
@@ -1760,17 +1749,20 @@ not be used frequently to avoid the risk of a performance hit.
 ## What is allowed and what is not
 ## What is allowed and what is not
 
 
 Most of the _ECS_ available out there don't allow to create and destroy entities
 Most of the _ECS_ available out there don't allow to create and destroy entities
-and components during iterations.<br/>
+and components during iterations, nor to have pointer stability.<br/>
 `EnTT` partially solves the problem with a few limitations:
 `EnTT` partially solves the problem with a few limitations:
 
 
-* Creating entities and components is allowed during iterations in most cases.
+* Creating entities and components is allowed during iterations in most cases
+  and it never invalidates already existing references.
 
 
 * Deleting the current entity or removing its components is allowed during
 * 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.
+  iterations but it could invalidate references. For all the other entities,
+  destroying them or removing their components isn't allowed and can result in
+  undefined behavior.
 
 
-In these cases, iterators aren't invalidated. To be clear, it doesn't mean that
-also references will continue to be valid.<br/>
+In other terms, iterators are never invalidated. Also, component references
+aren't invalidated when a new element is added while they could be invalidated
+upon deletion, due to the _swap-and-pop_ policy.<br/>
 Consider the following example:
 Consider the following example:
 
 
 ```cpp
 ```cpp
@@ -1780,13 +1772,15 @@ registry.view<position>([&](const auto entity, auto &pos) {
 });
 });
 ```
 ```
 
 
-The `each` member function won't break (because iterators aren't invalidated)
-but there are no guarantees on references. Use a common range-for loop and get
-components directly from the view or move the creation of components at the end
-of the function to avoid dangling pointers.
+The `each` member function won't break (because iterators remain valid) nor will
+any reference be invalidated. Instead, more attention should be paid to the
+destruction of entities or the removal of components.<br/>
+Use a common range-for loop and get components directly from the view or move
+the deletion of entities and components at the end of the function to avoid
+dangling pointers.
 
 
-Iterators are invalidated instead and the behavior is undefined if an entity is
-modified or destroyed and it's not the one currently returned by the iterator
+Conversely, 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 iterator
 nor a newly created one.<br/>
 nor a newly created one.<br/>
 To work around it, possible approaches are:
 To work around it, possible approaches are:
 
 
@@ -1852,9 +1846,9 @@ When an empty type is detected, it's not instantiated in any case. Therefore,
 only the entities to which it's assigned are made available.<br/>
 only the entities to which it's assigned are made available.<br/>
 There doesn't exist a way to _get_ empty types from a registry, views and groups
 There doesn't exist a way to _get_ empty types from a registry, views and groups
 will never return instances for them (for example, during a call to `each`) and
 will never return instances for them (for example, during a call to `each`) and
-some functions such as `try_get` or the raw access to the list of components
-won't be available. Finally, the `sort` functionality will onlyaccepts callbacks
-that require to return entities rather than components:
+some functions such as `try_get` aren't available for empty types. Finally, the
+`sort` functionality will only accepts callbacks that require to return entities
+rather than components:
 
 
 ```cpp
 ```cpp
 registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {
 registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {

+ 3 - 2
src/entt/entity/fwd.hpp

@@ -2,17 +2,18 @@
 #define ENTT_ENTITY_FWD_HPP
 #define ENTT_ENTITY_FWD_HPP
 
 
 
 
+#include <memory>
 #include "../core/fwd.hpp"
 #include "../core/fwd.hpp"
 
 
 
 
 namespace entt {
 namespace entt {
 
 
 
 
-template<typename>
+template<typename Entity, typename = std::allocator<Entity>>
 class basic_sparse_set;
 class basic_sparse_set;
 
 
 
 
-template<typename, typename, typename>
+template<typename, typename Type, typename = std::allocator<Type>, typename = void>
 class basic_storage;
 class basic_storage;
 
 
 
 

+ 2 - 22
src/entt/entity/group.hpp

@@ -680,26 +680,6 @@ public:
         return !*this || !*length;
         return !*this || !*length;
     }
     }
 
 
-    /**
-     * @brief Direct access to the raw representation offered by the storage.
-     *
-     * For fully contiguous storage classes, the returned pointer is such that
-     * range `[raw<Component>(), raw<Component>() + size())` is always a valid
-     * range, even if the container is empty.
-     *
-     * @warning
-     * This function is only available for owned types.
-     *
-     * @tparam Component Type of component in which one is interested.
-     * @return A pointer to the array of components.
-     */
-    template<typename Component>
-    [[nodiscard]] auto raw() const ENTT_NOEXCEPT {
-        static_assert((std::is_same_v<Component, Owned> || ...), "Non-owned type");
-        auto *cpool = std::get<storage_type<Component> *>(pools);
-        return cpool ? cpool->raw() : decltype(cpool->raw()){};
-    }
-
     /**
     /**
      * @brief Direct access to the list of entities.
      * @brief Direct access to the list of entities.
      *
      *
@@ -940,7 +920,7 @@ public:
     template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
     template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) const {
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) const {
         auto *cpool = std::get<0>(pools);
         auto *cpool = std::get<0>(pools);
-        
+
         if constexpr(sizeof...(Component) == 0) {
         if constexpr(sizeof...(Component) == 0) {
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
             cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
             cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
@@ -953,7 +933,7 @@ public:
                 return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
                 return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
             }, std::move(algo), std::forward<Args>(args)...);
             }, std::move(algo), std::forward<Args>(args)...);
         }
         }
-        
+
         [this](auto *head, auto *... other) {
         [this](auto *head, auto *... other) {
             for(auto next = *length; next; --next) {
             for(auto next = *length; next; --next) {
                 const auto pos = next - 1;
                 const auto pos = next - 1;

+ 16 - 3
src/entt/entity/helper.hpp

@@ -135,16 +135,29 @@ void invoke(basic_registry<Entity> &reg, const Entity entt) {
 
 
 /**
 /**
  * @brief Returns the entity associated with a given component.
  * @brief Returns the entity associated with a given component.
+ *
+ * @warning
+ * Currently, this function only works correctly with the default pool as it
+ * makes assumptions about how the components are laid out.
+ *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Type of component.
  * @tparam Component Type of component.
  * @param reg A registry that contains the given entity and its components.
  * @param reg A registry that contains the given entity and its components.
- * @param component A valid component instance.
+ * @param instance A valid component instance.
  * @return The entity associated with the given component.
  * @return The entity associated with the given component.
  */
  */
 template<typename Entity, typename Component>
 template<typename Entity, typename Component>
-Entity to_entity(const basic_registry<Entity> &reg, const Component &component) {
+Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
     const auto view = reg.template view<const Component>();
     const auto view = reg.template view<const Component>();
-    return *(view.data() + (&component - view.raw()));
+    const auto *addr = std::addressof(instance);
+
+    for(auto it = view.rbegin(), last = view.rend(); it < last; it += ENTT_PAGE_SIZE) {
+        if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < ENTT_PAGE_SIZE) {
+            return *(it + dist);
+        }
+    }
+
+    return entt::null;
 }
 }
 
 
 
 

+ 203 - 99
src/entt/entity/sparse_set.hpp

@@ -2,12 +2,11 @@
 #define ENTT_ENTITY_SPARSE_SET_HPP
 #define ENTT_ENTITY_SPARSE_SET_HPP
 
 
 
 
+#include <cstddef>
 #include <iterator>
 #include <iterator>
-#include <utility>
-#include <vector>
 #include <memory>
 #include <memory>
-#include <cstddef>
 #include <type_traits>
 #include <type_traits>
+#include <utility>
 #include "../config/config.h"
 #include "../config/config.h"
 #include "../core/algorithm.hpp"
 #include "../core/algorithm.hpp"
 #include "entity.hpp"
 #include "entity.hpp"
@@ -38,22 +37,34 @@ namespace entt {
  * a sparse set. Do not make assumption on the order in any case.
  * a sparse set. Do not make assumption on the order in any case.
  *
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam Allocator Type of allocator used to manage memory and elements.
  */
  */
-template<typename Entity>
+template<typename Entity, typename Allocator>
 class basic_sparse_set {
 class basic_sparse_set {
+    static constexpr auto growth_factor = 1.5;
     static constexpr auto page_size = ENTT_PAGE_SIZE;
     static constexpr auto page_size = ENTT_PAGE_SIZE;
 
 
     using traits_type = entt_traits<Entity>;
     using traits_type = entt_traits<Entity>;
-    using page_type = std::unique_ptr<Entity[]>;
+
+    using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>;
+    using alloc_traits = std::allocator_traits<alloc_type>;
+    using alloc_pointer = typename alloc_traits::pointer;
+    using alloc_const_pointer = typename alloc_traits::const_pointer;
+
+    using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
+    using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
+    using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
+
+    static_assert(alloc_traits::propagate_on_container_move_assignment::value);
+    static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
 
 
     class sparse_set_iterator final {
     class sparse_set_iterator final {
         friend class basic_sparse_set<Entity>;
         friend class basic_sparse_set<Entity>;
 
 
-        using packed_type = std::vector<Entity>;
         using index_type = typename traits_type::difference_type;
         using index_type = typename traits_type::difference_type;
 
 
-        sparse_set_iterator(const packed_type &ref, const index_type idx) ENTT_NOEXCEPT
-            : packed{&ref}, index{idx}
+        sparse_set_iterator(const alloc_const_pointer *ref, const index_type idx) ENTT_NOEXCEPT
+            : packed{ref}, index{idx}
         {}
         {}
 
 
     public:
     public:
@@ -144,32 +155,107 @@ class basic_sparse_set {
         }
         }
 
 
     private:
     private:
-        const packed_type *packed;
+        const alloc_const_pointer *packed;
         index_type index;
         index_type index;
     };
     };
 
 
-    [[nodiscard]] auto page(const Entity entt) const ENTT_NOEXCEPT {
+    [[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
         return size_type{(to_integral(entt) & traits_type::entity_mask) / page_size};
         return size_type{(to_integral(entt) & traits_type::entity_mask) / page_size};
     }
     }
 
 
-    [[nodiscard]] auto offset(const Entity entt) const ENTT_NOEXCEPT {
+    [[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
         return size_type{to_integral(entt) & (page_size - 1)};
         return size_type{to_integral(entt) & (page_size - 1)};
     }
     }
 
 
-    [[nodiscard]] page_type & assure(const std::size_t pos) {
-        if(!(pos < sparse.size())) {
-            sparse.resize(pos+1);
+    [[nodiscard]] auto assure_page(const std::size_t idx) {
+        if(!(idx < bucket)) {
+            const size_type sz = idx + 1u;
+            const auto old = std::exchange(sparse, bucket_alloc_traits::allocate(bucket_allocator, sz));
+            std::uninitialized_fill(sparse + bucket, sparse + sz, alloc_pointer{});
+
+            if(const auto curr = std::exchange(bucket, sz); curr) {
+                for(size_type pos{}; pos < curr; ++pos) {
+                    bucket_alloc_traits::construct(bucket_allocator, std::addressof(sparse[pos]), std::move(old[pos]));
+                    bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
+                }
+
+                bucket_alloc_traits::deallocate(bucket_allocator, old, curr);
+            }
         }
         }
 
 
-        if(!sparse[pos]) {
-            sparse[pos].reset(new entity_type[page_size]);
-            // null is safe in all cases for our purposes
-            for(auto *first = sparse[pos].get(), *last = first + page_size; first != last; ++first) {
-                *first = null;
+        if(!sparse[idx]) {
+            sparse[idx] = alloc_traits::allocate(allocator, page_size);
+            std::uninitialized_fill(sparse[idx], sparse[idx] + page_size, null);
+        }
+
+        return sparse[idx];
+    }
+
+    void resize_packed(const std::size_t req) {
+        ENTT_ASSERT(req && !(req < count), "Invalid request");
+        auto old = std::exchange(packed, alloc_traits::allocate(allocator, req));
+
+        if(const auto length = std::exchange(reserved, req); length) {
+            for(size_type pos{}; pos < count; ++pos) {
+                alloc_traits::construct(allocator, std::addressof(packed[pos]), std::move(old[pos]));
+                alloc_traits::destroy(allocator, std::addressof(old[pos]));
             }
             }
+
+            alloc_traits::deallocate(allocator, old, length);
+        }
+    }
+
+    template<typename It>
+    void push_back(It first, It last) {
+        if(const std::size_t req = count + std::distance(first, last); reserved < req) {
+            const std::size_t sz = reserved * growth_factor + !reserved;
+            resize_packed(sz < req ? req : sz);
         }
         }
 
 
-        return sparse[pos];
+        for(; first != last; ++first) {
+            ENTT_ASSERT(!contains(*first), "Set already contains entity");
+            assure_page(page(*first))[offset(*first)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
+            alloc_traits::construct(allocator, std::addressof(packed[count++]), *first);
+        }
+    }
+
+    void pop(const Entity entt, void *ud) {
+        ENTT_ASSERT(contains(entt), "Set does not contain entity");
+        // last chance to use the entity for derived classes and mixins, if any
+        about_to_erase(entt, ud);
+
+        auto &ref = sparse[page(entt)][offset(entt)];
+        const auto pos = size_type{to_integral(ref)};
+
+        const auto last = --count;
+        packed[pos] = std::exchange(packed[last], entt);
+        sparse[page(packed[pos])][offset(packed[pos])] = ref;
+        // no risks when pos == count, accessing packed is no longer required
+        alloc_traits::destroy(allocator, std::addressof(packed[last]));
+        ref = null;
+
+        // don't expect exceptions here, instead allow for nosy destructors
+        swap_and_pop(pos);
+    }
+
+    void reset_to_empty() {
+        if(const auto length = std::exchange(reserved, 0u); length) {
+            std::destroy(packed, packed + std::exchange(count, 0u));
+            alloc_traits::deallocate(allocator, packed, length);
+        }
+
+        if(const auto length = std::exchange(bucket, 0u); length) {
+            for(size_type pos{}; pos < length; ++pos) {
+                if(sparse[pos]) {
+                    std::destroy(sparse[pos], sparse[pos] + page_size);
+                    alloc_traits::deallocate(allocator, sparse[pos], page_size);
+                }
+
+                bucket_alloc_traits::destroy(bucket_allocator, std::addressof(sparse[pos]));
+            }
+
+            bucket_alloc_traits::deallocate(bucket_allocator, sparse, std::exchange(bucket, 0u));
+        }
     }
     }
 
 
 protected:
 protected:
@@ -194,26 +280,67 @@ protected:
     virtual void about_to_erase([[maybe_unused]] const Entity entity, [[maybe_unused]] void *ud) {}
     virtual void about_to_erase([[maybe_unused]] const Entity entity, [[maybe_unused]] void *ud) {}
 
 
 public:
 public:
+    /*! @brief Allocator type. */
+    using allocator_type = alloc_type;
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
     using entity_type = Entity;
     using entity_type = Entity;
     /*! @brief Unsigned integer type. */
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
     using size_type = std::size_t;
+    /*! @brief Pointer type to contained entities. */
+    using pointer = alloc_const_pointer;
     /*! @brief Random access iterator type. */
     /*! @brief Random access iterator type. */
     using iterator = sparse_set_iterator;
     using iterator = sparse_set_iterator;
     /*! @brief Reverse iterator type. */
     /*! @brief Reverse iterator type. */
-    using reverse_iterator = const entity_type *;
+    using reverse_iterator = pointer;
 
 
-    /*! @brief Default constructor. */
-    basic_sparse_set() = default;
+    /**
+     * @brief Default constructor.
+     * @param alloc Allocator to use (possibly default-constructed).
+     */
+    explicit basic_sparse_set(const allocator_type &alloc = {})
+        : allocator{alloc},
+          bucket_allocator{alloc},
+          sparse{},
+          packed{},
+          bucket{},
+          count{},
+          reserved{}
+    {}
 
 
-    /*! @brief Default move constructor. */
-    basic_sparse_set(basic_sparse_set &&) = default;
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
+        : allocator{std::move(other.allocator)},
+          bucket_allocator{std::move(other.bucket_allocator)},
+          sparse{std::exchange(other.sparse, bucket_alloc_pointer{})},
+          packed{std::exchange(other.packed, alloc_pointer{})},
+          bucket{std::exchange(other.bucket, 0u)},
+          count{std::exchange(other.count, 0u)},
+          reserved{std::exchange(other.reserved, 0u)}
+    {}
 
 
     /*! @brief Default destructor. */
     /*! @brief Default destructor. */
-    virtual ~basic_sparse_set() = default;
+    virtual ~basic_sparse_set() {
+        reset_to_empty();
+    }
 
 
-    /*! @brief Default move assignment operator. @return This sparse set. */
-    basic_sparse_set & operator=(basic_sparse_set &&) = default;
+    /**
+     * @brief Move assignment operator.
+     * @param other The instance to move from.
+     * @return This sparse set.
+     */
+    basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
+        allocator = std::move(other.allocator);
+        bucket_allocator = std::move(other.bucket_allocator);
+        sparse = std::exchange(other.sparse, bucket_alloc_pointer{});
+        packed = std::exchange(other.packed, alloc_pointer{});
+        bucket = std::exchange(other.bucket, 0u);
+        count = std::exchange(other.count, 0u);
+        reserved = std::exchange(other.reserved, 0u);
+        return *this;
+    }
 
 
     /**
     /**
      * @brief Increases the capacity of a sparse set.
      * @brief Increases the capacity of a sparse set.
@@ -224,7 +351,9 @@ public:
      * @param cap Desired capacity.
      * @param cap Desired capacity.
      */
      */
     void reserve(const size_type cap) {
     void reserve(const size_type cap) {
-        packed.reserve(cap);
+        if(cap > reserved) {
+            resize_packed(cap);
+        }
     }
     }
 
 
     /**
     /**
@@ -233,18 +362,16 @@ public:
      * @return Capacity of the sparse set.
      * @return Capacity of the sparse set.
      */
      */
     [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
-        return packed.capacity();
+        return reserved;
     }
     }
 
 
     /*! @brief Requests the removal of unused capacity. */
     /*! @brief Requests the removal of unused capacity. */
     void shrink_to_fit() {
     void shrink_to_fit() {
-        // conservative approach
-        if(packed.empty()) {
-            sparse.clear();
+        if(!count) {
+            reset_to_empty();
+        } else if(reserved > count) {
+            resize_packed(count);
         }
         }
-
-        sparse.shrink_to_fit();
-        packed.shrink_to_fit();
     }
     }
 
 
     /**
     /**
@@ -258,7 +385,7 @@ public:
      * @return Extent of the sparse set.
      * @return Extent of the sparse set.
      */
      */
     [[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
-        return sparse.size() * page_size;
+        return bucket * page_size;
     }
     }
 
 
     /**
     /**
@@ -272,7 +399,7 @@ public:
      * @return Number of elements.
      * @return Number of elements.
      */
      */
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
-        return packed.size();
+        return count;
     }
     }
 
 
     /**
     /**
@@ -280,23 +407,15 @@ public:
      * @return True if the sparse set is empty, false otherwise.
      * @return True if the sparse set is empty, false otherwise.
      */
      */
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
-        return packed.empty();
+        return (count == size_type{});
     }
     }
 
 
     /**
     /**
      * @brief Direct access to the internal packed array.
      * @brief Direct access to the internal packed array.
-     *
-     * The returned pointer is such that range `[data(), data() + size())` is
-     * always a valid range, even if the container is empty.
-     *
-     * @note
-     * Entities are in the reverse order as returned by the `begin`/`end`
-     * iterators.
-     *
      * @return A pointer to the internal packed array.
      * @return A pointer to the internal packed array.
      */
      */
-    [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
-        return packed.data();
+    [[nodiscard]] pointer data() const ENTT_NOEXCEPT {
+        return packed;
     }
     }
 
 
     /**
     /**
@@ -309,8 +428,7 @@ public:
      * @return An iterator to the first entity of the internal packed array.
      * @return An iterator to the first entity of the internal packed array.
      */
      */
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-        const typename traits_type::difference_type pos = packed.size();
-        return iterator{packed, pos};
+        return iterator{&packed, static_cast<typename traits_type::difference_type>(count)};
     }
     }
 
 
     /**
     /**
@@ -324,7 +442,7 @@ public:
      * internal packed array.
      * internal packed array.
      */
      */
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-        return iterator{packed, {}};
+        return iterator{&packed, {}};
     }
     }
 
 
     /**
     /**
@@ -338,7 +456,7 @@ public:
      * array.
      * array.
      */
      */
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-        return packed.data();
+        return data();
     }
     }
 
 
     /**
     /**
@@ -352,7 +470,7 @@ public:
      * reversed internal packed array.
      * reversed internal packed array.
      */
      */
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-        return rbegin() + packed.size();
+        return rbegin() + count;
     }
     }
 
 
     /**
     /**
@@ -373,7 +491,7 @@ public:
     [[nodiscard]] bool contains(const entity_type entt) const {
     [[nodiscard]] bool contains(const entity_type entt) const {
         const auto curr = page(entt);
         const auto curr = page(entt);
         // testing against null permits to avoid accessing the packed array
         // testing against null permits to avoid accessing the packed array
-        return (curr < sparse.size() && sparse[curr] && sparse[curr][offset(entt)] != null);
+        return (curr < bucket && sparse[curr] && sparse[curr][offset(entt)] != null);
     }
     }
 
 
     /**
     /**
@@ -397,7 +515,7 @@ public:
      * @return The entity at specified location if any, a null entity otherwise.
      * @return The entity at specified location if any, a null entity otherwise.
      */
      */
     [[nodiscard]] entity_type at(const size_type pos) const {
     [[nodiscard]] entity_type at(const size_type pos) const {
-        return pos < packed.size() ? packed[pos] : null;
+        return pos < count ? packed[pos] : null;
     }
     }
 
 
     /**
     /**
@@ -406,7 +524,7 @@ public:
      * @return The entity at specified location.
      * @return The entity at specified location.
      */
      */
     [[nodiscard]] entity_type operator[](const size_type pos) const {
     [[nodiscard]] entity_type operator[](const size_type pos) const {
-        ENTT_ASSERT(pos < packed.size(), "Position is out of bounds");
+        ENTT_ASSERT(pos < count, "Position is out of bounds");
         return packed[pos];
         return packed[pos];
     }
     }
 
 
@@ -420,9 +538,8 @@ public:
      * @param entt A valid entity identifier.
      * @param entt A valid entity identifier.
      */
      */
     void emplace(const entity_type entt) {
     void emplace(const entity_type entt) {
-        ENTT_ASSERT(!contains(entt), "Set already contains entity");
-        assure(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(packed.size())};
-        packed.push_back(entt);
+        entity_type arr[1u]{entt};
+        push_back(arr, arr + 1u);
     }
     }
 
 
     /**
     /**
@@ -438,13 +555,7 @@ public:
      */
      */
     template<typename It>
     template<typename It>
     void insert(It first, It last) {
     void insert(It first, It last) {
-        auto next = static_cast<typename traits_type::entity_type>(packed.size());
-        packed.insert(packed.end(), first, last);
-
-        for(; first != last; ++first) {
-            ENTT_ASSERT(!contains(*first), "Set already contains entity");
-            assure(page(*first))[offset(*first)] = entity_type{next++};
-        }
+        push_back(first, last);
     }
     }
 
 
     /**
     /**
@@ -458,24 +569,7 @@ public:
      * @param ud Optional user data that are forwarded as-is to derived classes.
      * @param ud Optional user data that are forwarded as-is to derived classes.
      */
      */
     void erase(const entity_type entt, void *ud = nullptr) {
     void erase(const entity_type entt, void *ud = nullptr) {
-        ENTT_ASSERT(contains(entt), "Set does not contain entity");
-
-        // last chance to use the entity for derived classes and mixins, if any
-        about_to_erase(entt, ud);
-
-        auto &ref = sparse[page(entt)][offset(entt)];
-        const auto pos = size_type{to_integral(ref)};
-
-        const auto other = packed.back();
-        sparse[page(other)][offset(other)] = ref;
-        ref = null;
-
-        // if it looks weird, imagine what the subtle bugs it prevents are
-        ENTT_ASSERT((packed.back() = entt, true), "");
-        packed[pos] = other;
-        packed.pop_back();
-
-        swap_and_pop(pos);
+        pop(entt, ud);
     }
     }
 
 
     /**
     /**
@@ -488,7 +582,7 @@ public:
     template<typename It>
     template<typename It>
     void erase(It first, It last, void *ud = nullptr) {
     void erase(It first, It last, void *ud = nullptr) {
         for(; first != last; ++first) {
         for(; first != last; ++first) {
-            erase(*first, ud);
+            pop(*first, ud);
         }
         }
     }
     }
 
 
@@ -499,7 +593,7 @@ public:
      * @return True if the entity is actually removed, false otherwise.
      * @return True if the entity is actually removed, false otherwise.
      */
      */
     bool remove(const entity_type entt, void *ud = nullptr) {
     bool remove(const entity_type entt, void *ud = nullptr) {
-        return contains(entt) ? (erase(entt, ud), true) : false;
+        return contains(entt) ? (pop(entt, ud), true) : false;
     }
     }
 
 
     /**
     /**
@@ -512,13 +606,13 @@ public:
      */
      */
     template<typename It>
     template<typename It>
     size_type remove(It first, It last, void *ud = nullptr) {
     size_type remove(It first, It last, void *ud = nullptr) {
-        size_type count{};
+        size_type found{};
 
 
         for(; first != last; ++first) {
         for(; first != last; ++first) {
-            count += remove(*first, ud);
+            found += remove(*first, ud);
         }
         }
 
 
-        return count;
+        return found;
     }
     }
 
 
     /**
     /**
@@ -537,9 +631,12 @@ public:
     void swap(const entity_type lhs, const entity_type rhs) {
     void swap(const entity_type lhs, const entity_type rhs) {
         const auto from = index(lhs);
         const auto from = index(lhs);
         const auto to = index(rhs);
         const auto to = index(rhs);
+
+        // derived classes first for a bare-minimum exception safety guarantee
+        swap_at(from, to);
+
         std::swap(sparse[page(lhs)][offset(lhs)], sparse[page(rhs)][offset(rhs)]);
         std::swap(sparse[page(lhs)][offset(lhs)], sparse[page(rhs)][offset(rhs)]);
         std::swap(packed[from], packed[to]);
         std::swap(packed[from], packed[to]);
-        swap_at(from, to);
     }
     }
 
 
     /**
     /**
@@ -567,16 +664,18 @@ public:
      * @tparam Compare Type of comparison function object.
      * @tparam Compare Type of comparison function object.
      * @tparam Sort Type of sort function object.
      * @tparam Sort Type of sort function object.
      * @tparam Args Types of arguments to forward to the sort function object.
      * @tparam Args Types of arguments to forward to the sort function object.
-     * @param count Number of elements to sort.
+     * @param length Number of elements to sort.
      * @param compare A valid comparison function object.
      * @param compare A valid comparison function object.
      * @param algo A valid sort function object.
      * @param algo A valid sort function object.
      * @param args Arguments to forward to the sort function object, if any.
      * @param args Arguments to forward to the sort function object, if any.
      */
      */
     template<typename Compare, typename Sort = std_sort, typename... Args>
     template<typename Compare, typename Sort = std_sort, typename... Args>
-    void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
-        algo(packed.rend() - count, packed.rend(), std::move(compare), std::forward<Args>(args)...);
+    void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
+        ENTT_ASSERT(!(length > count), "Length exceeds the number of elements");
+
+        algo(std::make_reverse_iterator(packed + length), std::make_reverse_iterator(packed), std::move(compare), std::forward<Args>(args)...);
 
 
-        for(size_type pos{}, last = (size() < count ? size() : count); pos < last; ++pos) {
+        for(size_type pos{}; pos < length; ++pos) {
             auto curr = pos;
             auto curr = pos;
             auto next = index(packed[curr]);
             auto next = index(packed[curr]);
 
 
@@ -607,7 +706,7 @@ public:
      */
      */
     template<typename Compare, typename Sort = std_sort, typename... Args>
     template<typename Compare, typename Sort = std_sort, typename... Args>
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
-        sort_n(size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
+        sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
     }
     }
 
 
     /**
     /**
@@ -629,7 +728,7 @@ public:
         const auto to = other.end();
         const auto to = other.end();
         auto from = other.begin();
         auto from = other.begin();
 
 
-        size_type pos = packed.size() - 1;
+        size_type pos = count - 1;
 
 
         while(pos && from != to) {
         while(pos && from != to) {
             if(contains(*from)) {
             if(contains(*from)) {
@@ -653,8 +752,13 @@ public:
     }
     }
 
 
 private:
 private:
-    std::vector<page_type> sparse;
-    std::vector<entity_type> packed;
+    alloc_type allocator;
+    bucket_alloc_type bucket_allocator;
+    bucket_alloc_pointer sparse;
+    alloc_pointer packed;
+    std::size_t bucket;
+    std::size_t count;
+    std::size_t reserved;
 };
 };
 
 
 
 

+ 225 - 81
src/entt/entity/storage.hpp

@@ -2,13 +2,12 @@
 #define ENTT_ENTITY_STORAGE_HPP
 #define ENTT_ENTITY_STORAGE_HPP
 
 
 
 
-#include <algorithm>
 #include <cstddef>
 #include <cstddef>
 #include <iterator>
 #include <iterator>
+#include <memory>
 #include <tuple>
 #include <tuple>
 #include <type_traits>
 #include <type_traits>
 #include <utility>
 #include <utility>
-#include <vector>
 #include "../config/config.h"
 #include "../config/config.h"
 #include "../core/algorithm.hpp"
 #include "../core/algorithm.hpp"
 #include "../core/type_traits.hpp"
 #include "../core/type_traits.hpp"
@@ -30,9 +29,7 @@ namespace entt {
  * to the entities.
  * to the entities.
  *
  *
  * @note
  * @note
- * Entities and objects have the same order. It's guaranteed both in case of raw
- * access (either to entities or objects) and when using random or input access
- * iterators.
+ * Entities and objects have the same order.
  *
  *
  * @note
  * @note
  * Internal data structures arrange elements to maximize performance. There are
  * Internal data structures arrange elements to maximize performance. There are
@@ -47,27 +44,40 @@ namespace entt {
  *
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Type Type of objects assigned to the entities.
  * @tparam Type Type of objects assigned to the entities.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
  */
  */
-template<typename Entity, typename Type, typename = void>
-class basic_storage: public basic_sparse_set<Entity> {
+template<typename Entity, typename Type, typename Allocator, typename>
+class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
     static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
     static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
 
 
+    static constexpr auto growth_factor = 1.5;
+    static constexpr auto page_size = ENTT_PAGE_SIZE;
+
     using underlying_type = basic_sparse_set<Entity>;
     using underlying_type = basic_sparse_set<Entity>;
     using traits_type = entt_traits<Entity>;
     using traits_type = entt_traits<Entity>;
 
 
+    using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Type>;
+    using alloc_traits = std::allocator_traits<alloc_type>;
+    using alloc_pointer = typename alloc_traits::pointer;
+
+    using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
+    using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
+    using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
+    using bucket_alloc_const_pointer = typename bucket_alloc_traits::const_pointer;
+
+    static_assert(alloc_traits::propagate_on_container_move_assignment::value);
+    static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
+
     template<typename Value>
     template<typename Value>
     class storage_iterator final {
     class storage_iterator final {
         friend class basic_storage<Entity, Type>;
         friend class basic_storage<Entity, Type>;
 
 
-        using instance_type = constness_as_t<std::vector<Type>, Value>;
-        using index_type = typename traits_type::difference_type;
-
-        storage_iterator(instance_type &ref, const index_type idx) ENTT_NOEXCEPT
-            : instances{&ref}, index{idx}
+        storage_iterator(const bucket_alloc_const_pointer *ref, const typename traits_type::difference_type idx) ENTT_NOEXCEPT
+            : packed{ref}, index{idx}
         {}
         {}
 
 
     public:
     public:
-        using difference_type = index_type;
+        using difference_type = typename traits_type::difference_type;
         using value_type = Value;
         using value_type = Value;
         using pointer = value_type *;
         using pointer = value_type *;
         using reference = value_type &;
         using reference = value_type &;
@@ -117,7 +127,7 @@ class basic_storage: public basic_sparse_set<Entity> {
 
 
         [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
         [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
             const auto pos = size_type(index-value-1);
             const auto pos = size_type(index-value-1);
-            return (*instances)[pos];
+            return (*packed)[page(pos)][offset(pos)];
         }
         }
 
 
         [[nodiscard]] bool operator==(const storage_iterator &other) const ENTT_NOEXCEPT {
         [[nodiscard]] bool operator==(const storage_iterator &other) const ENTT_NOEXCEPT {
@@ -146,7 +156,7 @@ class basic_storage: public basic_sparse_set<Entity> {
 
 
         [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
         [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
             const auto pos = size_type(index-1u);
             const auto pos = size_type(index-1u);
-            return &(*instances)[pos];
+            return &(*packed)[page(pos)][offset(pos)];
         }
         }
 
 
         [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
         [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
@@ -154,25 +164,104 @@ class basic_storage: public basic_sparse_set<Entity> {
         }
         }
 
 
     private:
     private:
-        instance_type *instances;
-        index_type index;
+        const bucket_alloc_const_pointer *packed;
+        difference_type index;
     };
     };
 
 
+    [[nodiscard]] static auto page(const std::size_t pos) ENTT_NOEXCEPT {
+        return pos / page_size;
+    }
+
+    [[nodiscard]] static auto offset(const std::size_t pos) ENTT_NOEXCEPT {
+        return pos & (page_size - 1);
+    }
+
+    void maybe_resize_packed(const std::size_t req) {
+        ENTT_ASSERT(req && !(req < count), "Invalid request");
+        if(const auto length = std::exchange(bucket, (req + page_size - 1u) / page_size); bucket != length) {
+            const auto old = std::exchange(packed, bucket_alloc_traits::allocate(bucket_allocator, bucket));
+
+            if(bucket > length) {
+                if(length) {
+                    for(size_type pos{}; pos < length; ++pos) {
+                        bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
+                        bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
+                    }
+
+                    bucket_alloc_traits::deallocate(bucket_allocator, old, length);
+                }
+
+                for(auto pos = length; pos < bucket; ++pos) {
+                    bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), alloc_traits::allocate(allocator, page_size));
+                }
+            } else if(bucket < length) {
+                for(size_type pos{}; pos < bucket; ++pos) {
+                    bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
+                    bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
+                }
+
+                for(auto pos = bucket; pos < length; ++pos) {
+                    alloc_traits::deallocate(allocator, old[pos], page_size);
+                    bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
+                }
+
+                bucket_alloc_traits::deallocate(bucket_allocator, old, length);
+            }
+        }
+    }
+
+    template<typename... Args>
+    Type & push_back(Args &&... args) {
+        ENTT_ASSERT(count != (bucket * page_size), "No more space left");
+
+        if constexpr(std::is_aggregate_v<value_type>) {
+            alloc_traits::construct(allocator, std::addressof(*(packed[page(count)] + offset(count))), Type{std::forward<Args>(args)...});
+        } else {
+            alloc_traits::construct(allocator, std::addressof(*(packed[page(count)] + offset(count))), std::forward<Args>(args)...);
+        }
+
+        const auto last = count++;
+        return packed[page(last)][offset(last)];
+    }
+
+    void reset_to_empty() {
+        if(const auto length = std::exchange(bucket, 0u); length) {
+            for(size_type pos{}; pos < length; ++pos) {
+                if(count) {
+                    const auto sz = count > page_size ? page_size : count;
+                    std::destroy(packed[pos], packed[pos] + sz);
+                    count -= sz;
+                }
+
+                alloc_traits::deallocate(allocator, packed[pos], page_size);
+                bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
+            }
+
+            bucket_alloc_traits::deallocate(bucket_allocator, packed, length);
+        }
+    }
+
 protected:
 protected:
     /*! @copydoc basic_sparse_set::swap_at */
     /*! @copydoc basic_sparse_set::swap_at */
     void swap_at(const std::size_t lhs, const std::size_t rhs) final {
     void swap_at(const std::size_t lhs, const std::size_t rhs) final {
-        std::swap(instances[lhs], instances[rhs]);
+        std::swap(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
     }
     }
 
 
     /*! @copydoc basic_sparse_set::swap_and_pop */
     /*! @copydoc basic_sparse_set::swap_and_pop */
     void swap_and_pop(const std::size_t pos) final {
     void swap_and_pop(const std::size_t pos) final {
-        // required because pop_back isn't guaranteed to shrink before removing the element
-        [[maybe_unused]] auto other = std::move(instances[pos]);
-        instances[pos] = std::move(instances.back());
-        instances.pop_back();
+        auto &&elem = packed[page(pos)][offset(pos)];
+        [[maybe_unused]] auto other = std::move(elem);
+
+        auto &&last = packed[page(count - 1)][offset(count - 1)];
+        elem = std::move(last);
+        alloc_traits::destroy(allocator, std::addressof(last));
+
+        --count;
     }
     }
 
 
 public:
 public:
+    /*! @brief Allocator type. */
+    using allocator_type = alloc_type;
     /*! @brief Type of the objects assigned to entities. */
     /*! @brief Type of the objects assigned to entities. */
     using value_type = Type;
     using value_type = Type;
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
@@ -184,9 +273,52 @@ public:
     /*! @brief Constant random access iterator type. */
     /*! @brief Constant random access iterator type. */
     using const_iterator = storage_iterator<const Type>;
     using const_iterator = storage_iterator<const Type>;
     /*! @brief Reverse iterator type. */
     /*! @brief Reverse iterator type. */
-    using reverse_iterator = Type *;
+    using reverse_iterator = std::reverse_iterator<iterator>;
     /*! @brief Constant reverse iterator type. */
     /*! @brief Constant reverse iterator type. */
-    using const_reverse_iterator = const Type *;
+    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+    /**
+     * @brief Default constructor.
+     * @param alloc Allocator to use (possibly default-constructed).
+     */
+    explicit basic_storage(const allocator_type &alloc = {})
+        : allocator{alloc},
+          bucket_allocator{alloc},
+          packed{},
+          bucket{},
+          count{}
+    {}
+
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    basic_storage(basic_storage &&other) ENTT_NOEXCEPT
+        : allocator{std::move(other.allocator)},
+          bucket_allocator{std::move(other.bucket_allocator)},
+          packed{std::exchange(other.packed, bucket_alloc_pointer{})},
+          bucket{std::exchange(other.bucket, 0u)},
+          count{std::exchange(other.count, 0u)}
+    {}
+
+    /*! @brief Default destructor. */
+    ~basic_storage() override {
+        reset_to_empty();
+    }
+
+    /**
+     * @brief Move assignment operator.
+     * @param other The instance to move from.
+     * @return This sparse set.
+     */
+    basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
+        allocator = std::move(other.allocator);
+        bucket_allocator = std::move(other.bucket_allocator);
+        packed = std::exchange(other.packed, bucket_alloc_pointer{});
+        bucket = std::exchange(other.bucket, 0u);
+        count = std::exchange(other.count, 0u);
+        return *this;
+    }
 
 
     /**
     /**
      * @brief Increases the capacity of a storage.
      * @brief Increases the capacity of a storage.
@@ -198,34 +330,25 @@ public:
      */
      */
     void reserve(const size_type cap) {
     void reserve(const size_type cap) {
         underlying_type::reserve(cap);
         underlying_type::reserve(cap);
-        instances.reserve(cap);
-    }
 
 
-    /*! @brief Requests the removal of unused capacity. */
-    void shrink_to_fit() {
-        underlying_type::shrink_to_fit();
-        instances.shrink_to_fit();
+        if(cap > (bucket * page_size)) {
+            maybe_resize_packed(cap);
+        }
     }
     }
 
 
     /**
     /**
-     * @brief Direct access to the array of objects.
-     *
-     * The returned pointer is such that range `[raw(), raw() + size())` is
-     * always a valid range, even if the container is empty.
-     *
-     * @note
-     * Objects are in the reverse order as returned by the `begin`/`end`
-     * iterators.
-     *
-     * @return A pointer to the array of objects.
+     * @brief Returns the number of elements that a storage has currently
+     * allocated space for.
+     * @return Capacity of the storage.
      */
      */
-    [[nodiscard]] const value_type * raw() const ENTT_NOEXCEPT {
-        return instances.data();
+    [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
+        return bucket * page_size;
     }
     }
 
 
-    /*! @copydoc raw */
-    [[nodiscard]] value_type * raw() ENTT_NOEXCEPT {
-        return const_cast<value_type *>(std::as_const(*this).raw());
+    /*! @brief Requests the removal of unused capacity. */
+    void shrink_to_fit() {
+        underlying_type::shrink_to_fit();
+        count ? maybe_resize_packed(count) : reset_to_empty();
     }
     }
 
 
     /**
     /**
@@ -238,7 +361,7 @@ public:
      */
      */
     [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
     [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
         const typename traits_type::difference_type pos = underlying_type::size();
         const typename traits_type::difference_type pos = underlying_type::size();
-        return const_iterator{instances, pos};
+        return const_iterator{&packed, pos};
     }
     }
 
 
     /*! @copydoc cbegin */
     /*! @copydoc cbegin */
@@ -249,7 +372,7 @@ public:
     /*! @copydoc begin */
     /*! @copydoc begin */
     [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
     [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
         const typename traits_type::difference_type pos = underlying_type::size();
         const typename traits_type::difference_type pos = underlying_type::size();
-        return iterator{instances, pos};
+        return iterator{&packed, pos};
     }
     }
 
 
     /**
     /**
@@ -263,7 +386,7 @@ public:
      * internal array.
      * internal array.
      */
      */
     [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
     [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
-        return const_iterator{instances, {}};
+        return const_iterator{&packed, {}};
     }
     }
 
 
     /*! @copydoc cend */
     /*! @copydoc cend */
@@ -273,7 +396,7 @@ public:
 
 
     /*! @copydoc end */
     /*! @copydoc end */
     [[nodiscard]] iterator end() ENTT_NOEXCEPT {
     [[nodiscard]] iterator end() ENTT_NOEXCEPT {
-        return iterator{instances, {}};
+        return iterator{&packed, {}};
     }
     }
 
 
     /**
     /**
@@ -286,7 +409,7 @@ public:
      * @return An iterator to the first instance of the reversed internal array.
      * @return An iterator to the first instance of the reversed internal array.
      */
      */
     [[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT {
     [[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT {
-        return instances.data();
+        return std::make_reverse_iterator(cend());
     }
     }
 
 
     /*! @copydoc crbegin */
     /*! @copydoc crbegin */
@@ -296,7 +419,7 @@ public:
 
 
     /*! @copydoc rbegin */
     /*! @copydoc rbegin */
     [[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT {
-        return instances.data();
+        return std::make_reverse_iterator(end());
     }
     }
 
 
     /**
     /**
@@ -310,7 +433,7 @@ public:
      * reversed internal array.
      * reversed internal array.
      */
      */
     [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT {
     [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT {
-        return crbegin() + instances.size();
+        return std::make_reverse_iterator(cbegin());
     }
     }
 
 
     /*! @copydoc crend */
     /*! @copydoc crend */
@@ -320,7 +443,7 @@ public:
 
 
     /*! @copydoc rend */
     /*! @copydoc rend */
     [[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT {
-        return rbegin() + instances.size();
+        return std::make_reverse_iterator(begin());
     }
     }
 
 
     /**
     /**
@@ -334,7 +457,8 @@ public:
      * @return The object assigned to the entity.
      * @return The object assigned to the entity.
      */
      */
     [[nodiscard]] const value_type & get(const entity_type entt) const {
     [[nodiscard]] const value_type & get(const entity_type entt) const {
-        return instances[underlying_type::index(entt)];
+        const auto idx = underlying_type::index(entt);
+        return packed[page(idx)][offset(idx)];
     }
     }
 
 
     /*! @copydoc get */
     /*! @copydoc get */
@@ -360,15 +484,11 @@ public:
      */
      */
     template<typename... Args>
     template<typename... Args>
     value_type & emplace(const entity_type entt, Args &&... args) {
     value_type & emplace(const entity_type entt, Args &&... args) {
-        if constexpr(std::is_aggregate_v<value_type>) {
-            instances.push_back(Type{std::forward<Args>(args)...});
-        } else {
-            instances.emplace_back(std::forward<Args>(args)...);
-        }
-
+        maybe_resize_packed(count + 1u);
+        auto &value = push_back(std::forward<Args>(args)...);
         // entity goes after component in case constructor throws
         // entity goes after component in case constructor throws
         underlying_type::emplace(entt);
         underlying_type::emplace(entt);
-        return instances.back();
+        return value;
     }
     }
 
 
     /**
     /**
@@ -380,9 +500,10 @@ public:
      */
      */
     template<typename... Func>
     template<typename... Func>
     decltype(auto) patch(const entity_type entity, Func &&... func) {
     decltype(auto) patch(const entity_type entity, Func &&... func) {
-        auto &&instance = instances[this->index(entity)];
-        (std::forward<Func>(func)(instance), ...);
-        return instance;
+        const auto idx = underlying_type::index(entity);
+        auto &&elem = packed[page(idx)][offset(idx)];
+        (std::forward<Func>(func)(elem), ...);
+        return elem;
     }
     }
 
 
     /**
     /**
@@ -400,9 +521,15 @@ public:
      */
      */
     template<typename It>
     template<typename It>
     void insert(It first, It last, const value_type &value = {}) {
     void insert(It first, It last, const value_type &value = {}) {
-        instances.insert(instances.end(), std::distance(first, last), value);
-        // entities go after components in case constructors throw
-        underlying_type::insert(first, last);
+        if(const auto length = std::distance(first, last); length) {
+            maybe_resize_packed(count + length);
+
+            for(auto pos = length; pos; --pos) {
+                push_back(value);
+            }
+
+            underlying_type::insert(first, last);
+        }
     }
     }
 
 
     /**
     /**
@@ -420,9 +547,15 @@ public:
      */
      */
     template<typename EIt, typename CIt>
     template<typename EIt, typename CIt>
     void insert(EIt first, EIt last, CIt from, CIt to) {
     void insert(EIt first, EIt last, CIt from, CIt to) {
-        instances.insert(instances.end(), from, to);
-        // entities go after components in case constructors throw
-        underlying_type::insert(first, last);
+        if(const auto length = std::distance(from, to); length) {
+            maybe_resize_packed(count + length);
+
+            for(; from != to; ++from) {
+                push_back(*from);
+            }
+
+            underlying_type::insert(first, last);
+        }
     }
     }
 
 
     /**
     /**
@@ -455,19 +588,20 @@ public:
      * @tparam Compare Type of comparison function object.
      * @tparam Compare Type of comparison function object.
      * @tparam Sort Type of sort function object.
      * @tparam Sort Type of sort function object.
      * @tparam Args Types of arguments to forward to the sort function object.
      * @tparam Args Types of arguments to forward to the sort function object.
-     * @param count Number of elements to sort.
+     * @param length Number of elements to sort.
      * @param compare A valid comparison function object.
      * @param compare A valid comparison function object.
      * @param algo A valid sort function object.
      * @param algo A valid sort function object.
      * @param args Arguments to forward to the sort function object, if any.
      * @param args Arguments to forward to the sort function object, if any.
      */
      */
     template<typename Compare, typename Sort = std_sort, typename... Args>
     template<typename Compare, typename Sort = std_sort, typename... Args>
-    void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
+    void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
         if constexpr(std::is_invocable_v<Compare, const value_type &, const value_type &>) {
         if constexpr(std::is_invocable_v<Compare, const value_type &, const value_type &>) {
-            underlying_type::sort_n(count, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
-                return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
+            underlying_type::sort_n(length, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
+                const auto ilhs = underlying_type::index(lhs), irhs = underlying_type::index(rhs);
+                return compare(std::as_const(packed[page(ilhs)][offset(ilhs)]), std::as_const(packed[page(irhs)][offset(irhs)]));
             }, std::move(algo), std::forward<Args>(args)...);
             }, std::move(algo), std::forward<Args>(args)...);
         } else {
         } else {
-            underlying_type::sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
+            underlying_type::sort_n(length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
         }
         }
     }
     }
 
 
@@ -485,17 +619,21 @@ public:
      */
      */
     template<typename Compare, typename Sort = std_sort, typename... Args>
     template<typename Compare, typename Sort = std_sort, typename... Args>
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
-        sort_n(this->size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
+        sort_n(underlying_type::size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
     }
     }
 
 
 private:
 private:
-    std::vector<value_type> instances;
+    alloc_type allocator;
+    bucket_alloc_type bucket_allocator;
+    bucket_alloc_pointer packed;
+    std::size_t bucket;
+    std::size_t count;
 };
 };
 
 
 
 
 /*! @copydoc basic_storage */
 /*! @copydoc basic_storage */
-template<typename Entity, typename Type>
-class basic_storage<Entity, Type, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity> {
+template<typename Entity, typename Type, typename Allocator>
+class basic_storage<Entity, Type, Allocator, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
     using underlying_type = basic_sparse_set<Entity>;
     using underlying_type = basic_sparse_set<Entity>;
 
 
 public:
 public:
@@ -516,7 +654,7 @@ public:
      * @param entt A valid entity identifier.
      * @param entt A valid entity identifier.
      */
      */
     void get([[maybe_unused]] const entity_type entt) const {
     void get([[maybe_unused]] const entity_type entt) const {
-        ENTT_ASSERT(this->contains(entt), "Storage does not contain entity");
+        ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
     }
     }
 
 
     /**
     /**
@@ -544,7 +682,7 @@ public:
     */
     */
     template<typename... Func>
     template<typename... Func>
     void patch([[maybe_unused]] const entity_type entity, Func &&... func) {
     void patch([[maybe_unused]] const entity_type entity, Func &&... func) {
-        ENTT_ASSERT(this->contains(entity), "Storage does not contain entity");
+        ENTT_ASSERT(underlying_type::contains(entity), "Storage does not contain entity");
         (std::forward<Func>(func)(), ...);
         (std::forward<Func>(func)(), ...);
     }
     }
 
 
@@ -579,6 +717,9 @@ struct storage_adapter_mixin: Type {
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
     using entity_type = typename Type::entity_type;
     using entity_type = typename Type::entity_type;
 
 
+    /*! @brief Inherited constructors. */
+    using Type::Type;
+
     /**
     /**
      * @brief Assigns entities to a storage.
      * @brief Assigns entities to a storage.
      * @tparam Args Types of arguments to use to construct the object.
      * @tparam Args Types of arguments to use to construct the object.
@@ -639,6 +780,9 @@ public:
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
     using entity_type = typename Type::entity_type;
     using entity_type = typename Type::entity_type;
 
 
+    /*! @brief Inherited constructors. */
+    using Type::Type;
+
     /**
     /**
      * @brief Returns a sink object.
      * @brief Returns a sink object.
      *
      *

+ 0 - 13
src/entt/entity/view.hpp

@@ -708,19 +708,6 @@ public:
         return std::get<0>(pools)->empty();
         return std::get<0>(pools)->empty();
     }
     }
 
 
-    /**
-     * @brief Direct access to the raw representation offered by the storage.
-     *
-     * For fully contiguous storage classes, the returned pointer is such that
-     * range `[raw<Component>(), raw<Component>() + size())` is always a valid
-     * range, even if the container is empty.
-     *
-     * @return A pointer to the array of components.
-     */
-    [[nodiscard]] auto raw() const ENTT_NOEXCEPT {
-        return std::get<0>(pools)->raw();
-    }
-
     /**
     /**
      * @brief Direct access to the list of entities.
      * @brief Direct access to the list of entities.
      *
      *

+ 29 - 44
test/entt/entity/group.cpp

@@ -665,8 +665,8 @@ TEST(OwningGroup, Functionalities) {
 
 
     ASSERT_EQ(group.size(), 1u);
     ASSERT_EQ(group.size(), 1u);
 
 
-    ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
-    ASSERT_EQ(*(group.raw<int>() + 0), 42);
+    ASSERT_EQ(*(cgroup.rbegin() + 0), e1);
+    ASSERT_EQ(*(group.rbegin() + 0), e1);
 
 
     for(auto entity: group) {
     for(auto entity: group) {
         ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
         ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
@@ -675,7 +675,6 @@ TEST(OwningGroup, Functionalities) {
     }
     }
 
 
     ASSERT_EQ(*(group.data() + 0), e1);
     ASSERT_EQ(*(group.data() + 0), e1);
-    ASSERT_EQ(*(group.raw<int>() + 0), 42);
 
 
     registry.erase<char>(e0);
     registry.erase<char>(e0);
     registry.erase<char>(e1);
     registry.erase<char>(e1);
@@ -706,7 +705,6 @@ TEST(OwningGroup, Invalid) {
     ASSERT_TRUE(group.empty());
     ASSERT_TRUE(group.empty());
     ASSERT_EQ(group.size(), 0u);
     ASSERT_EQ(group.size(), 0u);
 
 
-    ASSERT_EQ(group.raw<const int>(), nullptr);
     ASSERT_EQ(group.data(), nullptr);
     ASSERT_EQ(group.data(), nullptr);
 
 
     ASSERT_EQ(group.begin(), group.end());
     ASSERT_EQ(group.begin(), group.end());
@@ -844,18 +842,14 @@ TEST(OwningGroup, SortOrdered) {
     ASSERT_EQ(*(group.data() + 0u), entities[0]);
     ASSERT_EQ(*(group.data() + 0u), entities[0]);
     ASSERT_EQ(*(group.data() + 1u), entities[1]);
     ASSERT_EQ(*(group.data() + 1u), entities[1]);
     ASSERT_EQ(*(group.data() + 2u), entities[2]);
     ASSERT_EQ(*(group.data() + 2u), entities[2]);
-    ASSERT_EQ(*(group.data() + 3u), entities[3]);
-    ASSERT_EQ(*(group.data() + 4u), entities[4]);
 
 
-    ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
-    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
-    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
-    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
-    ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
 
 
-    ASSERT_EQ(*(group.raw<char>() + 0u), 'a');
-    ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
-    ASSERT_EQ(*(group.raw<char>() + 2u), 'c');
+    ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'a');
+    ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'b');
+    ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'c');
 
 
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
@@ -891,18 +885,14 @@ TEST(OwningGroup, SortReverse) {
     ASSERT_EQ(*(group.data() + 0u), entities[2]);
     ASSERT_EQ(*(group.data() + 0u), entities[2]);
     ASSERT_EQ(*(group.data() + 1u), entities[1]);
     ASSERT_EQ(*(group.data() + 1u), entities[1]);
     ASSERT_EQ(*(group.data() + 2u), entities[0]);
     ASSERT_EQ(*(group.data() + 2u), entities[0]);
-    ASSERT_EQ(*(group.data() + 3u), entities[3]);
-    ASSERT_EQ(*(group.data() + 4u), entities[4]);
 
 
-    ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
-    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
-    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
-    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
-    ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
 
 
-    ASSERT_EQ(*(group.raw<char>() + 0u), 'c');
-    ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
-    ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
+    ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'c');
+    ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'b');
+    ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'a');
 
 
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
@@ -948,16 +938,18 @@ TEST(OwningGroup, SortUnordered) {
     ASSERT_EQ(*(group.data() + 2u), entities[0]);
     ASSERT_EQ(*(group.data() + 2u), entities[0]);
     ASSERT_EQ(*(group.data() + 3u), entities[1]);
     ASSERT_EQ(*(group.data() + 3u), entities[1]);
     ASSERT_EQ(*(group.data() + 4u), entities[2]);
     ASSERT_EQ(*(group.data() + 4u), entities[2]);
-    ASSERT_EQ(*(group.data() + 5u), entities[5]);
-    ASSERT_EQ(*(group.data() + 6u), entities[6]);
 
 
-    ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
-    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
-    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
-    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 3);
-    ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 1);
-    ASSERT_EQ((group.raw<boxed_int>() + 5u)->value, 4);
-    ASSERT_EQ((group.raw<boxed_int>() + 6u)->value, 5);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 3u)).value, 3);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 4u)).value, 1);
+
+    ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'e');
+    ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'd');
+    ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'c');
+    ASSERT_EQ(group.get<char>(*(group.data() + 3u)), 'b');
+    ASSERT_EQ(group.get<char>(*(group.data() + 4u)), 'a');
 
 
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'c')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'c')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{3}, 'b')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{3}, 'b')));
@@ -993,15 +985,10 @@ TEST(OwningGroup, SortWithExclusionList) {
     ASSERT_EQ(*(group.data() + 2u), entities[1]);
     ASSERT_EQ(*(group.data() + 2u), entities[1]);
     ASSERT_EQ(*(group.data() + 3u), entities[0]);
     ASSERT_EQ(*(group.data() + 3u), entities[0]);
 
 
-    ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 4);
-    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 3);
-    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 1);
-    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 0);
-
-    ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
-    ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
-    ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3);
-    ASSERT_EQ(group.get<boxed_int>(entities[4]).value, 4);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 4);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 3);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 1);
+    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 3u)).value, 0);
 
 
     ASSERT_FALSE(group.contains(entities[2]));
     ASSERT_FALSE(group.contains(entities[2]));
 }
 }
@@ -1062,8 +1049,6 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
     static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
     static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
-    static_assert(std::is_same_v<decltype(group.raw<const char>()), const char *>);
-    static_assert(std::is_same_v<decltype(group.raw<int>()), int *>);
 
 
     group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
     group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
         static_assert(std::is_same_v<decltype(i), int &>);
         static_assert(std::is_same_v<decltype(i), int &>);

+ 26 - 6
test/entt/entity/helper.cpp

@@ -41,19 +41,39 @@ TEST(Helper, Invoke) {
 
 
 TEST(Helper, ToEntity) {
 TEST(Helper, ToEntity) {
     entt::registry registry;
     entt::registry registry;
+    const entt::entity null = entt::null;
+    const int value = 42;
+
+    ASSERT_EQ(entt::to_entity(registry, 42), null);
+    ASSERT_EQ(entt::to_entity(registry, value), null);
+
     const auto entity = registry.create();
     const auto entity = registry.create();
+    registry.emplace<int>(entity);
+
+    while(registry.size<int>() < (ENTT_PAGE_SIZE - 1u)) {
+        registry.emplace<int>(registry.create(), value);
+    }
+
     const auto other = registry.create();
     const auto other = registry.create();
+    const auto next = registry.create();
 
 
-    registry.emplace<int>(entity);
     registry.emplace<int>(other);
     registry.emplace<int>(other);
-    registry.emplace<char>(other);
+    registry.emplace<int>(next);
 
 
     ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
     ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
     ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
     ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
-    ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
+    ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
 
 
-    registry.destroy(entity);
+    ASSERT_EQ(&registry.get<int>(entity) + ENTT_PAGE_SIZE - 1u, &registry.get<int>(other));
+    ASSERT_NE(&registry.get<int>(entity) + ENTT_PAGE_SIZE, &registry.get<int>(next));
 
 
-    ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
-    ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
+    registry.destroy(other);
+
+    ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
+    ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
+
+    ASSERT_EQ(&registry.get<int>(entity) + ENTT_PAGE_SIZE - 1u, &registry.get<int>(next));
+
+    ASSERT_EQ(entt::to_entity(registry, 42), null);
+    ASSERT_EQ(entt::to_entity(registry, value), null);
 }
 }

+ 17 - 5
test/entt/entity/registry.cpp

@@ -154,8 +154,8 @@ TEST(Registry, Functionalities) {
     ASSERT_TRUE(registry.empty());
     ASSERT_TRUE(registry.empty());
 
 
     ASSERT_EQ(registry.capacity(), 42u);
     ASSERT_EQ(registry.capacity(), 42u);
-    ASSERT_EQ(registry.capacity<int>(), 8u);
-    ASSERT_EQ(registry.capacity<char>(), 8u);
+    ASSERT_EQ(registry.capacity<int>(), ENTT_PAGE_SIZE);
+    ASSERT_EQ(registry.capacity<char>(), ENTT_PAGE_SIZE);
     ASSERT_EQ(registry.size<int>(), 0u);
     ASSERT_EQ(registry.size<int>(), 0u);
     ASSERT_EQ(registry.size<char>(), 0u);
     ASSERT_EQ(registry.size<char>(), 0u);
     ASSERT_TRUE((registry.empty<int, char>()));
     ASSERT_TRUE((registry.empty<int, char>()));
@@ -295,8 +295,8 @@ TEST(Registry, Functionalities) {
     ASSERT_EQ(registry.size<char>(), 0u);
     ASSERT_EQ(registry.size<char>(), 0u);
     ASSERT_TRUE(registry.empty<int>());
     ASSERT_TRUE(registry.empty<int>());
 
 
-    ASSERT_EQ(registry.capacity<int>(), 8u);
-    ASSERT_EQ(registry.capacity<char>(), 8u);
+    ASSERT_EQ(registry.capacity<int>(), ENTT_PAGE_SIZE);
+    ASSERT_EQ(registry.capacity<char>(), ENTT_PAGE_SIZE);
 
 
     registry.shrink_to_fit<int, char>();
     registry.shrink_to_fit<int, char>();
 
 
@@ -530,13 +530,25 @@ TEST(Registry, VersionOverflow) {
     entt::registry registry;
     entt::registry registry;
     const auto entity = registry.create();
     const auto entity = registry.create();
 
 
-    registry.destroy(entity, typename traits_type::version_type(traits_type::version_mask));
+    registry.destroy(entity);
+
+    ASSERT_NE(registry.current(entity), registry.version(entity));
+    ASSERT_NE(registry.current(entity), typename traits_type::version_type{});
+
+    registry.destroy(registry.create(), typename traits_type::version_type{traits_type::version_mask});
     registry.destroy(registry.create());
     registry.destroy(registry.create());
 
 
     ASSERT_EQ(registry.current(entity), registry.version(entity));
     ASSERT_EQ(registry.current(entity), registry.version(entity));
     ASSERT_EQ(registry.current(entity), typename traits_type::version_type{});
     ASSERT_EQ(registry.current(entity), typename traits_type::version_type{});
 }
 }
 
 
+TEST(Registry, NullEntity) {
+    entt::registry registry;
+
+    ASSERT_FALSE(registry.valid(entt::null));
+    ASSERT_DEATH(static_cast<void>(registry.create(entt::null)), "");
+}
+
 TEST(Registry, Each) {
 TEST(Registry, Each) {
     entt::registry registry;
     entt::registry registry;
     entt::registry::size_type tot;
     entt::registry::size_type tot;

+ 0 - 1
test/entt/entity/registry_no_eto.cpp

@@ -14,7 +14,6 @@ TEST(Registry, NoEto) {
     registry.emplace<empty_type>(entity);
     registry.emplace<empty_type>(entity);
     registry.emplace<int>(entity, 42);
     registry.emplace<int>(entity, 42);
 
 
-    ASSERT_NE(registry.view<empty_type>().raw(), nullptr);
     ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
     ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
     ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));
     ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));
 
 

+ 38 - 25
test/entt/entity/storage.cpp

@@ -40,7 +40,7 @@ TEST(Storage, Functionalities) {
 
 
     pool.reserve(42);
     pool.reserve(42);
 
 
-    ASSERT_EQ(pool.capacity(), 42u);
+    ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
     ASSERT_TRUE(pool.empty());
     ASSERT_TRUE(pool.empty());
     ASSERT_EQ(pool.size(), 0u);
     ASSERT_EQ(pool.size(), 0u);
     ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end());
     ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end());
@@ -80,7 +80,7 @@ TEST(Storage, Functionalities) {
     ASSERT_FALSE(pool.contains(entt::entity{0}));
     ASSERT_FALSE(pool.contains(entt::entity{0}));
     ASSERT_FALSE(pool.contains(entt::entity{41}));
     ASSERT_FALSE(pool.contains(entt::entity{41}));
 
 
-    ASSERT_EQ(pool.capacity(), 42u);
+    ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
 
 
     pool.shrink_to_fit();
     pool.shrink_to_fit();
 
 
@@ -208,6 +208,35 @@ TEST(Storage, Remove) {
     ASSERT_EQ(*pool.begin(), 1);
     ASSERT_EQ(*pool.begin(), 1);
 }
 }
 
 
+TEST(Storage, ShrinkToFit) {
+    entt::storage<int> pool;
+
+    for(std::size_t next{}; next < ENTT_PAGE_SIZE; ++next) {
+        pool.emplace(entt::entity(next));
+    }
+
+    pool.emplace(entt::entity{ENTT_PAGE_SIZE});
+    pool.erase(entt::entity{ENTT_PAGE_SIZE});
+
+    ASSERT_EQ(pool.capacity(), 2 * ENTT_PAGE_SIZE);
+    ASSERT_EQ(pool.size(), ENTT_PAGE_SIZE);
+
+    pool.shrink_to_fit();
+
+    ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
+    ASSERT_EQ(pool.size(), ENTT_PAGE_SIZE);
+
+    pool.clear();
+
+    ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
+    ASSERT_EQ(pool.size(), 0u);
+
+    pool.shrink_to_fit();
+
+    ASSERT_EQ(pool.capacity(), 0u);
+    ASSERT_EQ(pool.size(), 0u);
+}
+
 TEST(Storage, AggregatesMustWork) {
 TEST(Storage, AggregatesMustWork) {
     struct aggregate_type { int value; };
     struct aggregate_type { int value; };
     // the goal of this test is to enforce the requirements for aggregate types
     // the goal of this test is to enforce the requirements for aggregate types
@@ -389,22 +418,6 @@ TEST(Storage, ConstReverseIterator) {
     ASSERT_GE(cend, pool.crend());
     ASSERT_GE(cend, pool.crend());
 }
 }
 
 
-TEST(Storage, Raw) {
-    entt::storage<int> pool;
-
-    pool.emplace(entt::entity{3}, 3);
-    pool.emplace(entt::entity{12}, 6);
-    pool.emplace(entt::entity{42}, 9);
-
-    ASSERT_EQ(pool.get(entt::entity{3}), 3);
-    ASSERT_EQ(std::as_const(pool).get(entt::entity{12}), 6);
-    ASSERT_EQ(pool.get(entt::entity{42}), 9);
-
-    ASSERT_EQ(pool.raw()[0u], 3);
-    ASSERT_EQ(std::as_const(pool).raw()[1u], 6);
-    ASSERT_EQ(pool.raw()[2u], 9);
-}
-
 TEST(Storage, SortOrdered) {
 TEST(Storage, SortOrdered) {
     entt::storage<boxed_int> pool;
     entt::storage<boxed_int> pool;
     entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
     entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
@@ -467,9 +480,9 @@ TEST(Storage, SortRange) {
 
 
     pool.sort_n(2u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
     pool.sort_n(2u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
 
 
-    ASSERT_EQ(pool.raw()[0u], boxed_int{6});
-    ASSERT_EQ(pool.raw()[1u], boxed_int{3});
-    ASSERT_EQ(pool.raw()[2u], boxed_int{1});
+    ASSERT_EQ(pool.rbegin()[0u], boxed_int{6});
+    ASSERT_EQ(pool.rbegin()[1u], boxed_int{3});
+    ASSERT_EQ(pool.rbegin()[2u], boxed_int{1});
 
 
     ASSERT_EQ(pool.data()[0u], entt::entity{42});
     ASSERT_EQ(pool.data()[0u], entt::entity{42});
     ASSERT_EQ(pool.data()[1u], entt::entity{12});
     ASSERT_EQ(pool.data()[1u], entt::entity{12});
@@ -650,12 +663,12 @@ TEST(Storage, CanModifyDuringIteration) {
     entt::storage<int> pool;
     entt::storage<int> pool;
     pool.emplace(entt::entity{0}, 42);
     pool.emplace(entt::entity{0}, 42);
 
 
-    ASSERT_EQ(pool.capacity(), 1u);
+    ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
 
 
     const auto it = pool.cbegin();
     const auto it = pool.cbegin();
-    pool.reserve(2u);
+    pool.reserve(ENTT_PAGE_SIZE + 1u);
 
 
-    ASSERT_EQ(pool.capacity(), 2u);
+    ASSERT_EQ(pool.capacity(), 2 * ENTT_PAGE_SIZE);
 
 
     // this should crash with asan enabled if we break the constraint
     // this should crash with asan enabled if we break the constraint
     const auto entity = *it;
     const auto entity = *it;
@@ -696,7 +709,7 @@ TEST(Storage, MoveOnlyComponent) {
     (void)pool;
     (void)pool;
 }
 }
 
 
-TEST(Storage, ConstructorExceptionDoesNotAddToStorage) {
+TEST(Storage, EmplaceStrongExceptionGuarantee) {
     entt::storage<throwing_component> pool;
     entt::storage<throwing_component> pool;
 
 
     try {
     try {

+ 1 - 14
test/entt/entity/view.cpp

@@ -47,9 +47,6 @@ TEST(SingleComponentView, Functionalities) {
     ASSERT_EQ(*(view.data() + 0), e1);
     ASSERT_EQ(*(view.data() + 0), e1);
     ASSERT_EQ(*(view.data() + 1), e0);
     ASSERT_EQ(*(view.data() + 1), e0);
 
 
-    ASSERT_EQ(*(view.raw() + 0), '2');
-    ASSERT_EQ(*(cview.raw() + 1), '1');
-
     registry.erase<char>(e0);
     registry.erase<char>(e0);
     registry.erase<char>(e1);
     registry.erase<char>(e1);
 
 
@@ -64,7 +61,7 @@ TEST(SingleComponentView, Functionalities) {
     ASSERT_FALSE(invalid);
     ASSERT_FALSE(invalid);
 }
 }
 
 
-TEST(SingleComponentView, RawData) {
+TEST(SingleComponentView, Data) {
     entt::registry registry;
     entt::registry registry;
     auto view = registry.view<int>();
     auto view = registry.view<int>();
     auto cview = std::as_const(registry).view<const int>();
     auto cview = std::as_const(registry).view<const int>();
@@ -73,8 +70,6 @@ TEST(SingleComponentView, RawData) {
 
 
     ASSERT_EQ(view.size(), 0u);
     ASSERT_EQ(view.size(), 0u);
     ASSERT_EQ(cview.size(), 0u);
     ASSERT_EQ(cview.size(), 0u);
-    ASSERT_EQ(view.raw(), nullptr);
-    ASSERT_EQ(cview.raw(), nullptr);
     ASSERT_EQ(view.data(), nullptr);
     ASSERT_EQ(view.data(), nullptr);
     ASSERT_EQ(cview.data(), nullptr);
     ASSERT_EQ(cview.data(), nullptr);
 
 
@@ -82,8 +77,6 @@ TEST(SingleComponentView, RawData) {
 
 
     ASSERT_NE(view.size(), 0u);
     ASSERT_NE(view.size(), 0u);
     ASSERT_NE(cview.size(), 0u);
     ASSERT_NE(cview.size(), 0u);
-    ASSERT_EQ(*view.raw(), 42);
-    ASSERT_EQ(*cview.raw(), 42);
     ASSERT_EQ(*view.data(), entity);
     ASSERT_EQ(*view.data(), entity);
     ASSERT_EQ(*cview.data(), entity);
     ASSERT_EQ(*cview.data(), entity);
 
 
@@ -105,7 +98,6 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) {
     ASSERT_TRUE(cview);
     ASSERT_TRUE(cview);
     ASSERT_TRUE(eview);
     ASSERT_TRUE(eview);
 
 
-    ASSERT_NE(cview.raw(), nullptr);
     ASSERT_NE(eview.data(), nullptr);
     ASSERT_NE(eview.data(), nullptr);
 
 
     ASSERT_FALSE(cview.empty());
     ASSERT_FALSE(cview.empty());
@@ -226,15 +218,10 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
     ASSERT_EQ(view.size(), 1u);
     ASSERT_EQ(view.size(), 1u);
     ASSERT_EQ(cview.size(), 1u);
     ASSERT_EQ(cview.size(), 1u);
 
 
-    static_assert(std::is_same_v<decltype(view.raw()), int *>);
-    static_assert(std::is_same_v<decltype(cview.raw()), const int *>);
-
     static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
     static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
     static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
     static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
-    static_assert(std::is_same_v<decltype(view.raw()), int *>);
     static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
     static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
     static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
     static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
-    static_assert(std::is_same_v<decltype(cview.raw()), const int *>);
 
 
     view.each([](auto &&i) {
     view.each([](auto &&i) {
         static_assert(std::is_same_v<decltype(i), int &>);
         static_assert(std::is_same_v<decltype(i), int &>);