Bläddra i källkod

entity:
* sparse_set: added optional user data to ::remove and ::clear
* sparse_set: remove the payload, it was really meh
* storage: review sigh_storage_mixin, simplified storage_adapter_mixin
* registry: pass itself twhen adding/patchin/removing components

test: updated tests for sparse set and poly storage
examples: added a test to avoid regressions on storage_adapter_mixin

Michele Caini 5 år sedan
förälder
incheckning
bb1acee36a

+ 20 - 48
src/entt/entity/registry.hpp

@@ -115,7 +115,6 @@ class basic_registry {
         if(auto &&pdata = pools[index]; !pdata.pool) {
         if(auto &&pdata = pools[index]; !pdata.pool) {
             pdata.pool.reset(new storage_type<Component>());
             pdata.pool.reset(new storage_type<Component>());
             pdata.poly = std::ref(*static_cast<storage_type<Component> *>(pdata.pool.get()));
             pdata.poly = std::ref(*static_cast<storage_type<Component> *>(pdata.pool.get()));
-            pdata.pool->payload(this);
         }
         }
 
 
         return static_cast<storage_type<Component> *>(pools[index].pool.get());
         return static_cast<storage_type<Component> *>(pools[index].pool.get());
@@ -186,38 +185,11 @@ public:
     /*! @brief Default constructor. */
     /*! @brief Default constructor. */
     basic_registry() = default;
     basic_registry() = default;
 
 
-    /**
-     * @brief Move constructor.
-     * @param other The instance to move from.
-     */
-    basic_registry(basic_registry &&other) ENTT_NOEXCEPT
-        : vars(std::move(other.vars)), // we can't use {} here, thanks to GCC
-          pools{std::move(other.pools)},
-          groups{std::move(other.groups)},
-          entities{std::move(other.entities)},
-          available{other.available}
-    {
-        rebind_pools();
-    }
+    /*! @brief Default move constructor. */
+    basic_registry(basic_registry &&) = default;
 
 
-    /**
-     * @brief Move assignment operator.
-     * @param other The instance to assign from.
-     * @return This registry.
-     */
-    basic_registry & operator=(basic_registry &&other) ENTT_NOEXCEPT {
-        if(this != &other) {
-            vars = std::move(other.vars);
-            pools = std::move(other.pools);
-            groups = std::move(other.groups);
-            entities = std::move(other.entities);
-            available = other.available;
-
-            rebind_pools();
-        }
-
-        return *this;
-    }
+    /*! @brief Default move assignment operator. @return This registry. */
+    basic_registry & operator=(basic_registry &&) = default;
 
 
     /**
     /**
      * @brief Prepares a pool for the given type if required.
      * @brief Prepares a pool for the given type if required.
@@ -573,7 +545,7 @@ public:
     template<typename Component, typename... Args>
     template<typename Component, typename... Args>
     decltype(auto) emplace(const entity_type entity, Args &&... args) {
     decltype(auto) emplace(const entity_type entity, Args &&... args) {
         ENTT_ASSERT(valid(entity));
         ENTT_ASSERT(valid(entity));
-        return assure<Component>()->emplace(entity, std::forward<Args>(args)...);
+        return assure<Component>()->emplace(*this, entity, std::forward<Args>(args)...);
     }
     }
 
 
     /**
     /**
@@ -590,7 +562,7 @@ public:
     template<typename Component, typename It>
     template<typename Component, typename It>
     void insert(It first, It last, const Component &value = {}) {
     void insert(It first, It last, const Component &value = {}) {
         ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
         ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
-        assure<Component>()->insert(first, last, value);
+        assure<Component>()->insert(*this, first, last, value);
     }
     }
 
 
     /**
     /**
@@ -610,7 +582,7 @@ public:
     void insert(EIt first, EIt last, CIt from, CIt to) {
     void insert(EIt first, EIt last, CIt from, CIt to) {
         static_assert(std::is_constructible_v<Component, typename std::iterator_traits<CIt>::value_type>, "Invalid value type");
         static_assert(std::is_constructible_v<Component, typename std::iterator_traits<CIt>::value_type>, "Invalid value type");
         ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
         ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
-        assure<Component>()->insert(first, last, from, to);
+        assure<Component>()->insert(*this, first, last, from, to);
     }
     }
 
 
     /**
     /**
@@ -639,8 +611,8 @@ public:
         auto *cpool = assure<Component>();
         auto *cpool = assure<Component>();
 
 
         return cpool->contains(entity)
         return cpool->contains(entity)
-            ? cpool->patch(entity, [&args...](auto &... curr) { ((curr = Component{std::forward<Args>(args)...}), ...); })
-            : cpool->emplace(entity, std::forward<Args>(args)...);
+            ? cpool->patch(*this, entity, [&args...](auto &... curr) { ((curr = Component{std::forward<Args>(args)...}), ...); })
+            : cpool->emplace(*this, entity, std::forward<Args>(args)...);
     }
     }
 
 
     /**
     /**
@@ -670,7 +642,7 @@ public:
     template<typename Component, typename... Func>
     template<typename Component, typename... Func>
     decltype(auto) patch(const entity_type entity, Func &&... func) {
     decltype(auto) patch(const entity_type entity, Func &&... func) {
         ENTT_ASSERT(valid(entity));
         ENTT_ASSERT(valid(entity));
-        return assure<Component>()->patch(entity, std::forward<Func>(func)...);
+        return assure<Component>()->patch(*this, entity, std::forward<Func>(func)...);
     }
     }
 
 
     /**
     /**
@@ -692,7 +664,7 @@ public:
      */
      */
     template<typename Component, typename... Args>
     template<typename Component, typename... Args>
     decltype(auto) replace(const entity_type entity, Args &&... args) {
     decltype(auto) replace(const entity_type entity, Args &&... args) {
-        return assure<Component>()->patch(entity, [&args...](auto &... curr) { ((curr = Component{std::forward<Args>(args)...}), ...); });
+        return assure<Component>()->patch(*this, entity, [&args...](auto &... curr) { ((curr = Component{std::forward<Args>(args)...}), ...); });
     }
     }
 
 
     /**
     /**
@@ -709,7 +681,7 @@ public:
     void remove(const entity_type entity) {
     void remove(const entity_type entity) {
         ENTT_ASSERT(valid(entity));
         ENTT_ASSERT(valid(entity));
         static_assert(sizeof...(Component) > 0, "Provide one or more component types");
         static_assert(sizeof...(Component) > 0, "Provide one or more component types");
-        (assure<Component>()->remove(entity), ...);
+        (assure<Component>()->remove(entity, this), ...);
     }
     }
 
 
     /**
     /**
@@ -726,7 +698,7 @@ public:
     void remove(It first, It last) {
     void remove(It first, It last) {
         ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
         ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
         static_assert(sizeof...(Component) > 0, "Provide one or more component types");
         static_assert(sizeof...(Component) > 0, "Provide one or more component types");
-        (assure<Component>()->remove(first, last), ...);
+        (assure<Component>()->remove(first, last, this), ...);
     }
     }
 
 
     /**
     /**
@@ -751,8 +723,8 @@ public:
     size_type remove_if_exists(const entity_type entity) {
     size_type remove_if_exists(const entity_type entity) {
         ENTT_ASSERT(valid(entity));
         ENTT_ASSERT(valid(entity));
 
 
-        return ([entity](auto *cpool) {
-            return cpool->contains(entity) ? (cpool->remove(entity), true) : false;
+        return ([this, entity](auto *cpool) {
+            return cpool->contains(entity) ? (cpool->remove(entity, this), true) : false;
         }(assure<Component>()) + ... + size_type{});
         }(assure<Component>()) + ... + size_type{});
     }
     }
 
 
@@ -776,7 +748,7 @@ public:
 
 
         for(auto pos = pools.size(); pos; --pos) {
         for(auto pos = pools.size(); pos; --pos) {
             if(auto &pdata = pools[pos-1]; pdata.pool && pdata.pool->contains(entity)) {
             if(auto &pdata = pools[pos-1]; pdata.pool && pdata.pool->contains(entity)) {
-                pdata.pool->remove(std::begin(wrap), std::end(wrap));
+                pdata.pool->remove(std::begin(wrap), std::end(wrap), this);
             }
             }
         }
         }
     }
     }
@@ -874,7 +846,7 @@ public:
     [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&... args) {
     [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&... args) {
         ENTT_ASSERT(valid(entity));
         ENTT_ASSERT(valid(entity));
         auto *cpool = assure<Component>();
         auto *cpool = assure<Component>();
-        return cpool->contains(entity) ? cpool->get(entity) : cpool->emplace(entity, std::forward<Args>(args)...);
+        return cpool->contains(entity) ? cpool->get(entity) : cpool->emplace(*this, entity, std::forward<Args>(args)...);
     }
     }
 
 
     /**
     /**
@@ -924,14 +896,14 @@ public:
         if constexpr(sizeof...(Component) == 0) {
         if constexpr(sizeof...(Component) == 0) {
             for(auto pos = pools.size(); pos; --pos) {
             for(auto pos = pools.size(); pos; --pos) {
                 if(auto &pdata = pools[pos-1]; pdata.pool) {
                 if(auto &pdata = pools[pos-1]; pdata.pool) {
-                    pdata.pool->clear();
+                    pdata.pool->clear(this);
                 }
                 }
             }
             }
 
 
             each([this](const auto entity) { release_entity(entity, version(entity) + 1u); });
             each([this](const auto entity) { release_entity(entity, version(entity) + 1u); });
         } else {
         } else {
-            ([](auto *cpool) {
-                cpool->remove(cpool->basic_sparse_set<entity_type>::begin(), cpool->basic_sparse_set<entity_type>::end());
+            ([this](auto *cpool) {
+                cpool->remove(cpool->basic_sparse_set<entity_type>::begin(), cpool->basic_sparse_set<entity_type>::end(), this);
             }(assure<Component>()), ...);
             }(assure<Component>()), ...);
         }
         }
     }
     }

+ 11 - 24
src/entt/entity/sparse_set.hpp

@@ -178,7 +178,7 @@ protected:
     virtual void swap_at(const std::size_t, const std::size_t) {}
     virtual void swap_at(const std::size_t, const std::size_t) {}
 
 
     /*! @brief Attempts to remove an entity from the internal packed array. */
     /*! @brief Attempts to remove an entity from the internal packed array. */
-    virtual void swap_and_pop(const std::size_t) {}
+    virtual void swap_and_pop(const std::size_t, void *) {}
 
 
 public:
 public:
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
@@ -432,13 +432,14 @@ public:
      * results in undefined behavior.
      * results in undefined behavior.
      *
      *
      * @param entt A valid entity identifier.
      * @param entt A valid entity identifier.
+     * @param ud Optional user data that are forwarded as-is to derived classes.
      */
      */
-    void remove(const entity_type entt) {
+    void remove(const entity_type entt, void *ud = nullptr) {
         ENTT_ASSERT(contains(entt));
         ENTT_ASSERT(contains(entt));
         auto &ref = sparse[page(entt)][offset(entt)];
         auto &ref = sparse[page(entt)][offset(entt)];
 
 
         // last chance to use the entity for derived classes and mixins, if any
         // last chance to use the entity for derived classes and mixins, if any
-        swap_and_pop(size_type{to_integral(ref)});
+        swap_and_pop(size_type{to_integral(ref)}, ud);
 
 
         const auto other = packed.back();
         const auto other = packed.back();
         sparse[page(other)][offset(other)] = ref;
         sparse[page(other)][offset(other)] = ref;
@@ -455,11 +456,12 @@ public:
      * @tparam It Type of input iterator.
      * @tparam It Type of input iterator.
      * @param first An iterator to the first element of the range of entities.
      * @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 last An iterator past the last element of the range of entities.
+     * @param ud Optional user data that are forwarded as-is to derived classes.
      */
      */
     template<typename It>
     template<typename It>
-    void remove(It first, It last) {
+    void remove(It first, It last, void *ud = nullptr) {
         for(; first != last; ++first) {
         for(; first != last; ++first) {
-            remove(*first);
+            remove(*first, ud);
         }
         }
     }
     }
 
 
@@ -588,32 +590,17 @@ public:
         }
         }
     }
     }
 
 
-    /*! @brief Clears a sparse set. */
-    void clear() ENTT_NOEXCEPT {
-        remove(begin(), end());
-    }
-
-    /**
-     * @brief Returns the opaque payload associated with the sparse set, if any.
-     * @return The opaque payload associated with the sparse set, if any.
-     */
-    void * payload() const ENTT_NOEXCEPT {
-        return user_data;
-    }
-
     /**
     /**
-     * @brief Set an opaque payload, typically used to attach information that
-     * are useful to storage mixins.
-     * @param ud Opaque payload, a sparse set won't use this data in any case.
+     * @brief Clears a sparse set.
+     * @param ud Optional user data that are forwarded as-is to derived classes.
      */
      */
-    void payload(void *ud) ENTT_NOEXCEPT {
-        user_data = ud;
+    void clear(void *ud = nullptr) ENTT_NOEXCEPT {
+        remove(begin(), end(), ud);
     }
     }
 
 
 private:
 private:
     std::vector<page_type> sparse;
     std::vector<page_type> sparse;
     std::vector<entity_type> packed;
     std::vector<entity_type> packed;
-    void *user_data{};
 };
 };
 
 
 
 

+ 79 - 21
src/entt/entity/storage.hpp

@@ -178,7 +178,7 @@ protected:
      * @copybrief basic_sparse_set::swap_and_pop
      * @copybrief basic_sparse_set::swap_and_pop
      * @param pos A valid position of an entity within storage.
      * @param pos A valid position of an entity within storage.
      */
      */
-    void swap_and_pop(const std::size_t pos) {
+    void swap_and_pop(const std::size_t pos, void *) {
         auto other = std::move(instances.back());
         auto other = std::move(instances.back());
         instances[pos] = std::move(other);
         instances[pos] = std::move(other);
         instances.pop_back();
         instances.pop_back();
@@ -393,7 +393,7 @@ public:
      * @return A reference to the updated instance.
      * @return A reference to the updated instance.
      */
      */
     template<typename... Func>
     template<typename... Func>
-    decltype(auto) patch(const entity_type entity, [[maybe_unused]] Func &&... func) {
+    decltype(auto) patch(const entity_type entity, Func &&... func) {
         auto &&instance = instances[this->index(entity)];
         auto &&instance = instances[this->index(entity)];
         (std::forward<Func>(func)(instance), ...);
         (std::forward<Func>(func)(instance), ...);
         return instance;
         return instance;
@@ -559,7 +559,7 @@ public:
     * @param func Valid function objects.
     * @param func Valid function objects.
     */
     */
     template<typename... Func>
     template<typename... Func>
-    void patch([[maybe_unused]] const entity_type entity, [[maybe_unused]] Func &&... func) {
+    void patch([[maybe_unused]] const entity_type entity, Func &&... func) {
         ENTT_ASSERT(this->contains(entity));
         ENTT_ASSERT(this->contains(entity));
         (std::forward<Func>(func)(), ...);
         (std::forward<Func>(func)(), ...);
     }
     }
@@ -583,21 +583,76 @@ public:
 
 
 
 
 /**
 /**
- * @brief Mixin type to use to add signal support to storage types.
+ * @brief Mixin type to use to wrap basic storage classes.
  * @tparam Type The type of the underlying storage.
  * @tparam Type The type of the underlying storage.
- * @tparam Owner Expected owner type.
  */
  */
-template<typename Type, typename Owner>
-class sigh_storage_mixin: public Type {
-    Owner & owner() ENTT_NOEXCEPT {
-        return *static_cast<Owner *>(this->payload());
+template<typename Type>
+struct storage_adapter_mixin: Type {
+    static_assert(std::is_same_v<typename Type::value_type, std::decay_t<typename Type::value_type>>, "Invalid object type");
+
+    /*! @brief Type of the objects associated with the entities. */
+    using value_type = typename Type::value_type;
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename Type::entity_type;
+    /*! @brief Storage category. */
+    using storage_category = typename Type::storage_category;
+
+    /**
+     * @brief Assigns entities to a storage.
+     * @tparam Args Types of arguments to use to construct the object.
+     * @param entity A valid entity identifier.
+     * @param args Parameters to use to initialize the object.
+     * @return A reference to the newly created object.
+     */
+    template<typename... Args>
+    decltype(auto) emplace(basic_registry<entity_type> &, const entity_type entity, Args &&... args) {
+        return Type::emplace(entity, std::forward<Args>(args)...);
     }
     }
 
 
-protected:
-    /*! @copydoc basic_storage::swap_and_pop */
-    void swap_and_pop(const std::size_t pos) {
-        destruction.publish(owner(), this->data()[pos]);
-        Type::swap_and_pop(pos);
+    /**
+     * @brief Assigns entities to a storage.
+     * @tparam It Type of input iterator.
+     * @tparam Args Types of arguments to use to construct the objects
+     * associated with the entities.
+     * @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 args Parameters to use to initialize the objects associated with
+     * the entities.
+     */
+    template<typename It, typename... Args>
+    void insert(basic_registry<entity_type> &, It first, It last, Args &&... args) {
+        Type::insert(first, last, std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Patches the given instance for an entity.
+     * @tparam Func Types of the function objects to invoke.
+     * @param entity A valid entity identifier.
+     * @param func Valid function objects.
+     * @return A reference to the patched instance.
+     */
+    template<typename... Func>
+    decltype(auto) patch(basic_registry<entity_type> &, const entity_type entity, Func &&... func) {
+        return Type::patch(entity, std::forward<Func>(func)...);
+    }
+};
+
+
+/**
+ * @brief Mixin type to use to add signal support to storage types.
+ * @tparam Type The type of the underlying storage.
+ */
+template<typename Type>
+class sigh_storage_mixin final: public Type {
+    /**
+     * @copybrief basic_sparse_set::swap_and_pop
+     * @param pos A valid position of an entity within storage.
+     * @param ud Optional user data that are forwarded as-is to derived classes.
+     */
+    void swap_and_pop(const std::size_t pos, void *ud) final {
+        ENTT_ASSERT(ud != nullptr);
+        destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), this->data()[pos]);
+        Type::swap_and_pop(pos, ud);
     }
     }
 
 
 public:
 public:
@@ -676,14 +731,15 @@ public:
     /**
     /**
      * @brief Assigns entities to a storage.
      * @brief Assigns entities to a storage.
      * @tparam Args Types of arguments to use to construct the object.
      * @tparam Args Types of arguments to use to construct the object.
+     * @param owner The registry that issued the request.
      * @param entity A valid entity identifier.
      * @param entity A valid entity identifier.
      * @param args Parameters to use to initialize the object.
      * @param args Parameters to use to initialize the object.
      * @return A reference to the newly created object.
      * @return A reference to the newly created object.
      */
      */
     template<typename... Args>
     template<typename... Args>
-    decltype(auto) emplace(const entity_type entity, Args &&... args) {
+    decltype(auto) emplace(basic_registry<entity_type> &owner, const entity_type entity, Args &&... args) {
         Type::emplace(entity, std::forward<Args>(args)...);
         Type::emplace(entity, std::forward<Args>(args)...);
-        construction.publish(owner(), entity);
+        construction.publish(owner, entity);
         return this->get(entity);
         return this->get(entity);
     }
     }
 
 
@@ -692,18 +748,19 @@ public:
      * @tparam It Type of input iterator.
      * @tparam It Type of input iterator.
      * @tparam Args Types of arguments to use to construct the objects
      * @tparam Args Types of arguments to use to construct the objects
      * associated with the entities.
      * associated with the entities.
+     * @param owner The registry that issued the request.
      * @param first An iterator to the first element of the range of entities.
      * @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 last An iterator past the last element of the range of entities.
      * @param args Parameters to use to initialize the objects associated with
      * @param args Parameters to use to initialize the objects associated with
      * the entities.
      * the entities.
      */
      */
     template<typename It, typename... Args>
     template<typename It, typename... Args>
-    void insert(It first, It last, Args &&... args) {
+    void insert(basic_registry<entity_type> &owner, It first, It last, Args &&... args) {
         Type::insert(first, last, std::forward<Args>(args)...);
         Type::insert(first, last, std::forward<Args>(args)...);
 
 
         if(!construction.empty()) {
         if(!construction.empty()) {
             for(; first != last; ++first) {
             for(; first != last; ++first) {
-                construction.publish(owner(), *first);
+                construction.publish(owner, *first);
             }
             }
         }
         }
     }
     }
@@ -711,14 +768,15 @@ public:
     /**
     /**
      * @brief Patches the given instance for an entity.
      * @brief Patches the given instance for an entity.
      * @tparam Func Types of the function objects to invoke.
      * @tparam Func Types of the function objects to invoke.
+     * @param owner The registry that issued the request.
      * @param entity A valid entity identifier.
      * @param entity A valid entity identifier.
      * @param func Valid function objects.
      * @param func Valid function objects.
      * @return A reference to the patched instance.
      * @return A reference to the patched instance.
      */
      */
     template<typename... Func>
     template<typename... Func>
-    decltype(auto) patch(const entity_type entity, [[maybe_unused]] Func &&... func) {
+    decltype(auto) patch(basic_registry<entity_type> &owner, const entity_type entity, Func &&... func) {
         Type::patch(entity, std::forward<Func>(func)...);
         Type::patch(entity, std::forward<Func>(func)...);
-        update.publish(owner(), entity);
+        update.publish(owner, entity);
         return this->get(entity);
         return this->get(entity);
     }
     }
 
 
@@ -745,7 +803,7 @@ private:
 template<typename Entity, typename Type, typename = void>
 template<typename Entity, typename Type, typename = void>
 struct storage_traits {
 struct storage_traits {
     /*! @brief Resulting type after component-to-storage conversion. */
     /*! @brief Resulting type after component-to-storage conversion. */
-    using storage_type = sigh_storage_mixin<basic_storage<Entity, Type>, entt::basic_registry<Entity>>;
+    using storage_type = sigh_storage_mixin<basic_storage<Entity, Type>>;
 };
 };
 
 
 
 

+ 1 - 0
test/CMakeLists.txt

@@ -97,6 +97,7 @@ endif()
 
 
 if(ENTT_BUILD_EXAMPLE)
 if(ENTT_BUILD_EXAMPLE)
     SETUP_BASIC_TEST(custom_identifier example/custom_identifier.cpp)
     SETUP_BASIC_TEST(custom_identifier example/custom_identifier.cpp)
+    SETUP_BASIC_TEST(signal_less example/signal_less.cpp)
 endif()
 endif()
 
 
 # Test lib
 # Test lib

+ 11 - 11
test/entt/entity/poly_storage.cpp

@@ -11,8 +11,8 @@ template<typename Entity>
 struct PolyStorage: entt::type_list_cat_t<
 struct PolyStorage: entt::type_list_cat_t<
     decltype(as_type_list(std::declval<entt::Storage<Entity>>())),
     decltype(as_type_list(std::declval<entt::Storage<Entity>>())),
     entt::type_list<
     entt::type_list<
-        void(const Entity *, const Entity *),
-        void(const Entity, const void *),
+        void(const Entity *, const Entity *, void *),
+        void(entt::basic_registry<Entity> &, const Entity, const void *),
         const void *(const Entity) const,
         const void *(const Entity) const,
         void(entt::basic_registry<Entity> &) const
         void(entt::basic_registry<Entity> &) const
     >
     >
@@ -24,12 +24,12 @@ struct PolyStorage: entt::type_list_cat_t<
     struct type: entt::Storage<Entity>::template type<Base> {
     struct type: entt::Storage<Entity>::template type<Base> {
         static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<entt::Storage<Entity>>::type>;
         static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<entt::Storage<Entity>>::type>;
 
 
-        void remove(const entity_type *first, const entity_type *last) {
-            entt::poly_call<base + 0>(*this, first, last);
+        void remove(entt::basic_registry<Entity> &owner, const entity_type *first, const entity_type *last) {
+            entt::poly_call<base + 0>(*this, first, last, &owner);
         }
         }
 
 
-        void emplace(const entity_type entity, const void *instance) {
-            entt::poly_call<base + 1>(*this, entity, instance);
+        void emplace(entt::basic_registry<Entity> &owner, const entity_type entity, const void *instance) {
+            entt::poly_call<base + 1>(*this, owner, entity, instance);
         }
         }
 
 
         const void * get(const entity_type entity) const {
         const void * get(const entity_type entity) const {
@@ -43,8 +43,8 @@ struct PolyStorage: entt::type_list_cat_t<
 
 
     template<typename Type>
     template<typename Type>
     struct members {
     struct members {
-        static void emplace(Type &self, const entity_type entity, const void *instance) {
-            self.emplace(entity, *static_cast<const typename Type::value_type *>(instance));
+        static void emplace(Type &self, entt::basic_registry<Entity> &owner, const entity_type entity, const void *instance) {
+            self.emplace(owner, entity, *static_cast<const typename Type::value_type *>(instance));
         }
         }
 
 
         static const typename Type::value_type * get(const Type &self, const entity_type entity) {
         static const typename Type::value_type * get(const Type &self, const entity_type entity) {
@@ -86,7 +86,7 @@ TEST(PolyStorage, CopyEntity) {
 
 
     registry.visit(entity, [&](const auto info) {
     registry.visit(entity, [&](const auto info) {
         auto &&storage = registry.storage(info);
         auto &&storage = registry.storage(info);
-        storage->emplace(other, storage->get(entity));
+        storage->emplace(registry, other, storage->get(entity));
     });
     });
 
 
     ASSERT_TRUE((registry.all_of<int, char>(entity)));
     ASSERT_TRUE((registry.all_of<int, char>(entity)));
@@ -131,11 +131,11 @@ TEST(PolyStorage, Constness) {
     // cannot invoke remove on a const storage, let's copy the returned value
     // cannot invoke remove on a const storage, let's copy the returned value
     auto cstorage = cregistry.storage(entt::type_id<int>());
     auto cstorage = cregistry.storage(entt::type_id<int>());
 
 
-    ASSERT_DEATH(cstorage->remove(std::begin(entity), std::end(entity)), ".*");
+    ASSERT_DEATH(cstorage->remove(registry, std::begin(entity), std::end(entity)), ".*");
     ASSERT_TRUE(registry.all_of<int>(entity[0]));
     ASSERT_TRUE(registry.all_of<int>(entity[0]));
 
 
     auto &&storage = registry.storage(entt::type_id<int>());
     auto &&storage = registry.storage(entt::type_id<int>());
-    storage->remove(std::begin(entity), std::end(entity));
+    storage->remove(registry, std::begin(entity), std::end(entity));
 
 
     ASSERT_FALSE(registry.all_of<int>(entity[0]));
     ASSERT_FALSE(registry.all_of<int>(entity[0]));
 }
 }

+ 0 - 8
test/entt/entity/sparse_set.cpp

@@ -14,12 +14,6 @@ struct boxed_int { int value; };
 TEST(SparseSet, Functionalities) {
 TEST(SparseSet, Functionalities) {
     entt::sparse_set set;
     entt::sparse_set set;
 
 
-    ASSERT_EQ(set.payload(), nullptr);
-
-    set.payload(&set);
-
-    ASSERT_EQ(set.payload(), &set);
-
     set.reserve(42);
     set.reserve(42);
 
 
     ASSERT_EQ(set.capacity(), 42u);
     ASSERT_EQ(set.capacity(), 42u);
@@ -84,8 +78,6 @@ TEST(SparseSet, Functionalities) {
     ASSERT_EQ(other.begin(), other.end());
     ASSERT_EQ(other.begin(), other.end());
     ASSERT_FALSE(other.contains(entt::entity{0}));
     ASSERT_FALSE(other.contains(entt::entity{0}));
     ASSERT_FALSE(other.contains(entt::entity{42}));
     ASSERT_FALSE(other.contains(entt::entity{42}));
-
-    ASSERT_EQ(set.payload(), &set);
 }
 }
 
 
 TEST(SparseSet, Pagination) {
 TEST(SparseSet, Pagination) {

+ 43 - 0
test/example/signal_less.cpp

@@ -0,0 +1,43 @@
+#include <iterator>
+#include <type_traits>
+#include <gtest/gtest.h>
+#include <entt/entity/entity.hpp>
+#include <entt/entity/registry.hpp>
+
+template<typename Entity, typename Type>
+struct entt::storage_traits<Entity, Type> {
+    // no signal regardless of component type ...
+    using storage_type = storage_adapter_mixin<basic_storage<Entity, Type>>;
+};
+
+template<typename Entity>
+struct entt::storage_traits<Entity, char> {
+    // ... unless it's char, because yes.
+    using storage_type = sigh_storage_mixin<basic_storage<Entity, char>>;
+};
+
+template<typename, typename, typename = void>
+struct has_on_construct: std::false_type {};
+
+template<typename Entity, typename Type>
+struct has_on_construct<Entity, Type, std::void_t<decltype(&entt::storage_traits<Entity, Type>::storage_type::on_construct)>>: std::true_type {};
+
+template<typename Entity, typename Type>
+inline constexpr auto has_on_construct_v = has_on_construct<Entity, Type>::value;
+
+TEST(Example, SignalLess) {
+    // invoking registry::on_construct<int> is a compile-time error
+    static_assert(!has_on_construct_v<entt::entity, int>);
+    static_assert(has_on_construct_v<entt::entity, char>);
+
+    entt::registry registry;
+    const entt::entity entity[1u]{registry.create()};
+
+    // literally a test for storage_adapter_mixin
+    registry.emplace<int>(entity[0], 0);
+    registry.remove<int>(entity[0]);
+    registry.insert<int>(std::begin(entity), std::end(entity), 3);
+    registry.patch<int>(entity[0], [](auto &value) { value = 42; });
+
+    ASSERT_EQ(registry.get<int>(entity[0]), 42);
+}