Browse Source

batch add is now available

Michele Caini 7 years ago
parent
commit
9810da6982

+ 0 - 1
TODO

@@ -17,7 +17,6 @@
 * allow some features by component type (eg registry.assign(entity, component);
 * allow some features by component type (eg registry.assign(entity, component);
   - it could be possible for eg default constructible types by storing aside (pool data) erased functions
   - it could be possible for eg default constructible types by storing aside (pool data) erased functions
   - does it worth it?
   - does it worth it?
-* add and bulk add with components (sort of registry.create<A, B>(first, last) and registry.create<A, B>())
 * events on replace, so that one can track updated components? indagate impact
 * events on replace, so that one can track updated components? indagate impact
   - define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
   - define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
   - define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
   - define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)

+ 28 - 6
docs/entity.md

@@ -6,9 +6,10 @@
 # Table of Contents
 # Table of Contents
 
 
 * [Introduction](#introduction)
 * [Introduction](#introduction)
-* [Design choices](#design-choices)
+* [Design decisions](#design-decisions)
   * [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
   * [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
   * [Pay per use](#pay-per-use)
   * [Pay per use](#pay-per-use)
+  * [All or nothing](#all-or-nothing)
 * [Vademecum](#vademecum)
 * [Vademecum](#vademecum)
 * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
 * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
   * [Observe changes](#observe-changes)
   * [Observe changes](#observe-changes)
@@ -49,7 +50,7 @@ more) written in modern C++.<br/>
 The entity-component-system (also known as _ECS_) is an architectural pattern
 The entity-component-system (also known as _ECS_) is an architectural pattern
 used mostly in game development.
 used mostly in game development.
 
 
-# Design choices
+# Design decisions
 
 
 ## A bitset-free entity-component system
 ## A bitset-free entity-component system
 
 
@@ -90,6 +91,22 @@ performance along critical paths is high.
 So far, this choice has proven to be a good one and I really hope it can be for
 So far, this choice has proven to be a good one and I really hope it can be for
 many others besides me.
 many others besides me.
 
 
+## All or nothing
+
+`EnTT` is such that at every moment a pair `(T *, size)` is available to
+directly access all the instances of a given component type `T`.<br/>
+This was a guideline and a design decision that influenced many choices, for
+better and for worse. I cannot say whether it will be useful or not to the
+reader, but it's worth to mention it, because it's of the corner stones of this
+library.
+
+Many of the tools described below, from the registry to the views and up to the
+groups give the possibility to get this information and have been designed
+around this need, which was and remains one of my main requirements during the
+development.<br/>
+The rest is experimentation and the desire to invent something new, hoping to
+have succeeded.
+
 # Vademecum
 # Vademecum
 
 
 The registry to store, the views and the groups to iterate. That's all.
 The registry to store, the views and the groups to iterate. That's all.
@@ -129,7 +146,7 @@ Entities are represented by _entity identifiers_. An entity identifier is an
 opaque type that users should not inspect or modify in any way. It carries
 opaque type that users should not inspect or modify in any way. It carries
 information about the entity itself and its version.
 information about the entity itself and its version.
 
 
-A registry can be used both to construct and destroy entities:
+A registry can be used both to construct and to destroy entities:
 
 
 ```cpp
 ```cpp
 // constructs a naked entity with no components and returns its identifier
 // constructs a naked entity with no components and returns its identifier
@@ -139,9 +156,9 @@ auto entity = registry.create();
 registry.destroy(entity);
 registry.destroy(entity);
 ```
 ```
 
 
-There exist also overloads of the `create` and `destroy` member functions that
-accept two iterators, that is a range to assign or to destroy. It can be used to
-create or destroy multiple entities at once:
+There exists also an overload of the `create` and `destroy` member functions
+that accepts two iterators, that is a range to assign or to destroy. It can be
+used to create or destroy multiple entities at once:
 
 
 ```cpp
 ```cpp
 // destroys all the entities in a range
 // destroys all the entities in a range
@@ -149,6 +166,11 @@ auto view = registry.view<a_component, another_component>();
 registry.destroy(view.begin(), view.end());
 registry.destroy(view.begin(), view.end());
 ```
 ```
 
 
+In both cases, the `create` member function accepts also a list of default
+constructible types of components to assign to the entities before to return.
+It's a faster alternative to the creation and subsequent assignment of
+components in separate steps.
+
 When an entity is destroyed, the registry can freely reuse it internally with a
 When an entity is destroyed, the registry can freely reuse it internally with a
 slightly different identifier. In particular, the version of an entity is
 slightly different identifier. In particular, the version of an entity is
 increased each and every time it's discarded.<br/>
 increased each and every time it's discarded.<br/>

+ 93 - 24
src/entt/entity/registry.hpp

@@ -63,6 +63,9 @@ class registry {
     using signal_type = sigh<void(registry &, const Entity)>;
     using signal_type = sigh<void(registry &, const Entity)>;
     using traits_type = entt_traits<Entity>;
     using traits_type = entt_traits<Entity>;
 
 
+    template<typename Component>
+    using pool_type = sparse_set<Entity, std::decay_t<Component>>;
+
     template<typename, typename>
     template<typename, typename>
     struct non_owning_group;
     struct non_owning_group;
 
 
@@ -136,6 +139,16 @@ class registry {
         std::size_t extent;
         std::size_t extent;
     };
     };
 
 
+    void release(const Entity entity) {
+        // lengthens the implicit list of destroyed entities
+        const auto entt = entity & traits_type::entity_mask;
+        const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift;
+        const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
+        entities[entt] = node;
+        next = entt;
+        ++available;
+    }
+
     template<typename Component>
     template<typename Component>
     inline auto pool() const ENTT_NOEXCEPT {
     inline auto pool() const ENTT_NOEXCEPT {
         const auto ctype = type<Component>();
         const auto ctype = type<Component>();
@@ -146,10 +159,10 @@ class registry {
             });
             });
 
 
             assert(it != pools.cend() && it->pool);
             assert(it != pools.cend() && it->pool);
-            return std::make_tuple(&*it, static_cast<sparse_set<Entity, std::decay_t<Component>> *>(it->pool.get()));
+            return std::make_tuple(&*it, static_cast<pool_type<Component> *>(it->pool.get()));
         } else {
         } else {
             assert(ctype < pools.size() && pools[ctype].pool && pools[ctype].runtime_type == ctype);
             assert(ctype < pools.size() && pools[ctype].pool && pools[ctype].runtime_type == ctype);
-            return std::make_tuple(&pools[ctype], static_cast<sparse_set<Entity, std::decay_t<Component>> *>(pools[ctype].pool.get()));
+            return std::make_tuple(&pools[ctype], static_cast<pool_type<Component> *>(pools[ctype].pool.get()));
         }
         }
     }
     }
 
 
@@ -179,11 +192,11 @@ class registry {
         }
         }
 
 
         if(!pdata->pool) {
         if(!pdata->pool) {
-            pdata->pool = std::make_unique<sparse_set<Entity, std::decay_t<Component>>>();
+            pdata->pool = std::make_unique<pool_type<Component>>();
             pdata->runtime_type = ctype;
             pdata->runtime_type = ctype;
         }
         }
 
 
-        return std::make_tuple(pdata, static_cast<sparse_set<Entity, std::decay_t<Component>> *>(pdata->pool.get()));
+        return std::make_tuple(pdata, static_cast<pool_type<Component> *>(pdata->pool.get()));
     }
     }
 
 
 public:
 public:
@@ -328,7 +341,7 @@ public:
      * There are no guarantees on the order of the components. Use a view if you
      * There are no guarantees on the order of the components. Use a view if you
      * want to iterate entities and components in the expected order.
      * want to iterate entities and components in the expected order.
      *
      *
-     * @warning
+     * @note
      * Empty components aren't explicitly instantiated. Therefore, this function
      * Empty components aren't explicitly instantiated. Therefore, this function
      * always returns `nullptr` for them.
      * always returns `nullptr` for them.
      *
      *
@@ -452,11 +465,18 @@ public:
      * function can be used to know if they are still valid or the entity has
      * function can be used to know if they are still valid or the entity has
      * been destroyed and potentially recycled.
      * been destroyed and potentially recycled.
      *
      *
-     * The returned entity has no components assigned.
+     * The returned entity has assigned the given components, if any. The
+     * components must be at least default constructible. A compilation error
+     * will occur otherwhise.
      *
      *
-     * @return A valid entity identifier.
+     * @tparam Component Types of components to assign to the entity.
+     * @return A valid entity identifier if the component list is empty, a tuple
+     * containing the entity identifier and the references to the components
+     * just created otherwise.
      */
      */
-    entity_type create() {
+    template<typename... Component>
+    std::conditional_t<sizeof...(Component) == 0, entity_type, std::tuple<entity_type, Component &...>>
+    create() {
         entity_type entity;
         entity_type entity;
 
 
         if(available) {
         if(available) {
@@ -472,7 +492,11 @@ public:
             assert(entity < traits_type::entity_mask);
             assert(entity < traits_type::entity_mask);
         }
         }
 
 
-        return entity;
+        if constexpr(sizeof...(Component) == 0) {
+            return entity;
+        } else {
+            return { entity, assign<Component>(entity)... };
+        }
     }
     }
 
 
     /**
     /**
@@ -488,30 +512,64 @@ public:
      * function can be used to know if they are still valid or the entity has
      * function can be used to know if they are still valid or the entity has
      * been destroyed and potentially recycled.
      * been destroyed and potentially recycled.
      *
      *
-     * The generated entities have no components assigned.
+     * The entities so generated have assigned the given components, if any. The
+     * components must be at least default constructible. A compilation error
+     * will occur otherwhise.
      *
      *
+     * @tparam Component Types of components to assign to the entity.
      * @tparam It Type of forward iterator.
      * @tparam It Type of forward iterator.
      * @param first An iterator to the first element of the range to generate.
      * @param first An iterator to the first element of the range to generate.
      * @param last An iterator past the last element of the range to generate.
      * @param last An iterator past the last element of the range to generate.
+     * @return No return value if the component list is empty, a tuple
+     * containing the pointers to the arrays of components just created and
+     * sorted the same of the entities otherwise.
      */
      */
-    template<typename It>
-    void create(It first, It last) {
+    template<typename... Component, typename It>
+    std::conditional_t<sizeof...(Component) == 0, void, std::tuple<Component *...>>
+    create(It first, It last) {
         static_assert(std::is_convertible_v<entity_type, typename std::iterator_traits<It>::value_type>);
         static_assert(std::is_convertible_v<entity_type, typename std::iterator_traits<It>::value_type>);
         const auto length = size_type(std::distance(first, last));
         const auto length = size_type(std::distance(first, last));
         const auto sz = std::min(available, length);
         const auto sz = std::min(available, length);
+        [[maybe_unused]] entity_type candidate{};
 
 
         available -= sz;
         available -= sz;
 
 
-        std::generate_n(first, sz, [this]() {
+        const auto tail = std::generate_n(first, sz, [&candidate, this]() mutable {
+            if constexpr(sizeof...(Component) > 0) {
+                candidate = std::max(candidate, next);
+            } else {
+                // suppress warnings
+                (void)candidate;
+            }
+
             const auto entt = next;
             const auto entt = next;
             const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift);
             const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift);
             next = entities[entt] & traits_type::entity_mask;
             next = entities[entt] & traits_type::entity_mask;
             return (entities[entt] = entt | version);
             return (entities[entt] = entt | version);
         });
         });
 
 
-        std::generate_n((first + sz), (length - sz), [this]() {
+        std::generate(tail, last, [this]() {
             return entities.emplace_back(entity_type(entities.size()));
             return entities.emplace_back(entity_type(entities.size()));
         });
         });
+
+        if constexpr(sizeof...(Component) > 0) {
+            const auto hint = size_type(std::max(candidate, *(last-1)))+1;
+
+            auto generator = [first, last, hint, this](auto &&adata) {
+                auto *comp = std::get<1>(adata)->construct(first, last, hint);
+                auto *pdata = std::get<0>(adata);
+
+                if(!pdata->construction.empty()) {
+                    std::for_each(first, last, [pdata, this](const auto entity) {
+                        pdata->construction.publish(*this, entity);
+                    });
+                }
+
+                return comp;
+            };
+
+            return { generator(assure<Component>())... };
+        }
     }
     }
 
 
     /**
     /**
@@ -550,14 +608,7 @@ public:
 
 
         // just a way to protect users from listeners that attach components
         // just a way to protect users from listeners that attach components
         assert(orphan(entity));
         assert(orphan(entity));
-
-        // lengthens the implicit list of destroyed entities
-        const auto entt = entity & traits_type::entity_mask;
-        const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift;
-        const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
-        entities[entt] = node;
-        next = entt;
-        ++available;
+        release(entity);
     }
     }
 
 
     /**
     /**
@@ -568,8 +619,26 @@ public:
      */
      */
     template<typename It>
     template<typename It>
     void destroy(It first, It last) {
     void destroy(It first, It last) {
+        assert(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
+
+        for(auto pos = pools.size(); pos; --pos) {
+            auto &pdata = pools[pos-1];
+
+            if(pdata.pool) {
+                std::for_each(first, last, [&pdata, this](const auto entity) {
+                    if(pdata.pool->has(entity)) {
+                        pdata.destruction.publish(*this, entity);
+                        pdata.pool->destroy(entity);
+                    }
+                });
+            }
+        };
+
+        // just a way to protect users from listeners that attach components
+        assert(std::all_of(first, last, [this](const auto entity) { return orphan(entity); }));
+
         std::for_each(first, last, [this](const auto entity) {
         std::for_each(first, last, [this](const auto entity) {
-            destroy(entity);
+            release(entity);
         });
         });
     }
     }
 
 
@@ -1433,7 +1502,7 @@ public:
      * more instances of this class in sync, as an example in a client-server
      * more instances of this class in sync, as an example in a client-server
      * architecture.
      * architecture.
      *
      *
-     * @warning
+     * @note
      * The loader returned by this function requires that the registry be empty.
      * The loader returned by this function requires that the registry be empty.
      * In case it isn't, all the data will be automatically deleted before to
      * In case it isn't, all the data will be automatically deleted before to
      * return.
      * return.

+ 84 - 6
src/entt/entity/sparse_set.hpp

@@ -370,6 +370,41 @@ public:
         direct.push_back(entity);
         direct.push_back(entity);
     }
     }
 
 
+    /**
+     * @brief Assigns one or more entities to a sparse set.
+     *
+     * This function requires to use a hint value for performance purposes.<br/>
+     * Its value indicates the size necessary to accommodate the largest entity
+     * if used as an index of a hypothetical array.
+     *
+     * @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.
+     * @param hint Hint value to avoid searching for the largest entity.
+     */
+    template<typename It>
+    void construct(It first, It last, size_type hint) {
+        if(hint > reverse.size()) {
+            // null is safe in all cases for our purposes
+            reverse.resize(hint, null);
+        }
+
+        std::for_each(first, last, [next = entity_type(direct.size()), this](const auto entity) mutable {
+            assert(!has(entity));
+            const auto pos = size_type(entity & traits_type::entity_mask);
+            assert(pos < reverse.size());
+            reverse[pos] = next++;
+        });
+
+        direct.insert(direct.end(), first, last);
+    }
+
     /**
     /**
      * @brief Removes an entity from a sparse set.
      * @brief Removes an entity from a sparse set.
      *
      *
@@ -750,7 +785,7 @@ public:
      * performance boost but less guarantees. Use `begin` and `end` if you want
      * performance boost but less guarantees. Use `begin` and `end` if you want
      * to iterate the sparse set in the expected order.
      * to iterate the sparse set in the expected order.
      *
      *
-     * @warning
+     * @note
      * Empty components aren't explicitly instantiated. Only one instance of the
      * Empty components aren't explicitly instantiated. Only one instance of the
      * given type is created. Therefore, this function always returns a pointer
      * given type is created. Therefore, this function always returns a pointer
      * to that instance.
      * to that instance.
@@ -873,7 +908,6 @@ public:
     /**
     /**
      * @brief Assigns an entity to a sparse set and constructs its object.
      * @brief Assigns an entity to a sparse set and constructs its object.
      *
      *
-     * @note
      * This version accept both types that can be constructed in place directly
      * 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
      * and types like aggregates that do not work well with a placement new as
      * performed usually under the hood during an _emplace back_.
      * performed usually under the hood during an _emplace back_.
@@ -907,6 +941,50 @@ public:
         }
         }
     }
     }
 
 
+    /**
+     * @brief Assigns one or more entities to a sparse set and constructs their
+     * objects.
+     *
+     * This function requires to use a hint value for performance purposes.<br/>
+     * Its value indicates the size necessary to accommodate the largest entity
+     * if used as an index of a hypothetical array.
+     *
+     * @note
+     * 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.
+     * @param hint Hint value to avoid searching for the largest entity.
+     * @return A pointer to the array of instances just created and sorted the
+     * same of the entities.
+     */
+    template<typename It>
+    object_type * construct(It first, It last, const size_type hint) {
+        if constexpr(std::is_empty_v<object_type>) {
+            underlying_type::construct(first, last, hint);
+            return &instances;
+        } else {
+            static_assert(std::is_default_constructible_v<object_type>);
+            const auto offset = instances.size();
+            instances.insert(instances.end(), last-first, {});
+            // entity goes after component in case constructor throws
+            underlying_type::construct(first, last, hint);
+            return instances.data() + offset;
+        }
+    }
+
     /**
     /**
      * @brief Removes an entity from a sparse set and destroies its object.
      * @brief Removes an entity from a sparse set and destroies its object.
      *
      *
@@ -960,14 +1038,14 @@ public:
      * this member function.
      * this member function.
      *
      *
      * @note
      * @note
+     * Empty components aren't explicitly instantiated. Therefore, this function
+     * isn't available for them.
+     *
+     * @note
      * Attempting to iterate elements using a raw pointer returned by a call to
      * 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
      * either `data` or `raw` gives no guarantees on the order, even though
      * `sort` has been invoked.
      * `sort` has been invoked.
      *
      *
-     * @warning
-     * Empty components aren't explicitly instantiated. Therefore, this function
-     * isn't available for them.
-     *
      * @tparam Compare Type of comparison function object.
      * @tparam Compare Type of comparison function object.
      * @tparam Sort Type of sort function object.
      * @tparam Sort Type of sort function object.
      * @tparam Args Types of arguments to forward to the sort function object.
      * @tparam Args Types of arguments to forward to the sort function object.

+ 29 - 0
test/benchmark/benchmark.cpp

@@ -56,6 +56,35 @@ TEST(Benchmark, ConstructMany) {
     timer.elapsed();
     timer.elapsed();
 }
 }
 
 
+TEST(Benchmark, ConstructManyAndAssignComponents) {
+    entt::registry<> registry;
+    std::vector<entt::registry<>::entity_type> entities(1000000);
+
+    std::cout << "Constructing 1000000 entities at once and assign components" << std::endl;
+
+    timer timer;
+
+    registry.create(entities.begin(), entities.end());
+
+    for(const auto entity: entities) {
+        registry.assign<position>(entity);
+        registry.assign<velocity>(entity);
+    }
+
+    timer.elapsed();
+}
+
+TEST(Benchmark, ConstructManyWithComponents) {
+    entt::registry<> registry;
+    std::vector<entt::registry<>::entity_type> entities(1000000);
+
+    std::cout << "Constructing 1000000 entities at once with components" << std::endl;
+
+    timer timer;
+    registry.create<position, velocity>(entities.begin(), entities.end());
+    timer.elapsed();
+}
+
 TEST(Benchmark, Destroy) {
 TEST(Benchmark, Destroy) {
     entt::registry<> registry;
     entt::registry<> registry;
 
 

+ 75 - 0
test/entt/entity/registry.cpp

@@ -938,6 +938,81 @@ TEST(Registry, CreateManyEntitiesAtOnce) {
     ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
     ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
 }
 }
 
 
+TEST(Registry, CreateAnEntityWithComponents) {
+    entt::registry<> registry;
+    const auto &[entity, ivalue, cvalue] = registry.create<int, char>();
+
+    ASSERT_FALSE(registry.empty<int>());
+    ASSERT_FALSE(registry.empty<char>());
+
+    ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{1});
+    ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{1});
+
+    ASSERT_TRUE((registry.has<int, char>(entity)));
+
+    ivalue = 42;
+    cvalue = 'c';
+
+    ASSERT_EQ(registry.get<int>(entity), 42);
+    ASSERT_EQ(registry.get<char>(entity), 'c');
+}
+
+TEST(Registry, CreateManyEntitiesWithComponentsAtOnce) {
+    entt::registry<> registry;
+    entt::registry<>::entity_type entities[3];
+
+    const auto entity = registry.create();
+    registry.destroy(registry.create());
+    registry.destroy(entity);
+    registry.destroy(registry.create());
+
+    const auto [iptr, cptr] = registry.create<int, char>(std::begin(entities), std::end(entities));
+
+    ASSERT_FALSE(registry.empty<int>());
+    ASSERT_FALSE(registry.empty<char>());
+
+    ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{3});
+    ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{3});
+
+    ASSERT_TRUE(registry.valid(entities[0]));
+    ASSERT_TRUE(registry.valid(entities[1]));
+    ASSERT_TRUE(registry.valid(entities[2]));
+
+    ASSERT_EQ(registry.entity(entities[0]), entt::registry<>::entity_type{0});
+    ASSERT_EQ(registry.version(entities[0]), entt::registry<>::version_type{2});
+
+    ASSERT_EQ(registry.entity(entities[1]), entt::registry<>::entity_type{1});
+    ASSERT_EQ(registry.version(entities[1]), entt::registry<>::version_type{1});
+
+    ASSERT_EQ(registry.entity(entities[2]), entt::registry<>::entity_type{2});
+    ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
+
+    ASSERT_TRUE((registry.has<int, char>(entities[0])));
+    ASSERT_TRUE((registry.has<int, char>(entities[1])));
+    ASSERT_TRUE((registry.has<int, char>(entities[2])));
+
+    for(auto i = 0; i < 3; ++i) {
+        iptr[i] = i;
+        cptr[i] = char('a'+i);
+    }
+
+    for(auto i = 0; i < 3; ++i) {
+        ASSERT_EQ(registry.get<int>(entities[i]), i);
+        ASSERT_EQ(registry.get<char>(entities[i]), char('a'+i));
+    }
+}
+
+TEST(Registry, CreateManyEntitiesWithComponentsAtOnceWithListener) {
+    entt::registry<> registry;
+    entt::registry<>::entity_type entities[3];
+    listener listener;
+
+    registry.construction<int>().connect<&listener::incr<int>>(&listener);
+    registry.create<int, char>(std::begin(entities), std::end(entities));
+
+    ASSERT_EQ(listener.counter, 3);
+}
+
 TEST(Registry, NonOwningGroupInterleaved) {
 TEST(Registry, NonOwningGroupInterleaved) {
     entt::registry<> registry;
     entt::registry<> registry;
     typename entt::registry<>::entity_type entity = entt::null;
     typename entt::registry<>::entity_type entity = entt::null;

+ 91 - 7
test/entt/entity/sparse_set.cpp

@@ -1,10 +1,13 @@
 #include <memory>
 #include <memory>
+#include <iterator>
 #include <exception>
 #include <exception>
 #include <algorithm>
 #include <algorithm>
 #include <unordered_set>
 #include <unordered_set>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <entt/entity/sparse_set.hpp>
 #include <entt/entity/sparse_set.hpp>
 
 
+struct empty_type {};
+
 TEST(SparseSetNoType, Functionalities) {
 TEST(SparseSetNoType, Functionalities) {
     entt::sparse_set<std::uint64_t> set;
     entt::sparse_set<std::uint64_t> set;
 
 
@@ -58,6 +61,32 @@ TEST(SparseSetNoType, Functionalities) {
     other = std::move(set);
     other = std::move(set);
 }
 }
 
 
+TEST(SparseSetNoType, ConstructMany) {
+    entt::sparse_set<std::uint64_t> set;
+    entt::sparse_set<std::uint64_t>::entity_type entities[2];
+
+    entities[0] = 3;
+    entities[1] = 42;
+
+    set.construct(12);
+    set.construct(std::begin(entities), std::end(entities), 43);
+    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(12), 0u);
+    ASSERT_EQ(set.get(entities[0]), 1u);
+    ASSERT_EQ(set.get(entities[1]), 2u);
+    ASSERT_EQ(set.get(24), 3u);
+}
+
 TEST(SparseSetNoType, Iterator) {
 TEST(SparseSetNoType, Iterator) {
     using iterator_type = typename entt::sparse_set<std::uint64_t>::iterator_type;
     using iterator_type = typename entt::sparse_set<std::uint64_t>::iterator_type;
 
 
@@ -397,8 +426,7 @@ TEST(SparseSetWithType, Functionalities) {
     other = std::move(set);
     other = std::move(set);
 }
 }
 
 
-TEST(SparseSetWithType, FunctionalitiesEmptyType) {
-    struct empty_type {};
+TEST(SparseSetWithType, EmptyType) {
     entt::sparse_set<std::uint64_t, empty_type> set;
     entt::sparse_set<std::uint64_t, empty_type> set;
 
 
     ASSERT_EQ(&set.construct(42), &set.construct(99));
     ASSERT_EQ(&set.construct(42), &set.construct(99));
@@ -407,6 +435,66 @@ TEST(SparseSetWithType, FunctionalitiesEmptyType) {
     ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
     ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
 }
 }
 
 
+TEST(SparseSetWithType, ConstructMany) {
+    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.construct(std::begin(entities), std::end(entities), 43);
+    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, ConstructManyEmptyType) {
+    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.construct(std::begin(entities), std::end(entities), 43);
+    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) {
 TEST(SparseSetWithType, AggregatesMustWork) {
     struct aggregate_type { int value; };
     struct aggregate_type { int value; };
     // the goal of this test is to enforce the requirements for aggregate types
     // the goal of this test is to enforce the requirements for aggregate types
@@ -509,7 +597,6 @@ TEST(SparseSetWithType, ConstIterator) {
 }
 }
 
 
 TEST(SparseSetWithType, IteratorEmptyType) {
 TEST(SparseSetWithType, IteratorEmptyType) {
-    struct empty_type {};
     using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::iterator_type;
     using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::iterator_type;
     entt::sparse_set<std::uint64_t, empty_type> set;
     entt::sparse_set<std::uint64_t, empty_type> set;
     set.construct(3);
     set.construct(3);
@@ -557,7 +644,6 @@ TEST(SparseSetWithType, IteratorEmptyType) {
 }
 }
 
 
 TEST(SparseSetWithType, ConstIteratorEmptyType) {
 TEST(SparseSetWithType, ConstIteratorEmptyType) {
-    struct empty_type {};
     using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::const_iterator_type;
     using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::const_iterator_type;
     entt::sparse_set<std::uint64_t, empty_type> set;
     entt::sparse_set<std::uint64_t, empty_type> set;
     set.construct(3);
     set.construct(3);
@@ -621,7 +707,6 @@ TEST(SparseSetWithType, Raw) {
 }
 }
 
 
 TEST(SparseSetWithType, RawEmptyType) {
 TEST(SparseSetWithType, RawEmptyType) {
-    struct empty_type {};
     entt::sparse_set<std::uint64_t, empty_type> set;
     entt::sparse_set<std::uint64_t, empty_type> set;
 
 
     set.construct(3);
     set.construct(3);
@@ -933,7 +1018,6 @@ TEST(SparseSetWithType, RespectUnordered) {
 }
 }
 
 
 TEST(SparseSetWithType, RespectOverlapEmptyType) {
 TEST(SparseSetWithType, RespectOverlapEmptyType) {
-    struct empty_type {};
     entt::sparse_set<std::uint64_t, empty_type> lhs;
     entt::sparse_set<std::uint64_t, empty_type> lhs;
     entt::sparse_set<std::uint64_t, empty_type> rhs;
     entt::sparse_set<std::uint64_t, empty_type> rhs;
 
 
@@ -1050,7 +1134,7 @@ TEST(SparseSetWithType, ConstructorExceptionDoesNotAddToSet) {
     struct throwing_component {
     struct throwing_component {
         struct constructor_exception: std::exception {};
         struct constructor_exception: std::exception {};
 
 
-        throwing_component() { throw constructor_exception{}; }
+        [[noreturn]] throwing_component() { throw constructor_exception{}; }
 
 
         // necessary to avoid the short-circuit construct() logic for empty objects
         // necessary to avoid the short-circuit construct() logic for empty objects
         int data;
         int data;