Procházet zdrojové kódy

rework to easily allow storage specializations

Michele Caini před 7 roky
rodič
revize
3a53cac607

+ 5 - 4
src/entt/entity/group.hpp

@@ -8,6 +8,7 @@
 #include "../config/config.h"
 #include "../core/type_traits.hpp"
 #include "sparse_set.hpp"
+#include "storage.hpp"
 #include "fwd.hpp"
 
 
@@ -85,10 +86,10 @@ class basic_group<Entity, get_t<Get...>> {
     friend class basic_registry<Entity>;
 
     template<typename Component>
-    using pool_type = std::conditional_t<std::is_const_v<Component>, const sparse_set<Entity, std::remove_const_t<Component>>, sparse_set<Entity, Component>>;
+    using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
 
     // we could use pool_type<Get> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
-    basic_group(sparse_set<Entity> *ref, sparse_set<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT
+    basic_group(sparse_set<Entity> *ref, storage<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT
         : handler{ref},
           pools{get...}
     {}
@@ -415,7 +416,7 @@ class basic_group<Entity, get_t<Get...>, Owned...> {
     friend class basic_registry<Entity>;
 
     template<typename Component>
-    using pool_type = std::conditional_t<std::is_const_v<Component>, const sparse_set<Entity, std::remove_const_t<Component>>, sparse_set<Entity, Component>>;
+    using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
 
     template<typename Component>
     using component_iterator_type = decltype(std::declval<pool_type<Component>>().begin());
@@ -430,7 +431,7 @@ class basic_group<Entity, get_t<Get...>, Owned...> {
     }
 
     // we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
-    basic_group(const typename basic_registry<Entity>::size_type *sz, sparse_set<Entity, std::remove_const_t<Owned>> *... owned, sparse_set<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT
+    basic_group(const typename basic_registry<Entity>::size_type *sz, storage<Entity, std::remove_const_t<Owned>> *... owned, storage<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT
         : length{sz},
           pools{owned..., get...}
     {}

+ 8 - 7
src/entt/entity/registry.hpp

@@ -20,6 +20,7 @@
 #include "runtime_view.hpp"
 #include "sparse_set.hpp"
 #include "snapshot.hpp"
+#include "storage.hpp"
 #include "entity.hpp"
 #include "group.hpp"
 #include "view.hpp"
@@ -62,7 +63,7 @@ class basic_registry {
     using traits_type = entt_traits<Entity>;
 
     template<typename Component>
-    struct pool_handler: sparse_set<Entity, Component> {
+    struct pool_handler: storage<Entity, Component> {
         sigh<void(basic_registry &, const Entity, Component &)> on_construct;
         sigh<void(basic_registry &, const Entity, Component &)> on_replace;
         sigh<void(basic_registry &, const Entity)> on_destroy;
@@ -70,20 +71,20 @@ class basic_registry {
 
         pool_handler() ENTT_NOEXCEPT = default;
 
-        pool_handler(const sparse_set<Entity, Component> &other)
-            : sparse_set<Entity, Component>{other}
+        pool_handler(const storage<Entity, Component> &other)
+            : storage<Entity, Component>{other}
         {}
 
         template<typename... Args>
         Component & assign(basic_registry &registry, const Entity entt, Args &&... args) {
-            auto &component = sparse_set<Entity, Component>::construct(entt, std::forward<Args>(args)...);
+            auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
             on_construct.publish(registry, entt, component);
             return component;
         }
 
         template<typename It>
         Component * batch(basic_registry &registry, It first, It last) {
-            auto *component = sparse_set<Entity, Component>::batch(first, last);
+            auto *component = storage<Entity, Component>::batch(first, last);
 
             if(!on_construct.empty()) {
                 std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
@@ -96,14 +97,14 @@ class basic_registry {
 
         void remove(basic_registry &registry, const Entity entt) {
             on_destroy.publish(registry, entt);
-            sparse_set<Entity, Component>::destroy(entt);
+            storage<Entity, Component>::destroy(entt);
         }
 
         template<typename... Args>
         Component & replace(basic_registry &registry, const Entity entt, Args &&... args) {
             Component component{std::forward<Args>(args)...};
             on_replace.publish(registry, entt, component);
-            return (sparse_set<Entity, Component>::get(entt) = std::move(component));
+            return (storage<Entity, Component>::get(entt) = std::move(component));
         }
     };
 

+ 3 - 663
src/entt/entity/sparse_set.hpp

@@ -4,30 +4,17 @@
 
 #include <algorithm>
 #include <iterator>
-#include <numeric>
 #include <utility>
 #include <vector>
 #include <memory>
 #include <cstddef>
-#include <type_traits>
 #include "../config/config.h"
-#include "../core/algorithm.hpp"
 #include "entity.hpp"
 
 
 namespace entt {
 
 
-/**
- * @brief Sparse set.
- *
- * Primary template isn't defined on purpose. All the specializations give a
- * compile-time error, but for a few reasonable cases.
- */
-template<typename...>
-class sparse_set;
-
-
 /**
  * @brief Basic sparse set implementation.
  *
@@ -36,8 +23,8 @@ class sparse_set;
  * _packed_ one; one used for direct access through contiguous memory, the other
  * one used to get the data through an extra level of indirection.<br/>
  * This is largely used by the registry to offer users the fastest access ever
- * to the components. Views in general are almost entirely designed around
- * sparse sets.
+ * to the components. Views and groups in general are almost entirely designed
+ * around sparse sets.
  *
  * This type of data structure is widely documented in the literature and on the
  * web. This is nothing more than a customized implementation suitable for the
@@ -56,7 +43,7 @@ class sparse_set;
  * @tparam Entity A valid entity type (see entt_traits for more details).
  */
 template<typename Entity>
-class sparse_set<Entity> {
+class sparse_set {
     using traits_type = entt_traits<Entity>;
 
     static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0));
@@ -542,653 +529,6 @@ private:
 };
 
 
-/**
- * @brief Extended sparse set implementation.
- *
- * This specialization of a sparse set associates an object to an entity. The
- * main purpose of this class is to use sparse sets to store components in a
- * registry. It guarantees fast access both to the elements and to the entities.
- *
- * @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 input iterators.
- *
- * @note
- * Internal data structures arrange elements to maximize performance. Because of
- * that, there are no guarantees that elements have the expected order when
- * iterate directly the internal packed array (see `raw` and `size` member
- * functions for that). Use `begin` and `end` instead.
- *
- * @sa sparse_set<Entity>
- *
- * @tparam Entity A valid entity type (see entt_traits for more details).
- * @tparam Type Type of objects assigned to the entities.
- */
-template<typename Entity, typename Type>
-class sparse_set<Entity, Type>: public sparse_set<Entity> {
-    using underlying_type = sparse_set<Entity>;
-    using traits_type = entt_traits<Entity>;
-
-    template<bool Const, bool = std::is_empty_v<Type>>
-    class iterator {
-        friend class sparse_set<Entity, Type>;
-
-        using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>;
-        using index_type = typename traits_type::difference_type;
-
-        iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
-            : instances{ref}, index{idx}
-        {}
-
-    public:
-        using difference_type = index_type;
-        using value_type = std::conditional_t<Const, const Type, Type>;
-        using pointer = value_type *;
-        using reference = value_type &;
-        using iterator_category = std::random_access_iterator_tag;
-
-        iterator() ENTT_NOEXCEPT = default;
-
-        iterator & operator++() ENTT_NOEXCEPT {
-            return --index, *this;
-        }
-
-        iterator operator++(int) ENTT_NOEXCEPT {
-            iterator orig = *this;
-            return ++(*this), orig;
-        }
-
-        iterator & operator--() ENTT_NOEXCEPT {
-            return ++index, *this;
-        }
-
-        iterator operator--(int) ENTT_NOEXCEPT {
-            iterator orig = *this;
-            return --(*this), orig;
-        }
-
-        iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
-            index -= value;
-            return *this;
-        }
-
-        iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
-            return iterator{instances, index-value};
-        }
-
-        inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
-            return (*this += -value);
-        }
-
-        inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
-            return (*this + -value);
-        }
-
-        difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
-            return other.index - index;
-        }
-
-        reference operator[](const difference_type value) const ENTT_NOEXCEPT {
-            const auto pos = size_type(index-value-1);
-            return (*instances)[pos];
-        }
-
-        bool operator==(const iterator &other) const ENTT_NOEXCEPT {
-            return other.index == index;
-        }
-
-        inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this == other);
-        }
-
-        bool operator<(const iterator &other) const ENTT_NOEXCEPT {
-            return index > other.index;
-        }
-
-        bool operator>(const iterator &other) const ENTT_NOEXCEPT {
-            return index < other.index;
-        }
-
-        inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this > other);
-        }
-
-        inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this < other);
-        }
-
-        pointer operator->() const ENTT_NOEXCEPT {
-            const auto pos = size_type(index-1);
-            return &(*instances)[pos];
-        }
-
-        inline reference operator*() const ENTT_NOEXCEPT {
-            return *operator->();
-        }
-
-    private:
-        instance_type *instances;
-        index_type index;
-    };
-
-    template<bool Const>
-    class iterator<Const, true> {
-        friend class sparse_set<Entity, Type>;
-
-        using instance_type = std::conditional_t<Const, const Type, Type>;
-        using index_type = typename traits_type::difference_type;
-
-        iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
-            : instance{ref}, index{idx}
-        {}
-
-    public:
-        using difference_type = index_type;
-        using value_type = std::conditional_t<Const, const Type, Type>;
-        using pointer = value_type *;
-        using reference = value_type &;
-        using iterator_category = std::random_access_iterator_tag;
-
-        iterator() ENTT_NOEXCEPT = default;
-
-        iterator & operator++() ENTT_NOEXCEPT {
-            return --index, *this;
-        }
-
-        iterator operator++(int) ENTT_NOEXCEPT {
-            iterator orig = *this;
-            return ++(*this), orig;
-        }
-
-        iterator & operator--() ENTT_NOEXCEPT {
-            return ++index, *this;
-        }
-
-        iterator operator--(int) ENTT_NOEXCEPT {
-            iterator orig = *this;
-            return --(*this), orig;
-        }
-
-        iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
-            index -= value;
-            return *this;
-        }
-
-        iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
-            return iterator{instance, index-value};
-        }
-
-        inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
-            return (*this += -value);
-        }
-
-        inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
-            return (*this + -value);
-        }
-
-        difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
-            return other.index - index;
-        }
-
-        reference operator[](const difference_type) const ENTT_NOEXCEPT {
-            return *instance;
-        }
-
-        bool operator==(const iterator &other) const ENTT_NOEXCEPT {
-            return other.index == index;
-        }
-
-        inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this == other);
-        }
-
-        bool operator<(const iterator &other) const ENTT_NOEXCEPT {
-            return index > other.index;
-        }
-
-        bool operator>(const iterator &other) const ENTT_NOEXCEPT {
-            return index < other.index;
-        }
-
-        inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this > other);
-        }
-
-        inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this < other);
-        }
-
-        pointer operator->() const ENTT_NOEXCEPT {
-            return instance;
-        }
-
-        inline reference operator*() const ENTT_NOEXCEPT {
-            return *operator->();
-        }
-
-    private:
-        instance_type *instance;
-        index_type index;
-    };
-
-public:
-    /*! @brief Type of the objects associated with the entities. */
-    using object_type = Type;
-    /*! @brief Underlying entity identifier. */
-    using entity_type = typename underlying_type::entity_type;
-    /*! @brief Unsigned integer type. */
-    using size_type = typename underlying_type::size_type;
-    /*! @brief Random access iterator type. */
-    using iterator_type = iterator<false>;
-    /*! @brief Constant random access iterator type. */
-    using const_iterator_type = iterator<true>;
-
-    /**
-     * @brief Increases the capacity of a sparse set.
-     *
-     * If the new capacity is greater than the current capacity, new storage is
-     * allocated, otherwise the method does nothing.
-     *
-     * @param cap Desired capacity.
-     */
-    void reserve(const size_type cap) override {
-        underlying_type::reserve(cap);
-
-        if constexpr(!std::is_empty_v<object_type>) {
-            instances.reserve(cap);
-        }
-    }
-
-    /**
-     * @brief Requests the removal of unused capacity.
-     *
-     * @note
-     * Empty components aren't explicitly instantiated. Only one instance of the
-     * given type is created. Therefore, this function does nothing.
-     */
-    void shrink_to_fit() override {
-        underlying_type::shrink_to_fit();
-
-        if constexpr(!std::is_empty_v<object_type>) {
-            instances.shrink_to_fit();
-        }
-    }
-
-    /**
-     * @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
-     * There are no guarantees on the order, even though either `sort` or
-     * `respect` has been previously invoked. Internal data structures arrange
-     * elements to maximize performance. Accessing them directly gives a
-     * performance boost but less guarantees. Use `begin` and `end` if you want
-     * to iterate the sparse set in the expected order.
-     *
-     * @note
-     * Empty components aren't explicitly instantiated. Only one instance of the
-     * given type is created. Therefore, this function always returns a pointer
-     * to that instance.
-     *
-     * @return A pointer to the array of objects.
-     */
-    const object_type * raw() const ENTT_NOEXCEPT {
-        if constexpr(std::is_empty_v<object_type>) {
-            return &instances;
-        } else {
-            return instances.data();
-        }
-    }
-
-    /*! @copydoc raw */
-    object_type * raw() ENTT_NOEXCEPT {
-        return const_cast<object_type *>(std::as_const(*this).raw());
-    }
-
-    /**
-     * @brief Returns an iterator to the beginning.
-     *
-     * The returned iterator points to the first instance of the given type. If
-     * the sparse set is empty, the returned iterator will be equal to `end()`.
-     *
-     * @note
-     * Input iterators stay true to the order imposed by a call to either `sort`
-     * or `respect`.
-     *
-     * @return An iterator to the first instance of the given type.
-     */
-    const_iterator_type cbegin() const ENTT_NOEXCEPT {
-        const typename traits_type::difference_type pos = underlying_type::size();
-        return const_iterator_type{&instances, pos};
-    }
-
-    /*! @copydoc cbegin */
-    inline const_iterator_type begin() const ENTT_NOEXCEPT {
-        return cbegin();
-    }
-
-    /*! @copydoc begin */
-    iterator_type begin() ENTT_NOEXCEPT {
-        const typename traits_type::difference_type pos = underlying_type::size();
-        return iterator_type{&instances, pos};
-    }
-
-    /**
-     * @brief Returns an iterator to the end.
-     *
-     * The returned iterator points to the element following the last instance
-     * of the given type. Attempting to dereference the returned iterator
-     * results in undefined behavior.
-     *
-     * @note
-     * Input iterators stay true to the order imposed by a call to either `sort`
-     * or `respect`.
-     *
-     * @return An iterator to the element following the last instance of the
-     * given type.
-     */
-    const_iterator_type cend() const ENTT_NOEXCEPT {
-        return const_iterator_type{&instances, {}};
-    }
-
-    /*! @copydoc cend */
-    inline const_iterator_type end() const ENTT_NOEXCEPT {
-        return cend();
-    }
-
-    /*! @copydoc end */
-    iterator_type end() ENTT_NOEXCEPT {
-        return iterator_type{&instances, {}};
-    }
-
-    /**
-     * @brief Returns the object associated with an entity.
-     *
-     * @warning
-     * Attempting to use an entity that doesn't belong to the sparse set results
-     * in undefined behavior.<br/>
-     * An assertion will abort the execution at runtime in debug mode if the
-     * sparse set doesn't contain the given entity.
-     *
-     * @param entt A valid entity identifier.
-     * @return The object associated with the entity.
-     */
-    const object_type & get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
-        if constexpr(std::is_empty_v<object_type>) {
-            ENTT_ASSERT(underlying_type::has(entt));
-            return instances;
-        } else {
-            return instances[underlying_type::get(entt)];
-        }
-    }
-
-    /*! @copydoc get */
-    inline object_type & get(const entity_type entt) ENTT_NOEXCEPT {
-        return const_cast<object_type &>(std::as_const(*this).get(entt));
-    }
-
-    /**
-     * @brief Returns a pointer to the object associated with an entity, if any.
-     * @param entt A valid entity identifier.
-     * @return The object associated with the entity, if any.
-     */
-    const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT {
-        if constexpr(std::is_empty_v<object_type>) {
-            return underlying_type::has(entt) ? &instances : nullptr;
-        } else {
-            return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr;
-        }
-    }
-
-    /*! @copydoc try_get */
-    inline object_type * try_get(const entity_type entt) ENTT_NOEXCEPT {
-        return const_cast<object_type *>(std::as_const(*this).try_get(entt));
-    }
-
-    /**
-     * @brief Assigns an entity to a sparse set and constructs its object.
-     *
-     * This version accept both types that can be constructed in place directly
-     * and types like aggregates that do not work well with a placement new as
-     * performed usually under the hood during an _emplace back_.
-     *
-     * @warning
-     * Attempting to use an entity that already belongs to the sparse set
-     * results in undefined behavior.<br/>
-     * An assertion will abort the execution at runtime in debug mode if the
-     * sparse set already contains the given entity.
-     *
-     * @tparam Args Types of arguments to use to construct the object.
-     * @param entt A valid entity identifier.
-     * @param args Parameters to use to construct an object for the entity.
-     * @return The object associated with the entity.
-     */
-    template<typename... Args>
-    object_type & construct(const entity_type entt, [[maybe_unused]] Args &&... args) {
-        if constexpr(std::is_empty_v<object_type>) {
-            underlying_type::construct(entt);
-            return instances;
-        } else {
-            if constexpr(std::is_aggregate_v<object_type>) {
-                instances.emplace_back(Type{std::forward<Args>(args)...});
-            } else {
-                instances.emplace_back(std::forward<Args>(args)...);
-            }
-
-            // entity goes after component in case constructor throws
-            underlying_type::construct(entt);
-            return instances.back();
-        }
-    }
-
-    /**
-     * @brief Assigns one or more entities to a sparse set and constructs their
-     * objects.
-     *
-     * The object type must be at least default constructible.
-     *
-     * @note
-     * Empty components aren't explicitly instantiated. Only one instance of the
-     * given type is created. Therefore, this function always returns a pointer
-     * to that instance.
-     *
-     * @warning
-     * Attempting to assign an entity that already belongs to the sparse set
-     * results in undefined behavior.<br/>
-     * An assertion will abort the execution at runtime in debug mode if the
-     * sparse set already contains the given entity.
-     *
-     * @tparam It Type of forward iterator.
-     * @param first An iterator to the first element of the range of entities.
-     * @param last An iterator past the last element of the range of entities.
-     * @return A pointer to the array of instances just created and sorted the
-     * same of the entities.
-     */
-    template<typename It>
-    object_type * batch(It first, It last) {
-        if constexpr(std::is_empty_v<object_type>) {
-            underlying_type::batch(first, last);
-            return &instances;
-        } else {
-            static_assert(std::is_default_constructible_v<object_type>);
-            const auto skip = instances.size();
-            instances.insert(instances.end(), last-first, {});
-            // entity goes after component in case constructor throws
-            underlying_type::batch(first, last);
-            return instances.data() + skip;
-        }
-    }
-
-    /**
-     * @brief Removes an entity from a sparse set and destroies its object.
-     *
-     * @warning
-     * Attempting to use an entity that doesn't belong to the sparse set results
-     * in undefined behavior.<br/>
-     * An assertion will abort the execution at runtime in debug mode if the
-     * sparse set doesn't contain the given entity.
-     *
-     * @param entt A valid entity identifier.
-     */
-    void destroy(const entity_type entt) override {
-        if constexpr(!std::is_empty_v<object_type>) {
-            std::swap(instances[underlying_type::get(entt)], instances.back());
-            instances.pop_back();
-        }
-
-        underlying_type::destroy(entt);
-    }
-
-    /**
-     * @brief Sort components according to the given comparison function.
-     *
-     * Sort the elements so that iterating the sparse set with a couple of
-     * iterators returns them in the expected order. See `begin` and `end` for
-     * more details.
-     *
-     * The comparison function object must return `true` if the first element
-     * is _less_ than the second one, `false` otherwise. The signature of the
-     * comparison function should be equivalent to one of the following:
-     *
-     * @code{.cpp}
-     * bool(const Entity, const Entity);
-     * bool(const Type &, const Type &);
-     * @endcode
-     *
-     * Moreover, the comparison function object shall induce a
-     * _strict weak ordering_ on the values.
-     *
-     * The sort function oject must offer a member function template
-     * `operator()` that accepts three arguments:
-     *
-     * * An iterator to the first element of the range to sort.
-     * * An iterator past the last element of the range to sort.
-     * * A comparison function to use to compare the elements.
-     *
-     * The comparison function object received by the sort function object
-     * hasn't necessarily the type of the one passed along with the other
-     * parameters to this member function.
-     *
-     * @note
-     * Empty components aren't explicitly instantiated. Therefore, the
-     * comparison function must necessarily accept entity identifiers.
-     *
-     * @note
-     * Attempting to iterate elements using a raw pointer returned by a call to
-     * either `data` or `raw` gives no guarantees on the order, even though
-     * `sort` has been invoked.
-     *
-     * @tparam Compare Type of comparison function object.
-     * @tparam Sort Type of sort function object.
-     * @tparam Args Types of arguments to forward to the sort function object.
-     * @param compare A valid comparison function object.
-     * @param algo A valid sort function object.
-     * @param args Arguments to forward to the sort function object, if any.
-     */
-    template<typename Compare, typename Sort = std_sort, typename... Args>
-    void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
-        std::vector<size_type> copy(instances.size());
-        std::iota(copy.begin(), copy.end(), 0);
-
-        if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
-            static_assert(!std::is_empty_v<object_type>);
-
-            algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
-                return compare(std::as_const(instances[lhs]), std::as_const(instances[rhs]));
-            }, std::forward<Args>(args)...);
-        } else {
-            algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = underlying_type::data()](const auto lhs, const auto rhs) {
-                return compare(data[lhs], data[rhs]);
-            }, std::forward<Args>(args)...);
-        }
-
-        for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
-            auto curr = pos;
-            auto next = copy[curr];
-
-            while(curr != next) {
-                const auto lhs = copy[curr];
-                const auto rhs = copy[next];
-
-                if constexpr(!std::is_empty_v<object_type>) {
-                    std::swap(instances[lhs], instances[rhs]);
-                }
-
-                underlying_type::swap(lhs, rhs);
-                copy[curr] = curr;
-                curr = next;
-                next = copy[curr];
-            }
-        }
-    }
-
-    /**
-     * @brief Sort components according to the order of the entities in another
-     * sparse set.
-     *
-     * Entities that are part of both the sparse sets are ordered internally
-     * according to the order they have in `other`. All the other entities goes
-     * to the end of the list and there are no guarantess on their order.
-     * Components are sorted according to the entities to which they
-     * belong.<br/>
-     * In other terms, this function can be used to impose the same order on two
-     * sets by using one of them as a master and the other one as a slave.
-     *
-     * Iterating the sparse set with a couple of iterators returns elements in
-     * the expected order after a call to `respect`. See `begin` and `end` for
-     * more details.
-     *
-     * @note
-     * Attempting to iterate elements using a raw pointer returned by a call to
-     * either `data` or `raw` gives no guarantees on the order, even though
-     * `respect` has been invoked.
-     *
-     * @param other The sparse sets that imposes the order of the entities.
-     */
-    void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override {
-        if constexpr(std::is_empty_v<object_type>) {
-            underlying_type::respect(other);
-        } else {
-            const auto to = other.end();
-            auto from = other.begin();
-
-            size_type pos = underlying_type::size() - 1;
-            const auto *local = underlying_type::data();
-
-            while(pos && from != to) {
-                const auto curr = *from;
-
-                if(underlying_type::has(curr)) {
-                    if(curr != *(local + pos)) {
-                        auto candidate = underlying_type::get(curr);
-                        std::swap(instances[pos], instances[candidate]);
-                        underlying_type::swap(pos, candidate);
-                    }
-
-                    --pos;
-                }
-
-                ++from;
-            }
-        }
-    }
-
-    /*! @brief Resets a sparse set. */
-    void reset() override {
-        underlying_type::reset();
-
-        if constexpr(!std::is_empty_v<object_type>) {
-            instances.clear();
-        }
-    }
-
-private:
-    std::conditional_t<std::is_empty_v<object_type>, object_type, std::vector<object_type>> instances;
-};
-
-
 }
 
 

+ 672 - 0
src/entt/entity/storage.hpp

@@ -0,0 +1,672 @@
+#ifndef ENTT_ENTITY_STORAGE_HPP
+#define ENTT_ENTITY_STORAGE_HPP
+
+
+#include <algorithm>
+#include <iterator>
+#include <numeric>
+#include <utility>
+#include <vector>
+#include <cstddef>
+#include <type_traits>
+#include "../config/config.h"
+#include "../core/algorithm.hpp"
+#include "sparse_set.hpp"
+#include "entity.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @brief Basic storage implementation.
+ *
+ * This class is a refinement of a sparse set that associates an object to an
+ * entity. The main purpose of this class is to use sparse sets to store
+ * components in a registry. It guarantees fast access both to the elements and
+ * to the entities.
+ *
+ * @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 input iterators.
+ *
+ * @note
+ * Internal data structures arrange elements to maximize performance. Because of
+ * that, there are no guarantees that elements have the expected order when
+ * iterate directly the internal packed array (see `raw` and `size` member
+ * functions for that). Use `begin` and `end` instead.
+ *
+ * @sa sparse_set<Entity>
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam Type Type of objects assigned to the entities.
+ */
+template<typename Entity, typename Type>
+class storage: public sparse_set<Entity> {
+    using underlying_type = sparse_set<Entity>;
+    using traits_type = entt_traits<Entity>;
+
+    template<bool Const, bool = std::is_empty_v<Type>>
+    class iterator {
+        friend class storage<Entity, Type>;
+
+        using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>;
+        using index_type = typename traits_type::difference_type;
+
+        iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
+            : instances{ref}, index{idx}
+        {}
+
+    public:
+        using difference_type = index_type;
+        using value_type = std::conditional_t<Const, const Type, Type>;
+        using pointer = value_type *;
+        using reference = value_type &;
+        using iterator_category = std::random_access_iterator_tag;
+
+        iterator() ENTT_NOEXCEPT = default;
+
+        iterator & operator++() ENTT_NOEXCEPT {
+            return --index, *this;
+        }
+
+        iterator operator++(int) ENTT_NOEXCEPT {
+            iterator orig = *this;
+            return ++(*this), orig;
+        }
+
+        iterator & operator--() ENTT_NOEXCEPT {
+            return ++index, *this;
+        }
+
+        iterator operator--(int) ENTT_NOEXCEPT {
+            iterator orig = *this;
+            return --(*this), orig;
+        }
+
+        iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
+            index -= value;
+            return *this;
+        }
+
+        iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
+            return iterator{instances, index-value};
+        }
+
+        inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
+            return (*this += -value);
+        }
+
+        inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+            return (*this + -value);
+        }
+
+        difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
+            return other.index - index;
+        }
+
+        reference operator[](const difference_type value) const ENTT_NOEXCEPT {
+            const auto pos = size_type(index-value-1);
+            return (*instances)[pos];
+        }
+
+        bool operator==(const iterator &other) const ENTT_NOEXCEPT {
+            return other.index == index;
+        }
+
+        inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this == other);
+        }
+
+        bool operator<(const iterator &other) const ENTT_NOEXCEPT {
+            return index > other.index;
+        }
+
+        bool operator>(const iterator &other) const ENTT_NOEXCEPT {
+            return index < other.index;
+        }
+
+        inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this > other);
+        }
+
+        inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this < other);
+        }
+
+        pointer operator->() const ENTT_NOEXCEPT {
+            const auto pos = size_type(index-1);
+            return &(*instances)[pos];
+        }
+
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return *operator->();
+        }
+
+    private:
+        instance_type *instances;
+        index_type index;
+    };
+
+    template<bool Const>
+    class iterator<Const, true> {
+        friend class storage<Entity, Type>;
+
+        using instance_type = std::conditional_t<Const, const Type, Type>;
+        using index_type = typename traits_type::difference_type;
+
+        iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
+            : instance{ref}, index{idx}
+        {}
+
+    public:
+        using difference_type = index_type;
+        using value_type = std::conditional_t<Const, const Type, Type>;
+        using pointer = value_type *;
+        using reference = value_type &;
+        using iterator_category = std::random_access_iterator_tag;
+
+        iterator() ENTT_NOEXCEPT = default;
+
+        iterator & operator++() ENTT_NOEXCEPT {
+            return --index, *this;
+        }
+
+        iterator operator++(int) ENTT_NOEXCEPT {
+            iterator orig = *this;
+            return ++(*this), orig;
+        }
+
+        iterator & operator--() ENTT_NOEXCEPT {
+            return ++index, *this;
+        }
+
+        iterator operator--(int) ENTT_NOEXCEPT {
+            iterator orig = *this;
+            return --(*this), orig;
+        }
+
+        iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
+            index -= value;
+            return *this;
+        }
+
+        iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
+            return iterator{instance, index-value};
+        }
+
+        inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
+            return (*this += -value);
+        }
+
+        inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+            return (*this + -value);
+        }
+
+        difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
+            return other.index - index;
+        }
+
+        reference operator[](const difference_type) const ENTT_NOEXCEPT {
+            return *instance;
+        }
+
+        bool operator==(const iterator &other) const ENTT_NOEXCEPT {
+            return other.index == index;
+        }
+
+        inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this == other);
+        }
+
+        bool operator<(const iterator &other) const ENTT_NOEXCEPT {
+            return index > other.index;
+        }
+
+        bool operator>(const iterator &other) const ENTT_NOEXCEPT {
+            return index < other.index;
+        }
+
+        inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this > other);
+        }
+
+        inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this < other);
+        }
+
+        pointer operator->() const ENTT_NOEXCEPT {
+            return instance;
+        }
+
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return *operator->();
+        }
+
+    private:
+        instance_type *instance;
+        index_type index;
+    };
+
+public:
+    /*! @brief Type of the objects associated with the entities. */
+    using object_type = Type;
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename underlying_type::entity_type;
+    /*! @brief Unsigned integer type. */
+    using size_type = typename underlying_type::size_type;
+    /*! @brief Random access iterator type. */
+    using iterator_type = iterator<false>;
+    /*! @brief Constant random access iterator type. */
+    using const_iterator_type = iterator<true>;
+
+    /**
+     * @brief Increases the capacity of a storage.
+     *
+     * If the new capacity is greater than the current capacity, new storage is
+     * allocated, otherwise the method does nothing.
+     *
+     * @param cap Desired capacity.
+     */
+    void reserve(const size_type cap) override {
+        underlying_type::reserve(cap);
+
+        if constexpr(!std::is_empty_v<object_type>) {
+            instances.reserve(cap);
+        }
+    }
+
+    /**
+     * @brief Requests the removal of unused capacity.
+     *
+     * @note
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function does nothing.
+     */
+    void shrink_to_fit() override {
+        underlying_type::shrink_to_fit();
+
+        if constexpr(!std::is_empty_v<object_type>) {
+            instances.shrink_to_fit();
+        }
+    }
+
+    /**
+     * @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
+     * There are no guarantees on the order, even though either `sort` or
+     * `respect` has been previously invoked. Internal data structures arrange
+     * elements to maximize performance. Accessing them directly gives a
+     * performance boost but less guarantees. Use `begin` and `end` if you want
+     * to iterate the storage in the expected order.
+     *
+     * @note
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
+     *
+     * @return A pointer to the array of objects.
+     */
+    const object_type * raw() const ENTT_NOEXCEPT {
+        if constexpr(std::is_empty_v<object_type>) {
+            return &instances;
+        } else {
+            return instances.data();
+        }
+    }
+
+    /*! @copydoc raw */
+    object_type * raw() ENTT_NOEXCEPT {
+        return const_cast<object_type *>(std::as_const(*this).raw());
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning.
+     *
+     * The returned iterator points to the first instance of the given type. If
+     * the storage is empty, the returned iterator will be equal to `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to either `sort`
+     * or `respect`.
+     *
+     * @return An iterator to the first instance of the given type.
+     */
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        const typename traits_type::difference_type pos = underlying_type::size();
+        return const_iterator_type{&instances, pos};
+    }
+
+    /*! @copydoc cbegin */
+    inline const_iterator_type begin() const ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /*! @copydoc begin */
+    iterator_type begin() ENTT_NOEXCEPT {
+        const typename traits_type::difference_type pos = underlying_type::size();
+        return iterator_type{&instances, pos};
+    }
+
+    /**
+     * @brief Returns an iterator to the end.
+     *
+     * The returned iterator points to the element following the last instance
+     * of the given type. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to either `sort`
+     * or `respect`.
+     *
+     * @return An iterator to the element following the last instance of the
+     * given type.
+     */
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        return const_iterator_type{&instances, {}};
+    }
+
+    /*! @copydoc cend */
+    inline const_iterator_type end() const ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /*! @copydoc end */
+    iterator_type end() ENTT_NOEXCEPT {
+        return iterator_type{&instances, {}};
+    }
+
+    /**
+     * @brief Returns the object associated with an entity.
+     *
+     * @warning
+     * Attempting to use an entity that doesn't belong to the storage results in
+     * undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * storage doesn't contain the given entity.
+     *
+     * @param entt A valid entity identifier.
+     * @return The object associated with the entity.
+     */
+    const object_type & get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
+        if constexpr(std::is_empty_v<object_type>) {
+            ENTT_ASSERT(underlying_type::has(entt));
+            return instances;
+        } else {
+            return instances[underlying_type::get(entt)];
+        }
+    }
+
+    /*! @copydoc get */
+    inline object_type & get(const entity_type entt) ENTT_NOEXCEPT {
+        return const_cast<object_type &>(std::as_const(*this).get(entt));
+    }
+
+    /**
+     * @brief Returns a pointer to the object associated with an entity, if any.
+     * @param entt A valid entity identifier.
+     * @return The object associated with the entity, if any.
+     */
+    const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT {
+        if constexpr(std::is_empty_v<object_type>) {
+            return underlying_type::has(entt) ? &instances : nullptr;
+        } else {
+            return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr;
+        }
+    }
+
+    /*! @copydoc try_get */
+    inline object_type * try_get(const entity_type entt) ENTT_NOEXCEPT {
+        return const_cast<object_type *>(std::as_const(*this).try_get(entt));
+    }
+
+    /**
+     * @brief Assigns an entity to a storage and constructs its object.
+     *
+     * This version accept both types that can be constructed in place directly
+     * and types like aggregates that do not work well with a placement new as
+     * performed usually under the hood during an _emplace back_.
+     *
+     * @warning
+     * Attempting to use an entity that already belongs to the storage results
+     * in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * storage already contains the given entity.
+     *
+     * @tparam Args Types of arguments to use to construct the object.
+     * @param entt A valid entity identifier.
+     * @param args Parameters to use to construct an object for the entity.
+     * @return The object associated with the entity.
+     */
+    template<typename... Args>
+    object_type & construct(const entity_type entt, [[maybe_unused]] Args &&... args) {
+        if constexpr(std::is_empty_v<object_type>) {
+            underlying_type::construct(entt);
+            return instances;
+        } else {
+            if constexpr(std::is_aggregate_v<object_type>) {
+                instances.emplace_back(Type{std::forward<Args>(args)...});
+            } else {
+                instances.emplace_back(std::forward<Args>(args)...);
+            }
+
+            // entity goes after component in case constructor throws
+            underlying_type::construct(entt);
+            return instances.back();
+        }
+    }
+
+    /**
+     * @brief Assigns one or more entities to a storage and constructs their
+     * objects.
+     *
+     * The object type must be at least default constructible.
+     *
+     * @note
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
+     *
+     * @warning
+     * Attempting to assign an entity that already belongs to the storage
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * storage already contains the given entity.
+     *
+     * @tparam It Type of forward iterator.
+     * @param first An iterator to the first element of the range of entities.
+     * @param last An iterator past the last element of the range of entities.
+     * @return A pointer to the array of instances just created and sorted the
+     * same of the entities.
+     */
+    template<typename It>
+    object_type * batch(It first, It last) {
+        if constexpr(std::is_empty_v<object_type>) {
+            underlying_type::batch(first, last);
+            return &instances;
+        } else {
+            static_assert(std::is_default_constructible_v<object_type>);
+            const auto skip = instances.size();
+            instances.insert(instances.end(), last-first, {});
+            // entity goes after component in case constructor throws
+            underlying_type::batch(first, last);
+            return instances.data() + skip;
+        }
+    }
+
+    /**
+     * @brief Removes an entity from a storage and destroys its object.
+     *
+     * @warning
+     * Attempting to use an entity that doesn't belong to the storage results in
+     * undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * storage doesn't contain the given entity.
+     *
+     * @param entt A valid entity identifier.
+     */
+    void destroy(const entity_type entt) override {
+        if constexpr(!std::is_empty_v<object_type>) {
+            std::swap(instances[underlying_type::get(entt)], instances.back());
+            instances.pop_back();
+        }
+
+        underlying_type::destroy(entt);
+    }
+
+    /**
+     * @brief Sort components according to the given comparison function.
+     *
+     * Sort the elements so that iterating the storage with a couple of
+     * iterators returns them in the expected order. See `begin` and `end` for
+     * more details.
+     *
+     * The comparison function object must return `true` if the first element
+     * is _less_ than the second one, `false` otherwise. The signature of the
+     * comparison function should be equivalent to one of the following:
+     *
+     * @code{.cpp}
+     * bool(const Entity, const Entity);
+     * bool(const Type &, const Type &);
+     * @endcode
+     *
+     * Moreover, the comparison function object shall induce a
+     * _strict weak ordering_ on the values.
+     *
+     * The sort function oject must offer a member function template
+     * `operator()` that accepts three arguments:
+     *
+     * * An iterator to the first element of the range to sort.
+     * * An iterator past the last element of the range to sort.
+     * * A comparison function to use to compare the elements.
+     *
+     * The comparison function object received by the sort function object
+     * hasn't necessarily the type of the one passed along with the other
+     * parameters to this member function.
+     *
+     * @note
+     * Empty components aren't explicitly instantiated. Therefore, the
+     * comparison function must necessarily accept entity identifiers.
+     *
+     * @note
+     * Attempting to iterate elements using a raw pointer returned by a call to
+     * either `data` or `raw` gives no guarantees on the order, even though
+     * `sort` has been invoked.
+     *
+     * @tparam Compare Type of comparison function object.
+     * @tparam Sort Type of sort function object.
+     * @tparam Args Types of arguments to forward to the sort function object.
+     * @param compare A valid comparison function object.
+     * @param algo A valid sort function object.
+     * @param args Arguments to forward to the sort function object, if any.
+     */
+    template<typename Compare, typename Sort = std_sort, typename... Args>
+    void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
+        std::vector<size_type> copy(instances.size());
+        std::iota(copy.begin(), copy.end(), 0);
+
+        if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
+            static_assert(!std::is_empty_v<object_type>);
+
+            algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
+                return compare(std::as_const(instances[lhs]), std::as_const(instances[rhs]));
+            }, std::forward<Args>(args)...);
+        } else {
+            algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = underlying_type::data()](const auto lhs, const auto rhs) {
+                return compare(data[lhs], data[rhs]);
+            }, std::forward<Args>(args)...);
+        }
+
+        for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
+            auto curr = pos;
+            auto next = copy[curr];
+
+            while(curr != next) {
+                const auto lhs = copy[curr];
+                const auto rhs = copy[next];
+
+                if constexpr(!std::is_empty_v<object_type>) {
+                    std::swap(instances[lhs], instances[rhs]);
+                }
+
+                underlying_type::swap(lhs, rhs);
+                copy[curr] = curr;
+                curr = next;
+                next = copy[curr];
+            }
+        }
+    }
+
+    /**
+     * @brief Sort components according to the order of the entities in another
+     * sparse set.
+     *
+     * Entities that are part of both the storage are ordered internally
+     * according to the order they have in `other`. All the other entities goes
+     * to the end of the list and there are no guarantess on their order.
+     * Components are sorted according to the entities to which they
+     * belong.<br/>
+     * In other terms, this function can be used to impose the same order on two
+     * sets by using one of them as a master and the other one as a slave.
+     *
+     * Iterating the storage with a couple of iterators returns elements in the
+     * expected order after a call to `respect`. See `begin` and `end` for more
+     * details.
+     *
+     * @note
+     * Attempting to iterate elements using a raw pointer returned by a call to
+     * either `data` or `raw` gives no guarantees on the order, even though
+     * `respect` has been invoked.
+     *
+     * @param other The sparse sets that imposes the order of the entities.
+     */
+    void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override {
+        if constexpr(std::is_empty_v<object_type>) {
+            underlying_type::respect(other);
+        } else {
+            const auto to = other.end();
+            auto from = other.begin();
+
+            size_type pos = underlying_type::size() - 1;
+            const auto *local = underlying_type::data();
+
+            while(pos && from != to) {
+                const auto curr = *from;
+
+                if(underlying_type::has(curr)) {
+                    if(curr != *(local + pos)) {
+                        auto candidate = underlying_type::get(curr);
+                        std::swap(instances[pos], instances[candidate]);
+                        underlying_type::swap(pos, candidate);
+                    }
+
+                    --pos;
+                }
+
+                ++from;
+            }
+        }
+    }
+
+    /*! @brief Resets a storage. */
+    void reset() override {
+        underlying_type::reset();
+
+        if constexpr(!std::is_empty_v<object_type>) {
+            instances.clear();
+        }
+    }
+
+private:
+    std::conditional_t<std::is_empty_v<object_type>, object_type, std::vector<object_type>> instances;
+};
+
+
+}
+
+
+#endif // ENTT_ENTITY_STORAGE_HPP

+ 4 - 3
src/entt/entity/view.hpp

@@ -10,6 +10,7 @@
 #include <type_traits>
 #include "../config/config.h"
 #include "sparse_set.hpp"
+#include "storage.hpp"
 #include "entity.hpp"
 #include "fwd.hpp"
 
@@ -61,7 +62,7 @@ class basic_view {
     friend class basic_registry<Entity>;
 
     template<typename Comp>
-    using pool_type = std::conditional_t<std::is_const_v<Comp>, const sparse_set<Entity, std::remove_const_t<Comp>>, sparse_set<Entity, Comp>>;
+    using pool_type = std::conditional_t<std::is_const_v<Comp>, const storage<Entity, std::remove_const_t<Comp>>, storage<Entity, Comp>>;
 
     using underlying_iterator_type = typename sparse_set<Entity>::iterator_type;
     using unchecked_type = std::array<const sparse_set<Entity> *, (sizeof...(Component) - 1)>;
@@ -139,7 +140,7 @@ class basic_view {
     };
 
     // we could use pool_type<Component> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
-    basic_view(sparse_set<Entity, std::remove_const_t<Component>> *... ref) ENTT_NOEXCEPT
+    basic_view(storage<Entity, std::remove_const_t<Component>> *... ref) ENTT_NOEXCEPT
         : pools{ref...}
     {}
 
@@ -449,7 +450,7 @@ class basic_view<Entity, Component> {
     /*! @brief A registry is allowed to create views. */
     friend class basic_registry<Entity>;
 
-    using pool_type = std::conditional_t<std::is_const_v<Component>, const sparse_set<Entity, std::remove_const_t<Component>>, sparse_set<Entity, Component>>;
+    using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
 
     basic_view(pool_type *ref) ENTT_NOEXCEPT
         : pool{ref}

+ 1 - 0
src/entt/entt.hpp

@@ -14,6 +14,7 @@
 #include "entity/runtime_view.hpp"
 #include "entity/snapshot.hpp"
 #include "entity/sparse_set.hpp"
+#include "entity/storage.hpp"
 #include "entity/view.hpp"
 #include "locator/locator.hpp"
 #include "meta/factory.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -101,6 +101,7 @@ SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp)
 SETUP_AND_ADD_TEST(runtime_view entt/entity/runtime_view.cpp)
 SETUP_AND_ADD_TEST(snapshot entt/entity/snapshot.cpp)
 SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp)
+SETUP_AND_ADD_TEST(storage entt/entity/storage.cpp)
 SETUP_AND_ADD_TEST(view entt/entity/view.cpp)
 
 # Test locator

+ 14 - 761
test/entt/entity/sparse_set.cpp

@@ -1,17 +1,14 @@
-#include <memory>
+#include <cstdint>
+#include <utility>
 #include <iterator>
-#include <exception>
-#include <algorithm>
 #include <type_traits>
-#include <unordered_set>
 #include <gtest/gtest.h>
-#include <entt/core/type_traits.hpp>
 #include <entt/entity/sparse_set.hpp>
 
 struct empty_type {};
 struct boxed_int { int value; };
 
-TEST(SparseSetNoType, Functionalities) {
+TEST(SparseSet, Functionalities) {
     entt::sparse_set<std::uint64_t> set;
 
     set.reserve(42);
@@ -80,7 +77,7 @@ TEST(SparseSetNoType, Functionalities) {
     ASSERT_FALSE(other.has(42));
 }
 
-TEST(SparseSetNoType, Pagination) {
+TEST(SparseSet, Pagination) {
     entt::sparse_set<std::uint64_t> set;
     constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(std::uint64_t);
 
@@ -116,7 +113,7 @@ TEST(SparseSetNoType, Pagination) {
     ASSERT_EQ(set.extent(), 0);
 }
 
-TEST(SparseSetNoType, BatchAdd) {
+TEST(SparseSet, BatchAdd) {
     entt::sparse_set<std::uint64_t> set;
     entt::sparse_set<std::uint64_t>::entity_type entities[2];
 
@@ -142,7 +139,7 @@ TEST(SparseSetNoType, BatchAdd) {
     ASSERT_EQ(set.get(24), 3u);
 }
 
-TEST(SparseSetNoType, Iterator) {
+TEST(SparseSet, Iterator) {
     using iterator_type = typename entt::sparse_set<std::uint64_t>::iterator_type;
 
     entt::sparse_set<std::uint64_t> set;
@@ -187,7 +184,7 @@ TEST(SparseSetNoType, Iterator) {
     ASSERT_EQ(*begin.operator->(), 3);
 }
 
-TEST(SparseSetNoType, Find) {
+TEST(SparseSet, Find) {
     entt::sparse_set<std::uint64_t> set;
     set.construct(3);
     set.construct(42);
@@ -207,7 +204,7 @@ TEST(SparseSetNoType, Find) {
     ASSERT_EQ(++set.find(3), set.end());
 }
 
-TEST(SparseSetNoType, Data) {
+TEST(SparseSet, Data) {
     entt::sparse_set<std::uint64_t> set;
 
     set.construct(3);
@@ -223,7 +220,7 @@ TEST(SparseSetNoType, Data) {
     ASSERT_EQ(*(set.data() + 2u), 42u);
 }
 
-TEST(SparseSetNoType, RespectDisjoint) {
+TEST(SparseSet, RespectDisjoint) {
     entt::sparse_set<std::uint64_t> lhs;
     entt::sparse_set<std::uint64_t> rhs;
 
@@ -242,7 +239,7 @@ TEST(SparseSetNoType, RespectDisjoint) {
     ASSERT_EQ(std::as_const(lhs).get(42), 2u);
 }
 
-TEST(SparseSetNoType, RespectOverlap) {
+TEST(SparseSet, RespectOverlap) {
     entt::sparse_set<std::uint64_t> lhs;
     entt::sparse_set<std::uint64_t> rhs;
 
@@ -263,7 +260,7 @@ TEST(SparseSetNoType, RespectOverlap) {
     ASSERT_EQ(std::as_const(lhs).get(42), 1u);
 }
 
-TEST(SparseSetNoType, RespectOrdered) {
+TEST(SparseSet, RespectOrdered) {
     entt::sparse_set<std::uint64_t> lhs;
     entt::sparse_set<std::uint64_t> rhs;
 
@@ -303,7 +300,7 @@ TEST(SparseSetNoType, RespectOrdered) {
     ASSERT_EQ(rhs.get(5), 5u);
 }
 
-TEST(SparseSetNoType, RespectReverse) {
+TEST(SparseSet, RespectReverse) {
     entt::sparse_set<std::uint64_t> lhs;
     entt::sparse_set<std::uint64_t> rhs;
 
@@ -343,7 +340,7 @@ TEST(SparseSetNoType, RespectReverse) {
     ASSERT_EQ(rhs.get(5), 5u);
 }
 
-TEST(SparseSetNoType, RespectUnordered) {
+TEST(SparseSet, RespectUnordered) {
     entt::sparse_set<std::uint64_t> lhs;
     entt::sparse_set<std::uint64_t> rhs;
 
@@ -383,7 +380,7 @@ TEST(SparseSetNoType, RespectUnordered) {
     ASSERT_EQ(rhs.get(5), 5u);
 }
 
-TEST(SparseSetNoType, CanModifyDuringIteration) {
+TEST(SparseSet, CanModifyDuringIteration) {
     entt::sparse_set<std::uint64_t> set;
     set.construct(0);
 
@@ -398,747 +395,3 @@ TEST(SparseSetNoType, CanModifyDuringIteration) {
     const auto entity = *it;
     (void)entity;
 }
-
-TEST(SparseSetWithType, Functionalities) {
-    entt::sparse_set<std::uint64_t, int> set;
-
-    set.reserve(42);
-
-    ASSERT_EQ(set.capacity(), 42);
-    ASSERT_TRUE(set.empty());
-    ASSERT_EQ(set.size(), 0u);
-    ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
-    ASSERT_EQ(set.begin(), set.end());
-    ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(41));
-
-    set.construct(41, 3);
-
-    ASSERT_FALSE(set.empty());
-    ASSERT_EQ(set.size(), 1u);
-    ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
-    ASSERT_NE(set.begin(), set.end());
-    ASSERT_FALSE(set.has(0));
-    ASSERT_TRUE(set.has(41));
-    ASSERT_EQ(set.get(41), 3);
-    ASSERT_EQ(*set.try_get(41), 3);
-    ASSERT_EQ(set.try_get(99), nullptr);
-
-    set.destroy(41);
-
-    ASSERT_TRUE(set.empty());
-    ASSERT_EQ(set.size(), 0u);
-    ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
-    ASSERT_EQ(set.begin(), set.end());
-    ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(41));
-
-    set.construct(41, 12);
-
-    ASSERT_EQ(set.get(41), 12);
-    ASSERT_EQ(*set.try_get(41), 12);
-    ASSERT_EQ(set.try_get(99), nullptr);
-
-    set.reset();
-
-    ASSERT_TRUE(set.empty());
-    ASSERT_EQ(set.size(), 0u);
-    ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
-    ASSERT_EQ(set.begin(), set.end());
-    ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(41));
-
-    ASSERT_EQ(set.capacity(), 42);
-
-    set.shrink_to_fit();
-
-    ASSERT_EQ(set.capacity(), 0);
-
-    (void)entt::sparse_set<std::uint64_t, int>{std::move(set)};
-    entt::sparse_set<std::uint64_t, int> other;
-    other = std::move(set);
-}
-
-TEST(SparseSetWithType, EmptyType) {
-    entt::sparse_set<std::uint64_t, empty_type> set;
-
-    ASSERT_EQ(&set.construct(42), &set.construct(99));
-    ASSERT_EQ(&set.get(42), set.try_get(42));
-    ASSERT_EQ(&set.get(42), &set.get(99));
-    ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
-}
-
-TEST(SparseSetWithType, BatchAdd) {
-    entt::sparse_set<std::uint64_t, int> set;
-    entt::sparse_set<std::uint64_t>::entity_type entities[2];
-
-    entities[0] = 3;
-    entities[1] = 42;
-
-    set.reserve(4);
-    set.construct(12, 21);
-    auto *component = set.batch(std::begin(entities), std::end(entities));
-    set.construct(24, 42);
-
-    ASSERT_TRUE(set.has(entities[0]));
-    ASSERT_TRUE(set.has(entities[1]));
-    ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(9));
-    ASSERT_TRUE(set.has(12));
-    ASSERT_TRUE(set.has(24));
-
-    ASSERT_FALSE(set.empty());
-    ASSERT_EQ(set.size(), 4u);
-    ASSERT_EQ(set.get(12), 21);
-    ASSERT_EQ(set.get(entities[0]), 0);
-    ASSERT_EQ(set.get(entities[1]), 0);
-    ASSERT_EQ(set.get(24), 42);
-
-    component[0] = 1;
-    component[1] = 2;
-
-    ASSERT_EQ(set.get(entities[0]), 1);
-    ASSERT_EQ(set.get(entities[1]), 2);
-}
-
-TEST(SparseSetWithType, BatchAddEmptyType) {
-    entt::sparse_set<std::uint64_t, empty_type> set;
-    entt::sparse_set<std::uint64_t>::entity_type entities[2];
-
-    entities[0] = 3;
-    entities[1] = 42;
-
-    set.reserve(4);
-    set.construct(12);
-    auto *component = set.batch(std::begin(entities), std::end(entities));
-    set.construct(24);
-
-    ASSERT_TRUE(set.has(entities[0]));
-    ASSERT_TRUE(set.has(entities[1]));
-    ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(9));
-    ASSERT_TRUE(set.has(12));
-    ASSERT_TRUE(set.has(24));
-
-    ASSERT_FALSE(set.empty());
-    ASSERT_EQ(set.size(), 4u);
-    ASSERT_EQ(&set.get(entities[0]), &set.get(entities[1]));
-    ASSERT_EQ(&set.get(entities[0]), &set.get(12));
-    ASSERT_EQ(&set.get(entities[0]), &set.get(24));
-    ASSERT_EQ(&set.get(entities[0]), component);
-}
-
-TEST(SparseSetWithType, AggregatesMustWork) {
-    struct aggregate_type { int value; };
-    // the goal of this test is to enforce the requirements for aggregate types
-    entt::sparse_set<std::uint64_t, aggregate_type>{}.construct(0, 42);
-}
-
-TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
-    // see #37 - this test shouldn't crash, that's all
-    entt::sparse_set<std::uint64_t, std::unordered_set<int>> set;
-    set.construct(0).insert(42);
-    set.destroy(0);
-}
-
-TEST(SparseSetWithType, Iterator) {
-    struct internal_type { int value; };
-
-    using iterator_type = typename entt::sparse_set<std::uint64_t, internal_type>::iterator_type;
-
-    entt::sparse_set<std::uint64_t, internal_type> set;
-    set.construct(3, 42);
-
-    iterator_type end{set.begin()};
-    iterator_type begin{};
-    begin = set.end();
-    std::swap(begin, end);
-
-    ASSERT_EQ(begin, set.begin());
-    ASSERT_EQ(end, set.end());
-    ASSERT_NE(begin, end);
-
-    ASSERT_EQ(begin++, set.begin());
-    ASSERT_EQ(begin--, set.end());
-
-    ASSERT_EQ(begin+1, set.end());
-    ASSERT_EQ(end-1, set.begin());
-
-    ASSERT_EQ(++begin, set.end());
-    ASSERT_EQ(--begin, set.begin());
-
-    ASSERT_EQ(begin += 1, set.end());
-    ASSERT_EQ(begin -= 1, set.begin());
-
-    ASSERT_EQ(begin + (end - begin), set.end());
-    ASSERT_EQ(begin - (begin - end), set.end());
-
-    ASSERT_EQ(end - (end - begin), set.begin());
-    ASSERT_EQ(end + (begin - end), set.begin());
-
-    ASSERT_EQ(begin[0].value, set.begin()->value);
-
-    ASSERT_LT(begin, end);
-    ASSERT_LE(begin, set.begin());
-
-    ASSERT_GT(end, begin);
-    ASSERT_GE(end, set.end());
-}
-
-TEST(SparseSetWithType, ConstIterator) {
-    struct internal_type { int value; };
-
-    using iterator_type = typename entt::sparse_set<std::uint64_t, internal_type>::const_iterator_type;
-
-    entt::sparse_set<std::uint64_t, internal_type> set;
-    set.construct(3, 42);
-
-    iterator_type cend{set.cbegin()};
-    iterator_type cbegin{};
-    cbegin = set.cend();
-    std::swap(cbegin, cend);
-
-    ASSERT_EQ(cbegin, set.cbegin());
-    ASSERT_EQ(cend, set.cend());
-    ASSERT_NE(cbegin, cend);
-
-    ASSERT_EQ(cbegin++, set.cbegin());
-    ASSERT_EQ(cbegin--, set.cend());
-
-    ASSERT_EQ(cbegin+1, set.cend());
-    ASSERT_EQ(cend-1, set.cbegin());
-
-    ASSERT_EQ(++cbegin, set.cend());
-    ASSERT_EQ(--cbegin, set.cbegin());
-
-    ASSERT_EQ(cbegin += 1, set.cend());
-    ASSERT_EQ(cbegin -= 1, set.cbegin());
-
-    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
-    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
-
-    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
-    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
-
-    ASSERT_EQ(cbegin[0].value, set.cbegin()->value);
-
-    ASSERT_LT(cbegin, cend);
-    ASSERT_LE(cbegin, set.cbegin());
-
-    ASSERT_GT(cend, cbegin);
-    ASSERT_GE(cend, set.cend());
-}
-
-TEST(SparseSetWithType, IteratorEmptyType) {
-    using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::iterator_type;
-    entt::sparse_set<std::uint64_t, empty_type> set;
-    set.construct(3);
-
-    iterator_type end{set.begin()};
-    iterator_type begin{};
-    begin = set.end();
-    std::swap(begin, end);
-
-    ASSERT_EQ(begin, set.begin());
-    ASSERT_EQ(end, set.end());
-    ASSERT_NE(begin, end);
-
-    ASSERT_EQ(begin++, set.begin());
-    ASSERT_EQ(begin--, set.end());
-
-    ASSERT_EQ(begin+1, set.end());
-    ASSERT_EQ(end-1, set.begin());
-
-    ASSERT_EQ(++begin, set.end());
-    ASSERT_EQ(--begin, set.begin());
-
-    ASSERT_EQ(begin += 1, set.end());
-    ASSERT_EQ(begin -= 1, set.begin());
-
-    ASSERT_EQ(begin + (end - begin), set.end());
-    ASSERT_EQ(begin - (begin - end), set.end());
-
-    ASSERT_EQ(end - (end - begin), set.begin());
-    ASSERT_EQ(end + (begin - end), set.begin());
-
-    ASSERT_EQ(&begin[0], set.begin().operator->());
-
-    ASSERT_LT(begin, end);
-    ASSERT_LE(begin, set.begin());
-
-    ASSERT_GT(end, begin);
-    ASSERT_GE(end, set.end());
-
-    set.construct(33);
-
-    ASSERT_EQ(&*begin, &*(begin+1));
-    ASSERT_EQ(&begin[0], &begin[1]);
-    ASSERT_EQ(begin.operator->(), (end-1).operator->());
-}
-
-TEST(SparseSetWithType, ConstIteratorEmptyType) {
-    using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::const_iterator_type;
-    entt::sparse_set<std::uint64_t, empty_type> set;
-    set.construct(3);
-
-    iterator_type cend{set.cbegin()};
-    iterator_type cbegin{};
-    cbegin = set.cend();
-    std::swap(cbegin, cend);
-
-    ASSERT_EQ(cbegin, set.cbegin());
-    ASSERT_EQ(cend, set.cend());
-    ASSERT_NE(cbegin, cend);
-
-    ASSERT_EQ(cbegin++, set.cbegin());
-    ASSERT_EQ(cbegin--, set.cend());
-
-    ASSERT_EQ(cbegin+1, set.cend());
-    ASSERT_EQ(cend-1, set.cbegin());
-
-    ASSERT_EQ(++cbegin, set.cend());
-    ASSERT_EQ(--cbegin, set.cbegin());
-
-    ASSERT_EQ(cbegin += 1, set.cend());
-    ASSERT_EQ(cbegin -= 1, set.cbegin());
-
-    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
-    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
-
-    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
-    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
-
-    ASSERT_EQ(&cbegin[0], set.cbegin().operator->());
-
-    ASSERT_LT(cbegin, cend);
-    ASSERT_LE(cbegin, set.cbegin());
-
-    ASSERT_GT(cend, cbegin);
-    ASSERT_GE(cend, set.cend());
-
-    set.construct(33);
-
-    ASSERT_EQ(&*cbegin, &*(cbegin+1));
-    ASSERT_EQ(&cbegin[0], &cbegin[1]);
-    ASSERT_EQ(cbegin.operator->(), (cend-1).operator->());
-}
-
-TEST(SparseSetWithType, Raw) {
-    entt::sparse_set<std::uint64_t, int> set;
-
-    set.construct(3, 3);
-    set.construct(12, 6);
-    set.construct(42, 9);
-
-    ASSERT_EQ(set.get(3), 3);
-    ASSERT_EQ(std::as_const(set).get(12), 6);
-    ASSERT_EQ(set.get(42), 9);
-
-    ASSERT_EQ(*(set.raw() + 0u), 3);
-    ASSERT_EQ(*(std::as_const(set).raw() + 1u), 6);
-    ASSERT_EQ(*(set.raw() + 2u), 9);
-}
-
-TEST(SparseSetWithType, RawEmptyType) {
-    entt::sparse_set<std::uint64_t, empty_type> set;
-
-    set.construct(3);
-
-    ASSERT_EQ(set.raw(), std::as_const(set).raw());
-    ASSERT_EQ(set.try_get(3), set.raw());
-}
-
-TEST(SparseSetWithType, SortOrdered) {
-    entt::sparse_set<std::uint64_t, boxed_int> set;
-
-    set.construct(12, boxed_int{12});
-    set.construct(42, boxed_int{9});
-    set.construct(7, boxed_int{6});
-    set.construct(3, boxed_int{3});
-    set.construct(9, boxed_int{1});
-
-    ASSERT_EQ(set.get(12).value, 12);
-    ASSERT_EQ(set.get(42).value, 9);
-    ASSERT_EQ(set.get(7).value, 6);
-    ASSERT_EQ(set.get(3).value, 3);
-    ASSERT_EQ(set.get(9).value, 1);
-
-    set.sort([](auto lhs, auto rhs) {
-        return lhs.value < rhs.value;
-    });
-
-    ASSERT_EQ((set.raw() + 0u)->value, 12);
-    ASSERT_EQ((set.raw() + 1u)->value, 9);
-    ASSERT_EQ((set.raw() + 2u)->value, 6);
-    ASSERT_EQ((set.raw() + 3u)->value, 3);
-    ASSERT_EQ((set.raw() + 4u)->value, 1);
-
-    auto begin = set.begin();
-    auto end = set.end();
-
-    ASSERT_EQ((begin++)->value, 1);
-    ASSERT_EQ((begin++)->value, 3);
-    ASSERT_EQ((begin++)->value, 6);
-    ASSERT_EQ((begin++)->value, 9);
-    ASSERT_EQ((begin++)->value, 12);
-    ASSERT_EQ(begin, end);
-}
-
-TEST(SparseSetWithType, SortReverse) {
-    entt::sparse_set<std::uint64_t, boxed_int> set;
-
-    set.construct(12, boxed_int{1});
-    set.construct(42, boxed_int{3});
-    set.construct(7, boxed_int{6});
-    set.construct(3, boxed_int{9});
-    set.construct(9, boxed_int{12});
-
-    ASSERT_EQ(set.get(12).value, 1);
-    ASSERT_EQ(set.get(42).value, 3);
-    ASSERT_EQ(set.get(7).value, 6);
-    ASSERT_EQ(set.get(3).value, 9);
-    ASSERT_EQ(set.get(9).value, 12);
-
-    set.sort([&set](std::uint64_t lhs, std::uint64_t rhs) {
-        return set.get(lhs).value < set.get(rhs).value;
-    });
-
-    ASSERT_EQ((set.raw() + 0u)->value, 12);
-    ASSERT_EQ((set.raw() + 1u)->value, 9);
-    ASSERT_EQ((set.raw() + 2u)->value, 6);
-    ASSERT_EQ((set.raw() + 3u)->value, 3);
-    ASSERT_EQ((set.raw() + 4u)->value, 1);
-
-    auto begin = set.begin();
-    auto end = set.end();
-
-    ASSERT_EQ((begin++)->value, 1);
-    ASSERT_EQ((begin++)->value, 3);
-    ASSERT_EQ((begin++)->value, 6);
-    ASSERT_EQ((begin++)->value, 9);
-    ASSERT_EQ((begin++)->value, 12);
-    ASSERT_EQ(begin, end);
-}
-
-TEST(SparseSetWithType, SortUnordered) {
-    entt::sparse_set<std::uint64_t, boxed_int> set;
-
-    set.construct(12, boxed_int{6});
-    set.construct(42, boxed_int{3});
-    set.construct(7, boxed_int{1});
-    set.construct(3, boxed_int{9});
-    set.construct(9, boxed_int{12});
-
-    ASSERT_EQ(set.get(12).value, 6);
-    ASSERT_EQ(set.get(42).value, 3);
-    ASSERT_EQ(set.get(7).value, 1);
-    ASSERT_EQ(set.get(3).value, 9);
-    ASSERT_EQ(set.get(9).value, 12);
-
-    set.sort([](auto lhs, auto rhs) {
-        return lhs.value < rhs.value;
-    });
-
-    ASSERT_EQ((set.raw() + 0u)->value, 12);
-    ASSERT_EQ((set.raw() + 1u)->value, 9);
-    ASSERT_EQ((set.raw() + 2u)->value, 6);
-    ASSERT_EQ((set.raw() + 3u)->value, 3);
-    ASSERT_EQ((set.raw() + 4u)->value, 1);
-
-    auto begin = set.begin();
-    auto end = set.end();
-
-    ASSERT_EQ((begin++)->value, 1);
-    ASSERT_EQ((begin++)->value, 3);
-    ASSERT_EQ((begin++)->value, 6);
-    ASSERT_EQ((begin++)->value, 9);
-    ASSERT_EQ((begin++)->value, 12);
-    ASSERT_EQ(begin, end);
-}
-
-TEST(SparseSetWithType, RespectDisjoint) {
-    entt::sparse_set<std::uint64_t, int> lhs;
-    entt::sparse_set<std::uint64_t, int> rhs;
-
-    lhs.construct(3, 3);
-    lhs.construct(12, 6);
-    lhs.construct(42, 9);
-
-    ASSERT_EQ(std::as_const(lhs).get(3), 3);
-    ASSERT_EQ(std::as_const(lhs).get(12), 6);
-    ASSERT_EQ(std::as_const(lhs).get(42), 9);
-
-    lhs.respect(rhs);
-
-    ASSERT_EQ(*(std::as_const(lhs).raw() + 0u), 3);
-    ASSERT_EQ(*(std::as_const(lhs).raw() + 1u), 6);
-    ASSERT_EQ(*(std::as_const(lhs).raw() + 2u), 9);
-
-    auto begin = lhs.begin();
-    auto end = lhs.end();
-
-    ASSERT_EQ(*(begin++), 9);
-    ASSERT_EQ(*(begin++), 6);
-    ASSERT_EQ(*(begin++), 3);
-    ASSERT_EQ(begin, end);
-}
-
-TEST(SparseSetWithType, RespectOverlap) {
-    entt::sparse_set<std::uint64_t, int> lhs;
-    entt::sparse_set<std::uint64_t, int> rhs;
-
-    lhs.construct(3, 3);
-    lhs.construct(12, 6);
-    lhs.construct(42, 9);
-    rhs.construct(12, 6);
-
-    ASSERT_EQ(std::as_const(lhs).get(3), 3);
-    ASSERT_EQ(std::as_const(lhs).get(12), 6);
-    ASSERT_EQ(std::as_const(lhs).get(42), 9);
-    ASSERT_EQ(rhs.get(12), 6);
-
-    lhs.respect(rhs);
-
-    ASSERT_EQ(*(std::as_const(lhs).raw() + 0u), 3);
-    ASSERT_EQ(*(std::as_const(lhs).raw() + 1u), 9);
-    ASSERT_EQ(*(std::as_const(lhs).raw() + 2u), 6);
-
-    auto begin = lhs.begin();
-    auto end = lhs.end();
-
-    ASSERT_EQ(*(begin++), 6);
-    ASSERT_EQ(*(begin++), 9);
-    ASSERT_EQ(*(begin++), 3);
-    ASSERT_EQ(begin, end);
-}
-
-TEST(SparseSetWithType, RespectOrdered) {
-    entt::sparse_set<std::uint64_t, int> lhs;
-    entt::sparse_set<std::uint64_t, int> rhs;
-
-    lhs.construct(1, 0);
-    lhs.construct(2, 0);
-    lhs.construct(3, 0);
-    lhs.construct(4, 0);
-    lhs.construct(5, 0);
-
-    ASSERT_EQ(lhs.get(1), 0);
-    ASSERT_EQ(lhs.get(2), 0);
-    ASSERT_EQ(lhs.get(3), 0);
-    ASSERT_EQ(lhs.get(4), 0);
-    ASSERT_EQ(lhs.get(5), 0);
-
-    rhs.construct(6, 0);
-    rhs.construct(1, 0);
-    rhs.construct(2, 0);
-    rhs.construct(3, 0);
-    rhs.construct(4, 0);
-    rhs.construct(5, 0);
-
-    ASSERT_EQ(rhs.get(6), 0);
-    ASSERT_EQ(rhs.get(1), 0);
-    ASSERT_EQ(rhs.get(2), 0);
-    ASSERT_EQ(rhs.get(3), 0);
-    ASSERT_EQ(rhs.get(4), 0);
-    ASSERT_EQ(rhs.get(5), 0);
-
-    rhs.respect(lhs);
-
-    ASSERT_EQ(*(lhs.data() + 0u), 1u);
-    ASSERT_EQ(*(lhs.data() + 1u), 2u);
-    ASSERT_EQ(*(lhs.data() + 2u), 3u);
-    ASSERT_EQ(*(lhs.data() + 3u), 4u);
-    ASSERT_EQ(*(lhs.data() + 4u), 5u);
-
-    ASSERT_EQ(*(rhs.data() + 0u), 6u);
-    ASSERT_EQ(*(rhs.data() + 1u), 1u);
-    ASSERT_EQ(*(rhs.data() + 2u), 2u);
-    ASSERT_EQ(*(rhs.data() + 3u), 3u);
-    ASSERT_EQ(*(rhs.data() + 4u), 4u);
-    ASSERT_EQ(*(rhs.data() + 5u), 5u);
-}
-
-TEST(SparseSetWithType, RespectReverse) {
-    entt::sparse_set<std::uint64_t, int> lhs;
-    entt::sparse_set<std::uint64_t, int> rhs;
-
-    lhs.construct(1, 0);
-    lhs.construct(2, 0);
-    lhs.construct(3, 0);
-    lhs.construct(4, 0);
-    lhs.construct(5, 0);
-
-    ASSERT_EQ(lhs.get(1), 0);
-    ASSERT_EQ(lhs.get(2), 0);
-    ASSERT_EQ(lhs.get(3), 0);
-    ASSERT_EQ(lhs.get(4), 0);
-    ASSERT_EQ(lhs.get(5), 0);
-
-    rhs.construct(5, 0);
-    rhs.construct(4, 0);
-    rhs.construct(3, 0);
-    rhs.construct(2, 0);
-    rhs.construct(1, 0);
-    rhs.construct(6, 0);
-
-    ASSERT_EQ(rhs.get(5), 0);
-    ASSERT_EQ(rhs.get(4), 0);
-    ASSERT_EQ(rhs.get(3), 0);
-    ASSERT_EQ(rhs.get(2), 0);
-    ASSERT_EQ(rhs.get(1), 0);
-    ASSERT_EQ(rhs.get(6), 0);
-
-    rhs.respect(lhs);
-
-    ASSERT_EQ(*(lhs.data() + 0u), 1u);
-    ASSERT_EQ(*(lhs.data() + 1u), 2u);
-    ASSERT_EQ(*(lhs.data() + 2u), 3u);
-    ASSERT_EQ(*(lhs.data() + 3u), 4u);
-    ASSERT_EQ(*(lhs.data() + 4u), 5u);
-
-    ASSERT_EQ(*(rhs.data() + 0u), 6u);
-    ASSERT_EQ(*(rhs.data() + 1u), 1u);
-    ASSERT_EQ(*(rhs.data() + 2u), 2u);
-    ASSERT_EQ(*(rhs.data() + 3u), 3u);
-    ASSERT_EQ(*(rhs.data() + 4u), 4u);
-    ASSERT_EQ(*(rhs.data() + 5u), 5u);
-}
-
-TEST(SparseSetWithType, RespectUnordered) {
-    entt::sparse_set<std::uint64_t, int> lhs;
-    entt::sparse_set<std::uint64_t, int> rhs;
-
-    lhs.construct(1, 0);
-    lhs.construct(2, 0);
-    lhs.construct(3, 0);
-    lhs.construct(4, 0);
-    lhs.construct(5, 0);
-
-    ASSERT_EQ(lhs.get(1), 0);
-    ASSERT_EQ(lhs.get(2), 0);
-    ASSERT_EQ(lhs.get(3), 0);
-    ASSERT_EQ(lhs.get(4), 0);
-    ASSERT_EQ(lhs.get(5), 0);
-
-    rhs.construct(3, 0);
-    rhs.construct(2, 0);
-    rhs.construct(6, 0);
-    rhs.construct(1, 0);
-    rhs.construct(4, 0);
-    rhs.construct(5, 0);
-
-    ASSERT_EQ(rhs.get(3), 0);
-    ASSERT_EQ(rhs.get(2), 0);
-    ASSERT_EQ(rhs.get(6), 0);
-    ASSERT_EQ(rhs.get(1), 0);
-    ASSERT_EQ(rhs.get(4), 0);
-    ASSERT_EQ(rhs.get(5), 0);
-
-    rhs.respect(lhs);
-
-    ASSERT_EQ(*(lhs.data() + 0u), 1u);
-    ASSERT_EQ(*(lhs.data() + 1u), 2u);
-    ASSERT_EQ(*(lhs.data() + 2u), 3u);
-    ASSERT_EQ(*(lhs.data() + 3u), 4u);
-    ASSERT_EQ(*(lhs.data() + 4u), 5u);
-
-    ASSERT_EQ(*(rhs.data() + 0u), 6u);
-    ASSERT_EQ(*(rhs.data() + 1u), 1u);
-    ASSERT_EQ(*(rhs.data() + 2u), 2u);
-    ASSERT_EQ(*(rhs.data() + 3u), 3u);
-    ASSERT_EQ(*(rhs.data() + 4u), 4u);
-    ASSERT_EQ(*(rhs.data() + 5u), 5u);
-}
-
-TEST(SparseSetWithType, RespectOverlapEmptyType) {
-    entt::sparse_set<std::uint64_t, empty_type> lhs;
-    entt::sparse_set<std::uint64_t, empty_type> rhs;
-
-    lhs.construct(3);
-    lhs.construct(12);
-    lhs.construct(42);
-
-    rhs.construct(12);
-
-    ASSERT_EQ(lhs.sparse_set<std::uint64_t>::get(3), 0u);
-    ASSERT_EQ(lhs.sparse_set<std::uint64_t>::get(12), 1u);
-    ASSERT_EQ(lhs.sparse_set<std::uint64_t>::get(42), 2u);
-
-    lhs.respect(rhs);
-
-    ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(3), 0u);
-    ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(12), 2u);
-    ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(42), 1u);
-}
-
-TEST(SparseSetWithType, CanModifyDuringIteration) {
-    entt::sparse_set<std::uint64_t, int> set;
-    set.construct(0, 42);
-
-    ASSERT_EQ(set.capacity(), entt::sparse_set<std::uint64_t>::size_type{1});
-
-    const auto it = set.cbegin();
-    set.reserve(entt::sparse_set<std::uint64_t>::size_type{2});
-
-    ASSERT_EQ(set.capacity(), entt::sparse_set<std::uint64_t>::size_type{2});
-
-    // this should crash with asan enabled if we break the constraint
-    const auto entity = *it;
-    (void)entity;
-}
-
-TEST(SparseSetWithType, ReferencesGuaranteed) {
-    struct internal_type { int value; };
-
-    entt::sparse_set<std::uint64_t, internal_type> set;
-
-    set.construct(0, 0);
-    set.construct(1, 1);
-
-    ASSERT_EQ(set.get(0).value, 0);
-    ASSERT_EQ(set.get(1).value, 1);
-
-    for(auto &&type: set) {
-        if(type.value) {
-            type.value = 42;
-        }
-    }
-
-    ASSERT_EQ(set.get(0).value, 0);
-    ASSERT_EQ(set.get(1).value, 42);
-
-    auto begin = set.begin();
-
-    while(begin != set.end()) {
-        (begin++)->value = 3;
-    }
-
-    ASSERT_EQ(set.get(0).value, 3);
-    ASSERT_EQ(set.get(1).value, 3);
-}
-
-TEST(SparseSetWithType, MoveOnlyComponent) {
-    // the purpose is to ensure that move only components are always accepted
-    entt::sparse_set<std::uint64_t, std::unique_ptr<int>> set;
-    (void)set;
-}
-
-TEST(SparseSetWithType, ConstructorExceptionDoesNotAddToSet) {
-    struct throwing_component {
-        struct constructor_exception: std::exception {};
-
-        [[noreturn]] throwing_component() { throw constructor_exception{}; }
-
-        // necessary to avoid the short-circuit construct() logic for empty objects
-        int data;
-    };
-
-    entt::sparse_set<std::uint64_t, throwing_component> set;
-
-    try {
-        set.construct(0);
-        FAIL() << "Expected constructor_exception to be thrown";
-    } catch (const throwing_component::constructor_exception &) {
-        ASSERT_TRUE(set.empty());
-    }
-}

+ 749 - 0
test/entt/entity/storage.cpp

@@ -0,0 +1,749 @@
+#include <memory>
+#include <utility>
+#include <iterator>
+#include <exception>
+#include <type_traits>
+#include <unordered_set>
+#include <gtest/gtest.h>
+#include <entt/entity/storage.hpp>
+
+struct empty_type {};
+struct boxed_int { int value; };
+
+struct throwing_component {
+    struct constructor_exception: std::exception {};
+
+    [[noreturn]] throwing_component() { throw constructor_exception{}; }
+
+    // necessary to avoid the short-circuit construct() logic for empty objects
+    int data;
+};
+
+TEST(SparseSetWithType, Functionalities) {
+    entt::storage<std::uint64_t, int> set;
+
+    set.reserve(42);
+
+    ASSERT_EQ(set.capacity(), 42);
+    ASSERT_TRUE(set.empty());
+    ASSERT_EQ(set.size(), 0u);
+    ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
+    ASSERT_EQ(set.begin(), set.end());
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(41));
+
+    set.construct(41, 3);
+
+    ASSERT_FALSE(set.empty());
+    ASSERT_EQ(set.size(), 1u);
+    ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
+    ASSERT_NE(set.begin(), set.end());
+    ASSERT_FALSE(set.has(0));
+    ASSERT_TRUE(set.has(41));
+    ASSERT_EQ(set.get(41), 3);
+    ASSERT_EQ(*set.try_get(41), 3);
+    ASSERT_EQ(set.try_get(99), nullptr);
+
+    set.destroy(41);
+
+    ASSERT_TRUE(set.empty());
+    ASSERT_EQ(set.size(), 0u);
+    ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
+    ASSERT_EQ(set.begin(), set.end());
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(41));
+
+    set.construct(41, 12);
+
+    ASSERT_EQ(set.get(41), 12);
+    ASSERT_EQ(*set.try_get(41), 12);
+    ASSERT_EQ(set.try_get(99), nullptr);
+
+    set.reset();
+
+    ASSERT_TRUE(set.empty());
+    ASSERT_EQ(set.size(), 0u);
+    ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
+    ASSERT_EQ(set.begin(), set.end());
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(41));
+
+    ASSERT_EQ(set.capacity(), 42);
+
+    set.shrink_to_fit();
+
+    ASSERT_EQ(set.capacity(), 0);
+
+    (void)entt::storage<std::uint64_t, int>{std::move(set)};
+    entt::storage<std::uint64_t, int> other;
+    other = std::move(set);
+}
+
+TEST(SparseSetWithType, EmptyType) {
+    entt::storage<std::uint64_t, empty_type> set;
+
+    ASSERT_EQ(&set.construct(42), &set.construct(99));
+    ASSERT_EQ(&set.get(42), set.try_get(42));
+    ASSERT_EQ(&set.get(42), &set.get(99));
+    ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
+}
+
+TEST(SparseSetWithType, BatchAdd) {
+    entt::storage<std::uint64_t, int> set;
+    entt::storage<std::uint64_t, int>::entity_type entities[2];
+
+    entities[0] = 3;
+    entities[1] = 42;
+
+    set.reserve(4);
+    set.construct(12, 21);
+    auto *component = set.batch(std::begin(entities), std::end(entities));
+    set.construct(24, 42);
+
+    ASSERT_TRUE(set.has(entities[0]));
+    ASSERT_TRUE(set.has(entities[1]));
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(9));
+    ASSERT_TRUE(set.has(12));
+    ASSERT_TRUE(set.has(24));
+
+    ASSERT_FALSE(set.empty());
+    ASSERT_EQ(set.size(), 4u);
+    ASSERT_EQ(set.get(12), 21);
+    ASSERT_EQ(set.get(entities[0]), 0);
+    ASSERT_EQ(set.get(entities[1]), 0);
+    ASSERT_EQ(set.get(24), 42);
+
+    component[0] = 1;
+    component[1] = 2;
+
+    ASSERT_EQ(set.get(entities[0]), 1);
+    ASSERT_EQ(set.get(entities[1]), 2);
+}
+
+TEST(SparseSetWithType, BatchAddEmptyType) {
+    entt::storage<std::uint64_t, empty_type> set;
+    entt::storage<std::uint64_t, empty_type>::entity_type entities[2];
+
+    entities[0] = 3;
+    entities[1] = 42;
+
+    set.reserve(4);
+    set.construct(12);
+    auto *component = set.batch(std::begin(entities), std::end(entities));
+    set.construct(24);
+
+    ASSERT_TRUE(set.has(entities[0]));
+    ASSERT_TRUE(set.has(entities[1]));
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(9));
+    ASSERT_TRUE(set.has(12));
+    ASSERT_TRUE(set.has(24));
+
+    ASSERT_FALSE(set.empty());
+    ASSERT_EQ(set.size(), 4u);
+    ASSERT_EQ(&set.get(entities[0]), &set.get(entities[1]));
+    ASSERT_EQ(&set.get(entities[0]), &set.get(12));
+    ASSERT_EQ(&set.get(entities[0]), &set.get(24));
+    ASSERT_EQ(&set.get(entities[0]), component);
+}
+
+TEST(SparseSetWithType, AggregatesMustWork) {
+    struct aggregate_type { int value; };
+    // the goal of this test is to enforce the requirements for aggregate types
+    entt::storage<std::uint64_t, aggregate_type>{}.construct(0, 42);
+}
+
+TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
+    // see #37 - this test shouldn't crash, that's all
+    entt::storage<std::uint64_t, std::unordered_set<int>> set;
+    set.construct(0).insert(42);
+    set.destroy(0);
+}
+
+TEST(SparseSetWithType, Iterator) {
+    using iterator_type = typename entt::storage<std::uint64_t, boxed_int>::iterator_type;
+
+    entt::storage<std::uint64_t, boxed_int> set;
+    set.construct(3, 42);
+
+    iterator_type end{set.begin()};
+    iterator_type begin{};
+    begin = set.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, set.begin());
+    ASSERT_EQ(end, set.end());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(begin++, set.begin());
+    ASSERT_EQ(begin--, set.end());
+
+    ASSERT_EQ(begin+1, set.end());
+    ASSERT_EQ(end-1, set.begin());
+
+    ASSERT_EQ(++begin, set.end());
+    ASSERT_EQ(--begin, set.begin());
+
+    ASSERT_EQ(begin += 1, set.end());
+    ASSERT_EQ(begin -= 1, set.begin());
+
+    ASSERT_EQ(begin + (end - begin), set.end());
+    ASSERT_EQ(begin - (begin - end), set.end());
+
+    ASSERT_EQ(end - (end - begin), set.begin());
+    ASSERT_EQ(end + (begin - end), set.begin());
+
+    ASSERT_EQ(begin[0].value, set.begin()->value);
+
+    ASSERT_LT(begin, end);
+    ASSERT_LE(begin, set.begin());
+
+    ASSERT_GT(end, begin);
+    ASSERT_GE(end, set.end());
+}
+
+TEST(SparseSetWithType, ConstIterator) {
+    using iterator_type = typename entt::storage<std::uint64_t, boxed_int>::const_iterator_type;
+
+    entt::storage<std::uint64_t, boxed_int> set;
+    set.construct(3, 42);
+
+    iterator_type cend{set.cbegin()};
+    iterator_type cbegin{};
+    cbegin = set.cend();
+    std::swap(cbegin, cend);
+
+    ASSERT_EQ(cbegin, set.cbegin());
+    ASSERT_EQ(cend, set.cend());
+    ASSERT_NE(cbegin, cend);
+
+    ASSERT_EQ(cbegin++, set.cbegin());
+    ASSERT_EQ(cbegin--, set.cend());
+
+    ASSERT_EQ(cbegin+1, set.cend());
+    ASSERT_EQ(cend-1, set.cbegin());
+
+    ASSERT_EQ(++cbegin, set.cend());
+    ASSERT_EQ(--cbegin, set.cbegin());
+
+    ASSERT_EQ(cbegin += 1, set.cend());
+    ASSERT_EQ(cbegin -= 1, set.cbegin());
+
+    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
+    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
+
+    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
+    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
+
+    ASSERT_EQ(cbegin[0].value, set.cbegin()->value);
+
+    ASSERT_LT(cbegin, cend);
+    ASSERT_LE(cbegin, set.cbegin());
+
+    ASSERT_GT(cend, cbegin);
+    ASSERT_GE(cend, set.cend());
+}
+
+TEST(SparseSetWithType, IteratorEmptyType) {
+    using iterator_type = typename entt::storage<std::uint64_t, empty_type>::iterator_type;
+    entt::storage<std::uint64_t, empty_type> set;
+    set.construct(3);
+
+    iterator_type end{set.begin()};
+    iterator_type begin{};
+    begin = set.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, set.begin());
+    ASSERT_EQ(end, set.end());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(begin++, set.begin());
+    ASSERT_EQ(begin--, set.end());
+
+    ASSERT_EQ(begin+1, set.end());
+    ASSERT_EQ(end-1, set.begin());
+
+    ASSERT_EQ(++begin, set.end());
+    ASSERT_EQ(--begin, set.begin());
+
+    ASSERT_EQ(begin += 1, set.end());
+    ASSERT_EQ(begin -= 1, set.begin());
+
+    ASSERT_EQ(begin + (end - begin), set.end());
+    ASSERT_EQ(begin - (begin - end), set.end());
+
+    ASSERT_EQ(end - (end - begin), set.begin());
+    ASSERT_EQ(end + (begin - end), set.begin());
+
+    ASSERT_EQ(&begin[0], set.begin().operator->());
+
+    ASSERT_LT(begin, end);
+    ASSERT_LE(begin, set.begin());
+
+    ASSERT_GT(end, begin);
+    ASSERT_GE(end, set.end());
+
+    set.construct(33);
+
+    ASSERT_EQ(&*begin, &*(begin+1));
+    ASSERT_EQ(&begin[0], &begin[1]);
+    ASSERT_EQ(begin.operator->(), (end-1).operator->());
+}
+
+TEST(SparseSetWithType, ConstIteratorEmptyType) {
+    using iterator_type = typename entt::storage<std::uint64_t, empty_type>::const_iterator_type;
+    entt::storage<std::uint64_t, empty_type> set;
+    set.construct(3);
+
+    iterator_type cend{set.cbegin()};
+    iterator_type cbegin{};
+    cbegin = set.cend();
+    std::swap(cbegin, cend);
+
+    ASSERT_EQ(cbegin, set.cbegin());
+    ASSERT_EQ(cend, set.cend());
+    ASSERT_NE(cbegin, cend);
+
+    ASSERT_EQ(cbegin++, set.cbegin());
+    ASSERT_EQ(cbegin--, set.cend());
+
+    ASSERT_EQ(cbegin+1, set.cend());
+    ASSERT_EQ(cend-1, set.cbegin());
+
+    ASSERT_EQ(++cbegin, set.cend());
+    ASSERT_EQ(--cbegin, set.cbegin());
+
+    ASSERT_EQ(cbegin += 1, set.cend());
+    ASSERT_EQ(cbegin -= 1, set.cbegin());
+
+    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
+    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
+
+    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
+    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
+
+    ASSERT_EQ(&cbegin[0], set.cbegin().operator->());
+
+    ASSERT_LT(cbegin, cend);
+    ASSERT_LE(cbegin, set.cbegin());
+
+    ASSERT_GT(cend, cbegin);
+    ASSERT_GE(cend, set.cend());
+
+    set.construct(33);
+
+    ASSERT_EQ(&*cbegin, &*(cbegin+1));
+    ASSERT_EQ(&cbegin[0], &cbegin[1]);
+    ASSERT_EQ(cbegin.operator->(), (cend-1).operator->());
+}
+
+TEST(SparseSetWithType, Raw) {
+    entt::storage<std::uint64_t, int> set;
+
+    set.construct(3, 3);
+    set.construct(12, 6);
+    set.construct(42, 9);
+
+    ASSERT_EQ(set.get(3), 3);
+    ASSERT_EQ(std::as_const(set).get(12), 6);
+    ASSERT_EQ(set.get(42), 9);
+
+    ASSERT_EQ(*(set.raw() + 0u), 3);
+    ASSERT_EQ(*(std::as_const(set).raw() + 1u), 6);
+    ASSERT_EQ(*(set.raw() + 2u), 9);
+}
+
+TEST(SparseSetWithType, RawEmptyType) {
+    entt::storage<std::uint64_t, empty_type> set;
+
+    set.construct(3);
+
+    ASSERT_EQ(set.raw(), std::as_const(set).raw());
+    ASSERT_EQ(set.try_get(3), set.raw());
+}
+
+TEST(SparseSetWithType, SortOrdered) {
+    entt::storage<std::uint64_t, boxed_int> set;
+
+    set.construct(12, boxed_int{12});
+    set.construct(42, boxed_int{9});
+    set.construct(7, boxed_int{6});
+    set.construct(3, boxed_int{3});
+    set.construct(9, boxed_int{1});
+
+    ASSERT_EQ(set.get(12).value, 12);
+    ASSERT_EQ(set.get(42).value, 9);
+    ASSERT_EQ(set.get(7).value, 6);
+    ASSERT_EQ(set.get(3).value, 3);
+    ASSERT_EQ(set.get(9).value, 1);
+
+    set.sort([](auto lhs, auto rhs) {
+        return lhs.value < rhs.value;
+    });
+
+    ASSERT_EQ((set.raw() + 0u)->value, 12);
+    ASSERT_EQ((set.raw() + 1u)->value, 9);
+    ASSERT_EQ((set.raw() + 2u)->value, 6);
+    ASSERT_EQ((set.raw() + 3u)->value, 3);
+    ASSERT_EQ((set.raw() + 4u)->value, 1);
+
+    auto begin = set.begin();
+    auto end = set.end();
+
+    ASSERT_EQ((begin++)->value, 1);
+    ASSERT_EQ((begin++)->value, 3);
+    ASSERT_EQ((begin++)->value, 6);
+    ASSERT_EQ((begin++)->value, 9);
+    ASSERT_EQ((begin++)->value, 12);
+    ASSERT_EQ(begin, end);
+}
+
+TEST(SparseSetWithType, SortReverse) {
+    entt::storage<std::uint64_t, boxed_int> set;
+
+    set.construct(12, boxed_int{1});
+    set.construct(42, boxed_int{3});
+    set.construct(7, boxed_int{6});
+    set.construct(3, boxed_int{9});
+    set.construct(9, boxed_int{12});
+
+    ASSERT_EQ(set.get(12).value, 1);
+    ASSERT_EQ(set.get(42).value, 3);
+    ASSERT_EQ(set.get(7).value, 6);
+    ASSERT_EQ(set.get(3).value, 9);
+    ASSERT_EQ(set.get(9).value, 12);
+
+    set.sort([&set](std::uint64_t lhs, std::uint64_t rhs) {
+        return set.get(lhs).value < set.get(rhs).value;
+    });
+
+    ASSERT_EQ((set.raw() + 0u)->value, 12);
+    ASSERT_EQ((set.raw() + 1u)->value, 9);
+    ASSERT_EQ((set.raw() + 2u)->value, 6);
+    ASSERT_EQ((set.raw() + 3u)->value, 3);
+    ASSERT_EQ((set.raw() + 4u)->value, 1);
+
+    auto begin = set.begin();
+    auto end = set.end();
+
+    ASSERT_EQ((begin++)->value, 1);
+    ASSERT_EQ((begin++)->value, 3);
+    ASSERT_EQ((begin++)->value, 6);
+    ASSERT_EQ((begin++)->value, 9);
+    ASSERT_EQ((begin++)->value, 12);
+    ASSERT_EQ(begin, end);
+}
+
+TEST(SparseSetWithType, SortUnordered) {
+    entt::storage<std::uint64_t, boxed_int> set;
+
+    set.construct(12, boxed_int{6});
+    set.construct(42, boxed_int{3});
+    set.construct(7, boxed_int{1});
+    set.construct(3, boxed_int{9});
+    set.construct(9, boxed_int{12});
+
+    ASSERT_EQ(set.get(12).value, 6);
+    ASSERT_EQ(set.get(42).value, 3);
+    ASSERT_EQ(set.get(7).value, 1);
+    ASSERT_EQ(set.get(3).value, 9);
+    ASSERT_EQ(set.get(9).value, 12);
+
+    set.sort([](auto lhs, auto rhs) {
+        return lhs.value < rhs.value;
+    });
+
+    ASSERT_EQ((set.raw() + 0u)->value, 12);
+    ASSERT_EQ((set.raw() + 1u)->value, 9);
+    ASSERT_EQ((set.raw() + 2u)->value, 6);
+    ASSERT_EQ((set.raw() + 3u)->value, 3);
+    ASSERT_EQ((set.raw() + 4u)->value, 1);
+
+    auto begin = set.begin();
+    auto end = set.end();
+
+    ASSERT_EQ((begin++)->value, 1);
+    ASSERT_EQ((begin++)->value, 3);
+    ASSERT_EQ((begin++)->value, 6);
+    ASSERT_EQ((begin++)->value, 9);
+    ASSERT_EQ((begin++)->value, 12);
+    ASSERT_EQ(begin, end);
+}
+
+TEST(SparseSetWithType, RespectDisjoint) {
+    entt::storage<std::uint64_t, int> lhs;
+    entt::storage<std::uint64_t, int> rhs;
+
+    lhs.construct(3, 3);
+    lhs.construct(12, 6);
+    lhs.construct(42, 9);
+
+    ASSERT_EQ(std::as_const(lhs).get(3), 3);
+    ASSERT_EQ(std::as_const(lhs).get(12), 6);
+    ASSERT_EQ(std::as_const(lhs).get(42), 9);
+
+    lhs.respect(rhs);
+
+    ASSERT_EQ(*(std::as_const(lhs).raw() + 0u), 3);
+    ASSERT_EQ(*(std::as_const(lhs).raw() + 1u), 6);
+    ASSERT_EQ(*(std::as_const(lhs).raw() + 2u), 9);
+
+    auto begin = lhs.begin();
+    auto end = lhs.end();
+
+    ASSERT_EQ(*(begin++), 9);
+    ASSERT_EQ(*(begin++), 6);
+    ASSERT_EQ(*(begin++), 3);
+    ASSERT_EQ(begin, end);
+}
+
+TEST(SparseSetWithType, RespectOverlap) {
+    entt::storage<std::uint64_t, int> lhs;
+    entt::storage<std::uint64_t, int> rhs;
+
+    lhs.construct(3, 3);
+    lhs.construct(12, 6);
+    lhs.construct(42, 9);
+    rhs.construct(12, 6);
+
+    ASSERT_EQ(std::as_const(lhs).get(3), 3);
+    ASSERT_EQ(std::as_const(lhs).get(12), 6);
+    ASSERT_EQ(std::as_const(lhs).get(42), 9);
+    ASSERT_EQ(rhs.get(12), 6);
+
+    lhs.respect(rhs);
+
+    ASSERT_EQ(*(std::as_const(lhs).raw() + 0u), 3);
+    ASSERT_EQ(*(std::as_const(lhs).raw() + 1u), 9);
+    ASSERT_EQ(*(std::as_const(lhs).raw() + 2u), 6);
+
+    auto begin = lhs.begin();
+    auto end = lhs.end();
+
+    ASSERT_EQ(*(begin++), 6);
+    ASSERT_EQ(*(begin++), 9);
+    ASSERT_EQ(*(begin++), 3);
+    ASSERT_EQ(begin, end);
+}
+
+TEST(SparseSetWithType, RespectOrdered) {
+    entt::storage<std::uint64_t, int> lhs;
+    entt::storage<std::uint64_t, int> rhs;
+
+    lhs.construct(1, 0);
+    lhs.construct(2, 0);
+    lhs.construct(3, 0);
+    lhs.construct(4, 0);
+    lhs.construct(5, 0);
+
+    ASSERT_EQ(lhs.get(1), 0);
+    ASSERT_EQ(lhs.get(2), 0);
+    ASSERT_EQ(lhs.get(3), 0);
+    ASSERT_EQ(lhs.get(4), 0);
+    ASSERT_EQ(lhs.get(5), 0);
+
+    rhs.construct(6, 0);
+    rhs.construct(1, 0);
+    rhs.construct(2, 0);
+    rhs.construct(3, 0);
+    rhs.construct(4, 0);
+    rhs.construct(5, 0);
+
+    ASSERT_EQ(rhs.get(6), 0);
+    ASSERT_EQ(rhs.get(1), 0);
+    ASSERT_EQ(rhs.get(2), 0);
+    ASSERT_EQ(rhs.get(3), 0);
+    ASSERT_EQ(rhs.get(4), 0);
+    ASSERT_EQ(rhs.get(5), 0);
+
+    rhs.respect(lhs);
+
+    ASSERT_EQ(*(lhs.data() + 0u), 1u);
+    ASSERT_EQ(*(lhs.data() + 1u), 2u);
+    ASSERT_EQ(*(lhs.data() + 2u), 3u);
+    ASSERT_EQ(*(lhs.data() + 3u), 4u);
+    ASSERT_EQ(*(lhs.data() + 4u), 5u);
+
+    ASSERT_EQ(*(rhs.data() + 0u), 6u);
+    ASSERT_EQ(*(rhs.data() + 1u), 1u);
+    ASSERT_EQ(*(rhs.data() + 2u), 2u);
+    ASSERT_EQ(*(rhs.data() + 3u), 3u);
+    ASSERT_EQ(*(rhs.data() + 4u), 4u);
+    ASSERT_EQ(*(rhs.data() + 5u), 5u);
+}
+
+TEST(SparseSetWithType, RespectReverse) {
+    entt::storage<std::uint64_t, int> lhs;
+    entt::storage<std::uint64_t, int> rhs;
+
+    lhs.construct(1, 0);
+    lhs.construct(2, 0);
+    lhs.construct(3, 0);
+    lhs.construct(4, 0);
+    lhs.construct(5, 0);
+
+    ASSERT_EQ(lhs.get(1), 0);
+    ASSERT_EQ(lhs.get(2), 0);
+    ASSERT_EQ(lhs.get(3), 0);
+    ASSERT_EQ(lhs.get(4), 0);
+    ASSERT_EQ(lhs.get(5), 0);
+
+    rhs.construct(5, 0);
+    rhs.construct(4, 0);
+    rhs.construct(3, 0);
+    rhs.construct(2, 0);
+    rhs.construct(1, 0);
+    rhs.construct(6, 0);
+
+    ASSERT_EQ(rhs.get(5), 0);
+    ASSERT_EQ(rhs.get(4), 0);
+    ASSERT_EQ(rhs.get(3), 0);
+    ASSERT_EQ(rhs.get(2), 0);
+    ASSERT_EQ(rhs.get(1), 0);
+    ASSERT_EQ(rhs.get(6), 0);
+
+    rhs.respect(lhs);
+
+    ASSERT_EQ(*(lhs.data() + 0u), 1u);
+    ASSERT_EQ(*(lhs.data() + 1u), 2u);
+    ASSERT_EQ(*(lhs.data() + 2u), 3u);
+    ASSERT_EQ(*(lhs.data() + 3u), 4u);
+    ASSERT_EQ(*(lhs.data() + 4u), 5u);
+
+    ASSERT_EQ(*(rhs.data() + 0u), 6u);
+    ASSERT_EQ(*(rhs.data() + 1u), 1u);
+    ASSERT_EQ(*(rhs.data() + 2u), 2u);
+    ASSERT_EQ(*(rhs.data() + 3u), 3u);
+    ASSERT_EQ(*(rhs.data() + 4u), 4u);
+    ASSERT_EQ(*(rhs.data() + 5u), 5u);
+}
+
+TEST(SparseSetWithType, RespectUnordered) {
+    entt::storage<std::uint64_t, int> lhs;
+    entt::storage<std::uint64_t, int> rhs;
+
+    lhs.construct(1, 0);
+    lhs.construct(2, 0);
+    lhs.construct(3, 0);
+    lhs.construct(4, 0);
+    lhs.construct(5, 0);
+
+    ASSERT_EQ(lhs.get(1), 0);
+    ASSERT_EQ(lhs.get(2), 0);
+    ASSERT_EQ(lhs.get(3), 0);
+    ASSERT_EQ(lhs.get(4), 0);
+    ASSERT_EQ(lhs.get(5), 0);
+
+    rhs.construct(3, 0);
+    rhs.construct(2, 0);
+    rhs.construct(6, 0);
+    rhs.construct(1, 0);
+    rhs.construct(4, 0);
+    rhs.construct(5, 0);
+
+    ASSERT_EQ(rhs.get(3), 0);
+    ASSERT_EQ(rhs.get(2), 0);
+    ASSERT_EQ(rhs.get(6), 0);
+    ASSERT_EQ(rhs.get(1), 0);
+    ASSERT_EQ(rhs.get(4), 0);
+    ASSERT_EQ(rhs.get(5), 0);
+
+    rhs.respect(lhs);
+
+    ASSERT_EQ(*(lhs.data() + 0u), 1u);
+    ASSERT_EQ(*(lhs.data() + 1u), 2u);
+    ASSERT_EQ(*(lhs.data() + 2u), 3u);
+    ASSERT_EQ(*(lhs.data() + 3u), 4u);
+    ASSERT_EQ(*(lhs.data() + 4u), 5u);
+
+    ASSERT_EQ(*(rhs.data() + 0u), 6u);
+    ASSERT_EQ(*(rhs.data() + 1u), 1u);
+    ASSERT_EQ(*(rhs.data() + 2u), 2u);
+    ASSERT_EQ(*(rhs.data() + 3u), 3u);
+    ASSERT_EQ(*(rhs.data() + 4u), 4u);
+    ASSERT_EQ(*(rhs.data() + 5u), 5u);
+}
+
+TEST(SparseSetWithType, RespectOverlapEmptyType) {
+    entt::storage<std::uint64_t, empty_type> lhs;
+    entt::storage<std::uint64_t, empty_type> rhs;
+
+    lhs.construct(3);
+    lhs.construct(12);
+    lhs.construct(42);
+
+    rhs.construct(12);
+
+    ASSERT_EQ(lhs.sparse_set<std::uint64_t>::get(3), 0u);
+    ASSERT_EQ(lhs.sparse_set<std::uint64_t>::get(12), 1u);
+    ASSERT_EQ(lhs.sparse_set<std::uint64_t>::get(42), 2u);
+
+    lhs.respect(rhs);
+
+    ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(3), 0u);
+    ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(12), 2u);
+    ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(42), 1u);
+}
+
+TEST(SparseSetWithType, CanModifyDuringIteration) {
+    entt::storage<std::uint64_t, int> set;
+    set.construct(0, 42);
+
+    ASSERT_EQ(set.capacity(), (entt::storage<std::uint64_t, int>::size_type{1}));
+
+    const auto it = set.cbegin();
+    set.reserve(entt::storage<std::uint64_t, int>::size_type{2});
+
+    ASSERT_EQ(set.capacity(), (entt::storage<std::uint64_t, int>::size_type{2}));
+
+    // this should crash with asan enabled if we break the constraint
+    const auto entity = *it;
+    (void)entity;
+}
+
+TEST(SparseSetWithType, ReferencesGuaranteed) {
+    entt::storage<std::uint64_t, boxed_int> set;
+
+    set.construct(0, 0);
+    set.construct(1, 1);
+
+    ASSERT_EQ(set.get(0).value, 0);
+    ASSERT_EQ(set.get(1).value, 1);
+
+    for(auto &&type: set) {
+        if(type.value) {
+            type.value = 42;
+        }
+    }
+
+    ASSERT_EQ(set.get(0).value, 0);
+    ASSERT_EQ(set.get(1).value, 42);
+
+    auto begin = set.begin();
+
+    while(begin != set.end()) {
+        (begin++)->value = 3;
+    }
+
+    ASSERT_EQ(set.get(0).value, 3);
+    ASSERT_EQ(set.get(1).value, 3);
+}
+
+TEST(SparseSetWithType, MoveOnlyComponent) {
+    // the purpose is to ensure that move only components are always accepted
+    entt::storage<std::uint64_t, std::unique_ptr<int>> set;
+    (void)set;
+}
+
+TEST(SparseSetWithType, ConstructorExceptionDoesNotAddToSet) {
+    entt::storage<std::uint64_t, throwing_component> set;
+
+    try {
+        set.construct(0);
+        FAIL() << "Expected constructor_exception to be thrown";
+    } catch (const throwing_component::constructor_exception &) {
+        ASSERT_TRUE(set.empty());
+    }
+}